glsl中手动计算偏导数(对比dFdx)
manual calculation of partial derivation in glsl (compare with dFdx)
我想 dFdx(variable) 可以通过手动实现,如下例所示:
#extension GL_OES_standard_derivatives : enable
float circle (const vec2 st, float r) {
return sin(300.0 * length(st));
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution.xy;
st.x *= u_resolution.x / u_resolution.y;
float dist = circle(st, 0.5);
float df;
df = dFdx(dist) * 100.0;
df = (circle(st + vec2(1.0 / u_resolution.x, 0.0), 0.5) - dist) * 100.0;
gl_FragColor = vec4(vec3(df), 1.0);
}
但是结果不一样,手动的方式好像很顺利,谁能解释一下?
(手动)
(dFdx)
您的假设并不完全正确。
是dFdx
计算2个相邻片段的表达式的差值(偏导数)。
但是不,它并不总是当前片段与行中下一个片段的差异。
因为可以考虑差异,一次评估2x2正方形的片段。在这个正方形中,计算 "left" 和 "right" 片段 (dFdx
) 的差异。结果是 "left" 片段的 dFdx
和 "right" 片段的反向结果:
有关详细规格,请参阅
- OpenGL Shading Language 4.60 Specification - 8.14.1. Derivative Functions; page 189
- OpenGL ES Shading Language 3.20 Specification - 8.14.1. Derivative Functions; page 153
- OES_standard_derivatives
要模拟这个,你必须计算后继者和前任者的结果:
float dist = circle(st, 0.5);
float dist_n = circle(st + offs_x, 0.5);
float dist_p = circle(st - offs_x, 0.5);
对于行中的偶数片段,您必须计算下一个片段和当前片段的表达差异:
df = (dist_n - dist) * 100.0;
对于行中的奇数片段,你必须计算当前片段和前一个片段的表达差异:
df = (dist - dist_p) * 100.0;
看WebGL例子,比较左边dFdx
的结果和右边模拟的结果:
(function loadscene() {
var gl, canvas, prog, bufObj = {};
function render(deltaMS) {
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
ShProg.Use( progDraw );
ShProg.SetF2( progDraw, "u_resolution", vp_size );
VertexBuffer.Draw( bufRect );
requestAnimationFrame(render);
}
function initScene() {
canvas = document.getElementById("canvas");
gl = canvas.getContext("experimental-webgl");
//gl = canvas.getContext( "webgl2" );
if (!gl)
return null;
var standard_derivatives = gl.getExtension("OES_standard_derivatives"); // dFdx, dFdy
if (!standard_derivatives)
alert('no standard derivatives support (no dFdx, dFdy)');
progDraw = ShProg.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progDraw.inPos = gl.getAttribLocation( progDraw.progObj, "inPos" );
if ( progDraw.progObj == 0 )
return;
bufRect = VertexBuffer.Create(
[ { data : [ -1, -1, 1, -1, 1, 1, -1, 1 ], attrSize : 2, attrLoc : progDraw.inPos } ],
[ 0, 1, 2, 0, 2, 3 ] );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight]
vp_size[0] = vp_size[1] = Math.min(vp_size[0], vp_size[1]);
//vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
var ShProg = {
Create: function (shaderList) {
var shaderObjs = [];
for (var i_sh = 0; i_sh < shaderList.length; ++i_sh) {
var shderObj = this.Compile(shaderList[i_sh].source, shaderList[i_sh].stage);
if (shderObj) shaderObjs.push(shderObj);
}
var prog = {}
prog.progObj = this.Link(shaderObjs)
if (prog.progObj) {
prog.attrInx = {};
var noOfAttributes = gl.getProgramParameter(prog.progObj, gl.ACTIVE_ATTRIBUTES);
for (var i_n = 0; i_n < noOfAttributes; ++i_n) {
var name = gl.getActiveAttrib(prog.progObj, i_n).name;
prog.attrInx[name] = gl.getAttribLocation(prog.progObj, name);
}
prog.uniLoc = {};
var noOfUniforms = gl.getProgramParameter(prog.progObj, gl.ACTIVE_UNIFORMS);
for (var i_n = 0; i_n < noOfUniforms; ++i_n) {
var name = gl.getActiveUniform(prog.progObj, i_n).name;
prog.uniLoc[name] = gl.getUniformLocation(prog.progObj, name);
}
}
return prog;
},
AttrI: function (prog, name) { return prog.attrInx[name]; },
UniformL: function (prog, name) { return prog.uniLoc[name]; },
Use: function (prog) { gl.useProgram(prog.progObj); },
SetI1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1i(prog.uniLoc[name], val); },
SetF1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1f(prog.uniLoc[name], val); },
SetF2: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform2fv(prog.uniLoc[name], arr); },
SetF3: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform3fv(prog.uniLoc[name], arr); },
SetF4: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform4fv(prog.uniLoc[name], arr); },
SetM33: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix3fv(prog.uniLoc[name], false, mat); },
SetM44: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix4fv(prog.uniLoc[name], false, mat); },
Compile: function (source, shaderStage) {
var shaderScript = document.getElementById(source);
if (shaderScript)
source = shaderScript.text;
var shaderObj = gl.createShader(shaderStage);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
var status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : null;
},
Link: function (shaderObjs) {
var prog = gl.createProgram();
for (var i_sh = 0; i_sh < shaderObjs.length; ++i_sh)
gl.attachShader(prog, shaderObjs[i_sh]);
gl.linkProgram(prog);
status = gl.getProgramParameter(prog, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(prog));
return status ? prog : null;
} };
var VertexBuffer = {
Create: function(attribs, indices, type) {
var buffer = { buf: [], attr: [], inx: gl.createBuffer(), inxLen: indices.length, primitive_type: type ? type : gl.TRIANGLES };
for (var i=0; i<attribs.length; ++i) {
buffer.buf.push(gl.createBuffer());
buffer.attr.push({ size : attribs[i].attrSize, loc : attribs[i].attrLoc, no_of: attribs[i].data.length/attribs[i].attrSize });
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buf[i]);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( attribs[i].data ), gl.STATIC_DRAW);
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);
if ( buffer.inxLen > 0 ) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.inx);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
return buffer;
},
Draw: function(bufObj) {
for (var i=0; i<bufObj.buf.length; ++i) {
gl.bindBuffer(gl.ARRAY_BUFFER, bufObj.buf[i]);
gl.vertexAttribPointer(bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray( bufObj.attr[i].loc);
}
if ( bufObj.inxLen > 0 ) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufObj.inx);
gl.drawElements(bufObj.primitive_type, bufObj.inxLen, gl.UNSIGNED_SHORT, 0);
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
}
else
gl.drawArrays(bufObj.primitive_type, 0, bufObj.attr[0].no_of );
for (var i=0; i<bufObj.buf.length; ++i)
gl.disableVertexAttribArray(bufObj.attr[i].loc);
gl.bindBuffer( gl.ARRAY_BUFFER, null );
} };
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 vpos;
void main()
{
vpos = inPos.xy;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision mediump float;
uniform vec2 u_resolution;
varying vec2 vpos;
float circle (const vec2 st, float r) {
return sin(100.0 * length(st));
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution.xy;
st.x -= 0.5;
st.y = 1.0 - st.y;
vec2 offs_x = vec2(1.0 / u_resolution.x, 0.0);
float dist = circle(st, 0.5);
float dist_n = circle(st + offs_x, 0.5);
float dist_p = circle(st - offs_x, 0.5);
float df1 = dFdx(dist) * 100.0;
int fc_x = int(gl_FragCoord.x) / 2;
float df2 = ((fc_x*2 == int(gl_FragCoord.x)) ? dist_n - dist : dist - dist_p) * 100.0;
vec3 color = mix(vec3(df1), vec3(df2), step(0.0,st.x));
gl_FragColor = vec4(color, 1.0);
}
</script>
<canvas id="canvas" style="border: none"></canvas>
我想 dFdx(variable) 可以通过手动实现,如下例所示:
#extension GL_OES_standard_derivatives : enable
float circle (const vec2 st, float r) {
return sin(300.0 * length(st));
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution.xy;
st.x *= u_resolution.x / u_resolution.y;
float dist = circle(st, 0.5);
float df;
df = dFdx(dist) * 100.0;
df = (circle(st + vec2(1.0 / u_resolution.x, 0.0), 0.5) - dist) * 100.0;
gl_FragColor = vec4(vec3(df), 1.0);
}
但是结果不一样,手动的方式好像很顺利,谁能解释一下?
您的假设并不完全正确。
是dFdx
计算2个相邻片段的表达式的差值(偏导数)。
但是不,它并不总是当前片段与行中下一个片段的差异。
因为可以考虑差异,一次评估2x2正方形的片段。在这个正方形中,计算 "left" 和 "right" 片段 (dFdx
) 的差异。结果是 "left" 片段的 dFdx
和 "right" 片段的反向结果:
有关详细规格,请参阅
- OpenGL Shading Language 4.60 Specification - 8.14.1. Derivative Functions; page 189
- OpenGL ES Shading Language 3.20 Specification - 8.14.1. Derivative Functions; page 153
- OES_standard_derivatives
要模拟这个,你必须计算后继者和前任者的结果:
float dist = circle(st, 0.5);
float dist_n = circle(st + offs_x, 0.5);
float dist_p = circle(st - offs_x, 0.5);
对于行中的偶数片段,您必须计算下一个片段和当前片段的表达差异:
df = (dist_n - dist) * 100.0;
对于行中的奇数片段,你必须计算当前片段和前一个片段的表达差异:
df = (dist - dist_p) * 100.0;
看WebGL例子,比较左边dFdx
的结果和右边模拟的结果:
(function loadscene() {
var gl, canvas, prog, bufObj = {};
function render(deltaMS) {
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
ShProg.Use( progDraw );
ShProg.SetF2( progDraw, "u_resolution", vp_size );
VertexBuffer.Draw( bufRect );
requestAnimationFrame(render);
}
function initScene() {
canvas = document.getElementById("canvas");
gl = canvas.getContext("experimental-webgl");
//gl = canvas.getContext( "webgl2" );
if (!gl)
return null;
var standard_derivatives = gl.getExtension("OES_standard_derivatives"); // dFdx, dFdy
if (!standard_derivatives)
alert('no standard derivatives support (no dFdx, dFdy)');
progDraw = ShProg.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progDraw.inPos = gl.getAttribLocation( progDraw.progObj, "inPos" );
if ( progDraw.progObj == 0 )
return;
bufRect = VertexBuffer.Create(
[ { data : [ -1, -1, 1, -1, 1, 1, -1, 1 ], attrSize : 2, attrLoc : progDraw.inPos } ],
[ 0, 1, 2, 0, 2, 3 ] );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight]
vp_size[0] = vp_size[1] = Math.min(vp_size[0], vp_size[1]);
//vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
var ShProg = {
Create: function (shaderList) {
var shaderObjs = [];
for (var i_sh = 0; i_sh < shaderList.length; ++i_sh) {
var shderObj = this.Compile(shaderList[i_sh].source, shaderList[i_sh].stage);
if (shderObj) shaderObjs.push(shderObj);
}
var prog = {}
prog.progObj = this.Link(shaderObjs)
if (prog.progObj) {
prog.attrInx = {};
var noOfAttributes = gl.getProgramParameter(prog.progObj, gl.ACTIVE_ATTRIBUTES);
for (var i_n = 0; i_n < noOfAttributes; ++i_n) {
var name = gl.getActiveAttrib(prog.progObj, i_n).name;
prog.attrInx[name] = gl.getAttribLocation(prog.progObj, name);
}
prog.uniLoc = {};
var noOfUniforms = gl.getProgramParameter(prog.progObj, gl.ACTIVE_UNIFORMS);
for (var i_n = 0; i_n < noOfUniforms; ++i_n) {
var name = gl.getActiveUniform(prog.progObj, i_n).name;
prog.uniLoc[name] = gl.getUniformLocation(prog.progObj, name);
}
}
return prog;
},
AttrI: function (prog, name) { return prog.attrInx[name]; },
UniformL: function (prog, name) { return prog.uniLoc[name]; },
Use: function (prog) { gl.useProgram(prog.progObj); },
SetI1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1i(prog.uniLoc[name], val); },
SetF1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1f(prog.uniLoc[name], val); },
SetF2: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform2fv(prog.uniLoc[name], arr); },
SetF3: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform3fv(prog.uniLoc[name], arr); },
SetF4: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform4fv(prog.uniLoc[name], arr); },
SetM33: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix3fv(prog.uniLoc[name], false, mat); },
SetM44: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix4fv(prog.uniLoc[name], false, mat); },
Compile: function (source, shaderStage) {
var shaderScript = document.getElementById(source);
if (shaderScript)
source = shaderScript.text;
var shaderObj = gl.createShader(shaderStage);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
var status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : null;
},
Link: function (shaderObjs) {
var prog = gl.createProgram();
for (var i_sh = 0; i_sh < shaderObjs.length; ++i_sh)
gl.attachShader(prog, shaderObjs[i_sh]);
gl.linkProgram(prog);
status = gl.getProgramParameter(prog, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(prog));
return status ? prog : null;
} };
var VertexBuffer = {
Create: function(attribs, indices, type) {
var buffer = { buf: [], attr: [], inx: gl.createBuffer(), inxLen: indices.length, primitive_type: type ? type : gl.TRIANGLES };
for (var i=0; i<attribs.length; ++i) {
buffer.buf.push(gl.createBuffer());
buffer.attr.push({ size : attribs[i].attrSize, loc : attribs[i].attrLoc, no_of: attribs[i].data.length/attribs[i].attrSize });
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buf[i]);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( attribs[i].data ), gl.STATIC_DRAW);
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);
if ( buffer.inxLen > 0 ) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.inx);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
return buffer;
},
Draw: function(bufObj) {
for (var i=0; i<bufObj.buf.length; ++i) {
gl.bindBuffer(gl.ARRAY_BUFFER, bufObj.buf[i]);
gl.vertexAttribPointer(bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray( bufObj.attr[i].loc);
}
if ( bufObj.inxLen > 0 ) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufObj.inx);
gl.drawElements(bufObj.primitive_type, bufObj.inxLen, gl.UNSIGNED_SHORT, 0);
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
}
else
gl.drawArrays(bufObj.primitive_type, 0, bufObj.attr[0].no_of );
for (var i=0; i<bufObj.buf.length; ++i)
gl.disableVertexAttribArray(bufObj.attr[i].loc);
gl.bindBuffer( gl.ARRAY_BUFFER, null );
} };
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 vpos;
void main()
{
vpos = inPos.xy;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision mediump float;
uniform vec2 u_resolution;
varying vec2 vpos;
float circle (const vec2 st, float r) {
return sin(100.0 * length(st));
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution.xy;
st.x -= 0.5;
st.y = 1.0 - st.y;
vec2 offs_x = vec2(1.0 / u_resolution.x, 0.0);
float dist = circle(st, 0.5);
float dist_n = circle(st + offs_x, 0.5);
float dist_p = circle(st - offs_x, 0.5);
float df1 = dFdx(dist) * 100.0;
int fc_x = int(gl_FragCoord.x) / 2;
float df2 = ((fc_x*2 == int(gl_FragCoord.x)) ? dist_n - dist : dist - dist_p) * 100.0;
vec3 color = mix(vec3(df1), vec3(df2), step(0.0,st.x));
gl_FragColor = vec4(color, 1.0);
}
</script>
<canvas id="canvas" style="border: none"></canvas>