Skip to main content

Chapter 24: Geometry and Tessellation Shaders

这一章讨论两个位于顶点处理之后、光栅化之前的可编程阶段:Geometry Shader 和 Tessellation Shaders。读完本章后,读者应能追踪一个 draw call 在顶点输出之后如何被按 primitive 或 patch 继续处理,并能判断某个几何扩增需求应放在 geometry shader、tessellation、mesh shader、compute preprocessing 还是 CPU/asset 管线中完成。

贯穿本章的材料是一帧地形渲染:CPU 提交一批低面数 terrain patch,顶点阶段只传递控制点和材质索引;细分阶段根据相机距离生成更密的三角形;domain/evaluation 阶段读取高度图做 displacement;可选 geometry shader 用于调试法线、写入不同 cubemap layer 或生成少量 per-primitive 可视化线段。这个例子把视觉目标、数据输入、API 状态、shader 输入输出和性能成本放在同一条路径上。

Geometry Shader 和 Tessellation Shaders 都能改变进入光栅化的几何形态。二者的工作粒度和适用目标差异很大:Geometry Shader 按已经装配好的 primitive 执行,适合少量 per-primitive 生成、分层输出和调试可视化;Tessellation 按 patch 执行,适合在 GPU 上把低细节曲面扩展为视距相关的高细节曲面。工程判断的核心是先确认输入粒度,再确认输出数量是否稳定、是否需要位移贴图、是否影响 LOD、是否会扩大后续 rasterization 与 fragment cost。

本文使用的外部依据主要来自 Microsoft Learn 的 Direct3D 11 Geometry Shader StageDirect3D 11 Tessellation Stages、Vulkan latest specification 中的 Geometry ShadingTessellation。这些资料支撑 API 阶段、输入输出约束和 pipeline state 边界;正文负责把它们整理成可迁移的渲染管线判断。

24.1 Geometry Shader 的概念与用途

Geometry Shader 的工作定义是:在 primitive assembly 之后,以一个点、线、三角形或带邻接信息的 primitive 为输入,执行一次 shader invocation,并向后续阶段发射零个或多个输出 primitive。Direct3D 文档把它描述为接收完整 primitive 顶点并能生成输出顶点的阶段;Vulkan 规范也把它定义为读取单个输入 primitive 的顶点组,发射零个或多个输出 primitive 的阶段。这个定义给出两个判断点:它的输入已经完成 primitive 组合,它的输出会继续进入 rasterization 或 stream output。

在地形帧中,Geometry Shader 最直观的用途是调试可视化。假设 terrain pass 的 vertex/domain 输出已经包含 worldPositionworldNormal,一个调试 draw 可以在 Geometry Shader 中把每个三角形的中心点和法线方向转成一小段 line primitive。屏幕上看到的法线箭头长度、方向和密度,可以帮助确认 displacement 之后的法线是否更新、三角形朝向是否一致、局部 patch 是否发生翻转。

下面这个简化 HLSL 片段展示的是调试用途。它的目标很窄:每个输入三角形输出一条法线线段,用于观察每个 primitive 的几何方向。

struct GSIn
{
float4 clipPosition : SV_Position;
float3 worldPosition : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
};

struct GSOut
{
float4 clipPosition : SV_Position;
float3 color : TEXCOORD0;
};

[maxvertexcount(2)]
void main(triangle GSIn inputTriangle[3], inout LineStream<GSOut> outputStream)
{
float3 center = (inputTriangle[0].worldPosition + inputTriangle[1].worldPosition + inputTriangle[2].worldPosition) / 3.0;
float3 normal = normalize(inputTriangle[0].worldNormal + inputTriangle[1].worldNormal + inputTriangle[2].worldNormal);

GSOut startPoint;
startPoint.clipPosition = mul(viewProjection, float4(center, 1.0));
startPoint.color = float3(0.2, 0.8, 1.0);
outputStream.Append(startPoint);

GSOut endPoint;
endPoint.clipPosition = mul(viewProjection, float4(center + normal * debugLength, 1.0));
endPoint.color = float3(1.0, 0.3, 0.1);
outputStream.Append(endPoint);

outputStream.RestartStrip();
}

这段代码的关键信息在 triangle GSIn inputTriangle[3]LineStream<GSOut>。输入粒度是三角形,shader 一次看到三个顶点;输出拓扑是 line strip,shader 通过 Append 发射顶点。maxvertexcount(2) 是静态上限,驱动和编译器需要根据这个上限准备资源。输出线段越多,后续光栅化和调试 pass 的顶点输出压力越高。

Geometry Shader 的第二类用途是 primitive amplification。它可以把一个 point 展开为 billboard,把一条线展开为有宽度的带状几何,把一个三角形生成若干 silhouette 辅助 primitive。这个能力适合“每个输入 primitive 只扩出少量输出”的场景。粒子 billboard、毛发 fin、shadow volume 辅助边和 barycentric debug 都属于这个范围。输出数量开始依赖复杂分支或大倍率扩增时,Geometry Shader 的吞吐和调度成本会快速成为问题。

Geometry Shader 的第三类用途是 layered rendering。它可以写出 SV_RenderTargetArrayIndex 或 Vulkan 中对应的 layer 语义,把同一批 primitive 分发到 texture array、cubemap face 或多视图 render target。地形帧中如果要把同一块地形写入多个 shadow cubemap face,Geometry Shader 可以按 primitive 选择 layer,减少 CPU 侧重复提交。这个做法需要同时检查 layer 数量、视图矩阵数组、输出 primitive 数、深度 pass 带宽和目标平台支持情况。

Geometry Shader 的性能成本来自几个位置。第一,输入按 primitive 粒度聚合,硬件需要把顶点阶段的输出重新组织给 GS invocation。第二,输出数量可变,后续 primitive ordering、stream emission 和 buffering 更难像普通 vertex path 那样稳定流水。第三,Geometry Shader 扩增出来的 primitive 会增加 clipping、rasterization、depth/stencil 和 fragment 阶段的工作量。调试线段开关开启时帧耗时上升,通常要从 GS invocation 数、输出顶点数、rasterized primitive 数和 render target 写入带宽四处一起看。

一个可迁移判断顺序是:先确认需求是否真的按 primitive 粒度表达;再估算每个输入 primitive 的最大输出顶点数;再确认输出是否进入颜色 pass、深度 pass 或 stream output;最后用工具查看 draw call 的 shader stage、generated primitives、rasterizer input 和 fragment cost。Geometry Shader 合适的信号是输出规模小、逻辑直接依赖单个 primitive、调试或 layer 分发收益明确。

24.2 Tessellation Control/ Evaluation Shaders 工作原理

Tessellation Shaders 的工作定义是:把一组控制点组成的 patch 交给 Tessellation Control Shader,再由固定功能 tessellator 按 tessellation factor 生成参数域上的采样点,最后由 Tessellation Evaluation Shader 把这些采样点求值为真实顶点位置。Direct3D 使用 Hull Shader、Tessellator、Domain Shader 三个名称;Vulkan 和 OpenGL 语境通常使用 Tessellation Control Shader 与 Tessellation Evaluation Shader。名称不同,数据路径的核心一致:patch 输入、factor 决定细分密度、evaluation 决定新顶点位置。

在贯穿的地形帧中,CPU 提交的 mesh 可以只包含稀疏网格控制点,例如每个地形块是 4 个或 16 个 control points。相机靠近山脊时,Tessellation Control Shader 根据距离、屏幕误差或材质规则写出较高的 outer/inner tessellation levels;固定功能 tessellator 在三角形、四边形或 isoline 参数域中生成更密的坐标;Tessellation Evaluation Shader 根据这些坐标插值控制点,并读取 height map 把点沿法线或高度方向移动。最终进入光栅化的三角形数量由 patch 与 tessellation factors 共同决定。

这条路径可以用一个小图固定下来。图中只有一个地形 patch,省略材质和阴影,重点是观察低面数输入如何变成高细节输出。

这张图里的关键边界是固定功能 tessellator。它负责把参数域切成点、线或三角形;它通常不读取 height map,也不理解材质语义。曲面形状、位移贴图、法线更新和材质属性插值都要在 shader 中完成。这个分工解释了为什么 Tessellation Control Shader 主要做“每个 patch 的密度决策”,Tessellation Evaluation Shader 主要做“每个生成点的位置和属性求值”。

Tessellation Control Shader 有两个输出层次。第一层是每个 control point 的输出,它可以变换、压缩或传递输入控制点。第二层是每个 patch 的常量输出,最重要的是 tessellation factors。Direct3D 文档把 hull shader 的输入输出 control point 数量限定在 1 到 32 这一类范围内,并强调 tessellation factors 决定每个 patch 的细分程度;Vulkan 规范通过 TessLevelOuterTessLevelInner 描述外边和内部细分级别。工程上应把它理解成一组 patch 级 LOD 参数。

Tessellation Evaluation Shader 直接接收参数域坐标,原始三角形顶点已经被抽象为 patch control points 和 patch constants。三角域通常使用 barycentric 形式,四边形域通常使用 u,vu,v,isoline 使用一组沿线参数。Evaluation 阶段根据参数坐标读取控制点、patch constants 和资源,计算最终 positionnormaluv 等输出。地形位移中常见的计算是先在 patch 控制点之间插值得到平面位置,再采样 height map 得到高度,最后重建或采样法线。

下面的伪代码只表达数据关系,省略具体 HLSL/GLSL 语法差异。

// Tessellation Control Shader, simplified pseudocode
for each control_point in patch:
output_control_point = input_control_point

patch_constant.outer_level = choose_level(camera_distance, screen_error)
patch_constant.inner_level = choose_level(camera_distance, screen_error)

// Tessellation Evaluation Shader, simplified pseudocode
param = TessCoord
surface_position = evaluate_patch(control_points, param)
height = sample_height_map(surface_position.xz)
final_position = surface_position + surface_normal * height
emit(final_position, normal, uv)

这段伪代码要证明一件事:Tessellation 的“生成更多几何”由 factor 决策、参数域切分和曲面求值三段协作完成。choose_level 影响三角形数量;evaluate_patch 影响曲面连续性;height map 采样影响视觉细节与纹理带宽。调优时把三者混在一起会导致误判:画面更细可能来自更高 factor,也可能来自 displacement 强度变化,还可能来自 normal map 或 shading 对比度变化。

Tessellation 的 LOD 价值来自 patch 级可控密度。远处地形 patch 可以使用低 tessellation level,近处 patch 使用高 level,边界使用 fractional spacing 或连续过渡降低裂缝和跳变。LOD 决策应优先基于屏幕空间误差,例如 patch 在屏幕上的边长、最大高度变化和相机距离组合出的误差估计。单纯按照世界距离切换会在超大地形、窄 FOV 或倾斜视角下产生密度浪费。

Displacement 是 Tessellation 最典型的视觉收益。普通 normal map 只改变光照法线,轮廓和遮挡关系仍由原网格控制;displacement 会真正改变顶点位置,山脊轮廓、地表交界和自阴影几何关系都会变化。这个收益也带来成本:domain/evaluation 阶段需要额外 texture fetch,生成的三角形会增加 rasterizer 和 fragment 输入,shadow pass 也可能需要同样的细分与位移来保持阴影一致。

失败情况通常集中在三类证据上。第一,patch 边界裂缝说明相邻 patch 的边 tessellation factor 或 displacement 求值不一致。第二,近处三角形密度过高但画面细节没有增加,说明瓶颈可能转到 fragment shading、texture bandwidth 或 height map 分辨率。第三,深度 pass 和 color pass 使用不同 tessellation 配置,容易出现阴影漂移、接触边错位和深度预通过失配。排查时先固定同一组 control points 和 height map,再逐项观察 factor、domain output position、wireframe 密度和 shadow/depth pass 的一致性。

24.3 在 Vulkan/DX 中启用细分着色器

启用 Tessellation 的 API 判断从“输入 primitive 类型”开始。普通 triangle list 直接进入 primitive assembly;Tessellation 路径要求输入被解释为 patch。Direct3D 11 需要把 Input Assembler topology 设置为 patch control point topology,并绑定 Hull Shader 与 Domain Shader;Vulkan 需要在 graphics pipeline 中包含 tessellation control/evaluation shader stages,并通过 VkPipelineTessellationStateCreateInfo 设置每个 patch 的 control point 数量。

Vulkan 的最小状态可以拆成四组。第一组是 feature 与 limits:设备需要开启 tessellationShader,并检查 maxTessellationPatchSizemaxTessellationGenerationLevel 等限制。第二组是 input assembly:topology 使用 VK_PRIMITIVE_TOPOLOGY_PATCH_LIST。第三组是 shader stages:pipeline 同时包含 TCS 和 TES。第四组是 tessellation state:patchControlPoints 与 shader 里的 patch 输入布局保持一致。

下面是 Vulkan 侧的配置骨架,只保留和 tessellation 相关的字段。

VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;

VkPipelineTessellationStateCreateInfo tessellationState{};
tessellationState.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
tessellationState.patchControlPoints = 4;

VkPipelineShaderStageCreateInfo shaderStages[] = {
vertexStage,
tessellationControlStage,
tessellationEvaluationStage,
fragmentStage
};

VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pTessellationState = &tessellationState;
pipelineInfo.stageCount = 4;
pipelineInfo.pStages = shaderStages;

这段配置的关键点是 patchControlPoints = 4。它表示 input assembly 每 4 个控制点组成一个 patch,随后传给 TCS。这个数字需要与 mesh 数据布局、shader 输入声明和 draw call 的顶点数量共同匹配。顶点数无法被 patch control point 数量整除时,输入 patch 组合已经错误;shader 内部看到的数据也会偏移。

Direct3D 11 / Direct3D 12 的概念路径类似,但命名不同。Input Assembler 使用 D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLISTD3D11_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST 这类 patch topology;shader 侧绑定 Hull Shader 与 Domain Shader;HLSL target 常见为 hs_5_0ds_5_0。Direct3D 11 文档说明,绑定 hull shader 会初始化 tessellator stage,domain shader 负责为 tessellator 输出点计算最终顶点位置。

一个最小 Direct3D 11 风格调用可以这样理解。

context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST);
context->HSSetShader(terrainHullShader, nullptr, 0);
context->DSSetShader(terrainDomainShader, nullptr, 0);
context->DSSetShaderResources(0, 1, &heightMapSrv);
context->DSSetSamplers(0, 1, &heightSampler);
context->Draw(controlPointCount, 0);

这里的 4_CONTROL_POINT_PATCHLIST 对应四控制点地形 patch。HSSetShaderDSSetShader 决定 tessellation stages 是否参与 draw;height map 绑定到 domain shader,因为位移发生在最终顶点求值阶段。若同一帧还有普通 mesh draw,应在普通 draw 前把 HS/DS 设为空或切换到普通 pipeline 状态,防止后续 draw 继续按 patch 路径解释输入。

Descriptor 或 resource binding 的检查顺序也要贴合阶段。TCS 需要读取相机参数、LOD 参数、patch bounding 信息时,资源应绑定到 control/hull stage;TES/domain 需要读取 height map、normal map、位移强度和 transform matrix 时,资源应绑定到 evaluation/domain stage。把资源只绑定给 fragment shader 时,位移阶段读取不到数据;把 height map 放在 TCS 中采样又会把每个 patch 的高度细节提前压缩成少量决策值,无法得到每个细分点的真实位移。

工具复查时应按 draw call 状态展开。先看 Input Assembly topology 是否是 patch list;再看 shader stage 列表是否包含 TCS/TES 或 HS/DS;再看 patch control point count 是否与 mesh 数据一致;再看 shader resource 是否出现在正确 stage;最后打开 wireframe 或 mesh viewer 观察 tessellated primitive 密度。RenderDoc、PIX、Nsight 或 Xcode GPU tools 的名称不同,但它们要回答的是同一组问题:输入是否为 patch,stage 是否启用,资源是否绑定,输出几何是否符合 LOD 预期。

Vulkan 与 Direct3D 的差异主要落在对象生命周期和状态组织。Vulkan 把 shader stages、patch control points、input topology 固化到 pipeline 或 dynamic state 组合中;Direct3D 11 更接近可变 context state;Direct3D 12 会把它们纳入 PSO 与 root signature 设计。跨 API 封装时应把 tessellation 能力抽象成 material/pass 级 feature:是否使用 tessellation、patch control point count、domain type、factor policy、domain/evaluation resources、fallback path。这样能把平台差异集中到 backend,保留上层材质和地形系统的稳定接口。

24.4 性能权衡与适用场景分析

选择几何生成路径时,先判断任务的天然粒度。Geometry Shader 的天然粒度是一个已经装配好的 primitive;Tessellation 的天然粒度是 patch;Mesh Shader 的天然粒度通常是一个 workgroup 负责一组 meshlet 或自定义几何批;Compute preprocessing 的天然粒度是 buffer 中的元素或间接绘制数据。粒度匹配,数据读取、输出规模和工具观察都会直接;粒度错配,性能和可维护性都会变差。

对于本章的地形帧,Tessellation 适合承担“根据距离和屏幕误差增加曲面密度,再做 displacement”的主路径。Geometry Shader 适合承担“显示法线线段、写 layer、输出少量 per-primitive 辅助几何”的辅助路径。Mesh Shader 更适合承担“在 shader 中完成 meshlet 级 culling、LOD 选择、primitive 生成和输出控制”的现代 GPU-driven 路径。Compute preprocessing 适合提前生成或压缩 draw 参数、可见 patch 列表、LOD buffer 和 indirect draw buffer。

可以用同一组维度比较这些选择。

方案输入粒度适合任务主要成本典型证据
Geometry Shaderprimitive调试线段、少量扩增、layered rendering可变输出、primitive emission、后续 raster costGS invocation 数、输出顶点数、generated primitives
Tessellationpatch曲面细分、位移贴图、视距 LODfactor 过高、TES 采样、三角形与 fragment 增长wireframe 密度、tess factor、TES 耗时、shadow pass 一致性
Mesh Shadermeshlet/workgroupGPU-driven culling、LOD、程序化 primitive 输出平台支持、编程复杂度、工具链要求meshlet 数、culled primitives、workgroup occupancy
Compute preprocessingbuffer element / tile可见集、LOD 列表、indirect draw、离线式生成barrier、buffer 写入、同步和内存带宽dispatch 耗时、indirect draw 参数、barrier stall

这张表的结论是:选择依据应从数据路径推导。要在每个三角形上生成一小段调试几何,Geometry Shader 的表达最短;要让低面数 patch 近处变细并产生真实轮廓,Tessellation 的管线位置更合适;要在 GPU 上重组 meshlet 并控制输出 primitive,Mesh Shader 的模型更自然;要先筛选大量 patch 再统一提交,Compute preprocessing 更容易控制 buffer 和间接绘制。

Geometry Shader 的收益边界要保守评估。它在调试、少量扩增、layer 写入上很方便,但高倍率扩增会把压力转移给 GS 输出缓冲、rasterizer 和 fragment 阶段。使用它生成大量草叶、毛发或复杂地形细节时,输出规模通常难以稳定控制,材质、阴影和透明排序也会变复杂。工程上更稳的做法是把大量可重复几何放到 instancing、mesh shader、compute-generated buffer 或资产预处理路径中。

Tessellation 的收益边界取决于“新增几何是否被画面使用”。如果新增三角形确实改变轮廓、遮挡、接触阴影或近景表面起伏,Tessellation 的成本有视觉回报。如果高度贴图分辨率低、远处 patch 细分过高、fragment shader 已经是主瓶颈,继续提高 tessellation level 只会扩大后续阶段工作量。最直接的检查方法是固定相机,分别显示 wireframe、height displacement、normal-only lighting 和 depth/shadow pass,确认新增几何在屏幕上产生可见差异。

Tessellation 与 displacement 还会影响 pass 一致性。地形在 color pass 中细分并位移,depth pre-pass 或 shadow pass 也需要使用等价的 patch、factor policy 和 displacement 资源。否则颜色表面、深度表面和阴影投射表面会分离。这个问题在近景山脊、角色脚下接触阴影、斜向光源下尤其明显。调试时应把同一块 patch 在 color、depth、shadow 三个 pass 的 wireframe 和深度输出并排观察。

Mesh Shader 是现代替代路径,但它要求平台和工具链支持。它把传统 vertex/geometry 的部分工作改成 workgroup 级自定义输出,更适合 meshlet culling、LOD、GPU-driven rendering 和程序化生成。它的代价是跨平台支持矩阵更复杂,shader 编写、调试和 fallback 设计也更重。已有 Vulkan/DX12 后端、目标平台支持 mesh shader、场景需要大规模 GPU culling 时,可以把它作为长期方向;需要覆盖 WebGL/OpenGL ES 或旧设备时,应保留传统 pipeline fallback。

Compute preprocessing 的价值在于把“决定画什么”提前到 graphics draw 之前。地形系统可以先用 compute 生成可见 patch 列表、每个 patch 的 LOD level、indirect draw 参数和材质索引,再让 tessellation 或普通 mesh pass 消耗这些 buffer。这样可以把复杂的 culling 和 LOD 决策放在 buffer 写入路径中,保留 graphics pass 的稳定性。代价是 barrier 和资源状态转换需要明确设计;错误的同步会让 GPU 等待,抵消减少 draw 或减少三角形带来的收益。

最终选择可以按下面顺序落地:先描述视觉目标,例如“近景地形有真实轮廓位移”或“调试每个三角形法线”;再确定输入粒度是 patch、primitive、meshlet 还是 buffer element;再估算输出规模和波动范围;再确认目标 API 的 feature、limits 和 fallback;最后用工具证据验证瓶颈位于哪个阶段。这个顺序能把“某个 shader 很强”转换成可复查的工程判断。

最小自检任务

给定一个地形渲染需求:近景山地需要真实轮廓起伏,远景保持低成本;开发阶段还需要显示每个三角形的法线方向;目标后端包含 Vulkan 和 Direct3D 11。请设计这条 draw path 的最小方案,并说明:哪些工作放在 Tessellation Control/Hull Shader,哪些工作放在 Tessellation Evaluation/Domain Shader,Geometry Shader 用于什么,API 状态要检查哪些项,性能瓶颈应如何定位。

答案要点

Tessellation Control/Hull Shader 应负责 patch 级 LOD 决策,读取相机距离、屏幕误差、patch bounding 信息,写出 outer/inner tessellation levels,并保持相邻 patch 边界 factor 一致。它输出的是每个 patch 的细分密度和必要 patch constants。

Tessellation Evaluation/Domain Shader 应负责每个细分点的曲面求值和 displacement。它读取 control points、参数域坐标、height map、位移强度和 transform matrix,输出最终位置、法线、UV 与后续 fragment shader 需要的 varyings。depth、shadow、color pass 需要使用一致的位移路径或明确的质量降级策略。

Geometry Shader 适合放在独立 debug pass 中,用每个输入三角形生成一条或少量法线线段,也可以用于少量 layered rendering。地形主体的高倍率几何扩增更适合交给 tessellation、mesh shader、compute-generated buffer 或资产预处理路径。判断依据是 GS 的输入粒度是 primitive,输出数量可变且会增加后续 rasterization 成本。

Vulkan 状态检查顺序是:设备 feature 开启 tessellationShader,input assembly 使用 VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,pipeline 同时包含 TCS/TES,VkPipelineTessellationStateCreateInfo::patchControlPoints 与 mesh 和 shader 一致,height map 与常量 buffer 绑定到实际读取它们的 stage。Direct3D 11 状态检查顺序是:IA topology 使用对应 control point patch list,HS/DS 已绑定,shader resource 和 sampler 绑定到 HS 或 DS 的正确 slot,普通 mesh draw 前切换回对应状态。

性能定位应先看 wireframe 密度和 tess factor,再看 TES/domain 的 texture fetch 与 ALU 成本,随后检查 rasterizer 输入 primitive 数、fragment shader 耗时、render target/depth/shadow pass 带宽和 barrier/synchronization。近景新增几何需要能改变轮廓、接触阴影或表面起伏;若画面没有对应收益,应降低 factor、调整 LOD 或把可见集与 LOD 决策前移到 compute preprocessing。

本章知识点总结

  • GS 粒度:Geometry Shader 按已经装配好的 primitive 执行,输入通常是点、线、三角形或带邻接信息的 primitive。
  • GS 输出:Geometry Shader 可以发射零个或多个输出 primitive,输出规模会影响后续 clipping、rasterization 和 fragment 阶段。
  • 调试用途:法线线段、barycentric 可视化和少量辅助 primitive 适合放在独立 Geometry Shader debug pass 中。
  • Layer 输出:Geometry Shader 可以服务 layered rendering,但需要同时检查 layer 语义、目标数组、输出 primitive 数和平台支持。
  • Patch 输入:Tessellation 的输入是 patch,patch 由固定数量 control points 组成,适合表达曲面和地形块。
  • TCS 职责:Tessellation Control Shader 或 Hull Shader 负责 control point 输出和 patch 级 tessellation factor 决策。
  • 固定细分:固定功能 tessellator 根据 outer/inner levels 在参数域中生成点、线或三角形。
  • TES 职责:Tessellation Evaluation Shader 或 Domain Shader 根据参数坐标、control points 和资源求值最终顶点位置。
  • 位移收益:Displacement 会真实改变顶点位置,能影响轮廓、遮挡和阴影关系,同时增加采样和后续管线成本。
  • API 状态:Vulkan 需要 patch list、TCS/TES stages 和 patchControlPoints;Direct3D 需要 patch topology、HS/DS 绑定和正确资源 slot。
  • Pass 一致性:color、depth 和 shadow pass 使用不同 tessellation 或 displacement 配置时,容易出现深度和阴影错位。
  • 方案选择:少量 per-primitive 生成优先考虑 Geometry Shader,曲面 LOD 与 displacement 优先考虑 Tessellation。
  • 现代替代:Mesh Shader 更适合 meshlet 级 GPU-driven 输出,Compute preprocessing 更适合可见集、LOD 列表和 indirect draw 生成。
  • 性能证据:判断瓶颈时应同时观察 tess factor、输出 primitive 数、shader 耗时、fragment 成本、带宽和同步等待。