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. 多实践: 从简单效果开始,逐步深入
相关推荐
Just_Paranoid7 小时前
【AOSP】Android Dump 信息快速定位方法
android·adb·framework·service·aosp·dumpsys
帅得不敢出门7 小时前
MTK Android11获取真实wifi mac地址
android·mtk
成都大菠萝7 小时前
2-2-16 快速掌握Kotlin-泛型扩展函数
android
I'm Jie7 小时前
Gradle 多模块依赖集中管理方案,Version Catalogs 详解(Kotlin DSL)
android·java·spring boot·kotlin·gradle·maven
BoomHe8 小时前
Android 13 (API 33)开发自己的 Settings ,如何配置 WiFi BT 权限
android
城东米粉儿8 小时前
ConcurrentHashMap实现原理 笔记
android
佳哥的技术分享8 小时前
System.setProperty vs adb setprop (Android SystemProperties)
android·adb
Railshiqian9 小时前
通过adb命令获取某个window或View/子View的绘制内容并输出为png图片的方法
android·adb·dump view
XI锐真的烦9 小时前
新手该如何选择 Android 开发框架?
android
走在路上的菜鸟9 小时前
Android学Dart学习笔记第二十六节 并发
android·笔记·学习·flutter