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.csdn.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代码:
[cpp]   view plain  copy
  1. //VertexShader  
  2. cbuffer CBMatrix:register(b0)  
  3. {  
  4.     matrix World;  
  5.     matrix View;  
  6.     matrix Proj;  
  7. };  
  8.   
  9. struct VertexIn  
  10. {  
  11.     float4 Pos:POSITION;  
  12. };  
  13.   
  14.   
  15. struct VertexOut  
  16. {  
  17.     float4 Pos:SV_POSITION;  
  18.     float4 Pos_Depth:POSITION;  
  19. };  
  20.   
  21.   
  22. VertexOut VS(VertexIn ina)  
  23. {  
  24.     VertexOut outa;  
  25.   
  26.     //------齐次裁剪空间-----(x,y,z,w)  -w=<x<=w -w=<y<=w  0=<z<=w  由于标记“SV_POSITION”,貌似之后会进行CVV裁剪,       // 这时透视除法和CVV裁剪是显卡自动进行的  
  27.     outa.Pos = mul(ina.Pos, World);  
  28.     outa.Pos = mul(outa.Pos, View);  
  29.     outa.Pos = mul(outa.Pos, Proj);  
  30.   
  31.     //顶点坐标,由于没标记“SV_POSITION”,不会自动进透视除法,一直停留在齐次裁剪空间  
  32.     outa.Pos_Depth = outa.Pos;  
  33.     return outa;  
  34. }  
  35.   
  36. float4 PS(VertexOut outa) : SV_Target  
  37. {  
  38.     float4 color;  
  39.     float Z;  
  40.     //求出每个像素的Z缓存值  0.0=<(Z缓存值=Zp/Wp)<=1.0  
  41.     Z = outa.Pos_Depth.z / outa.Pos_Depth.w;  
  42.   
  43.     //当Z缓存值小于0.9f时,显示为红色,占据了大部分的屏幕,Z越小,越接近屏幕  
  44.     if (Z < 0.9f)  
  45.     {  
  46.         color = float4(1.0, 0.0f, 0.0f, 1.0f);  
  47.     }  
  48.   
  49.     //当Z缓存值大于0.9f而小于0.95f时为绿色  
  50.     if (Z > 0.9f)  
  51.     {  
  52.         color = float4(0.0, 1.0f, 0.0f, 1.0f);  
  53.     }  
  54.   
  55.     //当Z缓存值大于0.925f为蓝色  
  56.     if (Z > 0.925f)  
  57.     {  
  58.         color = float4(0.0, 0.0f, 1.0f, 1.0f);  
  59.     }  
  60.   
  61.     return color;  
  62.       
  63. }  



放出我的程序运行图:





下面我的源代码链接:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是使用zbuffer深度缓存算法绘制一个简单的三维立方体的示例代码: ```python import numpy as np import matplotlib.pyplot as plt # 定义立方体的顶点和面 vertices = np.array([ [-1, -1, -1], [-1, -1, 1], [-1, 1, -1], [-1, 1, 1], [1, -1, -1], [1, -1, 1], [1, 1, -1], [1, 1, 1] ]) faces = np.array([ [0, 1, 3, 2], [0, 4, 5, 1], [0, 2, 6, 4], [1, 5, 7, 3], [2, 3, 7, 6], [4, 6, 7, 5] ]) # 设置画布大小 width, height = 512, 512 # 初始化深度缓存 zbuffer = np.full((width, height), -np.inf) # 定义光源和相机位置 light_position = np.array([1, 1, 1]) camera_position = np.array([0, 0, 3]) # 定义绘制函数 def draw(): # 初始化画布和像素颜色 canvas = np.zeros((width, height, 3)) pixel_colors = np.zeros((width, height, 3)) # 遍历每个面 for face in faces: # 获取面的三个顶点 v1, v2, v3, v4 = vertices[face] # 计算面的法向量 normal = np.cross(v2 - v1, v3 - v1) # 计算光线方向 light_direction = light_position - v1 # 如果光线和面的法向量方向相同,说明光线照射到了面上 if np.dot(normal, light_direction) > 0: # 计算面的中心点 center = (v1 + v2 + v3 + v4) / 4 # 计算相机到面的距离 distance = np.linalg.norm(camera_position - center) # 遍历面的每个像素 for x in range(width): for y in range(height): # 计算像素在相机坐标系下的坐标 pixel_pos = np.array([x / width * 2 - 1, -(y / height * 2 - 1), -1, 1]) # 计算像素在世界坐标系下的坐标 world_pos = np.linalg.inv(view_matrix) @ np.linalg.inv(projection_matrix) @ pixel_pos # 计算光线和像素的距离 light_pos = camera_position + world_pos[:3] * distance light_distance = np.linalg.norm(light_pos - light_position) # 如果像素和面的距离小于深度缓存中的值,说明像素在面的前面,需要更新深度缓存和像素颜色 if world_pos[2] > zbuffer[x, y]: zbuffer[x, y] = world_pos[2] # 计算像素的颜色 intensity = np.dot(normal, light_direction) / (np.linalg.norm(normal) * np.linalg.norm(light_direction)) color = np.array([1, 1, 1]) * intensity / (light_distance ** 2) pixel_colors[x, y] = color # 将像素颜色绘制到画布上 canvas = pixel_colors canvas[canvas > 1] = 1 # 显示画布 plt.imshow(canvas) plt.show() # 计算投影矩阵和视图矩阵 fov = 60 near = 0.1 far = 10 aspect = width / height projection_matrix = np.array([ [1 / (np.tan(np.radians(fov / 2)) * aspect), 0, 0, 0], [0, 1 / np.tan(np.radians(fov / 2)), 0, 0], [0, 0, (far + near) / (far - near), -2 * far * near / (far - near)], [0, 0, 1, 0] ]) view_matrix = np.array([ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, -3], [0, 0, 0, 1] ]) # 绘制立方体 draw() ``` 在上述代码中,我们首先定义了立方体的顶点和面,然后初始化了深度缓存,并定义了光源和相机位置。在绘制函数中,我们遍历每个面,并计算每个像素的颜色和深度值。最后,我们将像素颜色绘制到画布上并显示出来。在计算像素颜色和深度值时,我们使用了投影矩阵和视图矩阵将像素从相机坐标系转换到世界坐标系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值