转载,原文出自:http://politician2012.blog.163.com/blog/static/207842452201272410452665/?newFollowBlog
研究D3D11已经有一段时间了,现在通过以前练习过的Demo总结回顾一下自己的学习历程,一方面与大家分享我自己的学习经历,并向大家推荐一本书《Pratical Rendering & Computation with Direct3D11》,同时也可以进一步巩固以前做过的东西,欢迎在图形学上有相同爱好的朋友与我进一步相互学习交流。
首先声明,本文中的所有Demo都必须在支持DirectX11的Windows 7及更高版本的系统上才可以正确运行,并且要求主机显卡支持性能等级为11的显卡,一些老显卡可能无法运行本文中的Demo,启动这些可执行文件只会看到白色窗口。
这里给出Demo的下载地址:Bin.rar。
是一个rar压缩包,由于使用SkyDrive共享文件外链(找不到其他在博客上共享文件的方式了,有其他方法的话,望不吝赐教,呵呵),下载下来的文件的文件名可能只是一个ID值,需要大家自行命名文件名及文件格式。
InitD3D11.exe
P1 * B1 = P0;
P2 * B2 = P0;
P3 * B3 = P0;
W1 + w2 + w3 = 1.0;
所以有:P1 * B1 * w1 + P2 * B2 * w2 + P3 * B3 * w3 = P0;
再令B4、B5、B6分别为B1、B2、B3的逆矩阵,我们有:
P1 = P0 * B4;
P2 = P0 * B5;
P3 = P0 * B6;
我们假设在t时刻K1、K2、K3的当前变换矩阵(根据骨头相对其父母骨头(即父母节点)的相对偏移和相对旋转(一般在程序中使用四元数表示旋转)计算而来,别忘了骨架其实是一个树形结构,这个概念这里不再赘述)分别为B1t、B2t、B3t,则顶点在t时刻的位置坐标Pt为:
P1 * B1t * w1 + P2 * B2t * w2 + P3 * B3t * w3 = Pt
即:P0 * B4 * B1t * w1 + P0 * B5 * B2t * w2 + P0 * B6 * B3t * w3 = Pt
我们可以令B1f、B2f、B3f分别为t时刻K1、K2、K3的最终变换矩阵,则有:
B1f = B4 * B1t;
B2f = B5 * B2t;
B3f = B6 * B3t;
所以有:P0 * B1f * w1 + P0 * B2f * w2 + P0 * B3f * w3 = Pt
因此我们在CPU中计算出整个骨架的所有骨头的最终变换矩阵之后,将这些矩阵传送到图形管线,对于本Demo具体的说就是传送到顶点着色器的常量缓存中,然后在顶点着色器中计算模型的每个顶点的位置和法线向量。
图(6-1),原始三角形网格
// Sigma为根号2时,通过高斯方程计算得到的权值
{0.000904706, 0.00315773, 0.00668492, 0.00858361, 0.00668492, 0.00315773, 0.000904706},
{0.00315773, 0.0110216, 0.0233327, 0.0299597, 0.0233327, 0.0110216, 0.00315773},
{0.00668492, 0.0233327, 0.0493952, 0.0634248, 0.0493952, 0.0233327, 0.00668492},
{0.00858361, 0.0299597, 0.0634248, 0.081439, 0.0634248, 0.0299597, 0.00858361},
{0.00668492, 0.0233327, 0.0493952, 0.0634248, 0.0493952, 0.0233327, 0.00668492},
{0.00315773, 0.0110216, 0.0233327, 0.0299597, 0.0233327, 0.0110216, 0.00315773},
{0.000904706, 0.00315773, 0.00668492, 0.00858361, 0.00668492, 0.00315773, 0.000904706}
图(8-4),权值的对称性
直接对每个像素代入卷积计算当然可以得到正确的结果,但是这种方法可以被优化,如果我们先横向进行高斯模糊,然后再纵向进行高斯模糊,且每次高斯模糊的数据为:
{
0.030078323f, 0.104983664f, 0.222250419f, 0.285375187f, 0.222250419f, 0.104983664f, 0.030078323f
}
我们可以很容易的看出这组数据与上一组的关系,即第一个数值是上一组数据的第一行或第一列的和,其他类似。显然这组数据的和也为1.现在我们来证明分离高斯模糊(即先横向,再纵向)与直接高斯模糊的结果是一致的。 下面省略高斯方程的系数,因为在进行除法的时候系数会被消掉,针对一行像素,我们令:
A = exp(-(dx1 ^ 2) / (2 * σ ^ 2));
B = exp(-(dx2 ^ 2) / (2 * σ ^ 2));
C = exp(-(dx3 ^ 2) / (2 * σ ^ 2));
D = 1.0f;
E = 2 * (A + B + C) + D;
a = A / E; b = B / E; c = C / E; d = D / E;
其中dx为某像素到该行像素中心的距离。再针对一列像素,令:
F = exp(-(dy1 ^ 2) / (2 * σ ^ 2));
G = exp(-(dy2 ^ 2) / (2 * σ ^ 2));
H = exp(-(dy3 ^ 2) / (2 * σ ^ 2));
I = 1.0f;
J = 2 * (F + G + H) + I;
e = F / J; f = G / J; g = H / J; h = I / J;
其中dy为某像素到该列像素中心的距离。由于对称性,我们有:
a = e; b = f; c = g; d = h;
现在我们针对在所在行的权值为a,在所在列的权值为f的那个像素T来讨论,显然T为我们当前要进行高斯模糊的那个像素的最终颜色所提供的百分比为:a * f;
a * f = A * G / (E * J) = exp(-(dx1 ^ 2 + dy2 ^ 2) / (2 * σ ^ 2)) / (E * J);
我们可以进一步证明E * J就是在计算开始那组数据时所要除的分母,但由于计算繁琐,这里就不再一步步证明了。因此我们可以证明a * f的结果即为在进行直接高斯模糊模糊时的其中一个权值,由此可证直接高斯模糊的结果与分离高斯模糊的结果一致。使用直接高斯模糊计算一个像素最终颜色的算法复杂度为7 x 7 = 49,而使用分离高斯模糊计算一个像素最终颜色的算法复杂度为7 + 7 = 14,因此使用分离高斯模糊可以大大减少计算量。
截图(8-1)为本Demo要进行模糊的原始图像,截图(8-2)为使用直接高斯模糊所得的图像,截图(8-3)为使用分离高斯模糊所得的图像,从图(8-2)和(8-3)可以看出,这两种方法所得的结果完全一致。
FluidSimulation.exe
图(9-2)
其中每个顶点为一个水柱,每个水柱有8根虚拟的管道连接到相邻的8个水柱,水柱中的水可以通过这些管道流到相邻的水柱,顶点的高度由水柱的水量表示,关于该模型这里不做过多的阐述,有兴趣可参考《Pratical Rendering & Computation with Direct3D11》第十二章。
ParticleSystem.exe
图(10-2)
两个状态缓存都为“附加/消费”结构化缓存(Append / Consume StructuredBuffer),由于CPU不向GPU提供顶点数据,这里使用实例化GPU生成图元的渲染模式,即DrawInstancedIndirect,并且使用几何着色器(Geometry Shader)为每个粒子创建公告牌。
ParaboloidMap.exe
实现双抛物面环境映射。对于三维空间中的抛物面方程:
z = -0.5 * (x ^ 2 + y ^ 2) + 0.5;
图(11-2)
如图(11-2)所示,我们可以证明在z >= 0的整个半空间中的任意向量,都可以由垂直向下的入射向量(0.0, 0.0, -1.0),经过抛物面反射而得到,图(11-2)中I为入射向量的反向量(0.0, 0.0, 1.0),N为抛物面某点上的法线向量,R为入射向量在该点上的反射向量,很显然我们有:
N = I + R;
通过对抛物面上任一点求偏导数,得到该点上两条等值线的切向量,这两个切向量的叉乘即位该点上的法线向量,经过简单的数学推导,图(11-2)中抛物面上的任一点P(x, y, z)的法线向量为:
N = (x, y, 1);
因此,如果我们把从原点到单位半空间S = {P; P = (x, y, z); x, y, z ∈ [0, 1]}中的任意一点的向量作为反射向量,假设为P0 = (x0, y0, z0),即R0 = P0 – 0 = (x0, y0, z0),那么我们可以在抛物面上找到一个点Q,使得R0在Q上的入射向量为(0.0, 0.0, -1.0),这样我们就把单位半空间中的任意点映射到了平面上的一个单位圆上,且P0在单位圆上对应的点的坐标即为Q点上的法线向量的x,y坐标,我们再将单位圆映射到单位纹理坐标中,这个过程就是抛物面环境映射的基本原理,具体实现大家有兴趣的话可以查看《Pratical Rendering & Computation with Direct3D11》第十三章,或者在网上查找相关资料。
ParallelSplitShadowMaps.exe
实现平行分割阴影映射。阴影贴图是图形学中常用的阴影技术,因其比模板阴影更高效而更加普及,但基本的阴影贴图映射有其缺陷,即当摄像机靠近场景中模型的阴影时,会发现阴影失真,这是因为把看到的整个场景区域的阴影信息全部保存到一张纹理贴图中,但由于场景往往很大,这样导致了阴影信息的丢失,我们可以通过增加阴影贴图的分辨率来减缓这个问题,但显然这会造成更大的开销,甚至是不可行的。本Demo用到的“平行分割阴影映射”可以解决这个问题,基本思想就是把摄像机视景体(Frustum)平行分割成多个块,然后为每个块创建独立的阴影贴图,具体技术信息大家有兴趣的话可以参考《GPU Gems 3》Part II,Chapter 10,上面介绍了详细的技术细节,而且还提供了源代码。
本Demo将摄像机的视景体平行分割为3块,图(12-1)右侧从上到下显示了由远到近三个分块的阴影信息。
TessellationIsland.exe
使用D3D11嵌饰系统渲染有趣的海岛场景。
图(13-1)显示岩壁渲染结果,从图中可以看出岩壁的斑驳不平,表现还是很丰富的。这里面用到了多张Bump Map来实现这种效果,还有地表的纹理过渡也很自然。
图(13-6),渲染场景所用到的着色器
在本文结束前,我还想向想要学习Direct3D11的朋友推荐一本书,就是前面多次提及的《Pratical Rendering & Computation with Direct3D11》,不过建议在有了一定的图形学和着色器编程基础之后再去看,当然英文阅读能力是少不了了,这本书对Direct3D11介绍得非常详尽和系统,对于Direct3D11这样的新技术来说,这本书堪称是经典了,呵呵。
各Demo的输入操作说明(没有的表示不接受输入):
TextureArray.exe
按下鼠标左键,旋转木箱,鼠标滚轮前进和后退摄像机。
BezierSurface.exe
按下鼠标左键,旋转麦比乌斯带,鼠标滚轮前进和后退摄像机。
DetailTessellation.exe
'W','S','A','D'键上下左右平移摄像机,按下鼠标右键旋转摄像机,'0'键开启和关闭Wireframe。
CurvedPNTriangles.exe
按下鼠标左键,旋转飞船,鼠标滚轮前进和后退摄像机,'+'键增加嵌饰细节,'-'键降低嵌饰细节,'0'键开启和关闭Wireframe。
ComputeShader.exe
'W','S','A','D'键上下左右平移摄像机,按下鼠标右键旋转摄像机,'0'键开启和关闭Wireframe。
ImageProcess.exe
'P'键切换原始图像、直接高斯模糊图像、分离高斯模糊图像。
FluidSimulation.exe
'W','S','A','D'键上下左右平移摄像机,按下鼠标右键旋转摄像机。
ParallelSplitShadowMaps.exe
'W','S','A','D'键上下左右平移摄像机,按下鼠标右键旋转摄像机,'0'键开启和关闭Wireframe。
TessellationIsland.exe
'W','S','A','D'键上下左右平移摄像机,按下鼠标右键旋转摄像机,'0'键开启和关闭Wireframe。