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. 多实践: 从简单效果开始,逐步深入
相关推荐
故渊at2 小时前
系列三:组件化与模块化进阶 | 第11篇 组件化项目规范与问题根治:依赖、资源、Manifest 与混淆的全链路管控
android·架构·mvvm·模块化·组件化
故渊at2 小时前
系列二:MVVM 深度实战与项目重构 | 第7篇 LiveData & StateFlow 状态管理实战:从“粘包弹”到“丝滑流式”
android·重构
是阿建吖!2 小时前
【Linux】信号
android·linux·c语言·c++
alexhilton4 小时前
AppFunctions:让你的Android应用更容易被AI智能体发现
android·kotlin·android jetpack
qq3621967054 小时前
APK文件签名校验教程:验证APK真伪的完整方法
android·智能手机
赏金术士4 小时前
Android 组件化概念和特征
android·kotlin·组件化
2501_9159090610 小时前
深入解析Mock.js:功能、应用及实战案例,提升前端开发效率
android·ios·小程序·https·uni-app·iphone·webview
流星白龙12 小时前
【MySQL高阶】21.撤销表空间,撤销日志
android·mysql·adb
我命由我1234513 小时前
Android 开发,FragmentPagerAdapter 的 isViewFromObject 方法问题
android·java-ee·kotlin·android studio·android jetpack·android-studio·android runtime