本文为 Introduction to 3D Game Programming with DirectX 11 读书笔记
XMVECTOR XMLoadColor(CONST XMCOLOR* pSource);
VOID XMStoreColor(XMCOLOR* pDestination, FXMVECTOR V);
input assembler (IA) 阶段从内存中读取几何数据,然后用它组合成几何原型(三角形、线等)。
被传输到渲染管线的顶点集合称为 vertex buffer,通过指定 primitive topology 来告诉Direct3D怎么从vertex数据中构成集合基元。
void ID3D11DeviceContext::IASetPrimitiveTopology(
D3D11_PRIMITIVE_TOPOLOGY Topology);
typedef enum D3D11_PRIMITIVE_TOPOLOGY
{
D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED = 0,
D3D11_PRIMITIVE_TOPOLOGY_POINTLIST = 1,
D3D11_PRIMITIVE_TOPOLOGY_LINELIST = 2,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP = 3,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST = 4,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5,
D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = 10,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = 11,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = 12,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ = 13,
D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST = 33,
D3D11_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST = 34,
...
D3D11_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST = 64,
} D3D11_PRIMITIVE_TOPOLOGY;
例子:
md3dImmediateContext->IASetPrimitiveTopology(
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
/* ...draw objects using triangle strip... */
上图的图b表示带邻接三角形的三角形列表,adjacent triangles的表示也必须在输入中提供
因为直接重复的给顶点数据的话,顶点会有很大的冗余,所以使用Index来构成primitive。
例如:
Vertex v [9] = {v0, v1, v2, v3, v4, v5, v6, v7, v8};
UINT indexList[24] = {
0, 1, 2, // Triangle 0
0, 2, 3, // Triangle 1
0, 3, 4, // Triangle 2
0, 4, 5, // Triangle 3
0, 5, 6, // Triangle 4
0, 6, 7, // Triangle 5
0, 7, 8, // Triangle 6
0, 8, 1 // Triangle 7
};
顶点着色器对顶点数据进行处理。
在介绍View Space的时候,书上是先介绍的camera的坐标相对于world坐标的偏移,从view space到world space的转换矩阵 W,其中 W=RT W = R T ,那么world space到view space的转换矩阵就是
对Unity的shader比较熟悉的朋友可能会看到
#define COMPUTE_EYEDEPTH(o) o = -UnityObjectToViewPos( v.vertex ).z
这里取出z后在前面加了一个负号,这个还没有特别好的解释,关于这个计算
给定camera的坐标,构造camera坐标系统,因为world space的轴现在是作为标准的坐标,所以这里求出来的就是view space相对于world space的转换,再把上面说的 V=W−1 V = W − 1 考虑一下,就可以得到world space到view space的转矩阵了。 j=(0,1,0) j = ( 0 , 1 , 0 )
XNA也提供了函数实现
XMMATRIX XMMatrixLookAtLH( // Outputs resulting view matrix V
FXMVECTOR EyePosition, // Input camera position Q
FXMVECTOR FocusPosition, // Input target point T
FXMVECTOR UpDirection); // Input world up vector j
例子:
XMVECTOR pos = XMVectorSet(5, 3, -10, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
给定欧式平面上一点(x,y),对任意非零实数z,三元组(xZ,yZ,Z)即称之为该点的齐次坐标。与笛卡尔坐标不同,一个点可以有无限多个齐次坐标表示法。
- 当Z不为0,则表示欧氏平面上该点为(X/Z,Y/Z)
- 当Z为0,则该点表示一无穷远点
三元组(0,0,0)不表示任何点,原点表示为(0,0,1)。
定义视椎体的4个数:近平面 n n ,远平面 f f ,竖直视角(vertical field of view angle) α α ,aspect ratio r r .
aspect ratio : r=w/h r = w / h ,其中w和h分别为投影窗口的宽和高。
给定视椎体中一个顶点 (x,y,z) ( x , y , z ) ,投影到近平面上 (x′,y′,d) ( x ′ , y ′ , d )
公式如下:
为什么需要标准设备空间,因为从视椎体近平面和camera的设置有关,屏幕空间和硬件屏幕有关,这两者之间需要一个桥梁来做转换,所以NDC非常有必要。
在NDC中只有满足如下条件,一个点在camera space才处于视椎体中(下面的z还没有标准化)
这里用了一个小trick,将过程分解为线性和非线性的两个部分。非线性部分就是最后除以z
则投影矩阵为,假设 A,B A , B 为常数
上面假设了A和B为常数,最后要将深度值从 [n,f] [ n , f ] 映射到 [0,1] [ 0 , 1 ] 上。
令 g(z)=A+Bz g ( z ) = A + B z ,则必须满足 g(n)=0,g(f)=1 g ( n ) = 0 , g ( f ) = 1
则
该投影矩阵也被XNA实现了
XMMATRIX XMMatrixPerspectiveFovLH( // returns projection matrix
FLOAT FovAngleY, // vertical field of view angle in radians
FLOAT AspectRatio, // aspect ratio = width / height
FLOAT NearZ, // distance to near plane
FLOAT FarZ); // distance to far plane
例子:
XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathX::Pi,
AspectRatio(), 1.0f, 1000.0f);
//The aspect ratio is taken to match our window aspect ratio:
float D3DApp::AspectRatio()const
{
return static_cast<float>(mClientWidth) / mClientHeight;
}
Tessellation是指把mesh的三角形再分割,添加新的三角形
geometry shader是可选的,它的主要优势是可以create or destroy geometry
将对象处在视椎体之外的部分剪裁掉
在homogeneous clip space中,在除以w之前,4D坐标为 (x,y,z,w) ( x , y , z , w ) ,则
光栅化阶段主要是,从投影的3D三角形,计算pixel color
从NDC转换到真实的要输出的窗口或者屏幕坐标
背面剪裁是指将背对着camera的三角形剪裁掉
判断是否背对着camera
必须对3D space的顶点的深度值,纹理采样点等信息做差值,这需要 perspective correct interpolation,并不是线性差值,如果直接线性差值将会得到下面演示的错误情况。
下图就是对深度值错误的差值
上图中多边形中screen space各线性差值点对应的world space的z不是线性变化的,而1/z是线性变化的。
逐像素的计算最后输出的值,这里可以做跟颜色阴影相关的计算
所有没有被reject的输出都要写到back buffer。Blending就是在这个阶段做的。
参考自《3D游戏编程大师技巧》
z在屏幕空间中不呈线性变化,只有1/z才呈线性变化。
证明:
使用下述两点之间的差值公式:
其中p可以是向量,也可以是标量。
由下图推导出关系
在上图中,有两个位于世界坐标空间中的点,他们的坐标分别是 (y1,z1) ( y 1 , z 1 ) 和 (y2,z2) ( y 2 , z 2 ) 。将这些点投影到 z=d z = d 的视平面上,得到点 (p1,d) ( p 1 , d ) 和 (p2,d) ( p 2 , d ) 。另外,线段上任意一点 (y,z) ( y , z ) 投影到视平面上时得到点 (p,d) ( p , d ) 。
在3D空间中,位于y-z平面中(x=0)的直线的方程为:
将上述两个点以及其投影点带入上述等式,得到
这意味着可以在 1/z1 1 / z 1 和 1/z2 1 / z 2 之间进行线性差值。换句话说,任何3D空间量除以z的商在屏幕空间中都是呈线性变化的。