noteForOpenGL 点光源阴影进阶:多Pass方案与几何着色器方案深度对比
2026/8/23 10:51:31 网站建设 项目流程

noteForOpenGL 点光源阴影进阶:多Pass方案与几何着色器方案深度对比

【免费下载链接】noteForOpenGLOpenGL学习脚印配套代码项目地址: https://gitcode.com/gh_mirrors/no/noteForOpenGL

noteForOpenGL 是一个 OpenGL 学习脚印配套代码项目,本教程带你掌握点光源阴影(Point Light Shadow)的两条主流路线:基于CubeMap 深度图的多 Pass 渲染方案,以及用几何着色器(Geometry Shader)一次绘制六面的高性能方案,并附软阴影 PCF 与新手避坑清单。

为什么点光源阴影比平行光更难?

平行光阴影只需要一张 2D 深度图,而点光源向四面八方发光,必须从 6 个方向(±X、±Y、±Z)各拍一张深度图,存进一个CubeMap 立方体贴图里。

渲染时,着色器只需把"片元→光源"的方向向量直接作为坐标去采样 CubeMap,就能拿到"光源看到的最近距离",与"片元到光源的真实距离"一比,就知道这个片元是否在阴影里。

这个对比是点光源阴影相对平行光最优雅的地方:方向即坐标,采样即阴影

方案一:多Pass渲染六张深度图

多 Pass 方案思路直白:把场景从光源视角渲染 6 遍,每一遍绑定 CubeMap 的一个面:

for (GLuint i = 0; i < 6; ++i) { glUniformMatrix4fv(..., "lightVPMatrix", ..., shadowTransforms[i]); glBindFramebuffer(GL_FRAMEBUFFER, depthFBOId); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, depthCubeMapTextId, 0); glClear(GL_DEPTH_BUFFER_BIT); renderScene(depthMapShader); }

核心细节:

  • 6 个方向矩阵:每个面用透视投影 × lookAt合成,6 个方向的 up 向量各不相同(例如朝 +Z 方向时 up 取 (0,-1,0))
  • 手动写深度:片元着色器不用默认的gl_FragCoord.z,而是手动计算"片元到光源距离 / far_plane" 写入gl_FragDepth,这样深度图里存的就是真实距离
  • 法线反转:深度 Pass 中关闭背面剔除并翻转法线,因为从光源视角看,原来朝向光源的面反而是"背面"
  • 边界处理:CubeMap 必须用GL_CLAMP_TO_EDGE,且边框颜色设为白色(距离最远处),否则阴影边缘会出错

运行效果如下,按 B 键可开关阴影,P 键切换 PCF 软阴影:

优点:逻辑简单,容易调试。缺点:同一个场景画了 6 遍,几何量大时 CPU 端 6 次 draw call + 状态切换开销明显。

方案二:几何着色器一次绘制六面

几何着色器方案把"6 个 Pass"压缩成1 个 Pass、1 次 draw call

layout (triangles) in; layout (triangle_strip, max_vertices = 18) out; uniform mat4 shadowMatrices[6]; void main() { for (int face = 0; face < 6; ++face) { gl_Layer = face; // 指定写入 CubeMap 的第几面 for (int i = 0; i < 3; ++i) { FragPos = gl_in[i].gl_Position; gl_Position = shadowMatrices[face] * FragPos; EmitVertex(); } EndPrimitive(); } }

两个关键机制:

  • gl_Layer:几何着色器的"超能力",指定当前图元写入纹理数组的哪一层,CubeMap 恰好是 6 层
  • 6 个矩阵数组:CPU 端把 6 个方向的 View-Projection 矩阵一次性传给着色器,每个输入三角形被"复制"成 6 份,分别变换到 6 个面

代价是顶点被放大了 6 倍(1 个三角形 → 18 个顶点),对 GPU 顶点吞吐有压力,但省掉了 5 次 draw call 和 CPU 状态切换。几何量小、CPU 瓶颈明显时,GS 方案更快;几何量极大时反而多 Pass 更划算,这是典型的 GPU/CPU 权衡。

片元着色器与多 Pass 方案完全相同(手动gl_FragDepth),说明两方案只是深度图生成方式不同,阴影计算逻辑通用。

效果升级:PCF 软阴影与距离感知采样

硬阴影边缘锯齿明显,noteForOpenGL 用Percentage-Closer Filtering(PCF)生成软阴影:在光源方向上取 20 个采样点构成圆盘,统计"落在阴影中"的比例作为连续阴影值。

float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0;

注意采样半径会随观察距离变化,避免近处过软、远处过硬。效果对比如下:

新手避坑清单:点光源阴影3个高频错误

坑 1:投影矩阵忘记转弧度

glm::perspective的 FOV 参数是弧度,传入角度值(比如 90.0)会让视锥严重变形,典型症状是 CubeMap 面之间出现黑边或阴影断裂,务必写成glm::radians(90.0f)

坑 2:Shadow Acne(阴影痤疮)

浮点精度导致表面自我遮挡,产生斑点状条纹。解法是在比较时减去一个偏置量(bias),注意 PCF 的 bias 通常要比硬阴影大(如 0.05 → 0.15)。

坑 3:Peter Panning(彼得潘效应)

bias 开太大,阴影会"漂离"物体。多 Pass 方案靠精度足够的高程深度图控制,GS 方案还要额外注意 6 层写入时max_vertices必须设为 18。

模块路径速查

两个方案的完整可运行代码都在advancedLighting/pointShadow/目录下:

方案目录说明
多 Pass(立方体场景)advancedLighting/pointShadow/multiPassPointShadow/shadowMapping.cppdepthMap.vertexdepthMap.frag
多 Pass(模型场景)advancedLighting/pointShadow/multiPassPointShadowWithModel/加载纳米战斗服模型
几何着色器方案advancedLighting/pointShadow/geometryShader-PointShadow/核心文件depthMap.gs

关键文件一览:

  • 深度 Pass 片元(手动写gl_FragDepth):advancedLighting/pointShadow/multiPassPointShadow/depthMap.frag
  • 几何着色器(gl_Layer六面写入):advancedLighting/pointShadow/geometryShader-PointShadow/depthMap.gs
  • 场景着色器(CubeMap 采样 + PCF 软阴影):advancedLighting/pointShadow/multiPassPointShadow/scene.frag
  • 主程序(6 个方向矩阵与两 Pass 流程):advancedLighting/pointShadow/multiPassPointShadow/shadowMapping.cpp

配套基础(平行光阴影、PCF、bias 调试)可参考advancedLighting/shadowMapping/目录下的 shadowMapping1/2 示例,纹理资源位于resources/textures/(如wood.png)。

总结:怎么选?

  • 想快速理解原理:先读多 Pass 方案,6 次循环把 CubeMap 深度图流程讲得明明白白
  • 追求性能:换用几何着色器,1 次 draw call 搞定,记得max_vertices = 18
  • 阴影要好看:上 PCF,20 点圆盘采样 + 距离感知半径,软阴影立现
  • 效果不对先查glm::radians没加、CubeMap 边界不是 CLAMP_TO_EDGE、bias 取值失衡,三件套排查 90% 的问题

点光源阴影是阴影映射从"入门"迈向"进阶"的关键一步,把 CubeMap、几何着色器、PCF 三块积木搭好,你就具备了实现全局动态阴影(CSM、点光源集群)的全部基础。🚀

【免费下载链接】noteForOpenGLOpenGL学习脚印配套代码项目地址: https://gitcode.com/gh_mirrors/no/noteForOpenGL

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询