不透明与半透明物体能否共用同一张阴影贴图实现无瑕疵渲染?
可行方案(无需新增阴影贴图/FBO)
完全可以在现有框架下实现无明显瑕疵的混合阴影效果,不需要额外占用显存,核心是拆分不透明、半透明物体的阴影Pass职责,硬阴影完全靠深度判断,颜色通道仅用来存储半透明透射比,不要用颜色通道存储不透明物体的阴影值。
前期准备
现有FBO保持不变:绑定1个深度附件(和传统阴影映射一致,推荐用GL_DEPTH24_STENCIL8格式,和普通深度缓冲显存占用一致)、1个GL_R8单通道颜色附件。
阴影Pass开始前执行清屏:
- 深度缓冲清为远平面最大深度值
GL_R8颜色附件清为1.0(代表完全透光,无半透明阴影)
阴影Pass状态与绘制顺序
整个Pass只需要两次状态切换,不需要对所有物体做远近排序:
- 第一阶段:绘制所有不透明阴影投射物
状态配置:- 开启深度测试,深度函数设为
GL_LESS,开启深度写入glDepthMask(GL_TRUE) - 关闭混合
glDisable(GL_BLEND) - 关闭所有颜色通道写入:
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)——这一阶段只写深度缓冲,完全不修改颜色附件
这一阶段逻辑和传统不透明硬阴影绘制完全一致,跑完后深度缓冲里存储的是离光源最近的不透明物体的深度值,GL_R8通道保持初始值1.0。
- 开启深度测试,深度函数设为
- 第二阶段:绘制所有半透明烟雾粒子
状态配置:- 保持深度测试开启,深度函数仍为
GL_LESS,强制关闭深度写入glDepthMask(GL_FALSE)——禁止半透明物体修改深度缓冲,从根源避免远半透明覆盖近不透明、近半透明阻挡远不透明深度写入的问题 - 仅开启R通道写入:
glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE) - 开启混合,混合函数设为
glBlendFunc(GL_ZERO, GL_SRC_COLOR)——该配置下混合结果为「帧缓冲当前值 * 当前片元输出值」,正好对应多粒子透射比相乘的物理规律,且乘法满足交换律,不需要对粒子做远近排序 - 正常开启alpha测试,丢弃完全透明的粒子片元即可
片元着色器输出到R通道的值为粒子透射比:粒子alpha为n(阻挡n比例的光),就输出1.0 - n,值域0~1。
这一阶段跑完后,GL_R8通道存储的是所有比最近不透明物体离光源更近的半透明粒子的透射比乘积;在不透明物体后方的半透明粒子会因为深度测试失败被直接丢弃,不会写入颜色通道。
- 保持深度测试开启,深度函数仍为
主渲染Pass阴影采样逻辑
对每个接收阴影的像素,转换到光源裁剪空间后按如下逻辑计算阴影系数:
- 按传统阴影映射逻辑,采样阴影深度值,和当前像素的光源空间深度做带偏移的比较,得到硬阴影系数
hard_shadow:像素在不透明硬阴影区域为0,不在则为1.0 - 采样
GL_R8颜色附件得到半透明透射比transmittance - 最终阴影系数 =
hard_shadow * transmittance
瑕疵消除说明
该方案可以完全解决你遇到的三类视觉问题:
- 针对「近不透明阴影被远半透明覆盖」:远半透明深度大于不透明深度,深度测试直接失败,根本不会写入颜色通道,不可能覆盖不透明阴影
- 针对「近半透明导致远不透明阴影变半透明」:不透明的硬阴影靠深度测试判断,和颜色通道无关;半透明在不透明前方时,不透明深度正常写入,后方像素仍会正确判定为硬阴影
- 唯一的近似误差是:半透明粒子靠光源一侧的极窄区域会被错误判定为在半透明阴影中,该误差视觉上几乎不可见,如果追求完美可以给粒子片元深度加一个微小的正向偏移(在片元着色器中设置
gl_FragDepth = gl_FragCoord.z + 偏移值,偏移量取万分之几即可),就能完全消除。
内容的提问来源于stack exchange,提问作者lhog
相关产品推荐
相关产品推荐

