如何在片段着色器中用艾里斑模糊遥远恒星的片段位置?
解决方案:片段着色器中实现恒星光斑效果
一、高斯光斑实现(高效实时优先)
高斯分布平滑衰减、计算成本低,适合实时渲染场景,步骤如下:
- 传递核心参数
CPU端提前计算每个恒星的关键数据,打包成UBO(Uniform Buffer Object)传入着色器:
starScreenPos:恒星质心的屏幕归一化坐标(NDC,范围[-1,1])starRadius:光斑半径(由光度、距离、镜头参数共同推导,比如radius = k * sqrt(luminosity) / distance,k为自定义调整系数)starColor:恒星固有颜色starIntensity:归一化后的光度强度
- 片段着色器计算逻辑
对每个片段,遍历可见恒星(仅处理片段附近的恒星可优化性能),计算片段到恒星质心的屏幕距离,代入高斯函数计算颜色贡献,最终叠加到场景颜色上。
示例GLSL代码片段:
// 定义恒星数据结构 struct StarData { vec2 screenPos; // NDC坐标 float radius; // 光斑半径(NDC下的长度) vec3 color; // 恒星颜色 float intensity; // 归一化光度 }; // UBO存储恒星数组 layout(std140) uniform StarsUBO { StarData stars[1024]; int starCount; }; uniform vec2 resolution; // 屏幕分辨率 uniform sampler2D sceneTex; // 已渲染的场景纹理 void main() { vec3 finalColor = vec3(0.0); // 将当前片段坐标转换为NDC vec2 fragPos = gl_FragCoord.xy / resolution.xy * 2.0 - 1.0; for(int i = 0; i < starCount; i++) { StarData star = stars[i]; float dist = distance(fragPos, star.screenPos); // 高斯函数计算强度:exp(-d²/(2σ²)),σ取radius/3(覆盖99.7%的能量范围) float sigma = star.radius / 3.0; float gaussWeight = exp(-(dist * dist) / (2.0 * sigma * sigma)); // 仅处理光斑范围内的片段,减少无效计算 if(dist <= star.radius) { finalColor += star.color * star.intensity * gaussWeight; } } // 叠加恒星光斑到场景颜色 vec3 sceneColor = texture(sceneTex, gl_FragCoord.xy / resolution.xy).rgb; gl_FragColor = vec4(sceneColor + finalColor, 1.0); }
- 性能优化点
- 只遍历距离当前片段一定范围内的恒星,避免全局遍历
- 过滤掉光度极低的恒星,跳过计算
- 用实例化分组或稀疏存储减少UBO数据量
二、艾里斑实现(物理精准优先)
艾里斑是圆孔衍射的物理效果,适合追求真实光学表现的场景,计算量略高于高斯,但可通过近似优化:
数学近似简化
艾里斑的强度公式为I(r) = (2J₁(kr)/(kr))²,其中J₁是一阶贝塞尔函数,kr为归一化距离。直接计算贝塞尔函数成本高,可采用多项式近似或预计算纹理采样优化。片段着色器实现
示例GLSL代码片段(用多项式近似贝塞尔函数):
// 一阶贝塞尔函数J₁(x)的多项式近似(x≥0) float besselJ1(float x) { float x2 = x * x; float x4 = x2 * x2; float x6 = x4 * x2; return x * (0.5 - x2/16.0 + x4/384.0 - x6/18432.0); } void main() { vec3 finalColor = vec3(0.0); vec2 fragPos = gl_FragCoord.xy / resolution.xy * 2.0 - 1.0; for(int i = 0; i < starCount; i++) { StarData star = stars[i]; float dist = distance(fragPos, star.screenPos); // 归一化到艾里斑第一暗环位置(系数3.8317) float x = dist / starRadius * 3.8317; float airyWeight; // 避免x=0时的除以0问题 if(x < 0.001) { airyWeight = 1.0; } else { float j1 = besselJ1(x); airyWeight = (2.0 * j1 / x) * (2.0 * j1 / x); } // 保留前两个暗环内的贡献 if(dist <= starRadius * 2.0) { finalColor += star.color * star.intensity * airyWeight; } } vec3 sceneColor = texture(sceneTex, gl_FragCoord.xy / resolution.xy).rgb; gl_FragColor = vec4(sceneColor + finalColor, 1.0); }
- 优化技巧
- 预计算艾里斑强度纹理,着色器中直接采样替代实时计算
- 对小半径恒星,用高斯近似替代艾里斑计算
- 先通过距离场判断片段是否在光斑范围内,再执行强度计算
三、通用注意事项
- 坐标空间统一:确保恒星屏幕位置与片段坐标处于同一空间(NDC或像素坐标),避免计算偏差
- 色调映射:恒星数量较多时,需配合HDR色调映射,防止颜色溢出
- 镜头参数整合:将镜头光圈、焦距等参数加入光斑半径计算,可实现更真实的光学效果
内容的提问来源于stack exchange,提问作者space_doctor
相关产品推荐
相关产品推荐

