Direct3D基础--渲染管线
本文为 Introduction to 3D Game Programming with DirectX 11 读书笔记
Color的XNA实现
XMVECTOR XMLoadColor(CONST XMCOLOR* pSource);
VOID XMStoreColor(XMCOLOR* pDestination, FXMVECTOR V);
Rendering Pipline
Input Assembler stage
input assembler (IA) 阶段从内存中读取几何数据,然后用它组合成几何原型(三角形、线等)。
Primitive Topology
被传输到渲染管线的顶点集合称为 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... */
选择各种primitive_topology形成的基元
上图的图b表示带邻接三角形的三角形列表,adjacent triangles的表示也必须在输入中提供
Indices
因为直接重复的给顶点数据的话,顶点会有很大的冗余,所以使用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
};
The vertex shader stage
顶点着色器对顶点数据进行处理。
- 把对象从local坐标系转换到world坐标系
- 把world坐标系转换到camera坐标系(又称view space, eye space, or camera space),得到
在介绍View Space的时候,书上是先介绍的camera的坐标相对于world坐标的偏移,从view space到world space的转换矩阵 W,其中,那么world space到view space的转换矩阵就是
对Unity的shader比较熟悉的朋友可能会看到
#define COMPUTE_EYEDEPTH(o) o = -UnityObjectToViewPos( v.vertex ).z
这里取出z后在前面加了一个负号,这个还没有特别好的解释,关于这个计算
给定camera的坐标,构造camera坐标系统,因为world space的轴现在是作为标准的坐标,所以这里求出来的就是view space相对于world space的转换,再把上面说的考虑一下,就可以得到world space到view space的转矩阵了。
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);
Projection and Homogeneous Clip Space
齐次坐标
给定欧式平面上一点(x,y),对任意非零实数z,三元组(xZ,yZ,Z)即称之为该点的齐次坐标。与笛卡尔坐标不同,一个点可以有无限多个齐次坐标表示法。
- 当Z不为0,则表示欧氏平面上该点为(X/Z,Y/Z)
- 当Z为0,则该点表示一无穷远点
三元组(0,0,0)不表示任何点,原点表示为(0,0,1)。
Defining a Frustum
定义视椎体的4个数:*面,远平面,竖直视角(vertical field of view angle) ,aspect ratio .
aspect ratio : ,其中w和h分别为投影窗口的宽和高。
Projecting Vertices
给定视椎体中一个顶点,投影到*面上
公式如下:
如果一个点在视椎体中,那么就有:
标准设备空间 Normalized Device Coordinates (NDC)
为什么需要标准设备空间,因为从视椎体*面和camera的设置有关,屏幕空间和硬件屏幕有关,这两者之间需要一个桥梁来做转换,所以NDC非常有必要。
在NDC中只有满足如下条件,一个点在camera space才处于视椎体中(下面的z还没有标准化)
修改投影公式,使之直接将点映射到NDC
把投影等式写成矩阵形式
这里用了一个小trick,将过程分解为线性和非线性的两个部分。非线性部分就是最后除以z
则投影矩阵为,假设为常数
则对任意顶点
做完投影的线性变换之后,除以w维,。这就是非线性部分
除以有是被称为perspective divide或者homogeneous divide
标准化深度值
上面假设了A和B为常数,最后要将深度值从映射到上。
令,则必须满足
则
最终得到的正交投影矩阵(perspective projection matrix)为
XMMatrixPerspectiveFovLH
该投影矩阵也被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;
}
THE TESSELLATION STAGES
Tessellation是指把mesh的三角形再分割,添加新的三角形
THE GEOMETRY SHADER STAGE
geometry shader是可选的,它的主要优势是可以create or destroy geometry
剪裁 CLIPPING
将对象处在视椎体之外的部分剪裁掉
在homogeneous clip space中,在除以w之前,4D坐标为,则
THE RASTERIZATION STAGE(光栅化)
光栅化阶段主要是,从投影的3D三角形,计算pixel color
视口转换(Viewport Transform)
从NDC转换到真实的要输出的窗口或者屏幕坐标
背面剪裁(Backface Culling)
背面剪裁是指将背对着camera的三角形剪裁掉
判断是否背对着camera
法向量对着camera的是正面front face,否则就是back face
Vertex Attribute Interpolation
必须对3D space的顶点的深度值,纹理采样点等信息做差值,这需要 perspective correct interpolation,并不是线性差值,如果直接线性差值将会得到下面演示的错误情况。
下图就是对深度值错误的差值
上图中多边形中screen space各线性差值点对应的world space的z不是线性变化的,而1/z是线性变化的。
THE PIXEL SHADER STAGE
逐像素的计算最后输出的值,这里可以做跟颜色阴影相关的计算
THE OUTPUT MERGER STAGE
所有没有被reject的输出都要写到back buffer。Blending就是在这个阶段做的。
补充:透视纹理修正和1/z缓存
参考自《3D游戏编程大师技巧》
z在屏幕空间中不呈线性变化,只有1/z才呈线性变化。
证明:
使用下述两点之间的差值公式:
其中p可以是向量,也可以是标量。
由下图推导出关系
在上图中,有两个位于世界坐标空间中的点,他们的坐标分别是和。将这些点投影到的视平面上,得到点和。另外,线段上任意一点投影到视平面上时得到点。
在3D空间中,位于y-z平面中(x=0)的直线的方程为:
看上图,根据三角形相似可知,焦点和3D点是相似三角形上对应的点:
求解y,得到
,
带入到直线方程,得到
,
将等式两边除以,得到
,
等式两边取倒数,得到
,
将变量p与其他常量分离,得到
将上述两个点以及其投影点带入上述等式,得到
再带入到最上提到的差值公式(1),得到
做一下变换,得到
观察等式(2)与等式(3)的关系,使用替换右边的项,可以得到
这意味着可以在和之间进行线性差值。换句话说,任何3D空间量除以z的商在屏幕空间中都是呈线性变化的。