Direct3D基础--渲染管线

本文深入探讨3D游戏编程中的渲染管线,包括输入组装、顶点着色、投影及齐次裁剪空间等核心概念。同时介绍了如何利用DirectX 11进行3D场景渲染,并详细讲解了光栅化、像素着色等多个阶段的技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文为 Introduction to 3D Game Programming with DirectX 11 读书笔记

Color的XNA实现
XMVECTOR XMLoadColor(CONST XMCOLOR* pSource);

VOID XMStoreColor(XMCOLOR* pDestination, FXMVECTOR V);
Rendering Pipline

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形成的基元
primitive topology

triangle 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
};

An octagon built from eight triangles

The vertex shader stage

顶点着色器对顶点数据进行处理。

Local to World

View Transform

  • 把对象从local坐标系转换到world坐标系
  • 把world坐标系转换到camera坐标系(又称view space, eye space, or camera space),得到

在介绍View Space的时候,书上是先介绍的camera的坐标相对于world坐标的偏移,从view space到world space的转换矩阵 W,其中 W = R T W=RT W=RT,那么world space到view space的转换矩阵就是
W = [ u x u y u z 0 v x v y v z 0 w x w y w z 0 Q x Q y Q z 1 ] W = \begin{bmatrix} u_x & u_y & u_z & 0\\ v_x & v_y & v_z & 0\\ w_x & w_y & w_z & 0\\ Q_x & Q_y & Q_z & 1 \end{bmatrix} W=uxvxwxQxuyvywyQyuzvzwzQz0001
V = W − 1 = ( R T ) − 1 = T − 1 R − 1 = T − 1 R T = [ 1 0 0 0 0 1 0 0 0 0 1 0 − Q x − Q y − Q z 1 ] [ u x v x w x 0 u y v y w y 0 u z v z w z 0 0 0 0 1 ] = [ u x v x w x 0 u y v y w y 0 u z v z w z 0 − Q x − Q y − Q z 1 ] V = W^{-1}=(RT)^{-1}=T^{-1}R^{-1}=T^{-1}R^T\\ =\begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ -Q_x & -Q_y & -Q_z & 1 \end{bmatrix} \begin{bmatrix} u_x & v_x & w_x & 0\\ u_y & v_y & w_y & 0\\ u_z & v_z & w_z & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} =\begin{bmatrix} u_x & v_x & w_x & 0\\ u_y & v_y & w_y & 0\\ u_z & v_z & w_z & 0\\ -Q_x & -Q_y & -Q_z & 1 \end{bmatrix} V=W1=(RT)1=T1R1=T1RT=100Qx010Qy001Qz0001uxuyuz0vxvyvz0wxwywz00001=uxuyuzQxvxvyvzQywxwywzQz0001

对Unity的shader比较熟悉的朋友可能会看到
#define COMPUTE_EYEDEPTH(o) o = -UnityObjectToViewPos( v.vertex ).z
这里取出z后在前面加了一个负号,这个还没有特别好的解释,关于这个计算

给定camera坐标,构造camera坐标系统
给定camera的坐标,构造camera坐标系统,因为world space的轴现在是作为标准的坐标,所以这里求出来的就是view space相对于world space的转换,再把上面说的 V = W − 1 V=W^{-1} V=W1考虑一下,就可以得到world space到view space的转矩阵了。 j = ( 0 , 1 , 0 ) j = (0, 1, 0) j=(0,1,0)
w = T − Q ∣ ∣ T − Q ∣ ∣ u = j × w ∣ ∣ j × w ∣ ∣ v = w × u w = \frac{T-Q}{||T-Q||}\\ u = \frac{j \times w}{||j \times w||}\\ v = w \times u w=TQTQu=j×wj×wv=w×u

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个数:近平面 n n n,远平面 f f f,竖直视角(vertical field of view angle) α \alpha α,aspect ratio r r r.
aspect ratio : r = w / h r=w/h r=w/h,其中w和h分别为投影窗口的宽和高。

XZ轴视椎体
YZ轴视椎体

r = w h = w 2 ⇒ w = 2 r tan ⁡ ( α 2 ) = 1 d ⇒ d = cot ⁡ ( α 2 ) tan ⁡ ( β 2 ) = r d = r cot ⁡ ( α 2 ) = r ⋅ tan ⁡ ( α 2 ) horzontal field of view angle  β β = 2 tan ⁡ − 1 ( r ⋅ tan ⁡ ( α 2 ) ) r = \frac{w}{h} = \frac{w}{2} \Rightarrow w = 2r\\ \tan(\frac{\alpha}{2})=\frac{1}{d} \Rightarrow d=\cot(\frac{\alpha}{2})\\ \tan(\frac{\beta}{2})=\frac{r}{d}=\frac{r}{\cot(\frac{\alpha}{2})}=r \cdot \tan(\frac{\alpha}{2})\\ \text{horzontal field of view angle } \beta\\ \beta = 2\tan^{-1}(r \cdot \tan(\frac{\alpha}{2})) r=hw=2ww=2rtan(2α)=d1d=cot(2α)tan(2β)=dr=cot(2α)r=rtan(2α)horzontal field of view angle ββ=2tan1(rtan(2α))

Projecting Vertices

YZ投影
XZ投影
给定视椎体中一个顶点 ( x , y , z ) (x,y,z) (x,y,z),投影到近平面上 ( x ′ , y ′ , d ) (x^\prime,y^\prime,d) (x,y,d)
公式如下:
x ′ d = x z ⇒ x ′ = x d z = x cot ⁡ ( α / 2 ) z = x z tan ⁡ ( α / 2 ) y ′ d = y z ⇒ y ′ = y d z = y cot ⁡ ( α / 2 ) z = y z tan ⁡ ( α / 2 ) \frac{x^\prime}{d} = \frac{x}{z} \Rightarrow x^\prime = \frac{xd}{z} = \frac{x\cot(\alpha/2)}{z}=\frac{x}{z\tan(\alpha/2)}\\ \frac{y^\prime}{d} = \frac{y}{z} \Rightarrow y^\prime = \frac{yd}{z} = \frac{y\cot(\alpha/2)}{z}=\frac{y}{z\tan(\alpha/2)} dx=zxx=zxd=zxcot(α/2)=ztan(α/2)xdy=zyy=zyd=zycot(α/2)=ztan(α/2)y
如果一个点在视椎体中,那么就有:
− r ≤ x ′ ≤ r − 1 ≤ y ′ ≤ 1 n ≤ z ≤ f -r \leq x^\prime \leq r\\ -1 \leq y^\prime \leq 1\\ n \leq z \leq f rxr1y1nzf

标准设备空间 Normalized Device Coordinates (NDC)

为什么需要标准设备空间,因为从视椎体近平面和camera的设置有关,屏幕空间和硬件屏幕有关,这两者之间需要一个桥梁来做转换,所以NDC非常有必要。

在NDC中只有满足如下条件,一个点在camera space才处于视椎体中(下面的z还没有标准化)
− 1 ≤ x ′ / r ≤ 1 − 1 ≤ y ′ ≤ 1 n ≤ z ≤ f -1 \leq x^\prime/r \leq 1\\ -1 \leq y^\prime \leq 1\\ n \leq z \leq f 1x/r11y1nzf
修改投影公式,使之直接将点映射到NDC
x ′ = x r z tan ⁡ ( α / 2 ) y ′ = y z tan ⁡ ( α / 2 ) x^\prime = \frac{x}{rz\tan(\alpha/2)}\\ y^\prime = \frac{y}{z\tan(\alpha/2)} x=rztan(α/2)xy=ztan(α/2)y

把投影等式写成矩阵形式

这里用了一个小trick,将过程分解为线性和非线性的两个部分。非线性部分就是最后除以z

则投影矩阵为,假设 A , B A,B A,B为常数
P = [ 1 r tan ⁡ ( α / 2 ) 0 0 0 0 1 tan ⁡ ( α / 2 ) 0 0 0 0 A 1 0 0 B 0 ] P = \begin{bmatrix} \frac{1}{r\tan(\alpha/2)} & 0 & 0 & 0\\ 0 & \frac{1}{\tan(\alpha/2)} & 0 & 0\\ 0 & 0 & A & 1\\ 0 & 0 & B & 0 \end{bmatrix} P=rtan(α/2)10000tan(α/2)10000AB0010
则对任意顶点 ( x , y , z , 1 ) (x,y,z,1) (x,y,z,1)
[ x , y , z , 1 ] [ 1 r tan ⁡ ( α / 2 ) 0 0 0 0 1 tan ⁡ ( α / 2 ) 0 0 0 0 A 1 0 0 B 0 ] = [ x r tan ⁡ ( α / 2 ) , y tan ⁡ ( α / 2 ) , A z + B , z ] \begin{bmatrix} x, & y, & z, & 1 \end{bmatrix} \begin{bmatrix} \frac{1}{r\tan(\alpha/2)} & 0 & 0 & 0\\ 0 & \frac{1}{\tan(\alpha/2)} & 0 & 0\\ 0 & 0 & A & 1\\ 0 & 0 & B & 0 \end{bmatrix} =\begin{bmatrix} \frac{x}{r\tan(\alpha/2)} , & \frac{y}{\tan(\alpha/2)}, & Az+B, & z \end{bmatrix} [x,y,z,1]rtan(α/2)10000tan(α/2)10000AB0010=[rtan(α/2)x,tan(α/2)y,Az+B,z]
做完投影的线性变换之后,除以w维, w = z w=z w=z。这就是非线性部分
[ x r tan ⁡ ( α / 2 ) , y tan ⁡ ( α / 2 ) , A z + B , z ] → divide by w [ x r z tan ⁡ ( α / 2 ) , y z tan ⁡ ( α / 2 ) , A + B z , 1 ] \begin{bmatrix} \frac{x}{r\tan(\alpha/2)} , & \frac{y}{\tan(\alpha/2)}, & Az+B, & z \end{bmatrix} \overset{\text{divide by w}}{\rightarrow} \begin{bmatrix} \frac{x}{rz\tan(\alpha/2)} , & \frac{y}{z\tan(\alpha/2)}, & A+\frac{B}{z}, & 1 \end{bmatrix} [rtan(α/2)x,tan(α/2)y,Az+B,z]divide by w[rztan(α/2)x,ztan(α/2)y,A+zB,1]
除以 w w w有是被称为perspective divide或者homogeneous divide

标准化深度值

上面假设了A和B为常数,最后要将深度值从 [ n , f ] [n,f] [n,f]映射到 [ 0 , 1 ] [0,1] [0,1]上。
g ( z ) = A + B z g(z)=A+\frac{B}{z} g(z)=A+zB,则必须满足 g ( n ) = 0 , g ( f ) = 1 g(n)=0,g(f)=1 g(n)=0,g(f)=1

A = f f − n B = − n f f − n A=\frac{f}{f-n}\\ B=\frac{-nf}{f-n} A=fnfB=fnnf
最终得到的正交投影矩阵(perspective projection matrix)为
P = [ 1 r tan ⁡ ( α / 2 ) 0 0 0 0 1 tan ⁡ ( α / 2 ) 0 0 0 0 f f − n 1 0 0 − n f f − n 0 ] P = \begin{bmatrix} \frac{1}{r\tan(\alpha/2)} & 0 & 0 & 0\\ 0 & \frac{1}{\tan(\alpha/2)} & 0 & 0\\ 0 & 0 & \frac{f}{f-n} & 1\\ 0 & 0 & \frac{-nf}{f-n} & 0 \end{bmatrix} P=rtan(α/2)10000tan(α/2)10000fnffnnf0010

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的三角形再分割,添加新的三角形
after tessellation.

THE GEOMETRY SHADER STAGE

geometry shader是可选的,它的主要优势是可以create or destroy geometry

剪裁 CLIPPING

将对象处在视椎体之外的部分剪裁掉
clipping例子
clipping三角形
在homogeneous clip space中,在除以w之前,4D坐标为 ( x , y , z , w ) (x,y,z,w) (x,y,z,w),则
− w ≤ x ≤ w − w ≤ y ≤ w 0 ≤ z ≤ w -w \leq x \leq w\\ -w \leq y \leq w\\ 0 \leq z \leq w wxwwyw0zw

THE RASTERIZATION STAGE(光栅化)

光栅化阶段主要是,从投影的3D三角形,计算pixel color

视口转换(Viewport Transform)

从NDC转换到真实的要输出的窗口或者屏幕坐标

背面剪裁(Backface Culling)

背面剪裁是指将背对着camera的三角形剪裁掉
判断是否背对着camera
e 0 = v 1 − v 0 e 1 = v 2 − v 1 n = e 0 × e 1 ∣ ∣ e 0 × e 1 ∣ ∣ e_0 = v_1 - v_0\\ e_1 = v_2 - v_1\\ n = \frac{e_0 \times e1}{||e_0 \times e_1||} e0=v1v0e1=v2v1n=e0×e1e0×e1
法向量对着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 = ( 1 − t ) ∗ p 1 + ( t ) ∗ p 2 (1) p=(1-t)*p1+(t)*p2 \tag 1 p=(1t)p1+(t)p2(1)

其中p可以是向量,也可以是标量。
由下图推导出关系
线段差值和透视投影
在上图中,有两个位于世界坐标空间中的点,他们的坐标分别是 ( y 1 , z 1 ) (y1,z1) (y1,z1) ( y 2 , z 2 ) (y2,z2) (y2,z2)。将这些点投影到 z = d z=d z=d的视平面上,得到点 ( p 1 , d ) (p1,d) (p1,d) ( p 2 , d ) (p2,d) (p2,d)。另外,线段上任意一点 ( y , z ) (y,z) (y,z)投影到视平面上时得到点 ( p , d ) (p,d) (p,d)

在3D空间中,位于y-z平面中(x=0)的直线的方程为:
a ∗ y + b ∗ z = c a*y+b*z=c ay+bz=c
看上图,根据三角形相似可知,焦点 ( p , d ) (p,d) (p,d)和3D点 ( y , z ) (y,z) (y,z)是相似三角形上对应的点:
p / y = d / z p/y=d/z p/y=d/z
求解y,得到
y = ( p / d ) ∗ z y=(p/d)*z y=(p/d)z
带入到直线方程,得到
( a ∗ p / d ) ∗ z + b ∗ z = c (a*p/d)*z+b*z=c (ap/d)z+bz=c
将等式两边除以 ( a ∗ p / d ) (a*p/d) (ap/d),得到
z = c / ( ( a ∗ p / d ) + b ) z=c/((a*p/d)+b) z=c/((ap/d)+b)
等式两边取倒数,得到
1 / z = ( ( a ∗ p / d ) + b ) / c = ( a ∗ p / c ∗ d ) + ( b / c ) 1/z=((a*p/d)+b)/c=(a*p/c*d)+(b/c) 1/z=((ap/d)+b)/c=(ap/cd)+(b/c)
将变量p与其他常量分离,得到
1 / z = ( a / c ∗ d ) ∗ p + ( b / c ) (2) 1/z=(a/c*d)*p+(b/c) \tag 2 1/z=(a/cd)p+(b/c)(2)

将上述两个点以及其投影点带入上述等式,得到
1 / z 1 = ( a / c ∗ d ) ∗ p 1 + ( b / c ) 1 / z 2 = ( a / c ∗ d ) ∗ p 2 + ( b / c ) 1/z1=(a/c*d)*p1+(b/c)\\ 1/z2=(a/c*d)*p2+(b/c) 1/z1=(a/cd)p1+(b/c)1/z2=(a/cd)p2+(b/c)
再带入到最上提到的差值公式(1),得到
1 / z = ( a / c ∗ d ) [ ( 1 − t ) ∗ p 1 + ( t ) ∗ p 2 ] + ( b / c ) 1/z=(a/c*d)[(1-t)*p1+(t)*p2]+(b/c) 1/z=(a/cd)[(1t)p1+(t)p2]+(b/c)
做一下变换,得到
1 / z = [ ( a / c ∗ d ) ∗ p 1 + ( b / c ) ] ∗ ( 1 − t ) + [ ( a / c ∗ d ) ∗ p 2 + ( b / c ) ] ∗ ( t ) (3) 1/z=[(a/c*d)*p1+(b/c)]*(1-t)+[(a/c*d)*p2+(b/c)]*(t) \tag 3 1/z=[(a/cd)p1+(b/c)](1t)+[(a/cd)p2+(b/c)](t)(3)
观察等式(2)与等式(3)的关系,使用 1 / z 1 1/z1 1/z1替换右边的项,可以得到
1 / z = [ ( 1 / z 1 ) ] ∗ ( 1 − t ) + [ ( 1 / z 2 ) ] ∗ ( t ) 1/z=[(1/z1)]*(1-t)+[(1/z2)]*(t) 1/z=[(1/z1)](1t)+[(1/z2)](t)

这意味着可以在 1 / z 1 1/z1 1/z1 1 / z 2 1/z2 1/z2之间进行线性差值。换句话说,任何3D空间量除以z的商在屏幕空间中都是呈线性变化的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值