强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

OpenGL / OpenCL 编程指南 / 第 14 章:OpenGL ES 与 WebGL

第 14 章:OpenGL ES 与 WebGL

当 3D 渲染需要运行在手机、平板或浏览器中时,就进入了 OpenGL ES 和 WebGL 的世界。本章讲解它们与桌面 OpenGL 的关键差异以及实战适配策略。


14.1 OpenGL ES 概述

14.1.1 版本演进

OpenGL ES(Embedded Systems)是 OpenGL 的嵌入式子集,由 Khronos 维护:

版本 年份 GLSL ES 关键特性
1.0 2003 固定管线(类 OpenGL 1.5)
2.0 2007 1.00 可编程管线(类 OpenGL 2.0)
3.0 2012 3.00 MRT、纹理压缩、遮挡查询
3.1 2014 3.10 计算着色器、间接绘制
3.2 2015 3.20 几何/细分着色器、模板索引

14.1.2 平台分布

Android  →  OpenGL ES 2.0(最低)/ 3.0(主流)/ 3.2(旗舰)
iOS      →  OpenGL ES 2.0(旧设备)/ 3.0+(推荐,但 Apple 推荐 Metal)
嵌入式   →  OpenGL ES 2.0 / 3.0
Web      →  WebGL 1.0(≈ ES 2.0)/ WebGL 2.0(≈ ES 3.0)

14.2 OpenGL ES 与桌面版的关键差异

14.2.1 API 差异对照

特性 桌面 OpenGL 4.6 OpenGL ES 3.2 OpenGL ES 2.0
GLSL 版本 4.60 3.20 ES 1.00 ES
几何着色器
计算着色器
细分着色器
VAO 必须 必须 可选扩展
纹理压缩 BPTC/S3TC/ASTC ETC2/ASTC ETC1
帧缓冲 FBO FBO FBO
浮点纹理 ✅(精度受限) 扩展
instancing 扩展
3D 纹理
双精度
glPolygonMode
默认帧缓冲 系统管理 系统管理 系统管理

14.2.2 GLSL ES 与 GLSL 差异

// GLSL ES 3.00 着色器
#version 300 es
precision mediump float;   // 必须指定默认精度

in vec3 aPos;
in vec2 aTexCoord;
out vec2 vTexCoord;
uniform mat4 mvp;

void main() {
    gl_Position = mvp * vec4(aPos, 1.0);
    vTexCoord = aTexCoord;
}
// 片段着色器(必须声明精度)
#version 300 es
precision mediump float;  // mediump = 中精度, highp = 高精度, lowp = 低精度

in vec2 vTexCoord;
out vec4 FragColor;
uniform sampler2D tex;

void main() {
    FragColor = texture(tex, vTexCoord);
}
精度 float 范围 整数范围 典型用途
lowp ±2, 精度 1/256 ±256 颜色值
mediump ±16384, 精度 1/1024 ±2^15 纹理坐标、法线
highp 完整 float ±2^31 位置计算、深度

⚠️ 精度问题:移动端 GPU 的 mediump 精度远低于桌面 float。如果着色器结果在移动端出现色带或抖动,切换到 highp。

14.2.3 纹理压缩格式

格式 平台 每像素 说明
ETC1 Android (通用) 0.5 字节 无 Alpha
ETC2 OpenGL ES 3.0+ 0.5-1 字节 带 Alpha
ASTC OpenGL ES 3.2+ 0.8-3.5 字节 可变块大小,最高质量
PVRTC iOS (PowerVR) 0.5-1 字节 Apple 设备
S3TC/DXT 桌面 + WebGL 0.5-1 字节 桌面标准

14.3 WebGL 概述

14.3.1 版本对照

版本 OpenGL ES 对应 浏览器支持 关键特性
WebGL 1.0 ES 2.0 所有现代浏览器 基础 3D
WebGL 2.0 ES 3.0 Chrome, Firefox, Edge MRT、3D 纹理

14.3.2 WebGL 代码示例

// WebGL 2.0 初始化
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');

if (!gl) {
    alert('WebGL 2.0 not supported');
    throw new Error('WebGL 2 not supported');
}

// 着色器源码
const vertexShaderSource = `#version 300 es
precision mediump float;
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;
out vec2 vTexCoord;
uniform mat4 mvp;
void main() {
    gl_Position = mvp * vec4(aPos, 1.0);
    vTexCoord = aTexCoord;
}`;

const fragmentShaderSource = `#version 300 es
precision mediump float;
in vec2 vTexCoord;
out vec4 FragColor;
uniform sampler2D tex;
void main() {
    FragColor = texture(tex, vTexCoord);
}`;

// 编译着色器
function compileShader(gl, type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('Shader error:', gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

const vs = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error('Link error:', gl.getProgramInfoLog(program));
}

// 创建 VAO
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

// 顶点数据
const vertices = new Float32Array([
    // 位置           // UV
    -0.5, -0.5, 0.0,  0.0, 0.0,
     0.5, -0.5, 0.0,  1.0, 0.0,
     0.5,  0.5, 0.0,  1.0, 1.0,
    -0.5,  0.5, 0.0,  0.0, 1.0,
]);

const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 20, 0);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 20, 12);

// 索引数据
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
const ebo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

// 加载纹理
function loadTexture(url) {
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
                  new Uint8Array([128, 128, 128, 255]));  // 占位

    const image = new Image();
    image.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        gl.generateMipmap(gl.TEXTURE_2D);
    };
    image.src = url;
    return texture;
}

// 渲染循环
function render() {
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0.1, 0.1, 0.2, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.enable(gl.DEPTH_TEST);

    gl.useProgram(program);
    gl.bindVertexArray(vao);
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

    requestAnimationFrame(render);
}
render();

14.3.3 WebGL 与 OpenGL ES 差异

特性 OpenGL ES WebGL
上下文获取 窗口系统 API canvas.getContext()
纹理上传 glTexImage2D gl.texImage2D(image/video/canvas)
异步纹理 手动同步 image.onload 回调
深度范围 [-1, 1] (NDC) [0, 1](WebGL 2)
Y 轴方向 向上 向下(需要翻转)
调试 RenderDoc 等 浏览器 DevTools
安全限制 跨域纹理限制

14.4 跨平台适配策略

14.4.1 条件编译

// 桌面版
#ifdef GL_ES
    precision mediump float;
#else
    #version 460 core
#endif

// 功能检测
#if defined(GL_ES_VERSION_3_0) || __VERSION__ >= 300
    // ES 3.0+ / GL 3.3+ 功能
#else
    // ES 2.0 回退
#endif

14.4.2 抽象层设计

// 渲染后端抽象
class RenderBackend {
public:
    virtual void init() = 0;
    virtual void createBuffer(BufferType type, size_t size, void* data) = 0;
    virtual void drawIndexed(int count) = 0;
    virtual void setShader(const char* vertSrc, const char* fragSrc) = 0;
};

class OpenGLBackend : public RenderBackend { /* ... */ };
class OpenGLESBackend : public RenderBackend { /* ... */ };
class WebGLBackend : public RenderBackend { /* JavaScript bindings */ };

14.4.3 性能适配策略

策略 桌面 移动端 Web
纹理分辨率 4K 1K-2K 1K
Mipmap 三线性 双线性 双线性
阴影 PCF 5×5 硬阴影 无/简单
后处理 多级 最少
绘制调用 无限制 <100/帧 <100/帧
三角形数 无限制 <100K/帧 <50K/帧

14.5 Android OpenGL ES 开发

14.5.1 GLSurfaceView 配置

// Android OpenGL ES 基本配置
public class MyGLSurfaceView extends GLSurfaceView {
    public MyGLSurfaceView(Context context) {
        super(context);
        setEGLContextClientVersion(3);  // OpenGL ES 3.0
        setRenderer(new MyRenderer());
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }
}

public class MyRenderer implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        GLES30.glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
        // 初始化资源
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
        // 渲染
    }
}

14.5.2 Android 纹理加载

// 使用 BitmapFactory 加载纹理
public static int loadTexture(Context context, int resourceId) {
    int[] textureIds = new int[1];
    GLES30.glGenTextures(1, textureIds, 0);

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inScaled = false;  // 不缩放
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);

    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]);
    GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0);
    GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);

    bitmap.recycle();
    return textureIds[0];
}

14.6 注意事项

⚠️ 精度问题:mediump 的 float 精度在不同设备上可能不同。关键计算(如深度值)使用 highp。

⚠️ 纹理尺寸:OpenGL ES 要求纹理尺寸为 2 的幂次(ES 2.0 严格要求,ES 3.0 放宽)。

⚠️ WebGL 跨域限制:从其他域加载纹理需要设置 CORS 头,否则 texImage2D 会失败并污染画布。

⚠️ 移动端功耗:持续渲染会快速消耗电池。使用 RENDERMODE_WHEN_DIRTY 并在不需要时停止渲染。

⚠️ Apple 的方向:Apple 已在 iOS 上弃用 OpenGL ES,推荐使用 Metal。iOS 开发应考虑 Metal 或使用跨平台引擎(Unity/Unreal)。


14.7 业务场景

场景 1:Web 3D 产品展示

Three.js + WebGL 2.0 实现在线产品 360° 浏览器预览。

场景 2:移动游戏

OpenGL ES 3.0 实现移动端实时渲染,使用 ETC2 压缩纹理减少内存占用。

场景 3:地图引擎

WebGL 实现矢量地图渲染,大量 POI 使用实例化绘制。


14.8 扩展阅读

资源 说明
OpenGL ES 3.2 规范 官方规范
WebGL 2.0 规范 官方规范
Three.js 最流行的 WebGL 框架
WebGL Fundamentals WebGL 入门教程
Android OpenGL ES Android 官方文档

本章小结

  • OpenGL ES 是桌面 OpenGL 的嵌入式子集,ES 3.0+ 对应桌面版的大部分功能
  • GLSL ES 必须声明精度(lowp/mediump/highp),精度在不同设备上可能不同
  • WebGL 1.0 ≈ ES 2.0,WebGL 2.0 ≈ ES 3.0,通过 canvas.getContext 获取上下文
  • 跨平台适配需要关注纹理压缩格式、着色器精度和渲染负载
  • 移动端优化重点:减少绘制调用、使用压缩纹理、控制分辨率

上一章第 13 章:OpenCL 进阶 下一章第 15 章:Vulkan 入门