DirectX11延迟渲染场景下粒子系统渲染顺序与层级问题咨询
延迟着色下粒子系统的渲染整合方案
核心问题分析
你遇到的粒子始终在场景上层的问题,本质是粒子渲染阶段没有参与场景的深度测试,且渲染顺序不符合延迟着色的管线逻辑——延迟着色输出的场景图像已经是最终颜色缓冲,直接在上面画粒子会跳过深度遮挡检测,自然会覆盖所有场景内容。
正确的渲染执行顺序
调整后的完整流程如下,分阶段处理不透明场景和粒子:
GBuffer生成阶段
- 渲染所有不透明场景物体,将法线、世界空间位置、漫反射、粗糙度等数据写入GBuffer,同时把这些物体的深度值写入主
DepthStencilBuffer。 - 注:如果你的粒子是不透明类型(比如固体碎片),需要和普通物体一起在这一步渲染,写入GBuffer和深度缓冲;但绝大多数粒子是半透明的,这一步无需处理。
- 渲染所有不透明场景物体,将法线、世界空间位置、漫反射、粗糙度等数据写入GBuffer,同时把这些物体的深度值写入主
延迟光照计算阶段
- 利用GBuffer、主深度缓冲、光照信息完成场景光照计算,输出结果到一个HDR颜色缓冲(比如命名为
SceneColorBuffer),这个缓冲保存了不透明场景的最终光照效果。
- 利用GBuffer、主深度缓冲、光照信息完成场景光照计算,输出结果到一个HDR颜色缓冲(比如命名为
粒子模拟与渲染阶段
- 先通过计算着色器完成粒子的物理模拟(更新位置、速度、生命周期等数据)。
- 渲染粒子公告板:
- 开启深度测试,使用主深度缓冲,设置测试模式为
D3D11_COMPARISON_LESS_EQUAL,确保粒子会被场景中更近的物体遮挡。 - 半透明粒子要关闭深度写入(
DepthWriteMask_Zero),避免后续粒子被当前粒子错误遮挡;不透明粒子则保持深度写入。 - 设置对应混合模式:比如常规半透明用
SRC_ALPHA和INV_SRC_ALPHA,火焰类效果可以用ONE和ONE的加法混合。 - 将粒子直接渲染到
SceneColorBuffer上,实现和场景的正确叠加。
- 开启深度测试,使用主深度缓冲,设置测试模式为
公告板四边形的位置校正要点
要让公告板正确显示在场景空间中,需注意以下细节:
- 矩阵转换一致性:粒子顶点的转换必须和场景物体使用完全相同的视图、投影矩阵,确保从世界空间到裁剪空间的转换一致,避免位置偏移。
- 面向相机的正确实现:几何着色器中,基于相机的方向向量生成公告板的四个顶点,示例HLSL代码如下:
[maxvertexcount(4)] void GS(point ParticleInput input[1], inout TriangleStream<ParticleOutput> stream) { float3 particleWorldPos = input[0].worldPos; float3 cameraWorldPos = g_CameraPosition; // 全局相机世界位置 // 计算公告板的朝向向量 float3 lookDir = normalize(cameraWorldPos - particleWorldPos); float3 rightDir = normalize(cross(lookDir, float3(0.0f, 1.0f, 0.0f))); // 世界上向量,或相机上向量 float3 upDir = normalize(cross(rightDir, lookDir)); // 粒子半长 float halfSize = input[0].size * 0.5f; // 生成四个顶点 float3 v0 = particleWorldPos - rightDir * halfSize + upDir * halfSize; float3 v1 = particleWorldPos + rightDir * halfSize + upDir * halfSize; float3 v2 = particleWorldPos - rightDir * halfSize - upDir * halfSize; float3 v3 = particleWorldPos + rightDir * halfSize - upDir * halfSize; // 转换到裁剪空间并输出 ParticleOutput output; output.pos = mul(float4(v0, 1.0f), g_ViewProjectionMatrix); stream.Append(output); output.pos = mul(float4(v1, 1.0f), g_ViewProjectionMatrix); stream.Append(output); output.pos = mul(float4(v2, 1.0f), g_ViewProjectionMatrix); stream.Append(output); output.pos = mul(float4(v3, 1.0f), g_ViewProjectionMatrix); stream.Append(output); } - 深度值正确性:不要手动修改粒子的深度值,让矩阵转换自动生成齐次空间的深度,确保和场景深度缓冲的计算逻辑一致,深度测试才能正常工作。
额外提示
- 如果粒子需要接收光照:
- 简单方案:在粒子像素着色器中直接计算(比如使用场景环境光,或传递单个点光源参数),适合轻量效果。
- 复杂方案:仅针对不透明粒子,将其几何信息写入GBuffer,在延迟光照阶段统一计算光照,但半透明粒子不适用这种方式。
- 测试时可以先关闭混合,开启深度写入和测试,观察粒子是否被场景物体遮挡,确认深度逻辑正确后再开启混合效果。
内容的提问来源于stack exchange,提问作者Charlie.Q
相关产品推荐
相关产品推荐

