三维高斯泼溅(3DGS)在实时新视角合成中表现出色,但直接输出的辐射度往往处于高动态范围,且缺乏摄影机常见的光学现象。要让结果更贴近真实拍摄,就需要在泼溅渲染之后接入一系列后处理。色调映射负责把超出显示能力的亮度压回可见区间,泛光模拟镜头对强光源的散射,景深重现焦点之外逐渐模糊的视觉效果。这三者在工程上既可以串成独立的全屏pass,也能通过共享缓冲降低带宽开销。

色调映射在3DGS管线中的接入与实现
3DGS通常以累加混合方式将成千上万个高斯椭球光斑绘制到帧缓冲,得到的颜色值可能是远超1.0的HDR数据。如果直接截断到LDR,天空或反光面就会整片死白。色调映射的本质是一个从R^3到R^3的压缩函数,它应当保持亮度排序、降低局部对比度并避免色相偏移。最常用的是Reinhard与ACESfilm两种算子,前者计算轻量,后者在饱和高光下更耐看。
在实际工程中,我们会在高斯泼溅的最后一个绘制调用之后,把HDR纹理绑定到全屏三角形着色器。下面给出一段GLSL风格的片段代码,演示ACES近似实现。注意输入颜色应先做曝光乘法,再由函数处理。这种写法不依赖任何第三方库,方便移植到WebGL或移动端。
uniform sampler2D hdrTex;
uniform float exposure;
varying vec2 uv;
vec3 aces(vec3 x){
float a=2.51; float b=0.03; float c=2.43;
float d=0.59; float e=0.14;
return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0);
}
void main(){
vec3 hdr = texture2D(hdrTex, uv).rgb * exposure;
vec3 ldr = aces(hdr);
gl_FragColor = vec4(ldr, 1.0);
}
接入时容易忽略的是色彩空间。3DGS训练一般使用线性RGB,而显示器是sRGB,因此色调映射之后还要做gamma校正,或者让映射函数直接输出sRGB编码值。另一个坑是泛光缓冲如果也来自HDR,那么色调映射最好放在泛光叠加之后,否则光晕会被提前压暗,失去能量扩散的感觉。
泛光效果的亮度提取与多级模糊
泛光用来表现灯泡、太阳或高反射点的光芒外溢。它的核心是先做一个亮度阈值提取,把场景里最亮的部分单独拿出来,再做几次向下采样的高斯模糊,最后把模糊结果叠加回原图。对于3DGS来说,因为高斯本身就有柔和边缘,泛光能让这些光斑看起来更像体积光源而非硬纸片。
阈值提取可以用简单的step或者平滑的soft knee。下面是一段CUDA风格伪代码,展示如何生成亮度掩码。我们把超过阈值的部分保留,其余置零,这样后续金字塔模糊只处理有效像素,节省算力。实际项目里常用双线性降采样,每层尺寸减半,模糊半径随之放大。
__global__ void bright_pass(float* hdr, float* mask, int w, int h, float threshold){
int x = blockIdx.x*blockDim.x + threadIdx.x;
int y = blockIdx.y*blockDim.y + threadIdx.y;
if(x>=w || y>=h) return;
int i = y*w + x;
float l = 0.2126f*hdr[i*3] + 0.7152f*hdr[i*3+1] + 0.0722f*hdr[i*3+2];
float k = fmaxf(l - threshold, 0.0f) / fmaxf(l, 1e-4f);
mask[i*3] = hdr[i*3] * k;
mask[i*3+1] = hdr[i*3+1] * k;
mask[i*3+2] = hdr[i*3+2] * k;
}
多级模糊的层数一般取5到6层,过多会糊掉细节,过少则光晕半径不够。叠加时常用强度系数控制,例如0.6的权重可以避免过曝。在3DGS里,如果场景含有大量微小高光高斯,泛光还能掩盖重建噪声,让画面更干净。但要注意,泛光不能替代合理的曝光,否则亮部会发灰。
基于深度的景深模拟与散景控制
景深是摄影机焦点平面之外成像模糊的现象。3DGS本身记录了每个点的空间位置,因此我们可以在后处理阶段用深度缓冲计算像素到焦平面的距离,距离越大模糊越强。与泛光不同,景深需要保持边缘结构,否则前景会渗到背景上,常用方法是做基于圆形孔径的散景模糊,并按CoC(圆混淆)半径采样。
最简单的实现是在全屏pass里读取深度,换算成散景半径,然后用泊松_disk采样周围像素。下面给出一段简化的GLSL逻辑,焦点设在焦距处,前后线性过渡。注意深度需要从NDC还原成视空间线性值,否则远距离会突然全糊。散景形状也能换成六边形来模仿特定镜头。
uniform sampler2D colorTex;
uniform sampler2D depthTex;
uniform float focusDepth;
uniform float focalRange;
varying vec2 uv;
void main(){
float d = texture2D(depthTex, uv).r;
float coc = clamp(abs(d - focusDepth) / focalRange, 0.0, 1.0);
vec3 sum = vec3(0.0);
float total = 0.0;
for(int i=0; i<16; i++){
vec2 offset = poisson16[i] * coc * 0.05;
sum += texture2D(colorTex, uv + offset).rgb;
total += 1.0;
}
gl_FragColor = vec4(sum / total, 1.0);
}
当3DGS用于漫游视频时,景深能引导观众注意力,但也带来性能压力,因为大半径模糊要采很多样本。一种折中是先做半分辨率模糊再合成。另外,如果直接用泼溅的颜色做模糊,半透明高斯叠加区可能出现黑边,这时需要在渲染阶段输出不透明度的加权深度。总体看,景深参数要随镜头运动动态调整,避免焦平面跳变导致眩晕。
三者的顺序与性能权衡
把色调映射、泛光、景深组合进3DGS后处理时,顺序非常关键。常见做法是先由深度做景深模糊得到HDR图,再提取亮部做泛光并叠加,最后统一色调映射输出。这样高光光晕不会被景深误模糊,也不会被色调映射压平。若颠倒顺序,泛光可能在LDR空间计算,能量感明显下降。
性能方面,三个全屏pass在1080p下开销不大,但景深的大半径采样和泛光的多级降采样会占显存带宽。可以利用分块渲染,把深度和颜色打包到同一纹理,或者用计算着色器一次性完成亮部提取加模糊。对移动端,建议泛光层数降到4层,景深采样点减到12个,并用mediump精度。只要参数调好,3DGS后处理完全能跑在实时帧率,让重建场景拥有电影般的质感。
3DGStonemappingbloom_dof修改时间:2026-08-13 06:30:38