在三维高斯泼溅(3DGS)的重建与实时渲染中,原始管线往往只绘制离散的高斯基元,视域空白处直接暴露清屏颜色。为了呈现户外的天空、云层与远山,并让泼溅点表面带上环境反射,需要引入天空盒机制与环境贴图。前者负责背景绘制,后者参与光照。下面从原理到代码逐步说明落地方式。

天空盒的底层原理与在3DGS中的定位
天空盒本质是一个包裹相机的大立方体或球面,六个面(或一张全景图)预先存储天空颜色。渲染时它永远以相机为中心跟随移动,因此观察者无论转向哪边都能看到远处天空,且不会产生近大远小的透视畸变。传统光栅化管线在绘制不透明物体前先画天空盒,并关闭深度写入,保证后续物体正常遮挡它。
在3DGS里,泼溅点通过排序后的透明混合绘制,本身没有传统网格的深度缓冲竞争问题。但我们可以在帧开始阶段,用全屏三角形或立方体网格先光栅化天空盒到颜色缓冲,再让高斯泼溅的CUDA或OpenGL绘制叠加其上。由于高斯点通常半透明,天空颜色会自然透出,形成连贯背景。环境贴图则是同一份天空数据的另一种采样形式,供光照模型查询。
需要注意,3DGS常运行在自定义渲染后端(如基于WebGL的gsplat或Unity插件)。天空盒应复用同一相机视图与投影矩阵,且使用线性色彩空间,防止和泼溅点的色调映射冲突。若直接拿sRGB的JPG当背景,会和HDR训练出的高斯点亮度不匹配,出现亮边。
准备天空纹理与环境贴图数据
最简便的方案是使用等距柱状投影(equirectangular)HDR图,如从HDRI网站获取的天空。将其加载为浮点纹理,在着色时根据视线方向换算UV。如果想完全程序化,可用大气散射公式在片段着色器里实时算天空颜色,避免外部资源依赖。下面是一段GLSL风格的天空盒片段代码,输入视线方向返回天空色。
// 程序化天空:根据归一化视线方向计算颜色
vec3 computeSky(vec3 dir) {
float t = clamp(dir.y * 0.5 + 0.5, 0.0, 1.0);
vec3 horizon = vec3(0.8, 0.85, 0.9);
vec3 zenith = vec3(0.25, 0.45, 0.85);
vec3 ground = vec3(0.3, 0.28, 0.25);
if (dir.y >= 0.0) {
return mix(horizon, zenith, pow(t, 0.8));
} else {
return mix(horizon, ground, pow(1.0 - t, 0.8));
}
}
环境贴图建议采用立方体贴图(cubemap)或球面谐波(SH)系数。对于3DGS,训练时每个点已带SH球谐颜色,可额外用低频SH表达天空光,在泼溅点着色时加上来自天空的漫反射贡献。若做镜面反射,则对高斯点的法线方向(可由协方差矩阵推导)采样立方体环境贴图,得到反射颜色混入输出。
数据管线方面,在Python端可用numpy读取HDR存成.exr或二进制cube,传入前端纹理。注意WebGL中浮点纹理需开启OES_texture_float扩展;若不支持,可编码为RGBA8并解码,但会损失动态范围。环境贴图与天空盒共用同一份源图,只是采样接口不同。
在3DGS渲染循环中整合绘制与光照
具体集成时,渲染函数应分为三步:清屏、画天空盒、画高斯泼溅。以下伪代码展示OpenGL中的调用次序,其中drawSkybox使用上面提到的着色器,render3DGS为原有泼溅绘制。
// 每帧渲染逻辑
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
skyboxShader.use();
skyboxShader.setMat4("view", cameraViewNoTranslation);
skyboxShader.setMat4("proj", cameraProj);
glBindVertexArray(skyboxVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
// 3DGS泼溅绘制,背景已是天空
render3DGS(gaussianData, cameraView, cameraProj);
在泼溅点的着色阶段,需要把环境光加进去。假设每个高斯点有世界法线N和基础色baseColor,可采样环境立方体贴图envMap得到反射率。对于漫反射,用天空盒的平均色或SH低频项;对于高光,用反射向量查cube。如下GLSL片段演示混合:
vec3 N = normalize(vNormal); vec3 R = reflect(-viewDir, N); vec3 envSpec = texture(envCube, R).rgb; vec3 envDiff = skyAvgColor; vec3 ambient = baseColor * (envDiff * 0.7 + envSpec * 0.3); finalColor = mix(splatColor, ambient, 0.25);
调参时要注意混合权重,避免高斯点被环境光洗白。通常环境贡献控制在百分之二十到三十,其余保留训练出的外观。若场景含玻璃或水面,可提高镜面权重并模糊采样立方体模拟粗糙反射。最后,在色调映射阶段对整个帧统一处理,保证天空与泼溅点曝光一致,消除拼接痕。
常见问题与性能权衡
一个典型误区是认为天空盒会大幅增加绘制负担。实际上它仅36个顶点,片段数等于屏幕像素,开销极低。真正的成本在环境贴图采样,尤其对每个高斯点做cube查询时。若性能吃紧,可用预计算SH代替实时cube采样,虽然损失高频反射细节,但帧率更稳定。
另一个坑是深度处理。如果天空盒写入了深度,后续泼溅点可能被错误剔除。必须确认天空盒绘制时深度掩码关闭(glDepthMask(false)),或放在不依赖深度缓冲的单独pass。此外,当相机进入室内或洞穴,应支持淡出天空盒改用局部环境,否则会穿帮。
综合来看,3DGS加天空盒与环境贴图并不需要改动核心泼溅算法,只需在渲染边界做背景与光照注入。掌握纹理准备、绘制顺序、着色融合三点,就能让重建场景从悬浮黑底变为有天空映照的鲜活三维世界。