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

OpenGL ES 2.0与WebGL着色器表现不一致问题排查

波浪着色器移植到Android GLES2.0效果异常的问题及解决

我在ShaderToy上制作了一个波浪方程着色器,移植到基于GLES2.0的Android项目时,仅替换了texture()为texture2D()、fragColor/fragCoord为gl_FragColor/gl_FragCoord,但效果与ShaderToy上表现不一致。Android端效果如下:

Android着色器程序效果

以下是可能忽略的关键因素及修正方案:

1. 纹理过滤模式不匹配

ShaderToy默认使用LINEAR过滤,但GLES2.0纹理默认是NEAREST_MIPMAP_LINEAR。你的缓冲纹理用于帧缓冲迭代计算,必须设置为GL_NEAREST过滤,否则采样相邻像素会出现模糊,导致波浪计算逻辑出错。

修正代码(Java层):

// 创建纹理时设置参数
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
// 禁用纹理重复,避免边缘采样异常
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

2. 帧缓冲纹理格式错误

ShaderToy使用高精度浮点纹理存储计算数据,但GLES2.0默认仅支持GL_RGBA8格式,该格式会截断负数为0,而你的波浪计算中压力和速度可能为负,直接导致计算完全失效。

修正方案:
使用GL_RGBA16F半浮点纹理(需设备支持OES_texture_half_float扩展),创建纹理时:

int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
// 检查扩展支持
if (GLES20.glGetString(GLES20.GL_EXTENSIONS).contains("OES_texture_half_float")) {
    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_HALF_FLOAT_OES, null);
}

同时确保帧缓冲附着的纹理格式与渲染格式匹配。

3. 坐标空间方向反转

ShaderToy中fragCoord原点在左下角,而Android GLES的gl_FragCoord原点默认在左上角,会导致波浪Y轴方向反转,且鼠标交互坐标错位。

修正:

  • 片段着色器中反转Y坐标:
    vec2 uv = gl_FragCoord.xy / iResolution.xy;
    uv.y = 1.0 - uv.y; // 反转Y轴,匹配ShaderToy坐标
    
  • 鼠标坐标传递时同步反转Y轴:
    float mouseY = screenHeight - touchY; // touchY为触摸点的屏幕Y像素坐标
    GLES20.glUniform3f(mouseUniformLoc, touchX, mouseY, isTouchPressed ? 2.0f : 0.0f);
    

4. 精度设置过低

你在Android着色器中使用precision lowp float;,而ShaderToy默认是highp精度。低精度会导致波浪计算的累积误差放大,出现抖动、异常衰减等问题。

修正:
将精度改为mediump或highp(部分设备支持highp):

precision mediump float;
// 若设备支持,优先使用高精度:
// precision highp float;

5. 未实现双缓冲机制

ShaderToy自动处理帧缓冲的双缓冲(读取上一帧数据,写入当前帧结果),但Android中需手动维护两个帧缓冲:一个用于读取,一个用于写入,每一帧交换两者角色。若直接读写同一个帧缓冲,会采样到部分更新的像素,导致计算逻辑混乱。

修正逻辑:

  1. 创建两个帧缓冲对象(FBO)及对应的纹理
  2. 每一帧:
    • 绑定写入FBO,执行缓冲着色器渲染计算
    • 交换读取、写入FBO的角色
    • 绑定屏幕帧缓冲,执行主着色器,用读取FBO的纹理作为输入渲染到屏幕

附:原着色器代码

顶点着色器(通用)

attribute vec2 pos;

void main()
{
    gl_Position = vec4(pos.xy, 0.0, 1.0);
}

主片段着色器

precision lowp float;

uniform vec2 iResolution;
uniform sampler2D iChannel0;

void main()
{
    vec2 uv = gl_FragCoord.xy / iResolution.xy;
    gl_FragColor = texture2D(iChannel0, uv);
}

缓冲片段着色器

precision lowp float;

uniform vec3 iMouse;
uniform vec2 iResolution;
uniform float iTime;
uniform sampler2D iChannel0;

const float delta = 1.0;

void main()
{
    float pressure = texture2D(iChannel0, gl_FragCoord.xy / iResolution.xy).x;
    float pVel = texture2D(iChannel0, gl_FragCoord.xy / iResolution.xy).y;

    float pright = texture2D(iChannel0, (gl_FragCoord.xy + vec2(1., 0.)) / iResolution.xy).x;
    float pleft = texture2D(iChannel0, (gl_FragCoord.xy + vec2(-1., 0.)) / iResolution.xy).x;
    float pup = texture2D(iChannel0, (gl_FragCoord.xy + vec2(0., 1.)) / iResolution.xy).x;
    float pdown = texture2D(iChannel0, (gl_FragCoord.xy + vec2(0., -1.)) / iResolution.xy).x;

    // 应用水平波动方程
    pVel += delta * (-2.0 * pressure + pright + pleft) / 4.0;
    // 应用垂直波动方程
    pVel += delta * (-2.0 * pressure + pup + pdown) / 4.0;

    // 通过压力速度更新压力
    pressure += delta * pVel;

    // 弹簧效果,让波浪更像水波而非声波
    pVel -= 0.005 * delta * pressure;

    // 速度阻尼,让波浪最终平静
    pVel *= 1.0 - 0.002 * delta;

    // 压力阻尼,防止无限累积
    pressure *= 0.999;

    // x=压力,y=压力速度,z/w=XY梯度
    gl_FragColor.xyzw = vec4(pressure, pVel, (pright - pleft) / 2.0, (pup - pdown) / 2.0);

    if (iMouse.z > 1.0) {
        float dist = distance(gl_FragCoord.xy, iMouse.xy);
        if (dist <= 20.0) {
            gl_FragColor.x += 1.0 - dist / 20.0;
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 04:45:03