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. 多实践: 从简单效果开始,逐步深入
相关推荐
alexhilton7 小时前
Room Fts4:全文搜索替代 SQL LIKE
android·kotlin·android jetpack
hai_android12 小时前
把 Android App 打包成系统应用(System App)
android·kotlin·android jetpack
JMchen13 小时前
【Jetpack 内核实战 01】开篇:Jetpack 组件全家桶核心逻辑与专栏完整规划
android·kotlin·android jetpack
骑着蜗牛撵大象32715 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
hunterandroid15 小时前
Retrofit 网络请求的异常处理与重试策略实战
android
sejinan16 小时前
极致轻量与秒级起播:NativeWasmTv 开源硬核技术解析与升级版体验
android·开源·webview·tv
小羊没烦恼!16 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
mmsx16 小时前
Android 静默失败实录:五个没有日志的线上事故
android·kotlin
hai_android17 小时前
从零实现一个支持跨进程(IPC)的自定义 Toast
android·kotlin·android jetpack
龙腾-虎跃17 小时前
基于 SIP 协议的自研电话终端:FreeSWITCH 语音与视频通信实战
android·音视频·freeswitch·sip协议终端·wertc