Directx11教程二十五之ZBuffer(深度缓存,DepthBuffer)
这节教程的结构如下:
一,DepthBuffer(深度缓存)
深度缓存(Z缓存值)是用来记录视截体的每个像素的深度,其值范围是[0,1],在D3D11中 默认近裁剪面(Near_Z)上面的像素深度为0,而远裁剪面上的像素的像素深度为1, 注意stencilBuffer一般都是跟DepthBuffer绑定在一起的,创建DepthBuffer 时,指定数据格式DXGI_FORMAT_D24_UNORM_S8_UINT.,
其中深度缓存值中有24位用来存储真正的深度缓存值(Z值),有8位用来存储模板缓存值。
如图所示:
默认情况下 对于背后缓存的同一个位置的像素,Z缓存值小的将代替Z缓存值大的像素,即所谓的Z缓存测试剔除。
那么Z缓存值怎么求出来呢?
我一步一步的说出来,首先来看看D3D11渲染流水线:
首先说明一点,真正意义上的透视投影过程由两步组成:(1)乘以投影矩阵 (2)透视除法
第一步:乘以投影矩阵(Projection matrix),变换到齐次裁剪空间(Homogeneous clip space),并进行CVV裁剪
假设在相机空间(View Coordinate System) 一个顶点的坐标为(x,y,z,1),则该变乘以投影矩阵(Projection matrix)后,变换到齐次裁剪空间,
此时坐标变为(Xp,Yp,Zp,Wp),此时W不为1,因此称作为齐次坐标,给出此时坐标分值Xp,Yp,Zp的范围,
-Wp=<Xp<=Wp -Wp=<Yp<=Wp 0=<Zp<=Wp
这时将进行CVV裁剪算法(其实这裁剪算法也不能算CVV裁剪,下面会说原因)
第二步,进行透视除法,变换到NDC空间(Normalized Device Coordinate)
有上面可以知道齐次裁剪空间内 顶点(Xp,Yp,Zp,Wp) -Wp=<Xp<=Wp -Wp=<Yp<=Wp 0=<Zp<=Wp,
进行透视除法,就是将顶点(Xp,Yp,Zp,Wp)每个分值除以Wp,得到(Xn,Yn,Zn,1),其中
(Xn,Yn,Zn,1)
规则观察体(Canonical View Volume)称为CVV,人们称之为CVV,感觉大量人误认为是单位立方体(或者长方体),这里我跟着他们一起错,规定CVV也是单位立方体,而不是规则观察体。(其实按单词字面意思,立方体或者长方体不管是不是单位的都应该算是CVV,毕竟都是规则的,但是这里我跟着他们一起错吧,就认为是单位的)
上面说了这些也就是想表达一个意思:真正的透视投影过程不仅仅是乘以投影矩阵就行了,还需要进行接下来的透视除法。
当然很多人以为是在NDC空间进行裁剪的,也就是(Xn,Yn,Zn,1),-1=<Xn<=1,-1=<Yn<=1,0=<Zn<=1这样的空间进行裁剪,这样是错误的想法
这里给出两篇大牛的文章,深入论述了““透视投影变换的””的来龙去脉。
这里放出文章部分截图证明我的观点:(这是关于opengl的投影变换原理说明,-1=<Z(opengl)<=1, 0=<Z(d3d11)<1.0,但无伤大雅,照样可以理解)
不过这作者感觉有些论述自相矛盾,原因是最上面3D渲染流水线图也是该作者给出的,文章在这里 http://blog.****.net/popy007/article/details/5120158
然而我们在Shader里指定系统签名“”SV_POSITION” 之后裁剪算法和透视除法是显卡自动自动进行.,不用我们操心,而如果使用我们自己定义的签名“”POSITION“”,透视除法并不会自动进行,也就是乘以投影矩阵后(投影变换后)得到的坐标(Xp,Yp,Zp,Wp) -Wp=<Xp<=Wp -Wp=<Yp<=Wp 0=<Zp<=Wp
那么在齐次裁剪空间 0.0=<Zp/Wp<=1.0 Z缓存值=Zp/Wp;
当然我这里主要是关于D3D11渲染管线的,opengl的参见OpenGL渲染流水线之世界矩阵,相机变换矩阵,透视投影变换矩阵
下面放出我的Shader代码:
- //VertexShader
- cbuffer CBMatrix:register(b0)
- {
- matrix World;
- matrix View;
- matrix Proj;
- };
- struct VertexIn
- {
- float4 Pos:POSITION;
- };
- struct VertexOut
- {
- float4 Pos:SV_POSITION;
- float4 Pos_Depth:POSITION;
- };
- VertexOut VS(VertexIn ina)
- {
- VertexOut outa;
- //------齐次裁剪空间-----(x,y,z,w) -w=<x<=w -w=<y<=w 0=<z<=w 由于标记“SV_POSITION”,貌似之后会进行CVV裁剪, // 这时透视除法和CVV裁剪是显卡自动进行的
- outa.Pos = mul(ina.Pos, World);
- outa.Pos = mul(outa.Pos, View);
- outa.Pos = mul(outa.Pos, Proj);
- //顶点坐标,由于没标记“SV_POSITION”,不会自动进透视除法,一直停留在齐次裁剪空间
- outa.Pos_Depth = outa.Pos;
- return outa;
- }
- float4 PS(VertexOut outa) : SV_Target
- {
- float4 color;
- float Z;
- //求出每个像素的Z缓存值 0.0=<(Z缓存值=Zp/Wp)<=1.0
- Z = outa.Pos_Depth.z / outa.Pos_Depth.w;
- //当Z缓存值小于0.9f时,显示为红色,占据了大部分的屏幕,Z越小,越接近屏幕
- if (Z < 0.9f)
- {
- color = float4(1.0, 0.0f, 0.0f, 1.0f);
- }
- //当Z缓存值大于0.9f而小于0.95f时为绿色
- if (Z > 0.9f)
- {
- color = float4(0.0, 1.0f, 0.0f, 1.0f);
- }
- //当Z缓存值大于0.925f为蓝色
- if (Z > 0.925f)
- {
- color = float4(0.0, 0.0f, 1.0f, 1.0f);
- }
- return color;
- }
下面我的源代码链接: