在deferred shading 和post process 阶段, 通常要做的一件事情就是讲裁减空间坐标转换到屏幕空间的纹理坐标.
这里面通常的做法就是Vertex Shader 输出clip space position 到pixel shader.
pixel shader 做除法,然后再 + 1 * 0.5 变换到纹理坐标, 然后在D3D9 中还要根据采样纹理的大小进行texel 偏移.
我有太多的像素要处理, 所以, 这些没必要的计算能少则少! 或者交给vertex shader 来分担.
下面的目的就是让Vertexshader 来计算屏幕空间TexCoord , 在 pixel shader 中只做最少的事情.
/////////////////////////////////////////////////////////////////////////////
// 推导:
/////////////////////////////////////////////////////////////////////////////
Clip Space Vertex:
Vc = mul(matViewProj, Vworld)
Vc.xy = Vc.xy/Vc.w [-1 1]
接着设备会在光栅化阶段, Clip->NDC->Viewport 将其从裁减空间变换到屏幕空间(Vs). [0, ViewportSize]
Vs.x = (Vc.x +1)*ViewportWidth*0.5;
Vs.y = ({-}Vc.y + 1)*ViewportHeight*0.5; //NOTE: D3D窗口坐标. Y轴指向下方向和 CLIP 空间相反. OPENGL 则刚好反过来. 下面的推导基于D3D9
这里我们已经获得在屏幕空间坐标, 但是实际上, 需要的纹理坐标. [0 1]
TexCoord = Vs / ViewportSize ; TexCoord.u = (Vc.x +1)*0.5; TexCoord.v = (-Vc.y + 1)*0.5; // map texel to pixel. float2 texel_offset = float2(0.5,0.5); TexCoord = TexCoord + texel_offset * InvViewportSize; // 1/ViewportSize TexCoord.u = (Vc.x +1)*0.5 + 0.5*InvViewportSize.x; TexCoord.v = (-Vc.y + 1)*0.5 + 0.5*InvViewportSize.y;
再次展开:
HPos = mul(matViewProj, Vworld) TexCoord.u = (HPos.x/HPos.w +1)*0.5 + 0.5*InvViewportSize.x; TexCoord.v = (-HPos.y/HPos.w + 1)*0.5 + 0.5*InvViewportSize.y;
这部分的计算是在Vertex Shader中完成, 我们将结果TexCoord 以TEXCORRDn的语义传给Pixel Shader, 在光栅化阶段对每个pixel, 输入的数据会被插值.
假设针对顶点输入数据的插值是: [interp(X), interp(Y), interp(Z), interp(W)] ;
1/ interp(W) 不一定会等于 interp(1/W)
然后我们在回头来看上面的变换.
TexCoord 中正好就包含了一个 1/ HPos.w 的因子. 在这里跌了个跟头....:(
于是修改上面的结果, 将/ W 放到PIXEL SHADER 中进行.
TexCoord.u = (HPos.x/HPos.w +1)*0.5 + 0.5*InvViewportSize.x) *HPos.w; TexCoord.v = ((-HPos.y/HPos.w + 1)*0.5 + 0.5*InvViewportSize.y) *HPos.w;
// 最终结果:
Vertex Shader:
float4x4 ViewProjMat;// view projection matrix. float4 ViewportSize; //[width, height, 0.5f/width, 0.5f/height] struct VERTEX_INPUT { float4 Position : POSITION; }; struct VERTEX_OUTPUT { float4 HPos: POSITION; float4 ScreenTexCoord: TEXCOORD0; }; float4 HPosToScreenTexCoord( float4 p ) { float4 ScrTC = HPos; // clip space // [-1 1] --> [0 1] ScrTC.xy = (HPos.xy * float2(1,-1) + HPos.ww ) * 0.5; ScrTC.xy += ViewportSize.zw*HPos.w;// texel offset return ScrTC; } VERTEX_OUTPUT VMain(VERTEX_INPUT IN ) { VERTEX_OUTPUT OUT; OUT.HPos= mul(ViewProjMat, IN.Position); OUT.ScreenTexCoord =HPosToScreenTexCoord(OUT.HPos); return OUT;}
Pixel Shader:
float4 PMain(VERTEX_OUTPUT IN) : COLOR0 { IN.ScreenTexCoord.xy /= Input.ScreenTexCoord.w; return float4(IN.ScreenTexCoord.xy,0,1); }
screen u texcoord :
// screen v coord: