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.cpp、depthMap.vertex、depthMap.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),仅供参考