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
相关产品推荐
相关产品推荐

