一、OpenGL ES 简介
OpenGL ES (OpenGL for Embedded Systems) 是OpenGL的子集,专为移动设备和嵌入式系统设计。它是智能手机、平板电脑、游戏主机等设备上2D/3D图形渲染的标准API。
二、OpenGL ES版本
-
OpenGL ES 1.x: 固定功能管线
-
OpenGL ES 2.0: 可编程着色器管线,移除了固定功能
-
OpenGL ES 3.0/3.1/3.2: 扩展功能,更多特性
三、基础概念
1. 渲染管线
OpenGL ES 2.0+的可编程管线主要包含:
-
顶点着色器 - 处理每个顶点
-
图元装配 - 组成点、线、三角形
-
光栅化 - 将图元转换为片段
-
片段着色器 - 处理每个片段(像素)
-
逐片段操作 - 深度测试、混合等
2. 着色器(Shaders)
(1) 着色器类型
着色器类型 | 作用 |
---|---|
顶点着色器 | 处理每个顶点的位置、法线、UV坐标等属性 |
片段着色器 | 计算每个像素(片段)的最终颜色 |
计算着色器 | (ES 3.1+)通用计算,用于非图形任务(如物理模拟、粒子系统) |
(2) 着色器编写流程
-
编写 GLSL ES 代码(顶点/片段着色器)。
-
编译着色器(
glCreateShader
+glCompileShader
)。 -
链接着色器程序(
glCreateProgram
+glLinkProgram
)。 -
在渲染时使用该程序(
glUseProgram
)。
(3) 语法
(3.1) 语法版本声明
OpenGL ES 着色器需指定版本:
glsl
// OpenGL ES 2.0(WebGL 1.0)
#version 100
// OpenGL ES 3.0(WebGL 2.0)
#version 300 es
-
ES 2.0 使用较旧的语法(如
attribute
/varying
)。 -
ES 3.0+ 使用现代语法(如
in
/out
/layout
)。
(3.2) 变量限定符
顶点着色器输入(Vertex Shader Input)
限定符 | ES 2.0 | ES 3.0+ | 说明 |
---|---|---|---|
顶点属性 | attribute |
in + layout |
每个顶点独有的数据(如位置) |
统一变量 | uniform |
uniform |
全局常量(如变换矩阵) |
顶点→片段着色器传递数据
限定符 | ES 2.0 | ES 3.0+ | 说明 |
---|---|---|---|
插值变量 | varying |
out (VS) → in (FS) |
光栅化阶段插值(如UV坐标) |
片段着色器输出
限定符 | ES 2.0 | ES 3.0+ | 说明 |
---|---|---|---|
最终颜色 | gl_FragColor |
out vec4 fragColor |
片段着色器的输出颜色 |
(3.3) 精度限定符(Precision Qualifiers)
OpenGL ES 要求显式指定浮点数/整数精度(尤其是片段着色器):
glsl
// 设置默认浮点精度(ES 2.0 片段着色器必须声明)
precision mediump float;
// 声明变量时指定精度
highp vec3 position; // 高精度(32-bit,适合位置)
mediump vec2 uv; // 中等精度(16-bit,适合UV坐标)
lowp vec4 color; // 低精度(10-bit,适合颜色)
(4) OpenGL ES 2.0 着色器示例
(4.1) 顶点着色器(Vertex Shader)
glsl
#version 100
attribute vec3 aPosition; // 顶点位置
attribute vec2 aTexCoord; // 纹理坐标
varying vec2 vTexCoord; // 传递给片段着色器的UV
uniform mat4 uMVPMatrix; // 模型-视图-投影矩阵
void main() {
gl_Position = uMVPMatrix * vec4(aPosition, 1.0);
vTexCoord = aTexCoord; // 传递UV坐标
}
(4.2) 片段着色器(Fragment Shader)
glsl
#version 100
precision mediump float; // 必须声明默认精度
varying vec2 vTexCoord; // 来自顶点着色器的UV
uniform sampler2D uTexture; // 纹理采样器
void main() {
gl_FragColor = texture2D(uTexture, vTexCoord);
}
(5) OpenGL ES 3.0 着色器示例
(5.1) 顶点着色器(Vertex Shader)
glsl
#version 300 es
layout(location = 0) in vec3 aPosition; // 使用 layout 指定属性位置
layout(location = 1) in vec2 aTexCoord;
out vec2 vTexCoord; // 输出到片段着色器
uniform mat4 uMVPMatrix;
void main() {
gl_Position = uMVPMatrix * vec4(aPosition, 1.0);
vTexCoord = aTexCoord;
}
(5.2) 片段着色器(Fragment Shader)
glsl
#version 300 es
precision mediump float;
in vec2 vTexCoord; // 来自顶点着色器的输入
uniform sampler2D uTexture;
out vec4 fragColor; // 输出颜色(替代 gl_FragColor)
void main() {
fragColor = texture(uTexture, vTexCoord);
}
3. 坐标系统
-
模型坐标 → 世界坐标 → 视图坐标 → 裁剪坐标 → 屏幕坐标
-
通过模型(Model)、视图(View)、投影(Projection)矩阵变换
四、环境搭建
4.1 Android 开发环境
-
安装 Android Studio
-
配置 NDK (Native Development Kit)
-
在 CMakeLists.txt 中添加 OpenGL ES 依赖:
cmake
find_library( # Sets the name of the path variable.
log-lib
log )
find_library( gles-lib
GLESv2 )
target_link_libraries( # Specifies the target library.
native-lib
${log-lib}
${gles-lib} )
4.2 iOS 开发环境
-
在 Xcode 项目中添加 OpenGLES.framework
-
包含头文件:
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
五、OpenGL ES 程序示例
初始化 OpenGL ES 上下文
// Android 示例
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, nullptr, nullptr);
const EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 16,
EGL_NONE
};
EGLConfig config;
EGLint numConfigs;
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
EGLSurface surface = eglCreateWindowSurface(display, config, window, nullptr);
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext context = eglCreateContext(display, config, nullptr, contextAttribs);
eglMakeCurrent(display, surface, surface, context);
简单的三角形绘制
// 顶点着色器
const char* vertexShaderSource =
"attribute vec4 vPosition;"
"void main() {"
" gl_Position = vPosition;"
"}";
// 片段着色器
const char* fragmentShaderSource =
"precision mediump float;"
"void main() {"
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);"
"}";
// 编译着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
// 创建程序
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
glUseProgram(program);
// 顶点数据
GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
// 创建VBO
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 获取顶点属性位置
GLint positionLoc = glGetAttribLocation(program, "vPosition");
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
// 绘制
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
六、纹理映射
// 加载纹理
GLuint loadTexture(const char* data, int width, int height) {
GLuint textureId;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
// 设置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 加载纹理数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, data);
return textureId;
}
// 在着色器中使用纹理
const char* fragmentShaderSource =
"precision mediump float;"
"uniform sampler2D uTexture;"
"varying vec2 vTexCoord;"
"void main() {"
" gl_FragColor = texture2D(uTexture, vTexCoord);"
"}";
七、3D 变换
// 在顶点着色器中添加变换矩阵
const char* vertexShaderSource =
"uniform mat4 uMVPMatrix;"
"attribute vec4 vPosition;"
"attribute vec2 aTexCoord;"
"varying vec2 vTexCoord;"
"void main() {"
" gl_Position = uMVPMatrix * vPosition;"
" vTexCoord = aTexCoord;"
"}";
// C++ 代码中设置矩阵
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f),
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 projection = glm::perspective(glm::radians(45.0f),
(float)width/(float)height,
0.1f, 100.0f);
glm::mat4 mvp = projection * view * model;
GLint mvpLoc = glGetUniformLocation(program, "uMVPMatrix");
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvp));
八、最佳实践
-
避免频繁的状态切换: 尽量一次性设置好所有状态
-
使用顶点缓冲对象(VBO): 减少CPU-GPU数据传输
-
批处理绘制调用: 合并多个小绘制调用
-
纹理压缩: 使用ETC/PVRTC等压缩格式
-
着色器优化: 减少分支和复杂计算
-
帧率控制: 使用垂直同步(VSync)避免过度绘制
九、调试技巧
-
使用
glGetError()
检查错误 -
在着色器编译后检查日志:
GLint success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { GLchar infoLog[512]; glGetShaderInfoLog(shader, 512, nullptr, infoLog); // 输出错误信息 }
-
使用图形调试工具如 RenderDoc 或 Xcode OpenGL ES Debugger
十、高级示例代码
-
帧缓冲对象(FBO)离屏渲染
// OpenGL ES 2.0 FBO 离屏渲染完整示例 #include <GLES2/gl2.h> #include <EGL/egl.h> #include <iostream> // 全局变量 GLuint fbo; // 帧缓冲对象 GLuint colorTexture; // 颜色附件纹理 GLuint depthStencilRBO; // 深度和模板渲染缓冲对象 GLuint quadVAO; // 全屏四边形VAO GLuint quadVBO; // 全屏四边形VBO GLuint sceneProgram; // 场景着色器程序 GLuint postProcessProgram; // 后期处理着色器程序 int width = 800, height