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

ShaderToy着色器转Android GLSL遇异常,求修正与实现方案

问题描述

作为GLSL与着色器新手,我尝试将一款胶片颗粒ShaderToy着色器转换为适配Android GPUImage库的片段着色器,参考了GPUImageWhiteBalanceFilter示例。

原ShaderToy代码:

// Fork of "Simple Film Grain Shader" by terchapone.
// 2022-04-24 03:48:40

const float grainFactor = 0.1; //[0.0, 1.0]

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Calculate noise and sample texture
    float mdf = grainFactor; // increase for noise amount 
    float noise = (fract(sin(dot(uv, vec2(12.9898,78.233)*2.0)) * 43758.5453));
    vec4 tex = texture(iChannel0, uv);  
    vec4 col = tex - noise * mdf;

    // Output to screen
    fragColor = col;
}

转换后的GPUImage适配代码:

uniform sampler2D inputImageTexture;
varying highp vec2 textureCoordinate;

uniform lowp float grain;

void main(){
    lowp vec4 source = texture2D(inputImageTexture, textureCoordinate);
    
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = textureCoordinate.xy ;

    // Calculate noise and sample texture
    float mdf = grain; // increase for noise amount 
    float noise = (fract(sin(dot(uv, vec2(12.9898,78.233)*2.0)) * 43758.5453));
    
    vec4 col = source - (noise * mdf);

    // Output to screen
    gl_FragColor = col;
}

调整grain值在0f到1f之间时,输出画面出现对角线虚线,请问问题出在哪里?如果当前实现思路有误,求可行的胶片颗粒着色器实现方案。


问题原因分析

出现对角线虚线的核心问题是噪声函数的周期性与纹理坐标精度不匹配:

  • ShaderToy中fragCoord是像素级整数坐标,除以屏幕分辨率后得到的uv会被屏幕尺寸打散噪声周期,不会出现规律线条。
  • GPUImage的textureCoordinate是纹理归一化坐标(范围0-1),直接用它计算噪声时,函数周期会在整个纹理上重复,当纹理尺寸与噪声周期成特定比例时,就会出现规律性对角线。

此外,当前实现用单通道噪声直接做减法,会导致画面偏暗,且颗粒效果不符合真实胶片(真实颗粒是RGB通道独立随机噪声)。


修正与实现方案

1. 修复周期性问题的基础版本

引入纹理实际尺寸作为uniform,将归一化坐标转换为像素级坐标后计算噪声,打破周期规律:

uniform sampler2D inputImageTexture;
varying highp vec2 textureCoordinate;
uniform lowp float grain;
uniform highp vec2 textureSize; // 传入纹理宽高,如(1920.0, 1080.0)

void main(){
    highp vec2 pixelCoord = textureCoordinate * textureSize;
    float noise = fract(sin(dot(pixelCoord, vec2(12.9898, 78.233))) * 43758.5453);
    
    // 让噪声在-0.5到0.5区间波动,避免仅让画面变暗
    noise = noise * 2.0 - 1.0;
    lowp vec4 source = texture2D(inputImageTexture, textureCoordinate);
    lowp vec4 col = source + noise * grain;
    
    // 限制颜色值在合法范围[0,1]内
    col = clamp(col, 0.0, 1.0);
    gl_FragColor = col;
}

2. 贴近真实胶片的进阶实现(RGB独立动态颗粒)

模拟真实胶片RGB通道独立噪声,加入时间偏移实现动态颗粒效果:

uniform sampler2D inputImageTexture;
varying highp vec2 textureCoordinate;
uniform lowp float grain;
uniform highp vec2 textureSize;
uniform highp float time; // 可选,传入系统时间实现动态颗粒

float random(vec2 st) {
    return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123);
}

void main(){
    highp vec2 pixelCoord = textureCoordinate * textureSize + vec2(time * 100.0);
    lowp vec4 source = texture2D(inputImageTexture, textureCoordinate);
    
    // RGB通道生成独立噪声
    lowp float noiseR = random(pixelCoord + vec2(0.0, 0.0)) * 2.0 - 1.0;
    lowp float noiseG = random(pixelCoord + vec2(100.0, 100.0)) * 2.0 - 1.0;
    lowp float noiseB = random(pixelCoord + vec2(200.0, 200.0)) * 2.0 - 1.0;
    
    lowp vec3 noiseVec = vec3(noiseR, noiseG, noiseB) * grain;
    lowp vec3 finalColor = source.rgb + noiseVec;
    
    // 限制颜色范围,避免溢出
    finalColor = clamp(finalColor, 0.0, 1.0);
    gl_FragColor = vec4(finalColor, source.a);
}

使用注意事项
  • 需在GPUImage的Java代码中传入textureSize参数,示例:
    filter.setFloatVec2("textureSize", new float[]{bitmap.getWidth(), bitmap.getHeight()});
    
  • 若使用动态颗粒,需在渲染循环中更新time参数,比如传入系统时间的秒数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 07:23:22