C++负责预计算并上传大气查找表,GLSL着色器通过查表与插值实时计算天空颜色;公式必须在GPU端实现,否则无法满足性能与渲染管线要求。

大气散射核心公式在 GLSL/C++ 中怎么落地
直接说结论:C++ 本身不“编写着色器公式”,它负责把预计算参数或运行时变量传给 GPU;真正实现散射模型(如 Nishita 或 Preetham)的是 GLSL/HLSL 着色器。C++ 的角色是组织数据、构造 uniform buffer、绑定纹理——公式本身必须写在 shader 里,否则没有渲染管线参与。
常见误区是试图在 C++ 里用 std::sin 或 glm::dot 逐像素算天空颜色,这既慢又错:缺少插值、无深度/视角联动、绕过光栅化流程。正确路径是让顶点/片元着色器拿到太阳方向、视点位置、大气参数后,在 GPU 上并行求解瑞利+米氏散射积分近似。
GLSL 里怎么写 Nishita 模型的关键项
Nishita 模型本质是求解带多重散射近似的相函数与光学深度的乘积积分,但实时渲染必须简化。主流做法是预计算 2D 查找表(transmittanceTex、inscatterTex),着色器只做查表 + 插值 + 太阳直射修正。
关键片段(片元着色器中):
vec3 GetSkyColor(vec3 viewDir, vec3 sunDir, float cameraHeight) {
float rayleigh = 0.001;
float mie = 0.005;
float sunIntensity = 1000.0;
<pre class="brush:php;toolbar:false;">// 光学深度查表(高度 + 天顶角)
float tRay = texture(transmittanceTex, vec2(cameraHeight, dot(viewDir, sunDir))).r;
vec3 tMie = texture(inscatterTex, vec2(cameraHeight, dot(viewDir, sunDir))).rgb;
// 直射衰减 + 散射叠加
return (tRay * skyConst * rayleigh + tMie * sunIntensity) * exp(-cameraHeight / 8000.0);}
注意:transmittanceTex 和 inscatterTex 必须由 C++ 端预生成并上传,不能 runtime 实时计算——否则帧率崩到个位数。
C++ 怎么生成并上传大气查找表
查找表不是 magic,它是一组离线数值积分结果。C++ 要做三件事:分配内存、调用积分函数、绑定为 OpenGL/Vulkan 纹理。
- 用
std::vector<float></float>存储 256×64 的transmittanceTex数据(高度 0–1,天顶角 cosθ 0–1) - 对每个 (h, μ) 组合,调用自定义函数
ComputeTransmittance(h, mu),内部用 16 步梯形积分算 τ = ∫βₑ ds - 用
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width, height, 0, GL_RED, GL_FLOAT, data.data())上传
坑点:高度单位必须和着色器一致(例如归一化到 [0,1] 对应地表到 100km);ComputeTransmittance 若漏掉密度标高衰减项 exp(-h / H),整个天空会发灰且无渐变。
为什么不能直接在 C++ 里用 glm::vec3 算最终颜色
因为屏幕上有百万级像素,每个像素要算 2~3 次积分(瑞利 + 米氏 + 多重散射近似),CPU 单线程跑一遍就要几百毫秒。而 GPU 查一次 2D 纹理只要 1~2 个周期。
更隐蔽的问题是精度链断裂:C++ 用 double 积分得到的表,若上传时被 OpenGL 默认转成 GL_RGBA8(8bit 通道),颜色带状伪影立刻出现。必须用 GL_R32F 或 GL_RG16F 保持线性精度。
还有坐标系陷阱:viewDir 在着色器里是 eye-space,但 C++ 传过去的 cameraPosition 若没转成 world-space,整个散射方向全错——Nishita 公式里所有向量必须统一在世界坐标下运算。
最常被跳过的一步:大气模型依赖地球曲率,所以视线与大气层交点必须用球体-射线求交(IntersectAtmosphereSphere()),而不是简单用 length()。少这一步,地平线处颜色就突变。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











