引擎设计跟踪(九.14.3.1) deferred shading: Depthstencil as GBuffer depth
问题汇总
1.Light support for Editor
编辑器加入了灯光工具, 可以添加和修改灯光.
问题1. light object的用户互交.
point light可以把对应的volume (wireframe sphere/cone)画出来用于用户选中, 但是光源太多的时候, 球就有点凌乱了. 所以使用了HUD, 只要选中HUD就会选中灯光, 只有灯光被选中的时候才显示volume. 另外, 编辑器里面的很多"不可见"的逻辑对象都有这种需求, 虽然现在还没有. 另外, 可以直接拿deferred shading中的geometry复用, 来显示bounding volume helper, 对于方向光, 需要额外加一个箭头作为helper.
问题2: HUD的picking. 之前的物体picking, 是用3D方法, 拿screen sapce的x,y, 取任意的z(不同的z都投影到x,y), unproject到world space, 再拿world sapce的相机坐标, 构建一个ray (这个ray上的所有点投影到屏幕上都是x,y), 然后用3D空间算法拾取bounding box.
而HUD比较特殊, 如果复用之前的3D方法, 反而很麻烦. 需要根据screen rect反算bounding box, 而且world bounding并不能完全紧凑包围screen rect, 需要使用view space的bounding, 用view sapce ray计算.
考虑之后选择直接在screen space做picking, 这样做的结果是多加了一两个接口, 但是实现起来变得很轻松. 需要注意的是, screen space的z也需要保留, 用来排序出最靠前的对象.
问题3: HUD的icon, 直接使用的是编辑器的资源(64x64 png), 并没有离线压缩, 而是实时压缩为BC3 (DXT5). 另外这个icon需要背景, 用美术资源重复合成的话冗余太多, 同时又不想把贴图个数写死(必须是2?), 所以加了一个新的senmatic来获取texture的个数, 这样在shader里面就可以迭代采样了.
void BladeFSMain( in float4 pos : POSITION, in float2 uv : TEXCOORD0, uniform float4 texutreCount : SAMPLER_COUNT, uniform sampler2D hudDiffuse[MAX_DYNAMIC_TEXTURE_COUNT], out float4 outColor : COLOR0 ) { outColor = float4(0,0,0,0); for(int i = 0; i < texutreCount.x; ++i) { float4 color = tex2D(hudDiffuse[i],uv.xy); outColor = lerp(outColor, color, color.a); } }
2: View Distance v.s. View Depth
(view distance v.s. view space Z)
View space Z 就是camera distance吗? 两者很多时候可以几乎视为等价, 但并不相同.
首先两者都是线性的, 不同的是, distance相同的点构成了一个球面, 而Z相同的点构成了一个与view dir垂直的plane.
由于view space Z由于是z值, 所以可以用于Z buffering(depth write/test), 即可以写入depth stencil; 而view distance不能用于depth test.
Normalized Linear Depth | Can Be Used As ZBuffer(Depth Testing) | Geometry Points of The Same Value |
view Distance | NO | sphere |
view space Z | YES | plane |
而之前的计划为了节省GBuffer, 是准备用INTZ写入深度, 并作为GBufer的depth来采样, 所以Blade使用的View space Z.
然而, deferred shading 里面:
pos = eye_pos + dir * "depth", 实际上这里的depth是viewDist, 即
pos = eye_pos + dir * viewDistance.
对于直接读取depthstencil来说, 拿到的是view Z而不是view Dist. 这个问题之前没有发现, 因为只实现了方向光, 没有用到position.
简单分析可以得出|viewDistance| = |ViewZ| / cos(θ) = |viewZ| / dot(viewDir, dir).
Construct position In View space:
right handed: viewDir = (0,0,-1)
viewZ = tex2D(depthINTZ, uv).r; //normalized linear depth
viewPos = viewDir * (viewZ * farClipDist) / -viewDir.z ;
Construct position In World space:
viewZ = tex2D(depthINTZ, uv).r;
worldPos = worldEyePos + worldDir * (viewZ*farClipDist) / dot( worldLookatDir, worldDir);
这样就可以使用depthstencil作为Gbuffer的depth了. 既可以做depth test, 又能用来构建顶点位置坐标. 这样就不用把normal和depth压缩后挤在同一个buffer,
甚至在使用24位normal时, 还空出一个通道. 目前这个通道用来保存specular的power.
目前Blade的GBuffer如下:
Component | Format | Attachment | Usage |
Color | A8R8G8B8 | MRT color 0 | Diffuse:rgb, SpecularLevel:a |
Normal | A8R8G8B8 | MRT color 1 | WorldNormal:rgb, Specular exponent:a |
Depth | INTZ | depthstencil | normalized View space Z ZBuffer depth, converted to view space |
Blade 一开始在pixel shader里将normalized view space z输出, 后来因为效率不高, 所以改成了vertex shader里输出常规的z, 然后在deffered shading里根据zbuffer解算view sapce z:
viewZ = convertDepthToViewSpace( tex2D(depthINTZ, uv).r );
worldPos = worldEyePos + worldDir * viewZ* / dot( worldLookatDir, worldDir);
关于convertDepthToViewSpace的计算:
前面已经记录了一个链接, 这里简单记录一下思路. 根据projection matrix
projectedZ = f(viewZ) (linear conversion to [0, zfar] )
projectedW = -viewZ (right handed)
在perspective divide (z/w) 以后:
NDCz = -f(viewZ)/viewZ ranges [-1,1] (OGL) or [0,1] (D3D)
即NDCz = g(z) = a/z + b (non-linear)
其中b = prjoectionMatrix33 = projectionMatrix[2][2], a = projectMatrix34 = projectionMatrix[3][2]
如果忽略viewport 的depth range, 可以认为最终depth buffer里面存储的是NDC space的z,
现在带入projection matrix的参数, 反向计算viewZ = g-1(zbuffer) = a / (zbuffer + b)
只要在CPU端计算好a,b, 在shader里面计算出view space Z,就可以了. 也可以使用invertedProjectionMatrix来直接求出viewSpaceZ.
目前只是尝试阶段, 还没有发现问题, 比如可能的精度不够等等的可能情况(之前使用linear depth就是为了精度问题), 后面再继续完善.
如果精度不够用的话, 可能就需要将view distance直接渲染到MRT里, 不采样depthstencil了.
需要注意的是, 并不是所有SM3.0都支持INTZ, 只有G80以上才支持. 为了简化管线, 在不支持INTZ的显卡上, 直接使用forward shading.
目前Blade支持的shader model有2_0, 2_x, 3_0. 在shader model为3.0的时候, 检测INTZ的支持, 如果不支持,则设置为2_x, 这个细节备忘下, 后面再完善.
其他问题
Light Volume: Calculate Screen UV in Vertex Shader:
只有方向光的quad比较特殊, 可以这么做, shpere和cone是不行的.尝试过了不对. 详细见这里: http://gamedev.stackexchange.com/questions/63870/computing-pixels-screen-position-in-a-vertex-shader-right-or-wrong
UV scale
由于默认backbuffer是desktop的大小, 所以depthstencil创建时也是这么大. 然而使用时是实际窗口的大小, 需要根据viewport的pixel size(或window size)和depthstencil的size, 计算UV的scale, 只采样部分区域, 同时apply half pixel offset, 这个UV的scale和offset在CPU计算并传入pxiel shader.
后面准备完善spot light, 并加上stencil mask.