You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在片段着色器中用艾里斑模糊遥远恒星的片段位置?

解决方案:片段着色器中实现恒星光斑效果

一、高斯光斑实现(高效实时优先)

高斯分布平滑衰减、计算成本低,适合实时渲染场景,步骤如下:

  1. 传递核心参数
    CPU端提前计算每个恒星的关键数据,打包成UBO(Uniform Buffer Object)传入着色器:
  • starScreenPos:恒星质心的屏幕归一化坐标(NDC,范围[-1,1])
  • starRadius:光斑半径(由光度、距离、镜头参数共同推导,比如 radius = k * sqrt(luminosity) / distance,k为自定义调整系数)
  • starColor:恒星固有颜色
  • starIntensity:归一化后的光度强度
  1. 片段着色器计算逻辑
    对每个片段,遍历可见恒星(仅处理片段附近的恒星可优化性能),计算片段到恒星质心的屏幕距离,代入高斯函数计算颜色贡献,最终叠加到场景颜色上。

示例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);
}
  1. 性能优化点
  • 只遍历距离当前片段一定范围内的恒星,避免全局遍历
  • 过滤掉光度极低的恒星,跳过计算
  • 用实例化分组或稀疏存储减少UBO数据量

二、艾里斑实现(物理精准优先)

艾里斑是圆孔衍射的物理效果,适合追求真实光学表现的场景,计算量略高于高斯,但可通过近似优化:

  1. 数学近似简化
    艾里斑的强度公式为 I(r) = (2J₁(kr)/(kr))²,其中J₁是一阶贝塞尔函数,kr为归一化距离。直接计算贝塞尔函数成本高,可采用多项式近似或预计算纹理采样优化。

  2. 片段着色器实现
    示例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);
}
  1. 优化技巧
  • 预计算艾里斑强度纹理,着色器中直接采样替代实时计算
  • 对小半径恒星,用高斯近似替代艾里斑计算
  • 先通过距离场判断片段是否在光斑范围内,再执行强度计算

三、通用注意事项

  • 坐标空间统一:确保恒星屏幕位置与片段坐标处于同一空间(NDC或像素坐标),避免计算偏差
  • 色调映射:恒星数量较多时,需配合HDR色调映射,防止颜色溢出
  • 镜头参数整合:将镜头光圈、焦距等参数加入光斑半径计算,可实现更真实的光学效果

内容的提问来源于stack exchange,提问作者space_doctor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 22:35:18