一、什么是着色器?
想象一下,你要在屏幕上画一个三角形。在传统绘图中,你会直接告诉计算机"把这个点涂成红色"。但在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的核心,掌握它需要:
- 理解概念: 顶点着色器处理位置,片段着色器处理颜色
- 熟悉GLSL: 掌握基本语法和内置函数
- 编程流程: 编写→编译→链接→使用
- 多实践: 从简单效果开始,逐步深入