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

Android GLES 2.0火焰着色器效果异常及像素化问题求助

Android GLES 2.0 火焰着色器效果异常及后期像素化问题

问题概述

  • 基于参考示例实现的火焰着色器,实际效果呈斜向流动,且垂直方向过度拉伸,与预期的火焰迸发效果不符
  • 运行约10秒后,画面出现像素化,逐渐变为方形火焰,推测与传入的progress(秒级浮点值)过大有关

当前片段着色器代码

precision mediump float;

#define timeScale           iTime * 1.0
#define fireMovement        vec2(-0.01, -0.5)
#define distortionMovement  vec2(-0.01, -0.3)
#define normalStrength      40.0
#define distortionStrength  0.1

uniform vec2 screenSize;
uniform float progress;

// #define DEBUG_NORMAL

/** NOISE **/
float rand(vec2 co) {
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

vec2 hash( vec2 p ) {
    p = vec2( dot(p,vec2(127.1,311.7)),
    dot(p,vec2(269.5,183.3)) );

    return -1.0 + 2.0*fract(sin(p)*43758.5453123);
}

float noise( in vec2 p ) {
    const float K1 = 0.366025404; // (sqrt(3)-1)/2;
    const float K2 = 0.211324865; // (3-sqrt(3))/6;

    vec2 i = floor( p + (p.x+p.y)*K1 );

    vec2 a = p - i + (i.x+i.y)*K2;
    vec2 o = step(a.yx,a.xy);
    vec2 b = a - o + K2;
    vec2 c = a - 1.0 + 2.0*K2;

    vec3 h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 );

    vec3 n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0)));

    return dot( n, vec3(70.0) );
}

float fbm ( in vec2 p ) {
    float f = 0.0;
    mat2 m = mat2( 1.6,  1.2, -1.2,  1.6 );
    f  = 0.5000*noise(p); p = m*p;
    f += 0.2500*noise(p); p = m*p;
    f += 0.1250*noise(p); p = m*p;
    f += 0.0625*noise(p); p = m*p;
    f = 0.5 + 0.5 * f;
    return f;
}

/** DISTORTION **/
vec3 bumpMap(vec2 uv, vec2 resolution) {
    vec2 s = 1. / resolution;
    float p =  fbm(uv);
    float h1 = fbm(uv + s * vec2(1., 0));
    float v1 = fbm(uv + s * vec2(0, 1.));

    vec2 xy = (p - vec2(h1, v1)) * normalStrength;
    return vec3(xy + .5, 1.);
}

vec3 constructCampfire(vec2 resolution, vec2 normalized, float time) {
    vec3 normal = bumpMap(normalized * vec2(1.0, 0.3) + distortionMovement * time, resolution);

    vec2 displacement = clamp((normal.xy - .5) * distortionStrength, -1., 1.);
    normalized += displacement;

    vec2 uvT = (normalized * vec2(1.0, 0.5)) + time * fireMovement;
    float n = pow(fbm(8.0 * uvT), 1.0);

    float gradient = pow(1.0 - normalized.y, 2.0) * 5.;
    float finalNoise = n * gradient;
    return finalNoise * vec3(2.*n, 2.*n*n*n, n*n*n*n);
}

void main() {
    vec2 resolution = screenSize;
    vec2 normalized = gl_FragCoord.xy / resolution;
    vec3 campfire = constructCampfire(resolution, normalized, progress);
    gl_FragColor = vec4(campfire, 1.0);
}

问题分析与修复方案

1. 垂直拉伸与斜向流动问题

原因

  • 未处理屏幕宽高比:ShaderToy默认使用方形画布,Android设备屏幕为非方形,直接使用gl_FragCoord.xy / resolution会导致UV比例失衡,火焰垂直拉伸
  • 流动参数与UV缩放未适配宽高比,导致火焰流动方向异常

修复步骤

修改main函数添加宽高比校正,保证UV等比例映射:

void main() {
    vec2 resolution = screenSize;
    vec2 normalized = gl_FragCoord.xy / resolution;
    
    // 校正宽高比,避免拉伸
    float aspect = resolution.x / resolution.y;
    normalized.x *= aspect;
    
    vec3 campfire = constructCampfire(resolution, normalized, progress);
    gl_FragColor = vec4(campfire, 1.0);
}

同时调整constructCampfire中的UV缩放参数,例如将normalized * vec2(1.0, 0.3)改为normalized * vec2(1.0, 1.0),再根据效果微调fireMovement为vec2(0.0, -0.5)以消除斜向流动。

2. 后期像素化与方形火焰问题

原因

progress是秒级递增的浮点值,随着时间推移,time * fireMovement会让UV坐标数值变得极大。mediump float精度有限,大数值浮点数会丢失精度,导致noise函数输出重复块状图案,最终出现像素化和方形火焰。

修复步骤

对progress进行取模或取小数部分,限制时间参数范围:

void main() {
    vec2 resolution = screenSize;
    vec2 normalized = gl_FragCoord.xy / resolution;
    
    float aspect = resolution.x / resolution.y;
    normalized.x *= aspect;
    
    // 限制时间范围,避免精度丢失
    float time = mod(progress, 10.0); // 每10秒循环一次
    vec3 campfire = constructCampfire(resolution, normalized, time);
    gl_FragColor = vec4(campfire, 1.0);
}

也可使用fract(progress * 0.5)让时间始终在0-1之间循环,根据火焰流动速度调整系数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 14:32:09