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. 多实践: 从简单效果开始,逐步深入
相关推荐
西贝爱学习5 小时前
快速下载jdk17+Android Studio 2025
android·ide·android studio
2501_915921436 小时前
掌握 iOS 26 App 性能监控,从监测到优化的多工具组合流程
android·macos·ios·小程序·uni-app·cocoa·iphone
侑虎科技6 小时前
对Android游戏画面抖动现象的研究
android·性能优化
2501_916008897 小时前
手机 iOS 系统全解析,生态优势、开发机制与跨平台应用上架实践指南
android·ios·智能手机·小程序·uni-app·iphone·webview
2501_915918419 小时前
App 使用 HTTPS 的工程化实战,从接入到真机排查的一线指南
android·ios·小程序·https·uni-app·iphone·webview
恋猫de小郭10 小时前
第一台 Andriod XR 设备发布,Jetpack Compose XR 有什么不同?对原生开发有何影响?
android·前端·flutter
allk5511 小时前
List && Map在安卓中的优化
android·数据结构·性能优化·list·map
.豆鲨包11 小时前
【Android】从源码角度理解Handler机制
android
杨筱毅12 小时前
【Android】Handler/Looper机制相关的类图和流程图
android·java·流程图