OpenGL ES 深度剖析

OpenGL ES 深度剖析

一、OpenGL ES 概述

1.1 技术定位与应用场景

OpenGL ES(OpenGL for Embedded Systems)是专为嵌入式设备(如移动电话、PDA、游戏主机等)设计的图形API,它是OpenGL的子集,针对资源有限的设备进行了优化。OpenGL ES提供了一套跨平台的图形编程接口,允许开发者在不同的硬件平台上实现高效的2D和3D图形渲染。

从应用场景来看,OpenGL ES广泛应用于移动游戏开发、虚拟现实(VR)、增强现实(AR)、图形编辑器、医学图像处理、地理信息系统等领域。例如,在移动游戏中,OpenGL ES用于实现游戏场景的渲染、角色动画、特效等;在VR应用中,它用于实现沉浸式的3D环境渲染和交互。

架构图

graph TD A[OpenGL ES] --> B[移动游戏] A --> C[虚拟现实] A --> D[增强现实] A --> E[图形编辑器] A --> F[医学图像处理] A --> G[地理信息系统] B --> H[场景渲染] B --> I[角色动画] B --> J[特效] C --> K[沉浸式3D环境] C --> L[交互]

调用流程图

sequenceDiagram participant App as 应用程序 participant ESAPI as OpenGL ES API participant Driver as 驱动程序 participant GPU as GPU硬件 App->>ESAPI: 调用图形渲染函数 ESAPI->>Driver: 传递渲染命令 Driver->>GPU: 执行渲染操作 GPU-->>Driver: 返回渲染结果 Driver-->>ESAPI: 返回执行状态 ESAPI-->>App: 返回API调用结果

类的关系图

classDiagram class OpenGL_ES { +CreateContext() +MakeCurrent() +CreateShader() +CreateProgram() +BindBuffer() +DrawArrays() } class EGL { +Initialize() +ChooseConfig() +CreateContext() +CreateSurface() +MakeCurrent() } class GLES20 { +glClear() +glDrawArrays() +glUseProgram() +glVertexAttribPointer() } class GLES30 { +glBindVertexArray() +glDrawElementsInstanced() +glTexStorage2D() } OpenGL_ES <|-- EGL OpenGL_ES <|-- GLES20 OpenGL_ES <|-- GLES30

1.2 发展历程与版本演进

OpenGL ES的发展历程可以追溯到2003年,当时Khronos Group发布了OpenGL ES 1.0版本。此后,随着移动设备性能的不断提升和图形技术的不断发展,OpenGL ES也经历了多个版本的演进:

  • OpenGL ES 1.x:基于固定功能管线,主要用于简单的2D和3D图形渲染,不支持可编程着色器。
  • OpenGL ES 2.0:引入了可编程着色器模型,支持顶点着色器和片段着色器,大大增强了图形渲染的灵活性和表现力。
  • OpenGL ES 3.0:在2.0的基础上增加了许多新特性,如纹理压缩、多渲染目标、实例化渲染等,提升了图形渲染的性能和效率。
  • OpenGL ES 3.1:进一步扩展了功能,支持计算着色器、纹理存储、顶点数组对象等,为通用计算和高级图形效果提供了支持。
  • OpenGL ES 3.2:增加了对曲面细分着色器、几何着色器等高级特性的支持,提升了图形渲染的质量和复杂度。

每个版本的更新都带来了新的功能和性能提升,同时也保持了向后兼容性,使得开发者可以根据目标设备的支持情况选择合适的版本进行开发。

架构图

graph TD A[OpenGL ES 1.0] --> B[固定功能管线] A --> C[简单2D/3D渲染] B --> D[顶点处理] B --> E[图元装配] B --> F[光栅化] B --> G[片段处理] H[OpenGL ES 2.0] --> I[可编程着色器] H --> J[顶点着色器] H --> K[片段着色器] L[OpenGL ES 3.0] --> M[纹理压缩] L --> N[多渲染目标] L --> O[实例化渲染] P[OpenGL ES 3.1] --> Q[计算着色器] P --> R[纹理存储] P --> S[顶点数组对象] T[OpenGL ES 3.2] --> U[曲面细分着色器] T --> V[几何着色器]

调用流程图

sequenceDiagram participant Dev as 开发者 participant ES1 as OpenGL ES 1.x participant ES2 as OpenGL ES 2.0 participant ES3 as OpenGL ES 3.x Dev->>ES1: 使用固定功能管线 ES1->>ES1: 执行顶点处理 ES1->>ES1: 执行图元装配 ES1->>ES1: 执行光栅化 ES1->>ES1: 执行片段处理 Dev->>ES2: 使用可编程着色器 ES2->>ES2: 编译顶点着色器 ES2->>ES2: 编译片段着色器 ES2->>ES2: 链接着色器程序 ES2->>ES2: 执行着色器程序 Dev->>ES3: 使用高级特性 ES3->>ES3: 创建顶点数组对象 ES3->>ES3: 使用多渲染目标 ES3->>ES3: 执行实例化渲染 ES3->>ES3: 使用计算着色器

类的关系图

classDiagram class ES1_Context { +SetVertexArray() +SetNormalArray() +SetTexCoordArray() +DrawArrays() +DrawElements() } class ES2_Context { +CreateShader() +CompileShader() +CreateProgram() +LinkProgram() +UseProgram() +SetUniform() } class ES3_Context { +CreateVertexArray() +BindVertexArray() +TexStorage2D() +DrawElementsInstanced() +DispatchCompute() } class ES3_1_Context { +CreateShaderProgramv() +TexBufferRange() +BindImageTexture() } class ES3_2_Context { +CreateTransformFeedback() +BindTransformFeedback() +PatchParameteri() } ES1_Context <|-- ES2_Context ES2_Context <|-- ES3_Context ES3_Context <|-- ES3_1_Context ES3_1_Context <|-- ES3_2_Context

1.3 与其他图形API的对比

与其他图形API(如Direct3D、Vulkan、WebGL等)相比,OpenGL ES具有以下特点:

  • 跨平台性:OpenGL ES是一种跨平台的图形API,支持多种操作系统(如iOS、Android、Windows、Linux等)和硬件平台,具有良好的兼容性。
  • 轻量级设计:针对嵌入式设备的资源限制进行了优化,API设计简洁,内存占用少,运行效率高。
  • 广泛的硬件支持:几乎所有的移动设备和嵌入式系统都支持OpenGL ES,开发者可以放心地使用它进行跨设备开发。
  • 可编程着色器:从OpenGL ES 2.0开始支持可编程着色器,允许开发者通过编写自定义的着色器代码来实现复杂的图形效果。
  • 成熟的生态系统:OpenGL ES已经存在了很长时间,拥有丰富的开发工具、文档和社区资源,开发者可以很容易地找到所需的帮助和支持。

然而,OpenGL ES也有一些不足之处,例如:

  • API设计复杂:OpenGL ES的API设计相对复杂,学习曲线较陡,尤其是对于初学者来说可能会有一定的难度。
  • 缺乏现代特性:与最新的图形API(如Vulkan、DirectX 12)相比,OpenGL ES在性能优化和底层硬件控制方面可能存在一定的局限性。
  • 驱动兼容性问题:在某些设备上,OpenGL ES的驱动可能存在兼容性问题,导致应用程序出现渲染错误或性能下降。

架构图

graph TD A[图形API] --> B[OpenGL ES] A --> C[Direct3D] A --> D[Vulkan] A --> E[WebGL] B --> F[跨平台] B --> G[轻量级] B --> H[广泛硬件支持] B --> I[可编程着色器] B --> J[成熟生态系统] C --> K[Windows平台] C --> L[高性能] C --> M[DirectX系列] D --> N[低开销] D --> O[多线程优化] D --> P[底层硬件控制] E --> Q[Web浏览器] E --> R[JavaScript接口] E --> S[基于OpenGL ES]

对比表

特性 OpenGL ES Direct3D Vulkan WebGL
平台支持 跨平台(移动、嵌入式) Windows 跨平台(桌面、移动) 网页浏览器
API类型 状态机模型 面向对象 显式控制 基于OpenGL ES
着色器模型 GLSL ES HLSL SPIR-V GLSL ES
性能 中等 极高 中等
学习曲线 较陡 较陡 极陡 中等
多线程支持 有限 良好 优秀 有限
底层硬件控制 有限 中等 优秀 有限
生态系统 成熟 成熟 发展中 成熟

类的关系图

classDiagram class GraphicsAPI { <> +Init() +CreateContext() +Draw() +Destroy() } class OpenGL_ES { +InitEGL() +CreateShader() +CreateProgram() +BindBuffer() } class Direct3D { +CreateDevice() +CreateCommandQueue() +CreatePipelineState() +ExecuteCommandLists() } class Vulkan { +CreateInstance() +CreateDevice() +CreateCommandBuffer() +SubmitQueue() } class WebGL { +GetContext() +CreateShader() +CreateProgram() +BufferData() } GraphicsAPI <|.. OpenGL_ES GraphicsAPI <|.. Direct3D GraphicsAPI <|.. Vulkan GraphicsAPI <|.. WebGL

1.4 架构设计与核心组件

OpenGL ES的架构设计采用了分层的模型,主要由以下几个核心组件组成:

  • 客户端(Client):客户端是应用程序运行的环境,包括CPU、内存和应用程序代码。客户端负责生成图形数据(如顶点坐标、纹理数据等),并通过OpenGL ES API将这些数据发送到服务器。
  • 服务器(Server):服务器是图形硬件(GPU)及其驱动程序的抽象。服务器负责接收客户端发送的命令和数据,并执行实际的图形渲染操作。
  • 命令队列(Command Queue):命令队列是客户端和服务器之间的通信桥梁。客户端将OpenGL ES命令放入命令队列中,服务器从命令队列中取出命令并执行。
  • 状态机(State Machine):OpenGL ES使用状态机模型来管理渲染状态。应用程序可以设置各种状态参数(如着色器程序、纹理、变换矩阵等),这些状态参数会影响后续的渲染操作。

OpenGL ES的核心功能可以分为以下几个模块:

  • 上下文管理:负责创建、管理和销毁OpenGL ES上下文,以及在上下文中设置和查询状态。
  • 渲染管线:定义了从顶点数据到最终像素的处理流程,包括顶点处理、图元装配、光栅化、片段处理等阶段。
  • 着色器系统:支持可编程着色器,允许开发者通过编写顶点着色器和片段着色器来控制渲染过程。
  • 纹理管理:负责加载、创建和管理纹理数据,以及在渲染过程中使用纹理。
  • 帧缓冲对象:提供了对渲染目标的灵活控制,允许将渲染结果输出到纹理或其他缓冲区。
  • 同步机制:提供了各种同步机制,确保渲染操作按正确的顺序执行,并在需要时进行CPU和GPU之间的同步。

架构图

graph TD A[OpenGL ES架构] --> B[客户端] A --> C[命令队列] A --> D[服务器] A --> E[状态机] B --> F[应用程序] B --> G[CPU] B --> H[内存] D --> I[GPU硬件] D --> J[驱动程序] E --> K[着色器状态] E --> L[纹理状态] E --> M[变换状态] E --> N[渲染状态] E --> O[帧缓冲状态] A --> P[核心功能模块] P --> Q[上下文管理] P --> R[渲染管线] P --> S[着色器系统] P --> T[纹理管理] P --> U[帧缓冲对象] P --> V[同步机制]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant State as 状态机 participant Queue as 命令队列 participant Driver as 驱动程序 participant GPU as GPU硬件 App->>API: 设置状态参数 API->>State: 更新状态 App->>API: 提交渲染命令 API->>Queue: 将命令放入队列 Queue->>Driver: 驱动程序获取命令 Driver->>GPU: 执行渲染操作 GPU-->>Driver: 返回执行结果 Driver-->>Queue: 更新队列状态 Queue-->>API: 返回命令执行状态 API-->>App: 返回API调用结果

类的关系图

classDiagram class OpenGL_ES { +ContextManager contextManager +Pipeline pipeline +ShaderSystem shaderSystem +TextureManager textureManager +FramebufferManager framebufferManager +SyncManager syncManager } class ContextManager { +CreateContext() +DestroyContext() +MakeCurrent() +GetState() } class Pipeline { +VertexProcessing() +PrimitiveAssembly() +Rasterization() +FragmentProcessing() } class ShaderSystem { +CreateShader() +CompileShader() +CreateProgram() +LinkProgram() +UseProgram() } class TextureManager { +CreateTexture() +BindTexture() +TexImage2D() +GenerateMipmap() } class FramebufferManager { +CreateFramebuffer() +BindFramebuffer() +AttachTexture() +CheckStatus() } class SyncManager { +FenceSync() +ClientWaitSync() +WaitSync() +DeleteSync() } OpenGL_ES *-- ContextManager OpenGL_ES *-- Pipeline OpenGL_ES *-- ShaderSystem OpenGL_ES *-- TextureManager OpenGL_ES *-- FramebufferManager OpenGL_ES *-- SyncManager

二、OpenGL ES 上下文管理

2.1 上下文的基本概念

在OpenGL ES中,上下文(Context)是一个核心概念,它代表了一个OpenGL ES的运行环境。每个上下文都包含了OpenGL ES的所有状态信息,如当前的着色器程序、纹理、变换矩阵、渲染状态等。应用程序必须在一个上下文中执行OpenGL ES命令,才能进行图形渲染。

上下文的主要作用包括:

  • 存储OpenGL ES的状态:上下文维护了OpenGL ES的所有状态信息,应用程序可以通过设置这些状态来控制渲染行为。
  • 管理资源:上下文中管理了所有的OpenGL ES资源,如着色器程序、纹理、缓冲区对象等。这些资源只能在创建它们的上下文中使用。
  • 执行命令:OpenGL ES命令必须在一个上下文中执行。每个命令都会影响当前上下文中的状态或资源。

在多线程环境中,每个线程可以有自己的上下文,或者多个线程可以共享同一个上下文。上下文之间的资源共享需要特别注意,因为某些资源(如纹理和缓冲区对象)可能不是线程安全的。

架构图

graph TD A[OpenGL ES上下文] --> B[状态信息] A --> C[资源管理] A --> D[命令执行] B --> E[着色器状态] B --> F[纹理状态] B --> G[变换状态] B --> H[渲染状态] B --> I[帧缓冲状态] C --> J[着色器程序] C --> K[纹理对象] C --> L[缓冲区对象] C --> M[顶点数组对象] D --> N[命令队列] D --> O[状态验证] D --> P[命令执行]

调用流程图

sequenceDiagram participant App as 应用程序 participant Context as 上下文 participant State as 状态机 participant Resources as 资源管理器 App->>Context: 创建上下文 Context->>State: 初始化状态 Context->>Resources: 初始化资源管理器 App->>Context: 设置状态参数 Context->>State: 更新状态 App->>Context: 创建资源 Context->>Resources: 创建并管理资源 App->>Context: 执行渲染命令 Context->>State: 验证当前状态 Context->>Resources: 检查资源可用性 Context->>Context: 执行命令 Context-->>App: 返回执行结果

类的关系图

classDiagram class Context { -State state -ResourceManager resourceManager -CommandQueue commandQueue +MakeCurrent() +CreateShader() +CreateTexture() +CreateBuffer() +DrawArrays() +Flush() +Finish() } class State { -ShaderState shaderState -TextureState textureState -TransformState transformState -RenderState renderState -FramebufferState framebufferState +GetState() +SetState() } class ResourceManager { -std::map shaders -std::map textures -std::map buffers -std::map vertexArrays +CreateResource() +DestroyResource() +GetResource() } class CommandQueue { -std::queue commands +Enqueue() +Dequeue() +Execute() } Context *-- State Context *-- ResourceManager Context *-- CommandQueue

2.2 上下文的创建与初始化

在不同的平台上,创建OpenGL ES上下文的方式可能有所不同。下面以Android平台为例,介绍OpenGL ES上下文的创建过程。

在Android平台上,通常使用EGL(Embedded-System Graphics Library)来创建和管理OpenGL ES上下文。EGL是一个底层的图形库,它提供了OpenGL ES与本地窗口系统之间的接口。

下面是一个在Android平台上创建OpenGL ES 3.0上下文的示例代码:

java 复制代码
// 获取EGL显示
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (display == EGL14.EGL_NO_DISPLAY) {
    // 处理错误
    return;
}

// 初始化EGL
int[] version = new int[2];
if (!EGL14.eglInitialize(display, version, 0, version, 1)) {
    // 处理错误
    return;
}

// 配置EGL上下文属性
int[] configAttribs = {
    EGL14.EGL_RED_SIZE, 8,
    EGL14.EGL_GREEN_SIZE, 8,
    EGL14.EGL_BLUE_SIZE, 8,
    EGL14.EGL_ALPHA_SIZE, 8,
    EGL14.EGL_DEPTH_SIZE, 16,
    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES3_BIT_KHR,
    EGL14.EGL_NONE
};

// 选择EGL配置
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(display, configAttribs, 0, configs, 0, 1, numConfigs, 0)) {
    // 处理错误
    return;
}
EGLConfig config = configs[0];

// 创建EGL上下文
int[] contextAttribs = {
    EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,  // OpenGL ES 3.0
    EGL14.EGL_NONE
};
EGLContext context = EGL14.eglCreateContext(display, config, EGL14.EGL_NO_CONTEXT, contextAttribs, 0);
if (context == EGL14.EGL_NO_CONTEXT) {
    // 处理错误
    return;
}

// 创建EGL表面
EGLSurface surface = EGL14.eglCreateWindowSurface(display, config, surfaceHolder.getSurface(), null, 0);
if (surface == EGL14.EGL_NO_SURFACE) {
    // 处理错误
    return;
}

// 绑定上下文
if (!EGL14.eglMakeCurrent(display, surface, surface, context)) {
    // 处理错误
    return;
}

上述代码的主要步骤包括:

  1. 获取EGL显示 :通过eglGetDisplay函数获取与本地窗口系统的连接。
  2. 初始化EGL :调用eglInitialize函数初始化EGL库。
  3. 配置EGL上下文属性:设置所需的颜色缓冲区大小、深度缓冲区大小、渲染类型等属性。
  4. 选择EGL配置 :使用eglChooseConfig函数选择符合要求的EGL配置。
  5. 创建EGL上下文 :调用eglCreateContext函数创建OpenGL ES上下文。
  6. 创建EGL表面 :使用eglCreateWindowSurface函数创建与本地窗口关联的EGL表面。
  7. 绑定上下文 :通过eglMakeCurrent函数将上下文和表面绑定,使上下文成为当前上下文。

架构图

graph TD A[EGL初始化] --> B[获取EGL显示] A --> C[初始化EGL] A --> D[配置EGL属性] A --> E[选择EGL配置] F[上下文创建] --> G[创建EGL上下文] F --> H[创建EGL表面] F --> I[绑定上下文] J[错误处理] --> K[检查返回值] J --> L[记录错误信息] J --> M[释放资源] A --> F F --> J

调用流程图

sequenceDiagram participant App as 应用程序 participant EGL as EGL库 participant Window as 窗口系统 App->>EGL: eglGetDisplay() EGL->>Window: 获取显示连接 Window-->>EGL: 返回显示句柄 EGL-->>App: 返回EGLDisplay App->>EGL: eglInitialize() EGL->>EGL: 初始化内部状态 EGL-->>App: 返回初始化结果 App->>EGL: eglChooseConfig() EGL->>EGL: 查找匹配的配置 EGL-->>App: 返回EGLConfig App->>EGL: eglCreateContext() EGL->>EGL: 创建上下文对象 EGL-->>App: 返回EGLContext App->>EGL: eglCreateWindowSurface() EGL->>Window: 创建窗口表面 Window-->>EGL: 返回表面句柄 EGL-->>App: 返回EGLSurface App->>EGL: eglMakeCurrent() EGL->>EGL: 设置当前上下文和表面 EGL-->>App: 返回操作结果

类的关系图

classDiagram class EGL { +eglGetDisplay() +eglInitialize() +eglChooseConfig() +eglCreateContext() +eglCreateWindowSurface() +eglMakeCurrent() +eglSwapBuffers() +eglDestroySurface() +eglDestroyContext() +eglTerminate() } class EGLDisplay { -void* nativeDisplay +GetNativeDisplay() } class EGLConfig { -int redSize -int greenSize -int blueSize -int alphaSize -int depthSize -int renderableType +GetAttribute() } class EGLContext { -EGLDisplay display -EGLConfig config -void* nativeContext +GetDisplay() +GetConfig() } class EGLSurface { -EGLDisplay display -EGLConfig config -void* nativeSurface +GetDisplay() +GetConfig() } EGL --> EGLDisplay EGL --> EGLConfig EGL --> EGLContext EGL --> EGLSurface EGLContext *-- EGLDisplay EGLContext *-- EGLConfig EGLSurface *-- EGLDisplay EGLSurface *-- EGLConfig

2.3 上下文的状态管理

OpenGL ES上下文维护了大量的状态信息,这些状态信息控制着渲染过程的各个方面。应用程序可以通过各种OpenGL ES API来查询和修改这些状态。

上下文的状态可以分为以下几类:

  • 着色器状态:包括当前使用的着色器程序、顶点属性、统一变量等。
  • 纹理状态:包括当前绑定的纹理对象、纹理参数、纹理单元等。
  • 变换状态:包括模型视图矩阵、投影矩阵、纹理矩阵等。
  • 渲染状态:包括深度测试、模板测试、混合、裁剪等状态。
  • 帧缓冲状态:包括当前绑定的帧缓冲对象、颜色附件、深度附件等。

下面是一些常见的状态查询和修改操作的示例:

java 复制代码
// 查询当前上下文是否支持某个扩展
boolean isExtensionSupported = GLES30.glGetString(GLES30.GL_EXTENSIONS).contains("GL_OES_texture_compression_ETC1");

// 设置深度测试状态
GLES30.glEnable(GLES30.GL_DEPTH_TEST);  // 启用深度测试
GLES30.glDepthFunc(GLES30.GL_LESS);     // 设置深度比较函数

// 设置混合状态
GLES30.glEnable(GLES30.GL_BLEND);       // 启用混合
GLES30.glBlendFunc(GLES30.GL_SRC_ALPHA, GLES30.GL_ONE_MINUS_SRC_ALPHA);  // 设置混合函数

// 查询当前的视口
int[] viewport = new int[4];
GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, 0);

// 设置视口
GLES30.glViewport(0, 0, width, height);

// 查询当前的着色器程序
int[] currentProgram = new int[1];
GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, currentProgram, 0);

// 使用着色器程序
GLES30.glUseProgram(shaderProgram);

在OpenGL ES中,状态的修改是即时生效的,并且会一直保持直到被再次修改。这种状态机模型使得OpenGL ES的API设计相对简洁,但也要求开发者在编程时要特别注意状态的管理,避免不必要的状态切换。

架构图

graph TD A[OpenGL ES状态] --> B[着色器状态] A --> C[纹理状态] A --> D[变换状态] A --> E[渲染状态] A --> F[帧缓冲状态] B --> G[当前着色器程序] B --> H[顶点属性] B --> I[统一变量] C --> J[纹理绑定] C --> K[纹理参数] C --> L[纹理单元] D --> M[模型视图矩阵] D --> N[投影矩阵] D --> O[纹理矩阵] E --> P[深度测试] E --> Q[模板测试] E --> R[混合] E --> S[裁剪] F --> T[帧缓冲绑定] F --> U[颜色附件] F --> V[深度附件] F --> W[模板附件]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant State as 状态机 App->>API: 调用状态设置函数 API->>State: 验证参数 State->>State: 更新状态 State-->>API: 返回状态更新结果 API-->>App: 返回函数调用结果 App->>API: 调用状态查询函数 API->>State: 查询状态 State-->>API: 返回状态值 API-->>App: 返回查询结果

类的关系图

classDiagram class StateMachine { -ShaderState shaderState -TextureState textureState -TransformState transformState -RenderState renderState -FramebufferState framebufferState +GetState() +SetState() +ValidateState() } class ShaderState { -int currentProgram -std::map uniforms -std::map attributes +GetCurrentProgram() +SetCurrentProgram() +GetUniform() +SetUniform() } class TextureState { -int activeTextureUnit -std::map textureBindings -std::map textureParams +GetActiveTextureUnit() +SetActiveTextureUnit() +GetTextureBinding() +SetTextureBinding() } class TransformState { -Matrix4 modelViewMatrix -Matrix4 projectionMatrix -Matrix4 textureMatrix +GetModelViewMatrix() +SetModelViewMatrix() +GetProjectionMatrix() +SetProjectionMatrix() } class RenderState { -bool depthTestEnabled -bool stencilTestEnabled -bool blendEnabled -bool cullFaceEnabled -DepthFunc depthFunc -BlendFunc blendFunc +IsDepthTestEnabled() +SetDepthTestEnabled() +GetDepthFunc() +SetDepthFunc() } class FramebufferState { -int currentFramebuffer -std::map colorAttachments -Attachment depthAttachment -Attachment stencilAttachment +GetCurrentFramebuffer() +SetCurrentFramebuffer() +GetColorAttachment() +SetColorAttachment() } StateMachine *-- ShaderState StateMachine *-- TextureState StateMachine *-- TransformState StateMachine *-- RenderState StateMachine *-- FramebufferState

2.4 上下文的共享机制

在多线程环境中,有时候需要多个线程共享同一个OpenGL ES上下文,或者在不同的上下文之间共享资源。OpenGL ES提供了上下文共享机制来支持这种需求。

上下文共享的基本原理是:多个上下文可以共享同一个资源池,这样一个上下文中创建的资源(如纹理、缓冲区对象等)可以在其他共享的上下文中使用。

在Android平台上,可以通过EGL来实现上下文共享。下面是一个创建共享上下文的示例:

java 复制代码
// 创建主上下文(与前面的示例相同)
EGLContext mainContext = createMainContext(display, config);

// 创建共享上下文
int[] shareContextAttribs = {
    EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
    EGL14.EGL_NONE
};
EGLContext sharedContext = EGL14.eglCreateContext(display, config, mainContext, shareContextAttribs, 0);
if (sharedContext == EGL14.EGL_NO_CONTEXT) {
    // 处理错误
    return;
}

在上面的示例中,sharedContextmainContext共享资源。这意味着在mainContext中创建的资源可以在sharedContext中使用,反之亦然。

需要注意的是,虽然资源可以在共享的上下文中使用,但同一时间只能有一个上下文是当前上下文。也就是说,一个线程在使用某个上下文时,其他线程不能同时使用同一个上下文。因此,在多线程环境中使用共享上下文时,需要适当的同步机制来避免竞争条件。

另外,不是所有的OpenGL ES资源都可以共享。通常,纹理对象、缓冲区对象、着色器程序等可以共享,但帧缓冲对象、渲染缓冲对象等通常不能共享。具体的共享规则取决于平台和驱动实现。

架构图

graph TD A[上下文共享] --> B[主上下文] A --> C[共享上下文1] A --> D[共享上下文2] B --> E[资源池] C --> E D --> E E --> F[纹理对象] E --> G[缓冲区对象] E --> H[着色器程序] B --> I[线程1] C --> J[线程2] D --> K[线程3] I --> L[使用主上下文] J --> M[使用共享上下文1] K --> N[使用共享上下文2]

调用流程图

sequenceDiagram participant Thread1 as 线程1 participant Thread2 as 线程2 participant MainContext as 主上下文 participant SharedContext as 共享上下文 participant ResourcePool as 资源池 Thread1->>MainContext: 创建主上下文 MainContext->>ResourcePool: 初始化资源池 Thread1->>MainContext: 创建纹理资源 MainContext->>ResourcePool: 添加纹理资源 Thread2->>SharedContext: 创建共享上下文 SharedContext->>ResourcePool: 关联资源池 Thread1->>MainContext: 绑定当前上下文 MainContext->>MainContext: 执行渲染操作 Thread1->>MainContext: 解除当前上下文绑定 Thread2->>SharedContext: 绑定当前上下文 SharedContext->>ResourcePool: 获取共享纹理资源 SharedContext->>SharedContext: 执行渲染操作

类的关系图

classDiagram class Context { -EGLDisplay display -EGLConfig config -EGLContext eglContext -ResourcePool* sharedResourcePool +MakeCurrent() +CreateTexture() +CreateBuffer() +CreateShader() } class ResourcePool { -std::map textures -std::map buffers -std::map shaders +AddResource() +RemoveResource() +GetResource() } class Thread { -Context* currentContext +SetCurrentContext() +ExecuteCommands() } Context *-- ResourcePool : 共享 Thread *-- Context : 使用

2.5 上下文的销毁与资源释放

当不再需要OpenGL ES上下文时,应该正确地销毁上下文并释放相关的资源,以避免内存泄漏和资源浪费。

在Android平台上,销毁OpenGL ES上下文的基本步骤如下:

java 复制代码
// 释放EGL资源
EGL14.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroySurface(display, surface);
EGL14.eglDestroyContext(display, context);
EGL14.eglTerminate(display);

上述代码的主要步骤包括:

  1. 解除当前上下文绑定 :调用eglMakeCurrent函数,将当前上下文设置为EGL_NO_CONTEXT,解除上下文与表面的绑定。
  2. 销毁EGL表面 :使用eglDestroySurface函数销毁之前创建的EGL表面。
  3. 销毁EGL上下文 :调用eglDestroyContext函数销毁OpenGL ES上下文。
  4. 终止EGL :使用eglTerminate函数终止EGL库的使用,释放相关资源。

除了销毁上下文本身,还需要释放上下文中创建的所有资源,如纹理、缓冲区对象、着色器程序等。这些资源的释放通常需要调用相应的OpenGL ES API:

java 复制代码
// 释放纹理
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
// 使用纹理...
GLES30.glDeleteTextures(1, textures, 0);  // 释放纹理

// 释放缓冲区对象
int[] buffers = new int[1];
GLES30.glGenBuffers(1, buffers, 0);
// 使用缓冲区对象...
GLES30.glDeleteBuffers(1, buffers, 0);  // 释放缓冲区对象

// 释放着色器程序
int shaderProgram = createShaderProgram();
// 使用着色器程序...
GLES30.glDeleteProgram(shaderProgram);  // 释放着色器程序

在释放资源时,需要确保资源不再被使用。如果在上下文销毁之前没有释放资源,可能会导致内存泄漏。

架构图

graph TD A[上下文销毁] --> B[解除当前上下文绑定] A --> C[销毁EGL表面] A --> D[销毁EGL上下文] A --> E[终止EGL] F[资源释放] --> G[释放纹理] F[资源释放] --> H[释放缓冲区对象] F[资源释放] --> I[释放着色器程序] F[资源释放] --> J[释放帧缓冲对象] K[错误处理] --> L[检查返回值] K[错误处理] --> M[记录错误信息] A --> F F --> K

调用流程图

sequenceDiagram participant App as 应用程序 participant EGL as EGL库 participant Context as 上下文 participant Resources as 资源管理器 App->>Context: 请求销毁上下文 Context->>EGL: eglMakeCurrent(EGL_NO_CONTEXT) EGL-->>Context: 返回操作结果 Context->>EGL: eglDestroySurface() EGL-->>Context: 返回操作结果 Context->>EGL: eglDestroyContext() EGL-->>Context: 返回操作结果 Context->>EGL: eglTerminate() EGL-->>Context: 返回操作结果 Context->>Resources: 释放所有资源 Resources->>Resources: 遍历所有资源 Resources->>Context: 调用相应的释放函数 Context->>App: 返回销毁完成

类的关系图

classDiagram class Context { -EGLDisplay display -EGLSurface surface -EGLContext context -ResourceManager resourceManager +Destroy() +ReleaseResources() } class ResourceManager { -std::map textures -std::map buffers -std::map shaders -std::map framebuffers +ReleaseAllResources() +ReleaseTexture() +ReleaseBuffer() +ReleaseShader() +ReleaseFramebuffer() } class EGL { +eglMakeCurrent() +eglDestroySurface() +eglDestroyContext() +eglTerminate() } Context *-- ResourceManager Context *-- EGL

三、OpenGL ES 渲染管线

3.1 渲染管线概述

OpenGL ES渲染管线是一个图形处理流程,定义了从顶点数据到最终像素的转换过程。渲染管线将复杂的图形渲染任务分解为多个阶段,每个阶段都有特定的功能和处理逻辑。

渲染管线的主要阶段包括:

  • 顶点处理:处理顶点数据,包括顶点坐标变换、光照计算等。
  • 图元装配:将顶点组合成图元(如点、线、三角形等)。
  • 光栅化:将图元转换为片段(像素)。
  • 片段处理:处理每个片段,包括纹理采样、颜色计算等。
  • 逐片段操作:执行深度测试、模板测试、混合等操作,最终确定每个像素的颜色。

架构图

graph TD A[渲染管线] --> B[顶点处理] A --> C[图元装配] A --> D[光栅化] A --> E[片段处理] A --> F[逐片段操作] B --> G[顶点着色器] B --> H[顶点变换] B --> I[光照计算] C --> J[顶点组合] C --> K[裁剪] C --> L[透视除法] D --> M[生成片段] D --> N[插值计算] E --> O[片段着色器] E --> P[纹理采样] E --> Q[颜色计算] F --> R[深度测试] F --> S[模板测试] F --> T[混合] F --> U[帧缓冲写入]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant Pipeline as 渲染管线 participant VertexStage as 顶点处理阶段 participant PrimitiveStage as 图元装配阶段 participant RasterizationStage as 光栅化阶段 participant FragmentStage as 片段处理阶段 participant PerFragmentStage as 逐片段操作阶段 App->>API: 调用渲染函数 API->>Pipeline: 提交渲染命令 Pipeline->>VertexStage: 处理顶点数据 VertexStage->>PrimitiveStage: 传递处理后的顶点 PrimitiveStage->>RasterizationStage: 传递图元 RasterizationStage->>FragmentStage: 传递片段 FragmentStage->>PerFragmentStage: 传递处理后的片段 PerFragmentStage->>Pipeline: 返回最终像素 Pipeline-->>API: 返回渲染结果 API-->>App: 返回函数调用结果

类的关系图

classDiagram class RenderPipeline { -VertexProcessing vertexProcessing -PrimitiveAssembly primitiveAssembly -Rasterization rasterization -FragmentProcessing fragmentProcessing -PerFragmentOperations perFragmentOperations +ProcessVertices() +AssemblePrimitives() +Rasterize() +ProcessFragments() +PerformPerFragmentOperations() } class VertexProcessing { -Shader vertexShader -Transform transform +ProcessVertex() +ApplyTransform() +CalculateLighting() } class PrimitiveAssembly { -PrimitiveType primitiveType +AssemblePrimitives() +ClipPrimitives() +PerspectiveDivide() } class Rasterization { -Framebuffer framebuffer +RasterizePrimitive() +GenerateFragments() +InterpolateAttributes() } class FragmentProcessing { -Shader fragmentShader -TextureManager textureManager +ProcessFragment() +SampleTexture() +CalculateColor() } class PerFragmentOperations { -DepthBuffer depthBuffer -StencilBuffer stencilBuffer -BlendState blendState +DepthTest() +StencilTest() +Blend() +WriteToFramebuffer() } RenderPipeline *-- VertexProcessing RenderPipeline *-- PrimitiveAssembly RenderPipeline *-- Rasterization RenderPipeline *-- FragmentProcessing RenderPipeline *-- PerFragmentOperations

3.2 顶点处理阶段

顶点处理阶段是渲染管线的第一个阶段,负责处理输入的顶点数据。这个阶段的主要任务是对顶点进行变换和计算,将顶点从模型空间转换到裁剪空间。

顶点处理阶段的核心组件是顶点着色器,它是一段可编程的代码,用于对每个顶点进行处理。顶点着色器可以执行各种操作,如顶点变换、光照计算、纹理坐标生成等。

顶点处理阶段的主要步骤包括:

  1. 顶点数据输入:从顶点缓冲区读取顶点数据,包括顶点坐标、法线、纹理坐标等。
  2. 顶点着色器执行:对每个顶点执行顶点着色器程序,计算顶点的最终位置和其他属性。
  3. 顶点变换:将顶点从模型空间转换到世界空间,再到视图空间,最后到裁剪空间。
  4. 可选的固定功能处理:在某些情况下,可能会执行一些固定功能的处理,如光照计算。

下面是一个简单的顶点着色器示例:

glsl 复制代码
// 顶点着色器
#version 300 es

// 输入属性
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;

// 输出变量
out vec3 vNormal;
out vec2 vTexCoord;

// 统一变量
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat3 uNormalMatrix;

void main() {
    // 计算顶点位置
    gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
    
    // 传递法线和纹理坐标到片段着色器
    vNormal = uNormalMatrix * aNormal;
    vTexCoord = aTexCoord;
}

在这个顶点着色器中,我们接收顶点位置、法线和纹理坐标作为输入,计算顶点的最终位置,并将法线和纹理坐标传递给片段着色器。

架构图

graph TD A[顶点处理阶段] --> B[顶点数据输入] A --> C[顶点着色器执行] A --> D[顶点变换] A --> E[固定功能处理] B --> F[顶点坐标] B --> G[法线] B --> H[纹理坐标] B --> I[颜色] C --> J[顶点着色器程序] C --> K[顶点属性处理] C --> L[统一变量访问] D --> M[模型变换] D --> N[视图变换] D --> O[投影变换] D --> P[裁剪空间变换] E --> Q[光照计算] E --> R[雾效计算]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant VertexShader as 顶点着色器 participant Transform as 变换矩阵 participant Buffer as 顶点缓冲区 App->>API: 设置顶点数据 API->>Buffer: 存储顶点数据 App->>API: 设置变换矩阵 API->>Transform: 存储矩阵数据 App->>API: 调用绘制命令 API->>VertexShader: 传递顶点数据 API->>VertexShader: 传递统一变量 VertexShader->>Transform: 获取变换矩阵 VertexShader->>VertexShader: 执行顶点着色器程序 VertexShader-->>API: 返回处理后的顶点 API->>API: 执行后续渲染阶段 API-->>App: 返回绘制结果

类的关系图

classDiagram class VertexProcessing { -Shader vertexShader -VertexBuffer vertexBuffer -Transform transform +ProcessVertices() +SetVertexBuffer() +SetTransform() } class Shader { -std::string sourceCode -int shaderType -int shaderId +Compile() +GetUniformLocation() +SetUniform() } class VertexBuffer { -std::vector vertexData -std::vector attributeSizes -int bufferId +Bind() +SetData() +EnableAttributes() } class Transform { -Matrix4 modelMatrix -Matrix4 viewMatrix -Matrix4 projectionMatrix +SetModelMatrix() +SetViewMatrix() +SetProjectionMatrix() +GetModelViewMatrix() +GetProjectionMatrix() } VertexProcessing *-- Shader : 使用 VertexProcessing *-- VertexBuffer : 使用 VertexProcessing *-- Transform : 使用

3.3 图元装配阶段

图元装配阶段是渲染管线的第二个阶段,负责将处理后的顶点组合成图元(如点、线、三角形等),并进行裁剪和透视除法等操作。

图元装配阶段的主要步骤包括:

  1. 图元类型确定:根据API调用指定的图元类型(如GL_TRIANGLES、GL_LINES等),将顶点组合成相应的图元。
  2. 图元构建:根据图元类型,将顶点数据组织成完整的图元。
  3. 裁剪:将超出裁剪空间的图元进行裁剪,确保只有可见的部分进入下一阶段。
  4. 透视除法:将裁剪空间的坐标除以w分量,转换为标准化设备坐标(NDC)。
  5. 视口变换:将标准化设备坐标转换为窗口坐标。

下面是图元装配阶段的一些关键代码示例:

cpp 复制代码
// 图元装配类
class PrimitiveAssembly {
public:
    // 根据图元类型装配图元
    void AssemblePrimitives(PrimitiveType type, const std::vector<Vertex>& vertices, std::vector<Primitive>& primitives) {
        switch (type) {
            case POINTS:
                AssemblePoints(vertices, primitives);
                break;
            case LINES:
                AssembleLines(vertices, primitives);
                break;
            case LINE_STRIP:
                AssembleLineStrip(vertices, primitives);
                break;
            case LINE_LOOP:
                AssembleLineLoop(vertices, primitives);
                break;
            case TRIANGLES:
                AssembleTriangles(vertices, primitives);
                break;
            case TRIANGLE_STRIP:
                AssembleTriangleStrip(vertices, primitives);
                break;
            case TRIANGLE_FAN:
                AssembleTriangleFan(vertices, primitives);
                break;
        }
    }
    
    // 裁剪图元
    void ClipPrimitives(const std::vector<Primitive>& input, std::vector<Primitive>& output) {
        // 实现裁剪算法
        // ...
    }
    
    // 透视除法
    void PerspectiveDivide(std::vector<Vertex>& vertices) {
        for (auto& vertex : vertices) {
            vertex.position /= vertex.position.w;  // 透视除法
        }
    }
    
    // 视口变换
    void ViewportTransform(std::vector<Vertex>& vertices, const Viewport& viewport) {
        for (auto& vertex : vertices) {
            // 将NDC坐标转换为窗口坐标
            vertex.position.x = viewport.x + (vertex.position.x + 1.0f) * viewport.width / 2.0f;
            vertex.position.y = viewport.y + (1.0f - vertex.position.y) * viewport.height / 2.0f;
            vertex.position.z = viewport.zNear + vertex.position.z * (viewport.zFar - viewport.zNear) / 2.0f;
        }
    }
    
private:
    // 各种图元装配方法的实现
    void AssemblePoints(const std::vector<Vertex>& vertices, std::vector<Primitive>& primitives);
    void AssembleLines(const std::vector<Vertex>& vertices, std::vector<Primitive>& primitives);
    // ...
};

架构图

graph TD A[图元装配阶段] --> B[图元类型确定] A --> C[图元构建] A --> D[裁剪] A --> E[透视除法] A --> F[视口变换] B --> G[点] B --> H[线] B --> I[三角形] C --> J[顶点组合] C --> K[索引缓冲处理] D --> L[裁剪算法] D --> M[可见性判断] E --> N[除以w分量] E --> O[NDC坐标] F --> P[窗口坐标] F --> Q[视口参数]

调用流程图

sequenceDiagram participant VertexStage as 顶点处理阶段 participant PrimitiveStage as 图元装配阶段 participant RasterizationStage as 光栅化阶段 VertexStage->>PrimitiveStage: 传递处理后的顶点 PrimitiveStage->>PrimitiveStage: 确定图元类型 PrimitiveStage->>PrimitiveStage: 构建图元 PrimitiveStage->>PrimitiveStage: 裁剪图元 PrimitiveStage->>PrimitiveStage: 透视除法 PrimitiveStage->>PrimitiveStage: 视口变换 PrimitiveStage->>RasterizationStage: 传递装配好的图元

类的关系图

classDiagram class PrimitiveAssembly { -PrimitiveType primitiveType -Clipping clipping -Viewport viewport +AssemblePrimitives() +ClipPrimitives() +PerspectiveDivide() +ViewportTransform() } enum PrimitiveType { POINTS, LINES, LINE_STRIP, LINE_LOOP, TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN } class Clipping { +ClipPoint() +ClipLine() +ClipTriangle() } class Viewport { -int x, y -int width, height -float zNear, zFar +SetViewport() +GetViewport() } class Primitive { -std::vector vertices -PrimitiveType type +AddVertex() +GetType() } PrimitiveAssembly *-- Clipping : 使用 PrimitiveAssembly *-- Viewport : 使用 PrimitiveAssembly "1" --> "*" Primitive : 生成

3.4 光栅化阶段

光栅化阶段是渲染管线的第三个阶段,负责将图元(如三角形)转换为片段(像素)。这个阶段决定了哪些像素会受到图元的影响,并为每个像素计算相应的属性值。

光栅化阶段的主要步骤包括:

  1. 三角形设置:计算三角形边缘的参数方程,为光栅化做准备。
  2. 三角形遍历:遍历三角形覆盖的像素区域,确定哪些像素被三角形覆盖。
  3. 片段生成:为每个被覆盖的像素生成一个片段。
  4. 属性插值:为每个片段插值计算顶点属性(如颜色、纹理坐标、法线等)。

下面是光栅化阶段的一些关键代码示例:

cpp 复制代码
// 光栅化器类
class Rasterizer {
public:
    // 光栅化三角形
    void RasterizeTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2, Framebuffer& framebuffer) {
        // 三角形设置:计算边界框
        int minX = std::min({v0.position.x, v1.position.x, v2.position.x});
        int maxX = std::max({v0.position.x, v1.position.x, v2.position.x});
        int minY = std::min({v0.position.y, v1.position.y, v2.position.y});
        int maxY = std::max({v0.position.y, v1.position.y, v2.position.y});
        
        // 裁剪到视口
        minX = std::max(minX, 0);
        maxX = std::min(maxX, framebuffer.GetWidth() - 1);
        minY = std::max(minY, 0);
        maxY = std::min(maxY, framebuffer.GetHeight() - 1);
        
        // 遍历边界框内的所有像素
        for (int y = minY; y <= maxY; y++) {
            for (int x = minX; x <= maxX; x++) {
                // 计算重心坐标
                float w0 = EdgeFunction(v1.position, v2.position, {x, y});
                float w1 = EdgeFunction(v2.position, v0.position, {x, y});
                float w2 = EdgeFunction(v0.position, v1.position, {x, y});
                
                // 判断像素是否在三角形内部
                if (w0 >= 0 && w1 >= 0 && w2 >= 0) {
                    // 归一化重心坐标
                    float area = EdgeFunction(v0.position, v1.position, v2.position);
                    w0 /= area;
                    w1 /= area;
                    w2 /= area;
                    
                    // 深度插值
                    float depth = w0 * v0.position.z + w1 * v1.position.z + w2 * v2.position.z;
                    
                    // 属性插值
                    Vertex interpolatedVertex = InterpolateVertex(v0, v1, v2, w0, w1, w2);
                    
                    // 创建片段
                    Fragment fragment(x, y, depth, interpolatedVertex);
                    
                    // 处理片段
                    ProcessFragment(fragment, framebuffer);
                }
            }
        }
    }
    
    // 处理片段
    void ProcessFragment(const Fragment& fragment, Framebuffer& framebuffer) {
        // 深度测试
        if (fragment.depth < framebuffer.GetDepth(fragment.x, fragment.y)) {
            // 更新深度缓冲
            framebuffer.SetDepth(fragment.x, fragment.y, fragment.depth);
            
            // 调用片段着色器
            Color color = FragmentShader(fragment);
            
            // 应用混合
            ApplyBlending(fragment.x, fragment.y, color, framebuffer);
        }
    }
    
private:
    // 计算边缘函数值
    float EdgeFunction(const Vec2& a, const Vec2& b, const Vec2& c) {
        return (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x);
    }
    
    // 插值顶点属性
    Vertex InterpolateVertex(const Vertex& v0, const Vertex& v1, const Vertex& v2, float w0, float w1, float w2) {
        Vertex result;
        // 插值位置
        result.position = w0 * v0.position + w1 * v1.position + w2 * v2.position;
        // 插值法线
        result.normal = w0 * v0.normal + w1 * v1.normal + w2 * v2.normal;
        // 插值纹理坐标
        result.texCoord = w0 * v0.texCoord + w1 * v1.texCoord + w2 * v2.texCoord;
        // 其他属性插值...
        return result;
    }
};

架构图

graph TD A[光栅化阶段] --> B[三角形设置] A --> C[三角形遍历] A --> D[片段生成] A --> E[属性插值] B --> F[计算边界框] B --> G[计算边缘方程] C --> H[遍历像素] C --> I[判断覆盖] D --> J[生成片段] D --> K[设置片段属性] E --> L[重心坐标计算] E --> M[线性插值] E --> N[透视校正插值]

调用流程图

sequenceDiagram participant PrimitiveStage as 图元装配阶段 participant Rasterizer as 光栅化器 participant FragmentStage as 片段处理阶段 PrimitiveStage->>Rasterizer: 传递装配好的图元 Rasterizer->>Rasterizer: 三角形设置 Rasterizer->>Rasterizer: 三角形遍历 Rasterizer->>Rasterizer: 片段生成 Rasterizer->>Rasterizer: 属性插值 Rasterizer->>FragmentStage: 传递生成的片段

类的关系图

classDiagram class Rasterizer { -Framebuffer* framebuffer -FragmentShader fragmentShader +RasterizeTriangle() +RasterizeLine() +RasterizePoint() +ProcessFragment() } class Fragment { -int x, y -float depth -Vertex attributes +GetPosition() +GetDepth() +GetAttributes() } class Framebuffer { -std::vector colorBuffer -std::vector depthBuffer -int width, height +GetWidth() +GetHeight() +GetColor() +SetColor() +GetDepth() +SetDepth() } class FragmentShader { -ShaderProgram program -TextureManager textureManager +Execute() +SampleTexture() } Rasterizer *-- Framebuffer : 使用 Rasterizer *-- FragmentShader : 使用 Rasterizer "1" --> "*" Fragment : 生成

3.5 片段处理阶段

片段处理阶段是渲染管线的第四个阶段,负责处理光栅化阶段生成的每个片段。这个阶段的核心是片段着色器,它是一段可编程的代码,用于计算每个片段的最终颜色。

片段处理阶段的主要步骤包括:

  1. 片段着色器执行:对每个片段执行片段着色器程序,计算片段的颜色。
  2. 纹理采样:从纹理中获取颜色值,通常使用插值后的纹理坐标。
  3. 颜色计算:结合纹理颜色、光照信息、材质属性等,计算片段的最终颜色。
  4. 可选的输出处理:可以对片段着色器的输出进行一些后处理,如雾效计算。

下面是一个简单的片段着色器示例:

glsl 复制代码
// 片段着色器
#version 300 es

precision mediump float;

// 输入变量(从顶点着色器插值而来)
in vec3 vNormal;
in vec2 vTexCoord;

// 输出变量
out vec4 fragColor;

// 统一变量
uniform sampler2D uTexture;
uniform vec3 uLightDir;
uniform vec3 uLightColor;
uniform vec3 uAmbientColor;

void main() {
    // 采样纹理
    vec4 texColor = texture(uTexture, vTexCoord);
    
    // 计算法线(归一化)
    vec3 normal = normalize(vNormal);
    
    // 计算光照方向(归一化)
    vec3 lightDir = normalize(uLightDir);
    
    // 计算漫反射光照
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = diff * uLightColor;
    
    // 计算最终颜色
    vec3 finalColor = (uAmbientColor + diffuse) * texColor.rgb;
    
    // 设置片段颜色
    fragColor = vec4(finalColor, texColor.a);
}

在这个片段着色器中,我们接收插值后的法线和纹理坐标,从纹理中采样颜色,计算漫反射光照,并最终确定片段的颜色。

架构图

graph TD A[片段处理阶段] --> B[片段着色器执行] A --> C[纹理采样] A --> D[颜色计算] A --> E[输出处理] B --> F[片段着色器程序] B --> G[插值属性] B --> H[统一变量] C --> I[纹理坐标] C --> J[纹理过滤] C --> K[多级渐远纹理] D --> L[光照模型] D --> M[材质属性] D --> N[混合计算] E --> O[雾效] E --> P[色调调整] E --> Q[后期处理]

调用流程图

sequenceDiagram participant RasterizationStage as 光栅化阶段 participant FragmentShader as 片段着色器 participant TextureManager as 纹理管理器 participant PerFragmentStage as 逐片段操作阶段 RasterizationStage->>FragmentShader: 传递片段 FragmentShader->>TextureManager: 请求纹理采样 TextureManager-->>FragmentShader: 返回纹理颜色 FragmentShader->>FragmentShader: 执行着色器程序 FragmentShader->>PerFragmentStage: 传递计算后的颜色

类的关系图

classDiagram class FragmentProcessing { -Shader fragmentShader -TextureManager textureManager +ProcessFragment() +ExecuteFragmentShader() } class Shader { -std::string sourceCode -int shaderId -int programId +Compile() +Link() +Use() +SetUniform() } class TextureManager { -std::map textures +BindTexture() +SampleTexture() +GenerateMipmaps() } class Texture { -int width, height -TextureFormat format -TextureFilter filter -TextureWrap wrap -unsigned char* data +Load() +Bind() +SetParameters() } class Fragment { -int x, y -float depth -vec4 color -Vertex attributes +SetColor() +GetColor() +GetDepth() } FragmentProcessing *-- Shader : 使用 FragmentProcessing *-- TextureManager : 使用 FragmentProcessing "1" --> "*" Fragment : 处理

3.6 逐片段操作阶段

逐片段操作阶段是渲染管线的最后一个阶段,负责对每个片段执行一系列测试和操作,最终确定该片段是否会被绘制到帧缓冲中。

逐片段操作阶段的主要步骤包括:

  1. 裁剪测试:检查片段是否在视口范围内。
  2. Alpha测试:根据片段的Alpha值决定是否丢弃该片段。
  3. 模板测试:根据模板缓冲的值决定是否丢弃该片段。
  4. 深度测试:根据深度缓冲的值决定是否丢弃该片段。
  5. 模板操作:如果启用了模板测试,更新模板缓冲的值。
  6. 深度操作:如果深度测试通过,更新深度缓冲的值。
  7. 混合:将片段的颜色与帧缓冲中已有的颜色进行混合。
  8. 抖动:对最终颜色进行抖动处理,增加颜色精度。
  9. 帧缓冲写入:将最终颜色写入帧缓冲。

下面是逐片段操作阶段的一些关键代码示例:

cpp 复制代码
// 逐片段操作类
class PerFragmentOperations {
public:
    // 执行逐片段操作
    bool ProcessFragment(Fragment& fragment, Framebuffer& framebuffer, StencilBuffer& stencilBuffer, DepthBuffer& depthBuffer) {
        // 裁剪测试
        if (!ScissorTest(fragment)) {
            return false;
        }
        
        // Alpha测试
        if (!AlphaTest(fragment)) {
            return false;
        }
        
        // 模板测试
        if (!StencilTest(fragment, stencilBuffer)) {
            return false;
        }
        
        // 深度测试
        if (!DepthTest(fragment, depthBuffer)) {
            return false;
        }
        
        // 执行模板操作
        StencilOperation(fragment, stencilBuffer);
        
        // 执行深度操作
        DepthOperation(fragment, depthBuffer);
        
        // 混合
        Blend(fragment, framebuffer);
        
        // 抖动(简化处理)
        Dither(fragment);
        
        // 写入帧缓冲
        WriteToFramebuffer(fragment, framebuffer);
        
        return true;
    }
    
private:
    // 裁剪测试
    bool ScissorTest(const Fragment& fragment) {
        // 检查片段是否在裁剪矩形内
        return (fragment.x >= scissorRect.x && 
                fragment.x < scissorRect.x + scissorRect.width &&
                fragment.y >= scissorRect.y && 
                fragment.y < scissorRect.y + scissorRect.height);
    }
    
    // Alpha测试
    bool AlphaTest(const Fragment& fragment) {
        if (!alphaTestEnabled) {
            return true;
        }
        
        // 执行Alpha测试
        switch (alphaFunc) {
            case GL_NEVER: return false;
            case GL_LESS: return fragment.color.a < alphaRef;
            case GL_EQUAL: return fragment.color.a == alphaRef;
            case GL_LEQUAL: return fragment.color.a <= alphaRef;
            case GL_GREATER: return fragment.color.a > alphaRef;
            case GL_NOTEQUAL: return fragment.color.a != alphaRef;
            case GL_GEQUAL: return fragment.color.a >= alphaRef;
            case GL_ALWAYS: return true;
            default: return true;
        }
    }
    
    // 模板测试
    bool StencilTest(const Fragment& fragment, StencilBuffer& stencilBuffer) {
        if (!stencilTestEnabled) {
            return true;
        }
        
        // 获取当前模板值
        unsigned char stencilValue = stencilBuffer.GetValue(fragment.x, fragment.y);
        
        // 执行模板测试
        return ((stencilValue & stencilMask) compareFunc (stencilRef & stencilMask));
    }
    
    // 深度测试
    bool DepthTest(const Fragment& fragment, DepthBuffer& depthBuffer) {
        if (!depthTestEnabled) {
            return true;
        }
        
        // 获取当前深度值
        float depthValue = depthBuffer.GetValue(fragment.x, fragment.y);
        
        // 执行深度测试
        switch (depthFunc) {
            case GL_NEVER: return false;
            case GL_LESS: return fragment.depth < depthValue;
            case GL_EQUAL: return fragment.depth == depthValue;
            case GL_LEQUAL: return fragment.depth <= depthValue;
            case GL_GREATER: return fragment.depth > depthValue;
            case GL_NOTEQUAL: return fragment.depth != depthValue;
            case GL_GEQUAL: return fragment.depth >= depthValue;
            case GL_ALWAYS: return true;
            default: return true;
        }
    }
    
    // 其他操作的实现...
};

架构图

graph TD A[逐片段操作阶段] --> B[裁剪测试] A --> C[Alpha测试] A --> D[模板测试] A --> E[深度测试] A --> F[模板操作] A --> G[深度操作] A --> H[混合] A --> I[抖动] A --> J[帧缓冲写入] B --> K[视口范围] C --> L[Alpha函数] C --> M[Alpha参考值] D --> N[模板函数] D --> O[模板参考值] D --> P[模板掩码] E --> Q[深度函数] E --> R[深度缓冲] F --> S[模板操作] F --> T[模板缓冲] G --> U[深度写入] G --> V[深度缓冲] H --> W[混合函数] H --> X[源因子] H --> Y[目标因子] I --> Z[颜色精度] J --> AA[帧缓冲]

调用流程图

sequenceDiagram participant FragmentStage as 片段处理阶段 participant PerFragmentOps as 逐片段操作阶段 participant Framebuffer as 帧缓冲 participant DepthBuffer as 深度缓冲 participant StencilBuffer as 模板缓冲 FragmentStage->>PerFragmentOps: 传递计算后的片段 PerFragmentOps->>PerFragmentOps: 裁剪测试 PerFragmentOps->>PerFragmentOps: Alpha测试 PerFragmentOps->>StencilBuffer: 读取模板值 StencilBuffer-->>PerFragmentOps: 返回模板值 PerFragmentOps->>PerFragmentOps: 模板测试 PerFragmentOps->>DepthBuffer: 读取深度值 DepthBuffer-->>PerFragmentOps: 返回深度值 PerFragmentOps->>PerFragmentOps: 深度测试 PerFragmentOps->>StencilBuffer: 更新模板值 PerFragmentOps->>DepthBuffer: 更新深度值 PerFragmentOps->>Framebuffer: 读取当前颜色 Framebuffer-->>PerFragmentOps: 返回当前颜色 PerFragmentOps->>PerFragmentOps: 混合 PerFragmentOps->>Framebuffer: 写入最终颜色

四、OpenGL ES 着色器系统

4.1 着色器概述

着色器是OpenGL ES中实现图形渲染可编程部分的关键组件。它们是运行在GPU上的小程序,用于控制渲染管线中的特定阶段,如顶点处理和片段处理。

OpenGL ES着色器使用专门的着色器语言编写,主要有两种类型的着色器:

  1. 顶点着色器:处理每个顶点,执行坐标变换、光照计算等操作。
  2. 片段着色器:处理每个片段(像素),执行纹理采样、颜色计算等操作。

从OpenGL ES 3.1开始,还引入了其他类型的着色器,如计算着色器,用于通用计算任务。

着色器的主要优势在于它们能够充分利用GPU的并行计算能力,实现高效的图形渲染和复杂的视觉效果。

架构图

graph TD A[着色器系统] --> B[顶点着色器] A --> C[片段着色器] A --> D[计算着色器] A --> E[几何着色器] A --> F[曲面细分着色器] B --> G[顶点处理] B --> H[坐标变换] B --> I[光照计算] C --> J[片段处理] C --> K[纹理采样] C --> L[颜色计算] D --> M[通用计算] D --> N[并行处理] E --> O[图元处理] E --> P[图元生成] F --> Q[曲面细分控制] F --> R[曲面细分计算]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant ShaderCompiler as 着色器编译器 participant ShaderProgram as 着色器程序 participant GPU as GPU硬件 App->>API: 创建着色器对象 API->>ShaderCompiler: 编译顶点着色器 API->>ShaderCompiler: 编译片段着色器 API->>ShaderProgram: 链接着色器程序 App->>API: 设置统一变量 API->>ShaderProgram: 更新统一变量 App->>API: 调用绘制命令 API->>ShaderProgram: 使用着色器程序 ShaderProgram->>GPU: 执行顶点着色器 ShaderProgram->>GPU: 执行片段着色器 GPU-->>API: 返回渲染结果 API-->>App: 返回绘制结果

4.2 顶点着色器

顶点着色器是处理顶点数据的可编程阶段,它对每个输入顶点执行一次。顶点着色器的主要任务是将顶点从模型空间转换到裁剪空间,并为后续阶段提供必要的顶点属性。

顶点着色器的基本结构包括:

  • 输入变量:接收顶点属性数据,如位置、法线、纹理坐标等。
  • 统一变量:全局变量,对所有顶点相同,如变换矩阵、光照参数等。
  • 输出变量:传递数据到后续阶段,通常是经过插值后传递给片段着色器。
  • 主函数 :顶点着色器的入口点,必须定义gl_Position变量作为顶点的最终位置。

下面是一个顶点着色器的示例:

glsl 复制代码
#version 300 es

// 输入属性
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;

// 输出变量
out vec3 vNormal;
out vec2 vTexCoord;

// 统一变量
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat3 uNormalMatrix;

void main() {
    // 计算顶点位置
    gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
    
    // 传递法线和纹理坐标到片段着色器
    vNormal = uNormalMatrix * aNormal;
    vTexCoord = aTexCoord;
}

这个顶点着色器接收顶点位置、法线和纹理坐标,计算顶点的最终位置,并将法线和纹理坐标传递给片段着色器。

顶点着色器的主要功能包括:

  1. 坐标变换:将顶点从模型空间转换到裁剪空间。
  2. 光照计算:计算顶点的光照效果,如漫反射、镜面反射等。
  3. 顶点属性传递:将顶点属性(如法线、纹理坐标)传递给片段着色器。
  4. 骨骼动画:实现基于骨骼的顶点动画。
  5. 顶点变形:实现各种顶点变形效果,如波浪、扭曲等。

架构图

graph TD A[顶点着色器] --> B[输入变量] A --> C[统一变量] A --> D[输出变量] A --> E[主函数] B --> F[顶点位置] B --> G[法线] B --> H[纹理坐标] B --> I[颜色] C --> J[模型矩阵] C --> K[视图矩阵] C --> L[投影矩阵] C --> M[光照参数] D --> N[插值变量] D --> O[传递给片段着色器] E --> P[坐标变换] E --> Q[光照计算] E --> R[赋值gl_Position]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant VertexShader as 顶点着色器 participant Transform as 变换矩阵 participant VertexBuffer as 顶点缓冲区 App->>API: 设置顶点数据 API->>VertexBuffer: 存储顶点数据 App->>API: 设置变换矩阵 API->>Transform: 存储矩阵数据 App->>API: 调用绘制命令 API->>VertexShader: 传递顶点数据 API->>VertexShader: 传递统一变量 VertexShader->>Transform: 获取变换矩阵 VertexShader->>VertexShader: 执行顶点着色器程序 VertexShader-->>API: 返回处理后的顶点 API->>API: 执行后续渲染阶段 API-->>App: 返回绘制结果

类的关系图

classDiagram class VertexShader { -std::string sourceCode -int shaderId -std::map attributeLocations -std::map uniformLocations +Compile() +SetAttributeLocation() +SetUniform() +Execute() } class ShaderProgram { -int programId -VertexShader vertexShader -FragmentShader fragmentShader +AttachVertexShader() +AttachFragmentShader() +Link() +Use() } class Transform { -Matrix4 modelMatrix -Matrix4 viewMatrix -Matrix4 projectionMatrix +SetModelMatrix() +SetViewMatrix() +SetProjectionMatrix() +GetModelViewMatrix() +GetProjectionMatrix() } class VertexBuffer { -std::vector vertexData -int bufferId +Bind() +SetData() } VertexShader *-- ShaderProgram : 属于 ShaderProgram *-- Transform : 使用 ShaderProgram *-- VertexBuffer : 使用

4.3 片段着色器

片段着色器是处理片段(像素)的可编程阶段,它对光栅化阶段生成的每个片段执行一次。片段着色器的主要任务是计算每个片段的最终颜色。

片段着色器的基本结构包括:

  • 输入变量:接收从顶点着色器插值而来的数据,如法线、纹理坐标等。
  • 统一变量:全局变量,对所有片段相同,如纹理、光照参数等。
  • 输出变量:定义片段的最终颜色。
  • 主函数:片段着色器的入口点,必须定义输出颜色变量。

下面是一个片段着色器的示例:

glsl 复制代码
#version 300 es

precision mediump float;

// 输入变量(从顶点着色器插值而来)
in vec3 vNormal;
in vec2 vTexCoord;

// 输出变量
out vec4 fragColor;

// 统一变量
uniform sampler2D uTexture;
uniform vec3 uLightDir;
uniform vec3 uLightColor;
uniform vec3 uAmbientColor;

void main() {
    // 采样纹理
    vec4 texColor = texture(uTexture, vTexCoord);
    
    // 计算法线(归一化)
    vec3 normal = normalize(vNormal);
    
    // 计算光照方向(归一化)
    vec3 lightDir = normalize(uLightDir);
    
    // 计算漫反射光照
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = diff * uLightColor;
    
    // 计算最终颜色
    vec3 finalColor = (uAmbientColor + diffuse) * texColor.rgb;
    
    // 设置片段颜色
    fragColor = vec4(finalColor, texColor.a);
}

这个片段着色器接收插值后的法线和纹理坐标,从纹理中采样颜色,计算漫反射光照,并最终确定片段的颜色。

片段着色器的主要功能包括:

  1. 纹理采样:从纹理中获取颜色值。
  2. 光照计算:实现复杂的光照模型,如Phong模型、Blinn-Phong模型等。
  3. 颜色混合:将多种颜色源混合,如纹理颜色、光照颜色等。
  4. 特效实现:实现各种特效,如阴影、反射、折射等。
  5. 后处理效果:实现图像后处理效果,如模糊、锐化、色调调整等。

架构图

graph TD A[片段着色器] --> B[输入变量] A --> C[统一变量] A --> D[输出变量] A --> E[主函数] B --> F[插值法线] B --> G[插值纹理坐标] B --> H[插值颜色] C --> I[纹理] C --> J[光照参数] C --> K[材质属性] D --> L[最终颜色] E --> M[纹理采样] E --> N[光照计算] E --> O[颜色混合] E --> P[赋值fragColor]

调用流程图

sequenceDiagram participant Rasterizer as 光栅化器 participant FragmentShader as 片段着色器 participant TextureManager as 纹理管理器 participant Framebuffer as 帧缓冲 Rasterizer->>FragmentShader: 传递片段数据 FragmentShader->>TextureManager: 请求纹理采样 TextureManager-->>FragmentShader: 返回纹理颜色 FragmentShader->>FragmentShader: 执行光照计算 FragmentShader->>FragmentShader: 执行颜色混合 FragmentShader-->>Framebuffer: 传递最终颜色

类的关系图

classDiagram class FragmentShader { -std::string sourceCode -int shaderId -std::map uniformLocations +Compile() +SetUniform() +Execute() } class ShaderProgram { -int programId -VertexShader vertexShader -FragmentShader fragmentShader +AttachVertexShader() +AttachFragmentShader() +Link() +Use() } class TextureManager { -std::map textures +LoadTexture() +BindTexture() +SampleTexture() } class Texture { -int width, height -unsigned char* data -TextureFormat format +Load() +Bind() } class Framebuffer { -std::vector colorBuffer -std::vector depthBuffer -int width, height +SetColor() } FragmentShader *-- ShaderProgram : 属于 ShaderProgram *-- TextureManager : 使用 TextureManager "1" --> "*" Texture : 管理 FragmentShader --> Framebuffer : 输出颜色

4.4 着色器语言

OpenGL ES着色器使用专门的着色器语言编写,称为OpenGL ES着色器语言(GLSL ES)。GLSL ES是一种C风格的编程语言,专为图形渲染而设计。

GLSL ES的主要特点包括:

  1. 强类型语言:所有变量和表达式都必须有明确的类型。
  2. 内置数据类型:包括标量类型(float、int、bool)、向量类型(vec2、vec3、vec4)、矩阵类型(mat2、mat3、mat4)等。
  3. 内置函数:提供了丰富的数学函数、几何函数、纹理采样函数等。
  4. 没有指针:不支持指针操作,避免了内存管理的复杂性。
  5. 并行执行:着色器程序在GPU上并行执行,每个顶点或片段独立处理。

下面是GLSL ES中一些重要的概念和语法:

变量类型

glsl 复制代码
// 标量类型
float a = 1.0;
int b = 2;
bool c = true;

// 向量类型
vec2 v2 = vec2(1.0, 2.0);
vec3 v3 = vec3(1.0, 2.0, 3.0);
vec4 v4 = vec4(v3, 1.0);

// 矩阵类型
mat4 m4 = mat4(1.0);  // 单位矩阵

内置函数

glsl 复制代码
// 数学函数
float sinVal = sin(angle);
float cosVal = cos(angle);
float lengthVal = length(vec3(1.0, 2.0, 3.0));

// 几何函数
float dotProduct = dot(normal, lightDir);
vec3 crossProduct = cross(vec3(1.0), vec3(0.0, 1.0, 0.0));
vec3 normalizedVec = normalize(vector);

// 纹理采样函数
vec4 texColor = texture(sampler2D, texCoord);

控制结构

glsl 复制代码
// 条件语句
if (condition) {
    // 执行代码
} else {
    // 执行代码
}

// 循环语句
for (int i = 0; i < 10; i++) {
    // 执行代码
}

着色器变量限定符

glsl 复制代码
// 输入变量(顶点着色器)
in vec3 aPosition;

// 输出变量(顶点着色器)
out vec3 vPosition;

// 输入变量(片段着色器)
in vec3 vPosition;

// 输出变量(片段着色器)
out vec4 fragColor;

// 统一变量
uniform mat4 uModelViewProjectionMatrix;

// 常量
const float PI = 3.1415926;

架构图

graph TD A[GLSL ES语言] --> B[数据类型] A --> C[变量限定符] A --> D[控制结构] A --> E[内置函数] B --> F[标量类型] B --> G[向量类型] B --> H[矩阵类型] B --> I[采样器类型] C --> J[in] C --> K[out] C --> L[uniform] C --> M[const] D --> N[if-else] D --> O[for] D --> P[while] D --> Q[do-while] E --> R[数学函数] E --> S[几何函数] E --> T[纹理函数] E --> U[三角函数]

4.5 着色器编译与链接

着色器程序在使用前需要进行编译和链接。这个过程涉及将着色器源代码编译成GPU可执行的形式,并将多个着色器组合成一个完整的着色器程序。

着色器编译和链接的主要步骤包括:

  1. 创建着色器对象:为顶点着色器和片段着色器创建OpenGL ES对象。
  2. 指定着色器源代码:将GLSL ES源代码加载到着色器对象中。
  3. 编译着色器:编译着色器源代码。
  4. 检查编译状态:检查编译是否成功,如果失败,获取错误信息。
  5. 创建着色器程序对象:创建一个OpenGL ES程序对象。
  6. 附加着色器:将编译好的顶点着色器和片段着色器附加到程序对象上。
  7. 链接着色器程序:链接着色器程序,生成可执行代码。
  8. 检查链接状态:检查链接是否成功,如果失败,获取错误信息。
  9. 使用着色器程序:激活着色器程序,使其成为当前渲染状态的一部分。

下面是着色器编译和链接的代码示例:

cpp 复制代码
// 着色器编译和链接着色器程序的函数
GLuint CreateShaderProgram(const char* vertexShaderSource, const char* fragmentShaderSource) {
    // 创建顶点着色器
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    if (vertexShader == 0) {
        std::cerr << "无法创建顶点着色器!" << std::endl;
        return 0;
    }
    
    // 指定顶点着色器源代码
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    
    // 编译顶点着色器
    glCompileShader(vertexShader);
    
    // 检查编译状态
    GLint compiled;
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
        GLint infoLen = 0;
        glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 1) {
            char* infoLog = new char[infoLen];
            glGetShaderInfoLog(vertexShader, infoLen, NULL, infoLog);
            std::cerr << "顶点着色器编译错误: " << infoLog << std::endl;
            delete[] infoLog;
        }
        glDeleteShader(vertexShader);
        return 0;
    }
    
    // 创建片段着色器
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    if (fragmentShader == 0) {
        std::cerr << "无法创建片段着色器!" << std::endl;
        glDeleteShader(vertexShader);
        return 0;
    }
    
    // 指定片段着色器源代码
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    
    // 编译片段着色器
    glCompileShader(fragmentShader);
    
    // 检查编译状态
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
        GLint infoLen = 0;
        glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 1) {
            char* infoLog = new char[infoLen];
            glGetShaderInfoLog(fragmentShader, infoLen, NULL, infoLog);
            std::cerr << "片段着色器编译错误: " << infoLog << std::endl;
            delete[] infoLog;
        }
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        return 0;
    }
    
    // 创建着色器程序
    GLuint shaderProgram = glCreateProgram();
    if (shaderProgram == 0) {
        std::cerr << "无法创建着色器程序!" << std::endl;
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        return 0;
    }
    
    // 附加着色器到程序
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    
    // 链接着色器程序
    glLinkProgram(shaderProgram);
    
    // 检查链接状态
    GLint linked;
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &linked);
    if (!linked) {
        GLint infoLen = 0;
        glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 1) {
            char* infoLog = new char[infoLen];
            glGetProgramInfoLog(shaderProgram, infoLen, NULL, infoLog);
            std::cerr << "着色器程序链接错误: " << infoLog << std::endl;
            delete[] infoLog;
        }
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        glDeleteProgram(shaderProgram);
        return 0;
    }
    
    // 着色器已链接到程序,现在可以删除
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    
    return shaderProgram;
}

架构图

graph TD A[着色器编译与链接] --> B[创建着色器对象] A --> C[指定源代码] A --> D[编译着色器] A --> E[检查编译状态] A --> F[创建程序对象] A --> G[附加着色器] A --> H[链接着色器程序] A --> I[检查链接状态] A --> J[使用着色器程序] B --> K[顶点着色器] B --> L[片段着色器] D --> M[编译顶点着色器] D --> N[编译片段着色器] E --> O[顶点着色器错误] E --> P[片段着色器错误] G --> Q[附加顶点着色器] G --> R[附加片段着色器] I --> S[链接错误]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant ShaderCompiler as 着色器编译器 participant ShaderProgram as 着色器程序 App->>API: 创建顶点着色器对象 API->>ShaderCompiler: 分配顶点着色器资源 App->>API: 指定顶点着色器源代码 API->>ShaderCompiler: 接收源代码 App->>API: 编译顶点着色器 API->>ShaderCompiler: 编译顶点着色器 ShaderCompiler-->>API: 返回编译结果 App->>API: 创建片段着色器对象 API->>ShaderCompiler: 分配片段着色器资源 App->>API: 指定片段着色器源代码 API->>ShaderCompiler: 接收源代码 App->>API: 编译片段着色器 API->>ShaderCompiler: 编译片段着色器 ShaderCompiler-->>API: 返回编译结果 App->>API: 创建着色器程序对象 API->>ShaderProgram: 分配程序资源 App->>API: 附加顶点着色器 API->>ShaderProgram: 附加顶点着色器 App->>API: 附加片段着色器 API->>ShaderProgram: 附加片段着色器 App->>API: 链接着色器程序 API->>ShaderProgram: 链接着色器程序 ShaderProgram-->>API: 返回链接结果 App->>API: 使用着色器程序 API->>ShaderProgram: 激活程序

五、OpenGL ES 纹理系统

5.1 纹理概述

纹理是OpenGL ES中用于向图形表面添加细节的一种方式。它们是存储在GPU内存中的图像数据,可以在渲染过程中被采样和应用到几何体表面。

纹理的主要用途包括:

  1. 表面细节:为物体表面添加颜色、图案和细节。
  2. 环境映射:模拟反射和折射效果。
  3. 光照贴图:存储预计算的光照信息。
  4. 高度图:用于地形渲染和位移映射。
  5. 法线贴图:模拟表面细节而不增加几何复杂度。
  6. 立方体贴图:用于天空盒、环境反射等。

OpenGL ES支持多种纹理类型,包括:

  • 2D纹理:最常见的纹理类型,是一个二维图像。
  • 立方体贴图:由六个2D纹理组成,代表立方体的六个面,用于环境映射。
  • 3D纹理:类似于体积数据,有三个维度。
  • 2D纹理数组:一组2D纹理,共享相同的尺寸。

架构图

graph TD A[纹理系统] --> B[纹理类型] A --> C[纹理格式] A --> D[纹理采样] A --> E[纹理过滤] A --> F[多级渐远纹理] B --> G[2D纹理] B --> H[立方体贴图] B --> I[3D纹理] B --> J[2D纹理数组] C --> K[颜色格式] C --> L[压缩格式] C --> M[深度格式] D --> N[纹理坐标] D --> O[采样函数] E --> P[最近邻过滤] E --> Q[线性过滤] F --> R[生成Mipmap] F --> S[Mipmap过滤]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant TextureManager as 纹理管理器 participant GPU as GPU硬件 App->>API: 创建纹理对象 API->>TextureManager: 分配纹理资源 App->>API: 指定纹理参数 API->>TextureManager: 设置纹理参数 App->>API: 加载纹理数据 API->>TextureManager: 上传纹理数据 App->>API: 生成Mipmap API->>TextureManager: 生成多级渐远纹理 App->>API: 绑定纹理 API->>TextureManager: 激活纹理单元 App->>API: 调用绘制命令 API->>GPU: 执行渲染 GPU->>TextureManager: 请求纹理采样 TextureManager-->>GPU: 返回采样结果

5.2 2D纹理

2D纹理是OpenGL ES中最常见的纹理类型,它是一个二维图像,由宽度和高度定义。2D纹理可以用来表示物体的表面颜色、法线、高度等信息。

创建和使用2D纹理的主要步骤包括:

  1. 创建纹理对象 :使用glGenTextures创建一个纹理对象。
  2. 绑定纹理 :使用glBindTexture将纹理对象绑定到纹理单元。
  3. 设置纹理参数 :使用glTexParameteri设置纹理参数,如环绕模式和过滤模式。
  4. 加载纹理数据 :使用glTexImage2DglCompressedTexImage2D加载纹理数据。
  5. 生成Mipmap (可选):使用glGenerateMipmap生成多级渐远纹理。
  6. 在着色器中使用纹理 :在着色器中使用texture函数采样纹理。

下面是一个创建和使用2D纹理的代码示例:

cpp 复制代码
// 创建2D纹理
GLuint CreateTexture2D(int width, int height, GLenum format, const unsigned char* data) {
    GLuint textureId;
    
    // 生成纹理对象
    glGenTextures(1, &textureId);
    
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    // 加载纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
    
    // 生成Mipmap
    glGenerateMipmap(GL_TEXTURE_2D);
    
    // 解除绑定
    glBindTexture(GL_TEXTURE_2D, 0);
    
    return textureId;
}

// 在着色器中使用纹理
void UseTextureInShader(GLuint textureId, GLuint shaderProgram, const char* uniformName) {
    // 激活纹理单元0
    glActiveTexture(GL_TEXTURE0);
    
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    // 获取统一变量位置
    GLint textureUniform = glGetUniformLocation(shaderProgram, uniformName);
    
    // 设置统一变量,指定使用纹理单元0
    glUniform1i(textureUniform, 0);
}

在片段着色器中,可以这样采样2D纹理:

glsl 复制代码
#version 300 es

precision mediump float;

in vec2 vTexCoord;
out vec4 fragColor;

uniform sampler2D uTexture;

void main() {
    fragColor = texture(uTexture, vTexCoord);
}

架构图

graph TD A[2D纹理] --> B[创建纹理对象] A --> C[绑定纹理] A --> D[设置纹理参数] A --> E[加载纹理数据] A --> F[生成Mipmap] A --> G[在着色器中使用] D --> H[环绕模式] D --> I[过滤模式] D --> J[边界颜色] E --> K[未压缩数据] E --> L[压缩数据] G --> M[纹理坐标] G --> N[采样函数]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant Texture as 纹理对象 participant Shader as 着色器程序 App->>API: 创建纹理对象 API->>Texture: 分配资源 App->>API: 绑定纹理 API->>Texture: 激活纹理 App->>API: 设置环绕模式 API->>Texture: 更新参数 App->>API: 设置过滤模式 API->>Texture: 更新参数 App->>API: 加载纹理数据 API->>Texture: 存储图像数据 App->>API: 生成Mipmap API->>Texture: 生成多级渐远纹理 App->>API: 激活纹理单元 API->>Texture: 选择纹理单元 App->>API: 绑定纹理到单元 API->>Texture: 关联纹理 App->>API: 设置着色器统一变量 API->>Shader: 更新纹理单元引用 App->>API: 调用绘制命令 API->>Shader: 执行着色器 Shader->>Texture: 请求纹理采样 Texture-->>Shader: 返回采样颜色

5.3 立方体贴图

立方体贴图由六个2D纹理组成,每个纹理代表立方体的一个面。立方体贴图通常用于环境映射、天空盒和反射效果。

立方体贴图的六个面分别是:

  • GL_TEXTURE_CUBE_MAP_POSITIVE_X:右
  • GL_TEXTURE_CUBE_MAP_NEGATIVE_X:左
  • GL_TEXTURE_CUBE_MAP_POSITIVE_Y:上
  • GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:下
  • GL_TEXTURE_CUBE_MAP_POSITIVE_Z:后
  • GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:前

创建和使用立方体贴图的主要步骤包括:

  1. 创建纹理对象 :使用glGenTextures创建一个纹理对象。
  2. 绑定纹理 :使用glBindTexture(GL_TEXTURE_CUBE_MAP, textureId)将纹理对象绑定到立方体贴图目标。
  3. 设置纹理参数:设置环绕模式和过滤模式。
  4. 加载六个面的纹理数据 :使用glTexImage2D为每个面加载纹理数据。
  5. 生成Mipmap (可选):使用glGenerateMipmap(GL_TEXTURE_CUBE_MAP)生成多级渐远纹理。
  6. 在着色器中使用立方体贴图:在着色器中使用三维方向向量采样立方体贴图。

下面是一个创建和使用立方体贴图的代码示例:

cpp 复制代码
// 创建立方体贴图
GLuint CreateCubeMapTexture(const unsigned char* faces[6], int width, int height, GLenum format) {
    GLuint textureId;
    
    // 生成纹理对象
    glGenTextures(1, &textureId);
    
    // 绑定纹理
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);
    
    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    
    // 加载六个面的纹理数据
    for (GLuint i = 0; i < 6; i++) {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, faces[i]);
    }
    
    // 生成Mipmap
    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
    
    // 解除绑定
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    
    return textureId;
}

// 在着色器中使用立方体贴图
void UseCubeMapTextureInShader(GLuint textureId, GLuint shaderProgram, const char* uniformName) {
    // 激活纹理单元0
    glActiveTexture(GL_TEXTURE0);
    
    // 绑定立方体贴图
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);
    
    // 获取统一变量位置
    GLint textureUniform = glGetUniformLocation(shaderProgram, uniformName);
    
    // 设置统一变量,指定使用纹理单元0
    glUniform1i(textureUniform, 0);
}

在片段着色器中,可以这样采样立方体贴图:

glsl 复制代码
#version 300 es

precision mediump float;

in vec3 vNormal;
out vec4 fragColor;

uniform samplerCube uCubeMap;

void main() {
    // 使用法线向量作为方向向量采样立方体贴图
    fragColor = texture(uCubeMap, normalize(vNormal));
}

架构图

graph TD A[立方体贴图] --> B[创建纹理对象] A --> C[绑定纹理] A --> D[设置纹理参数] A --> E[加载六个面的纹理数据] A --> F[生成Mipmap] A --> G[在着色器中使用] E --> H[正X面] E --> I[负X面] E --> J[正Y面] E --> K[负Y面] E --> L[正Z面] E --> M[负Z面] G --> N[三维方向向量] G --> O[采样函数]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant CubeMap as 立方体贴图 participant Shader as 着色器程序 App->>API: 创建纹理对象 API->>CubeMap: 分配资源 App->>API: 绑定立方体贴图 API->>CubeMap: 激活纹理 App->>API: 设置纹理参数 API->>CubeMap: 更新参数 App->>API: 加载正X面数据 API->>CubeMap: 存储正X面数据 App->>API: 加载负X面数据 API->>CubeMap: 存储负X面数据 App->>API: 加载正Y面数据 API->>CubeMap: 存储正Y面数据 App->>API: 加载负Y面数据 API->>CubeMap: 存储负Y面数据 App->>API: 加载正Z面数据 API->>CubeMap: 存储正Z面数据 App->>API: 加载负Z面数据 API->>CubeMap: 存储负Z面数据 App->>API: 生成Mipmap API->>CubeMap: 生成多级渐远纹理 App->>API: 激活纹理单元 API->>CubeMap: 选择纹理单元 App->>API: 绑定立方体贴图到单元 API->>CubeMap: 关联纹理 App->>API: 设置着色器统一变量 API->>Shader: 更新纹理单元引用 App->>API: 调用绘制命令 API->>Shader: 执行着色器 Shader->>CubeMap: 使用方向向量请求采样 CubeMap-->>Shader: 返回采样颜色

5.4 纹理采样与过滤

纹理采样是从纹理中获取颜色值的过程。在渲染过程中,纹理坐标通常是浮点数,而纹理数据是离散的,因此需要一种方法来确定如何从离散的纹理数据中获取连续的颜色值。这就是纹理过滤的作用。

OpenGL ES提供了多种纹理过滤模式,主要分为两类:

  1. 放大过滤:当纹理被放大时使用(纹理像素比屏幕像素少)。
  2. 缩小过滤:当纹理被缩小时使用(纹理像素比屏幕像素多)。

常见的纹理过滤模式包括:

  • GL_NEAREST:最近邻过滤,使用最接近纹理坐标的纹理元素。
  • GL_LINEAR:线性过滤,使用周围纹理元素的加权平均值。
  • Mipmap过滤:使用多级渐远纹理,根据距离选择合适的纹理级别。

下面是各种过滤模式的效果比较:

过滤模式 描述 质量 性能
GL_NEAREST 最近邻过滤,没有Mipmap
GL_LINEAR 线性过滤,没有Mipmap 中等 中等
GL_NEAREST_MIPMAP_NEAREST 最近邻Mipmap选择,最近邻过滤 中等
GL_LINEAR_MIPMAP_NEAREST 最近邻Mipmap选择,线性过滤 中等偏高 中等
GL_NEAREST_MIPMAP_LINEAR 线性Mipmap选择,最近邻过滤 中等偏高 中等
GL_LINEAR_MIPMAP_LINEAR 线性Mipmap选择,线性过滤(三线性过滤)

纹理环绕模式控制当纹理坐标超出[0,1]范围时的行为。常见的环绕模式包括:

  • GL_REPEAT:重复纹理。
  • GL_MIRRORED_REPEAT:镜像重复纹理。
  • GL_CLAMP_TO_EDGE:夹紧到边缘。
  • GL_CLAMP_TO_BORDER:夹紧到边界。

下面是纹理采样和过滤的代码示例:

cpp 复制代码
// 设置纹理过滤和环绕模式
void SetTextureParameters(GLuint textureId, GLenum minFilter, GLenum magFilter, GLenum wrapS, GLenum wrapT) {
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    // 设置缩小过滤模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    
    // 设置放大过滤模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    
    // 设置S方向环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
    
    // 设置T方向环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
    
    // 解除绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}

在着色器中,可以使用内置的texture函数进行纹理采样:

glsl 复制代码
#version 300 es

precision mediump float;

in vec2 vTexCoord;
out vec4 fragColor;

uniform sampler2D uTexture;

void main() {
    // 采样纹理
    fragColor = texture(uTexture, vTexCoord);
}

调用流程图

sequenceDiagram participant Shader as 着色器 participant TextureUnit as 纹理单元 participant Texture as 纹理 participant Sampler as 采样器 Shader->>TextureUnit: 请求纹理采样 TextureUnit->>Sampler: 传递纹理坐标 Sampler->>Texture: 确定Mipmap级别 Texture-->>Sampler: 返回Mipmap级别 Sampler->>Texture: 获取纹理元素 Texture-->>Sampler: 返回纹理元素 Sampler->>Sampler: 应用过滤算法 Sampler-->>TextureUnit: 返回采样颜色 TextureUnit-->>Shader: 返回采样颜色

5.5 纹理压缩

纹理压缩是一种减少纹理内存占用和提高渲染性能的技术。通过压缩纹理数据,可以减少GPU内存的使用,降低带宽需求,并提高缓存效率。

OpenGL ES支持多种纹理压缩格式,包括:

  1. ETC (Ericsson Texture Compression):一种专为移动设备设计的压缩格式,支持RGB和RGBA。
  2. ASTC (Adaptive Scalable Texture Compression):一种现代的纹理压缩格式,支持各种压缩率和纹理尺寸。
  3. DXT/BC (Block Compression):一种广泛使用的压缩格式,也称为S3TC。
  4. PVRTC (PowerVR Texture Compression):Imagination Technologies开发的压缩格式,主要用于PowerVR GPU。

使用纹理压缩的主要步骤包括:

  1. 检查压缩格式支持 :使用glGetString(GL_EXTENSIONS)检查设备支持哪些压缩格式。
  2. 加载压缩纹理数据 :使用glCompressedTexImage2D或类似函数加载压缩纹理数据。
  3. 设置纹理参数:设置过滤模式、环绕模式等。
  4. 在着色器中使用压缩纹理:与未压缩纹理相同。

下面是一个使用ETC2压缩格式的代码示例:

cpp 复制代码
// 检查ETC2压缩格式支持
bool IsETC2Supported() {
    const char* extensions = (const char*)glGetString(GL_EXTENSIONS);
    return (strstr(extensions, "GL_ETC2_RGBA8") != nullptr);
}

// 加载ETC2压缩纹理
GLuint LoadETC2Texture(const unsigned char* compressedData, int width, int height, int dataSize) {
    GLuint textureId;
    
    // 生成纹理对象
    glGenTextures(1, &textureId);
    
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    
    // 加载压缩纹理数据
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC2_RGBA8, width, height, 0, dataSize, compressedData);
    
    // 生成Mipmap
    glGenerateMipmap(GL_TEXTURE_2D);
    
    // 解除绑定
    glBindTexture(GL_TEXTURE_2D, 0);
    
    return textureId;
}

架构图

graph TD A[纹理压缩] --> B[压缩格式] A --> C[压缩工具] A --> D[压缩过程] A --> E[解压缩过程] A --> F[使用压缩纹理] B --> G[ETC] B --> H[ASTC] B --> I[DXT/BC] B --> J[PVRTC] C --> K[离线压缩] C --> L[实时压缩] D --> M[图像数据] D --> N[压缩算法] D --> O[压缩参数] E --> P[压缩数据] E --> Q[解压缩算法] F --> R[检查支持] F --> S[加载压缩数据] F --> T[设置纹理参数]

调用流程图

sequenceDiagram participant App as 应用程序 participant API as OpenGL ES API participant Texture as 纹理对象 participant GPU as GPU硬件 App->>API: 检查压缩格式支持 API-->>App: 返回支持的格式列表 App->>API: 创建纹理对象 API->>Texture: 分配资源 App->>API: 绑定纹理 API->>Texture: 激活纹理 App->>API: 设置纹理参数 API->>Texture: 更新参数 App->>API: 加载压缩纹理数据 API->>Texture: 存储压缩数据 App->>API: 生成Mipmap API->>Texture: 生成多级渐远纹理 App->>API: 绑定纹理到单元 API->>Texture: 关联纹理单元 App->>API: 调用绘制命令 API->>GPU: 执行渲染 GPU->>Texture: 请求纹理采样 Texture-->>GPU: 返回解压缩后的颜色
相关推荐
whysqwhw17 分钟前
OkHttp-TLS 模块概要分析
android
byte轻骑兵1 小时前
【Bluedroid】蓝牙协议栈enable流程深度解析
android·c++·bluedroid
天天扭码1 小时前
很全面的前端面试题——CSS篇(下)
前端·css·面试
Java中文社群2 小时前
面试官:谈谈你AI项目的具体实现?
java·后端·面试
然我2 小时前
react-router-dom 完全指南:从零实现动态路由与嵌套布局
前端·react.js·面试
Industio_触觉智能2 小时前
量产技巧之RK3588 Android12默认移除导航栏&状态栏
android·rk3588·开发板·核心板·瑞芯微·rk3588j
小馬佩德罗2 小时前
Android系统的问题分析笔记 - Android上的调试方式 bugreport
android·调试
VividnessYao2 小时前
Android Handler 消息机制
android
前端拿破轮3 小时前
刷了这么久LeetCode了,挑战一道hard。。。
前端·javascript·面试