OpenGL ES 着色器(Shader)详解

一、什么是着色器?

想象一下,你要在屏幕上画一个三角形。在传统绘图中,你会直接告诉计算机"把这个点涂成红色"。但在OpenGL ES中,你需要写一段小程序 来告诉GPU(图形处理器)"怎么涂色"------这个小程序就是着色器(Shader)

通俗理解: 着色器就像是给GPU的"工作指南",告诉它如何处理每个顶点和每个像素。

二、为什么需要着色器?

OpenGL ES采用可编程管线,这意味着:

1.你可以自定义图形的渲染方式

2.可以实现各种炫酷效果

3.充分发挥GPU的并行计算能力

三、着色器的两兄弟

OpenGL ES主要使用两种着色器,它们必须成对出现:

1. 顶点着色器(Vertex Shader):主要处理每个顶点的位置。

js 复制代码
// 顶点着色器示例
attribute vec4 aPosition;  // 输入:顶点坐标
attribute vec2 aTexCoord;  // 输入:纹理坐标
varying vec2 vTexCoord;    // 输出:传递给片段着色器

void main() {
    gl_Position = aPosition;  // 设置顶点最终位置
    vTexCoord = aTexCoord;    // 传递纹理坐标
}

2. 片段着色器(Fragment Shader)

js 复制代码
// 片段着色器示例
precision mediump float;           // 设置精度
varying vec2 vTexCoord;           // 从顶点着色器接收
uniform sampler2D uTexture;       // 纹理采样器

void main() {
    // 从纹理中采样颜色
    gl_FragColor = texture2D(uTexture, vTexCoord);
}

四、Android中使用着色器的完整流程

1.编写着色器代码

js 复制代码
// 顶点着色器源码
private final String vertexShaderCode =
    "attribute vec4 vPosition;" +
    "attribute vec2 aTexCoord;" +
    "varying vec2 vTexCoord;" +
    "void main() {" +
    "  gl_Position = vPosition;" +
    "  vTexCoord = aTexCoord;" +
    "}";

// 片段着色器源码
private final String fragmentShaderCode =
    "precision mediump float;" +
    "varying vec2 vTexCoord;" +
    "uniform sampler2D vTexture;" +
    "void main() {" +
    "  gl_FragColor = texture2D(vTexture, vTexCoord);" +
    "}";

着色器代码可以以字符串的形式硬编码在代码中,也可以创建glsl文件,在文件中写着色器代码,比如下面这种:

然后通过以下代码便可以读取两种着色器代码。

js 复制代码
fun readRawTextFile(context: Context, rawId: Int): String {
    val inputStream = context.resources.openRawResource(rawId)
    val br = BufferedReader(InputStreamReader(inputStream))
    var line: String?
    val sb = StringBuilder()
    try {
        while ((br.readLine().also { line = it }) != null) {
            sb.append(line)
            sb.append("\n")
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
    try {
        br.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }
    return sb.toString()
}

2.编译着色器

js 复制代码
public int loadShader(int type, String shaderCode) {
    // 创建着色器对象
    int shader = GLES20.glCreateShader(type);
    
    // 加载着色器源码
    GLES20.glShaderSource(shader, shaderCode);
    
    // 编译着色器
    GLES20.glCompileShader(shader);
    
    // 检查编译状态
    int[] compiled = new int[1];
    GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
    if (compiled[0] == 0) {
        Log.e("Shader", "编译失败: " + GLES20.glGetShaderInfoLog(shader));
        GLES20.glDeleteShader(shader);
        return 0;
    }
    
    return shader;
}

3.创建程序并链接

js 复制代码
public int createProgram() {
    // 编译着色器
    int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
    int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
    
    // 创建程序对象
    int program = GLES20.glCreateProgram();
    
    // 附加着色器
    GLES20.glAttachShader(program, vertexShader);
    GLES20.glAttachShader(program, fragmentShader);
    
    // 链接程序
    GLES20.glLinkProgram(program);
    
    // 检查链接状态
    int[] linkStatus = new int[1];
    GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
    if (linkStatus[0] == 0) {
        Log.e("Program", "链接失败: " + GLES20.glGetProgramInfoLog(program));
        GLES20.glDeleteProgram(program);
        return 0;
    }
    
    return program;
}

4.获取变量句柄并传值

js 复制代码
// 使用程序
GLES20.glUseProgram(program)

//定位到GPU的变量地址
vPosition = GLES20.glGetAttribLocation(program,"vPosition")
aTexCoord = GLES20.glGetAttribLocation(program,"aTexCoord")
vTexture = GLES20.glGetUniformLocation(program, "vTexture")

// 这两行代码必须成对出现。只有先详细地描述了数据,然后才能启用它
GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,0)// 这行代码是描述性的
GLES20.glEnableVertexAttribArray(vPosition) // 这行代码是执行性的

GLES20.glVertexAttribPointer(aTexCoord,2,GLES20.GL_FLOAT,false,0,0)
GLES20.glEnableVertexAttribArray(aTexCoord)

// 激活0号纹理单元
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
// 把摄像头画面(身份证号为textures的那个纹理)绑定到当前激活的0号纹理单元上。"
GLES20.glBindTexture(GLES20.GL_TEXTURE0,textures)

// 告诉片元着色器里的vTexture这个采样器(sampler2D),让它去0号纹理单元采样颜色
GLES20.glUniform1i(vTexture,0)

// 通知GPU渲染
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4)

// 释放资源
GLES20.glDisableVertexAttribArray(vPosition)
GLES20.glDisableVertexAttribArray(aTexCoord)

五、总结

着色器是OpenGL ES的核心,掌握它需要:

  1. 理解概念: 顶点着色器处理位置,片段着色器处理颜色
  2. 熟悉GLSL: 掌握基本语法和内置函数
  3. 编程流程: 编写→编译→链接→使用
  4. 多实践: 从简单效果开始,逐步深入
相关推荐
工程师老罗15 小时前
如何在Android工程中配置NDK版本
android
Libraeking18 小时前
破壁行动:在旧项目中丝滑嵌入 Compose(混合开发实战)
android·经验分享·android jetpack
市场部需要一个软件开发岗位19 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
JMchen12321 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio
crmscs21 小时前
剪映永久解锁版/电脑版永久会员VIP/安卓SVIP手机永久版下载
android·智能手机·电脑
localbob21 小时前
杀戮尖塔 v6 MOD整合版(Slay the Spire)安卓+PC端免安装中文版分享 卡牌肉鸽神作!杀戮尖塔中文版,电脑和手机都能玩!杀戮尖塔.exe 杀戮尖塔.apk
android·杀戮尖塔apk·杀戮尖塔exe·游戏分享
机建狂魔21 小时前
手机秒变电影机:Blackmagic Camera + LUT滤镜包的专业级视频解决方案
android·拍照·摄影·lut滤镜·拍摄·摄像·录像
hudawei99621 小时前
flutter和Android动画的对比
android·flutter·动画
lxysbly1 天前
md模拟器安卓版带金手指2026
android
儿歌八万首1 天前
硬核春节:用 Compose 打造“赛博鞭炮”
android·kotlin·compose·春节