本章深入探讨计算机图形学中的高级着色技术。我们将从插值技术的数学基础开始,深入理解GPU如何在三角形内部进行属性插值。随后,我们将探索高级纹理映射技术,包括MIP映射、各向异性过滤等现代GPU技术。最后,我们将学习程序化纹理生成和着色器优化技术,为实现高性能渲染打下基础。
在三角形内部进行插值是光栅化的核心操作。对于三角形ABC和内部点P,重心坐标$(u, v, w)$定义为:
\[P = u \cdot A + v \cdot B + w \cdot C\]其中 $u + v + w = 1$。
重心坐标具有直观的几何解释:
重心坐标的物理意义可以理解为质点系统:如果在三个顶点分别放置质量$u$、$v$、$w$的质点,则系统的质心恰好位于点P。这解释了”重心”这一名称的由来。
点P在三角形内部的充要条件是: \(u \geq 0, \quad v \geq 0, \quad w \geq 0\)
当某个坐标为0时,P位于对应顶点的对边上;当两个坐标为0时,P位于相应的顶点上。
方法1:面积比
\[u = \frac{\text{Area}(PBC)}{\text{Area}(ABC)}, \quad v = \frac{\text{Area}(APC)}{\text{Area}(ABC)}, \quad w = \frac{\text{Area}(ABP)}{\text{Area}(ABC)}\]面积计算使用叉积:对于2D三角形ABC,其有向面积为: \(\text{Area}(ABC) = \frac{1}{2}(B_x - A_x)(C_y - A_y) - (C_x - A_x)(B_y - A_y)\)
方法2:叉积形式
对于2D情况: \(u = \frac{(B-P) \times (C-P)}{(B-A) \times (C-A)}\)
这里的叉积理解为2D伪叉积:$(x_1, y_1) \times (x_2, y_2) = x_1y_2 - y_1x_2$
对于3D情况,需要投影到合适的平面: \(u = \frac{|(B-P) \times (C-P) \cdot \mathbf{n}|}{|(B-A) \times (C-A) \cdot \mathbf{n}|}\)
其中$\mathbf{n}$是三角形法向量。投影的选择原则:
| 例如:若$ | n_z | $最大,则投影到XY平面 |
方法3:直接求解线性系统
设$P = (p_x, p_y)$,可以建立方程: \(\begin{pmatrix} a_x & b_x & c_x \\ a_y & b_y & c_y \\ 1 & 1 & 1 \end{pmatrix} \begin{pmatrix} u \\ v \\ w \end{pmatrix} = \begin{pmatrix} p_x \\ p_y \\ 1 \end{pmatrix}\)
使用Cramer法则求解: \(u = \frac{\begin{vmatrix} p_x & b_x & c_x \\ p_y & b_y & c_y \\ 1 & 1 & 1 \end{vmatrix}}{\begin{vmatrix} a_x & b_x & c_x \\ a_y & b_y & c_y \\ 1 & 1 & 1 \end{vmatrix}}\)
在实际实现中,需要考虑:
| 检测条件:$ | \text{Area}(ABC) | < \epsilon$ |
对于光栅化中的大量计算,可以使用增量算法: \(u(x+1, y) = u(x, y) + \Delta u_x\) \(u(x, y+1) = u(x, y) + \Delta u_y\)
其中$\Delta u_x$和$\Delta u_y$是常数,可预计算:
边函数方法: 定义边函数$E_{AB}(x, y) = (y_A - y_B)x + (x_B - x_A)y + x_A y_B - x_B y_A$
则:
重心坐标通过边函数计算: \(u = \frac{E_{BC}(x, y)}{E_{BC}(x_A, y_A)}\)
并行化策略:
屏幕空间的线性插值在透视投影下会产生错误。这是因为透视投影是非线性变换,破坏了属性的线性关系。
考虑世界空间中的线段,其属性(如纹理坐标)线性变化。经过透视投影后:
直观例子: 想象一条铁轨向远处延伸。铁轨枕木在世界空间中等距分布,但在屏幕空间中,远处的枕木间距明显变小。如果使用屏幕空间线性插值,会错误地认为远处枕木更密集。
数学表示: 设世界空间参数$t \in [0,1]$,屏幕空间参数$s \in [0,1]$,它们的关系为: \(s = \frac{t/z_0}{t/z_0 + (1-t)/z_1}\)
这是一个有理函数,而非线性关系。
设顶点属性为$\phi_0, \phi_1, \phi_2$,视空间深度为$z_0, z_1, z_2$。
关键观察:在齐次坐标系中,属性$\phi/w$(其中$w=z$)保持线性。
推导步骤:
齐次空间的线性插值: \(\left(\frac{\phi}{z}\right)_p = u\frac{\phi_0}{z_0} + v\frac{\phi_1}{z_1} + w\frac{\phi_2}{z_2}\)
深度的倒数也线性插值: \(\frac{1}{z_p} = u\frac{1}{z_0} + v\frac{1}{z_1} + w\frac{1}{z_2}\)
恢复原始属性: \(\phi_p = \frac{\left(\frac{\phi}{z}\right)_p}{\frac{1}{z_p}} = \frac{u\frac{\phi_0}{z_0} + v\frac{\phi_1}{z_1} + w\frac{\phi_2}{z_2}}{u\frac{1}{z_0} + v\frac{1}{z_1} + w\frac{1}{z_2}}\)
硬件透视校正流程:
特殊情况处理:
// 需要屏幕空间导数的计算
vec2 dx = dFdx(texcoord); // 硬件自动处理透视校正
vec2 dy = dFdy(texcoord); // 用于MIP级别计算
屏幕空间属性:某些属性(如屏幕空间导数)不需要透视校正,使用noperspective限定符:
noperspective out vec2 screen_coord;
常见错误案例:
现代GPU使用专门的插值器硬件,这是固定功能管线的关键组件之一。
参数缓存:
插值管线:
顶点属性 → 参数计算 → 参数缓存 → 片元插值 → 属性输出
↓ ↑
梯度计算单元 扫描线生成器
对于属性$\phi$在屏幕坐标$(x,y)$的值:
\[\phi(x,y) = \phi_0 + \frac{\partial \phi}{\partial x}(x-x_0) + \frac{\partial \phi}{\partial y}(y-y_0)\]梯度计算: \(\frac{\partial \phi}{\partial x} = \frac{(\phi_1 - \phi_0)(y_2 - y_0) - (\phi_2 - \phi_0)(y_1 - y_0)}{(x_1 - x_0)(y_2 - y_0) - (x_2 - x_0)(y_1 - y_0)}\)
扫描线遍历优化:
// 沿扫描线的增量
for (x = x_start; x <= x_end; x++) {
phi += dphi_dx;
inv_w += dinv_w_dx;
}
// 换行时的调整
phi_row += dphi_dy;
inv_w_row += dinv_w_dy;
2×2 Quad并行:
导数计算: \(\frac{\partial \phi}{\partial x} \approx \phi_{x+1,y} - \phi_{x,y}\) \(\frac{\partial \phi}{\partial y} \approx \phi_{x,y+1} - \phi_{x,y}\)
定点数优化:
属性压缩:
缓存优化策略:
线性插值可能不足以表示复杂的属性变化,特别是在曲面渲染和高质量着色中。
Bézier三角形表示: 对于二次Bézier三角形,使用6个控制点: \(\phi(u,v,w) = \sum_{i+j+k=2} \binom{2}{i,j,k} u^i v^j w^k \phi_{ijk}\)
其中$\binom{2}{i,j,k} = \frac{2!}{i!j!k!}$是多项式系数。
展开形式: \(\phi(u,v,w) = u^2\phi_{200} + v^2\phi_{020} + w^2\phi_{002} + 2uv\phi_{110} + 2vw\phi_{011} + 2uw\phi_{101}\)
控制点布局:
φ200
/ \
φ110 φ101
/ \
φ020--φ011--φ002
PN三角形(Point-Normal Triangles): 用于几何细分和光滑表面:
几何控制点: \(b_{ijk} = \frac{i\mathbf{P}_1 + j\mathbf{P}_2 + k\mathbf{P}_3}{3} + \frac{ij}{3}\mathbf{w}_{12} + \frac{jk}{3}\mathbf{w}_{23} + \frac{ki}{3}\mathbf{w}_{31}\)
其中$\mathbf{w}_{ij}$是修正向量。
法线插值: 使用二次插值保证$C^1$连续性
NURBS三角形: \(\phi(u,v,w) = \frac{\sum_{i+j+k=n} w_{ijk} \phi_{ijk} B_{ijk}^n(u,v,w)}{\sum_{i+j+k=n} w_{ijk} B_{ijk}^n(u,v,w)}\)
其中$w_{ijk}$是权重,$B_{ijk}^n$是Bernstein基函数。
误差驱动细分: 根据插值误差动态选择插值阶数:
\[E = \max_{\mathbf{p} \in T} |\phi_{linear}(\mathbf{p}) - \phi_{exact}(\mathbf{p})|\]当$E > \epsilon$时,使用高阶插值或细分三角形。
存储开销:
计算复杂度:
硬件支持:
应用场景:
纹理映射是计算机图形学中增加表面细节的核心技术。本节深入探讨高级纹理技术,包括坐标生成、过滤算法、压缩格式等现代GPU的关键特性。
纹理坐标是将二维图像映射到三维几何体的桥梁。不同的生成方法适用于不同的几何形状和应用场景。
UV展开算法:
| 能量函数:$E = \sum_T A_T \cdot | \nabla u + i\nabla v | ^2$ |
平面投影: \(u = \frac{(\mathbf{p} - \mathbf{o}) \cdot \mathbf{u}_{axis}}{scale_u}\) \(v = \frac{(\mathbf{p} - \mathbf{o}) \cdot \mathbf{v}_{axis}}{scale_v}\)
应用场景:
圆柱投影: \(u = \frac{1}{2\pi} \arctan2(y, x) + 0.5\) \(v = \frac{z - z_{min}}{z_{max} - z_{min}}\)
接缝处理:
球面投影: \(u = \frac{1}{2\pi} \arctan2(y, x) + 0.5\) \(v = \frac{1}{\pi} \arccos\left(\frac{z}{|\mathbf{r}|}\right)\)
奇异点处理:
立方体投影优化: 避免三角函数的快速方法: \(u = 0.5 + 0.5 \times \frac{-d_z}{|d_x|}\)(对于+X面)
立方体贴图(Cube Mapping):
absX = |direction.x|
absY = |direction.y|
absZ = |direction.z|
if (absX >= absY && absX >= absZ) {
face = (direction.x > 0) ? +X : -X
}
// 类似处理Y和Z
球面环境贴图:
三平面映射(Triplanar Mapping):
vec3 blendWeights = abs(normal);
blendWeights = normalize(max(blendWeights, 0.00001));
vec3 xaxis = texture(tex, position.yz) * blendWeights.x;
vec3 yaxis = texture(tex, position.xz) * blendWeights.y;
vec3 zaxis = texture(tex, position.xy) * blendWeights.z;
return xaxis + yaxis + zaxis;
投影纹理: 从光源视角投影: \(\mathbf{uv} = \frac{1}{2} + \frac{1}{2} \times \frac{\mathbf{P}_{light}}{w_{light}}\)
仿射变换: \(\begin{pmatrix} u' \\ v' \end{pmatrix} = \begin{pmatrix} a & b \\ c & d \end{pmatrix} \begin{pmatrix} u \\ v \end{pmatrix} + \begin{pmatrix} t_x \\ t_y \end{pmatrix}\)
透视变换: \(u' = \frac{au + bv + c}{gu + hv + 1}\) \(v' = \frac{du + ev + f}{gu + hv + 1}\)
MIP(Multum In Parvo,”多在小中”)映射是解决纹理走样的核心技术。它通过预计算不同分辨率的纹理金字塔,在渲染时选择合适的级别。
欠采样(Undersampling):
Nyquist-Shannon采样定理: 要完整重建信号,采样频率必须至少是信号最高频率的两倍: \(f_s \geq 2f_{max}\)
在纹理映射中:
过采样(Oversampling):
屏幕空间导数: 使用雅可比矩阵计算纹理坐标的变化率:
\[\mathbf{J} = \begin{pmatrix} \frac{\partial u}{\partial x} & \frac{\partial u}{\partial y} \\ \frac{\partial v}{\partial x} & \frac{\partial v}{\partial y} \end{pmatrix}\]像素覆盖区域估计:
各向同性估计(标准MIP): \(\rho = \max\left(||\mathbf{J} \cdot (1, 0)^T||, ||\mathbf{J} \cdot (0, 1)^T||\right)\)
展开形式: \(\rho = \max\left(\sqrt{\left(\frac{\partial u}{\partial x}\right)^2 + \left(\frac{\partial v}{\partial x}\right)^2}, \sqrt{\left(\frac{\partial u}{\partial y}\right)^2 + \left(\frac{\partial v}{\partial y}\right)^2}\right)\)
完整LOD计算: \(\text{LOD} = \log_2(\rho \cdot \text{textureSize})\)
直观理解:如果一个屏幕像素覆盖了$2^n$个纹素,应该使用第$n$级MIP。
LOD偏移与约束: \(\text{LOD}_{final} = \text{clamp}(\text{LOD} + \text{bias}, \text{minLOD}, \text{maxLOD})\)
滤波核选择:
简单平均(Box Filter): \(\text{MIP}_{i+1}(x,y) = \frac{1}{4}\sum_{dx,dy \in \{0,1\}} \text{MIP}_i(2x+dx, 2y+dy)\)
高斯滤波: \(w(x,y) = \frac{1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}}\)
Lanczos滤波: \(L(x) = \begin{cases} \text{sinc}(x) \cdot \text{sinc}(x/a) & |x| < a \\ 0 & \text{otherwise} \end{cases}\)
边界处理:
在两个MIP级别之间进行线性插值:
\[\text{color} = (1-\alpha) \cdot \text{sample}(\lfloor\text{LOD}\rfloor, u, v) + \alpha \cdot \text{sample}(\lceil\text{LOD}\rceil, u, v)\]其中$\alpha = \text{fract}(\text{LOD})$。
显式导数传递:
vec4 textureGrad(sampler2D tex, vec2 uv, vec2 dPdx, vec2 dPdy)
自动导数计算: GPU在2×2 quad内使用差分: \(\frac{\partial f}{\partial x} \approx f(x+1, y) - f(x, y)\)
内存占用: 完整MIP链增加约33%的存储: \(\text{Total} = \text{Base} \times \sum_{i=0}^{\infty} \frac{1}{4^i} = \frac{4}{3} \times \text{Base}\)
紧凑布局:
+--------+----+--+-+
| Level0 | L1 |L2|3|
| +----+--+-+
| | L1 |L2|3|
+--------+----+--+-+
各向异性过滤解决了标准MIP映射在倾斜视角下的模糊问题。当纹理在不同方向上拉伸程度不同时,各向同性的MIP滤波会过度模糊。
像素在纹理空间的投影: 屏幕像素在纹理空间形成一个近似椭圆的区域。
雅可比矩阵: \(\mathbf{J} = \begin{pmatrix} \frac{\partial u}{\partial x} & \frac{\partial u}{\partial y} \\ \frac{\partial v}{\partial x} & \frac{\partial v}{\partial y} \end{pmatrix}\)
形状矩阵: \(\mathbf{M} = \mathbf{J}^T \mathbf{J} = \begin{pmatrix} E & F \\ F & G \end{pmatrix}\)
其中:
特征值分解: \(\lambda_{1,2} = \frac{E + G \pm \sqrt{(E-G)^2 + 4F^2}}{2}\)
椭圆主轴:
各向异性比: \(r = \frac{a}{b} = \sqrt{\frac{\lambda_{max}}{\lambda_{min}}}\)
椭圆加权平均(EWA): \(c = \frac{\sum_{(s,t) \in E} w(s,t) \cdot \text{texture}(s,t)}{\sum_{(s,t) \in E} w(s,t)}\)
高斯权重函数: \(w(s,t) = \exp\left(-\frac{1}{2}(s,t)\mathbf{M}^{-1}(s,t)^T\right)\)
1. Ripmap:
2. 多采样近似:
vec4 anisotropicSample(sampler2D tex, vec2 uv, vec2 ddx, vec2 ddy) {
float maxAniso = 16.0;
// 计算各向异性方向
vec2 dx = ddx * textureSize(tex, 0);
vec2 dy = ddy * textureSize(tex, 0);
float px = dot(dx, dx);
float py = dot(dy, dy);
float maxLod = 0.5 * log2(max(px, py));
float minLod = 0.5 * log2(min(px, py));
float anisoRatio = min(2.0 * (maxLod - minLod), log2(maxAniso));
float samples = exp2(anisoRatio);
// 沿主轴采样
vec2 majorAxis = (px > py) ? normalize(ddx) : normalize(ddy);
vec4 color = vec4(0.0);
for (float i = 0; i < samples; i++) {
float t = (i + 0.5) / samples - 0.5;
vec2 offset = t * majorAxis * length(ddx + ddy);
color += textureLod(tex, uv + offset, minLod);
}
return color / samples;
}
3. 硬件实现策略:
自适应采样:
LOD偏移优化: \(\text{LOD}_{aniso} = \text{LOD}_{iso} - \frac{1}{2}\log_2(\text{samples})\)
纹理压缩是平衡内存占用和视觉质量的关键技术。现代GPU在硬件级别支持实时解压。
基本思想:
数据结构(每块64位):
color0: 16 bits (R5G6B5)
color1: 16 bits (R5G6B5)
indices: 32 bits (2 bits × 16 pixels)
颜色插值规则: 当color0 > color1:
当color0 ≤ color1(透明模式):
压缩比:$\frac{16 \times 24}{64} = 6:1$
高级特性:
模式选择策略:
Mode 0: 3 subsets, 4-bit endpoints
Mode 1: 2 subsets, 6-bit endpoints
Mode 2: 3 subsets, 5-bit endpoints
...
Mode 7: 2 subsets, 5-bit RGBA
分区模式: 将块分为多个子集,每个子集独立插值: \(c = \sum_{i} w_i \cdot endpoint_i\)
灵活块大小:
编码原理:
根据内容类型:
性能考虑:
缓存命中率 ∝ 1/压缩后大小
带宽需求 = 采样率 × 压缩后大小
快速编码算法:
| 误差度量:$E = \sum_{i} | c_i - \hat{c}_i | ^2$ |
GPU加速编码:
虚拟纹理系统的页表管理:
\[\text{PhysicalAddr} = \text{PageTable}[\text{VirtualPage}] + \text{PageOffset}\]缺页处理的优先级计算: \(\text{Priority} = \frac{\text{ScreenCoverage}}{\text{LOD}^2}\)
Perlin噪声的构造:
其中$w_i = \text{fade}(x - \lfloor x \rfloor)$,fade函数: \(\text{fade}(t) = 6t^5 - 15t^4 + 10t^3\)
fBm (Fractional Brownian Motion): \(f(x) = \sum_{i=0}^{n} \frac{\text{noise}(2^i x)}{2^i}\)
Turbulence函数: \(t(x) = \sum_{i=0}^{n} \frac{|\text{noise}(2^i x)|}{2^i}\)
算术强度优化:
纹理带宽优化:
分支优化: 动态分支的成本模型: \(\text{Cost} = p \cdot C_{\text{true}} + (1-p) \cdot C_{\text{false}} + C_{\text{divergence}}\)
优化策略:
工作组大小优化: 占用率计算: \(\text{Occupancy} = \frac{\text{ActiveWarps}}{\text{MaxWarps}}\)
影响因素:
内存访问模式:
本章深入探讨了高级着色技术的核心概念:
透视正确插值: \(\phi = \frac{u\frac{\phi_0}{z_0} + v\frac{\phi_1}{z_1} + w\frac{\phi_2}{z_2}}{u\frac{1}{z_0} + v\frac{1}{z_1} + w\frac{1}{z_2}}\)
MIP级别计算: \(\text{LOD} = \log_2(\max(||\frac{\partial \mathbf{u}}{\partial x}||, ||\frac{\partial \mathbf{u}}{\partial y}||) \cdot \text{textureSize})\)
Perlin噪声fade函数: \(\text{fade}(t) = 6t^5 - 15t^4 + 10t^3\)
fBm分形: \(f(x) = \sum_{i=0}^{n} \frac{\text{noise}(2^i x)}{2^i}\)
练习5.1:重心坐标计算 给定三角形顶点$A(0,0)$,$B(4,0)$,$C(2,3)$和点$P(2,1)$,计算P的重心坐标。
提示:使用面积法或叉积法计算。
练习5.2:透视插值误差 考虑一条从$z=1$到$z=10$的线段,其纹理坐标从0到1线性变化。在屏幕空间$x=0.5$处,比较线性插值和透视正确插值的结果。
提示:设置合适的投影矩阵参数。
练习5.3:MIP级别选择 纹理大小为1024×1024,当前像素的纹理坐标导数为$\frac{\partial u}{\partial x} = 0.01$,$\frac{\partial v}{\partial x} = 0.02$,$\frac{\partial u}{\partial y} = 0.015$,$\frac{\partial v}{\partial y} = 0.01$。计算应选择的MIP级别。
提示:使用最大各向异性导数。
练习5.4:自定义插值基函数 设计一个C²连续的插值基函数$h(t)$,满足$h(0)=0$,$h(1)=1$,$h’(0)=h’(1)=0$,$h’‘(0)=h’‘(1)=0$,并与Perlin的fade函数比较。
提示:考虑7次多项式。
练习5.5:各向异性采样优化 给定纹理空间的雅可比矩阵$\mathbf{J} = \begin{pmatrix} 2 & 1 \ 0 & 0.5 \end{pmatrix}$,设计一个采样策略,使用最少的采样点覆盖95%的能量。
提示:分析椭圆的主轴。
练习5.6:虚拟纹理缓存策略 设计一个虚拟纹理系统的页面替换算法,考虑:页面大小128×128,物理内存可存储256页,每帧预测未来3帧的访问模式。
提示:结合LRU和预测信息。
练习5.7:着色器自动优化 给定着色器代码片段,设计一个优化流程,目标是减少50%的纹理带宽。考虑纹理重用、计算与访存平衡等因素。
提示:构建数据依赖图。
normalize(normal + tangent)