1357 字
7 分钟
DirectX 11 3D 核心原理与着色器
一. 3D 空间矩阵变换
之前提到了 MVP 变换,这里需要深入讲解为什么以及怎么做。这是 3D 引擎的数学心脏。
1 仿射变换 (Affine Transformation)
在 3D 图形学中,物体的运动(位移、旋转、缩放)都是通过4x4 矩阵来实现的。
- 齐次坐标 (Homogeneous Coordinates):为了让平移(加法)也能用矩阵乘法表示,我们将 3D 向量 扩展为 4D 向量 。
- 变换顺序:矩阵乘法不满足交换律。标准的变换顺序是 ** (SRT)**。
- 原理:先缩放(改变大小),再自转(改变朝向),最后平移(放到世界中的位置)。
- 如果先平移再缩放,物体会一边移动一边变大,产生错误的位移偏差。
2 摄像机 (The View Matrix)
在计算机图形学中,摄像机是不存在的。
- 相对运动原理:当你向左移动摄像机时,数学上等价于整个世界向右移动。
- LookAt 矩阵:你需要提供三个向量——眼位置 (Eye)、目标点 (At/Focus)、头顶朝向 (Up)。DirectXMath 库会通过向量叉乘(Cross Product)构建出一个正交基,算出一个矩阵,将世界里的所有物体搬到摄像机面前。
3 透视投影 (Perspective Projection)
为什么会有“近大远小”?这是由投影矩阵决定的。
- 视锥体 (Frustum):摄像机能看到的空间是一个被切掉尖顶的金字塔形状。
- FOV (Field of View):视野角度。
- 非线性深度:投影后的 Z 值不是均匀分布的。近处的精度极高,远处的精度极低。这就是为什么要设置
NearZ(0.01) 和FarZ(100.0)。如果NearZ设为 0,会导致 Z-Fighting(深度冲突,画面闪烁)。
二. CPU 与 GPU 的通信 —— 常量缓冲区
C++ 代码(CPU)怎么告诉 Shader(GPU)这一帧的内容?
1 Constant Buffer (常量缓冲区)
- 定义:这是一块 CPU 写、GPU 读的显存区域。
- 特点:
- 高频更新:通常每一帧、每个物体都会更新一次(UpdateSubresource)。
- 小容量:不像顶点缓冲存几万个点,常量缓冲通常只存几个矩阵和光照参数。
2 16 字节对齐 (16-Byte Alignment)
GPU 的寄存器是 SIMD(单指令多数据)架构,每个寄存器宽 128 位(16 字节,即 float4)。
- HLSL 规则:如果你的变量跨越了 16 字节的边界,它会被强制推到下一个寄存器。
- 后果:如果 C++ 的结构体没有手动填充(Padding)对齐,C++ 传过去的数据会和 Shader 里读到的数据错位。
- 规范:在 C++ 结构体中,尽量使用
DirectX::XMMATRIX或XMFLOAT4,确保数据紧凑且对齐。
三. 纹理采样与过滤
如何把一张 2D 的纹理图贴在 3D 的球面上?
1 UV 坐标系
- 归一化:无论纹理图片是 1024x1024 还是 256x256,UV 坐标永远是 。
- 寻址模式 (Address Mode):
- 如果 UV 超过了 1.0 怎么办?
- Wrap (重复):像铺地砖一样重复纹理。
- Clamp (截断):超过部分强制取边缘颜色。
2 纹理过滤 (Texture Filtering)
当 3D 物体离摄像机很近或很远时,一个屏幕像素可能对应纹理上的 0.1 个像素,也可能对应 100 个像素。
- Minification (缩小):物体很远。多个纹素挤在一个像素里。如果不处理,会产生摩尔纹 (Moiré pattern) 和闪烁。
- Magnification (放大):物体很近。一个纹素要覆盖多个像素。如果不处理,会看到马赛克。
- 双线性插值 (Bilinear):取周围 4 个纹素的加权平均值。解决马赛克问题,使画面模糊变平滑。
- 各向异性过滤 (Anisotropic):解决侧视物体(如地面)纹理模糊的问题。
四. 深度测试 —— 遮挡剔除
显卡如何保证不会发生透视,透过前面的物体看到后面的模型?
1 Z-Buffer (深度缓冲)
- 这是一张和屏幕分辨率一样大的“隐形图片”。
- 它不存颜色,只存每个像素当前的深度值(0.0 代表最近,1.0 代表最远)。
2 深度测试流程
当像素着色器算出一个像素的颜色和深度 时:
- 比较:读取深度缓冲中该位置已有的深度 。
- 判断:
- 如果 :说明新像素离镜头更近。写入颜色,并更新深度缓冲。
- 如果 :说明新像素被前面的物体挡住了。丢弃 (Discard),什么都不做。
这也是为什么每帧开始要 ClearDepthStencilView,把深度全部重置为 1.0(无限远)。
五. 着色器编程 (HLSL)
High Level Shading Language (HLSL) 是运行在显卡上的 C 语言变体。
1 语义 (Semantics)
在 HLSL 中,变量名不重要,重要的是冒号后面的标签。
float4 Pos : POSITION:告诉 GPU,这个变量存放的是顶点位置。float2 Tex : TEXCOORD:告诉 GPU,这个变量存放的是纹理坐标。float4 Pos : SV_POSITION:System Value。这是 Vertex Shader 的必须输出。它告诉光栅化器:“这就是我要你在屏幕上画的位置”。
2 插值器 (Interpolator)
从 Vertex Shader 输出的数据,传到 Pixel Shader 时,会发生线性插值。
- 如果你在 VS 中输出:顶点A是红色,顶点B是绿色。
- 在 PS 中:A 和 B 连线中间的点,会自动变成渐变的黄色。
- 这种机制构成了现代图形学平滑着色的基础。
DirectX 11 3D 核心原理与着色器
https://www.m4doka.xyz/posts/dx11/dx11-2/