我们将主要使用 Google 推荐的 OpenGL ES (Open Graphics Library for Embedded Systems) API,并重点介绍现代、高效的 Vulkan API,也会提及一些更高级的框架,如 Unity 和 Android Studio 内置的 Sceneform。

目录
- 第一步:准备工作与环境搭建
- 核心概念:3D 图形流水线
- 技术路线选择:OpenGL ES vs. Vulkan
- 深入 OpenGL ES
- 1. 创建一个 OpenGL ES 项目
- 2. 绘制你的第一个三角形:渲染管线详解
- 3. 添加颜色与纹理
- 4. 实现 3D 效果:模型视图投影矩阵
- 5. 添加光照
- 6. 加载 3D 模型
- 探索 Vulkan
- 更高级的选择:Sceneform
- 专业级选择:Unity
- 推荐学习资源
- 总结与学习路径
第一步:准备工作与环境搭建
在开始之前,请确保你的开发环境已经就绪。
- Android Studio: 最新版本的 Android Studio。
- Android SDK: 安装最新的 SDK 和 SDK Build-Tools。
- NDK (Native Development Kit): 如果你打算用 C/C++ 编写 OpenGL/Vulkan 代码,需要安装 NDK,这通常是最现代和推荐的方式。
- 模拟器或真机: 强烈建议使用支持 OpenGL ES 3.0 或更高版本的真机进行开发,性能和兼容性更好。
项目创建: 在 Android Studio 中创建一个新项目时,选择 "Native C++" 模板,这会自动为你设置好 CMake 和 JNI 的环境,让你可以轻松地在 Java/Kotlin 和 C/C++ 代码之间进行交互。
核心概念:3D 图形流水线
在写代码之前,理解 3D 图形是如何在屏幕上显示出来的至关重要,这被称为 图形渲染管线,它是一个将 3D 模型数据最终转换为 2D 像素图像的固定流程。
主要阶段如下:
- 顶点数据: 你的 3D 模型由无数个顶点组成,每个顶点包含位置、颜色、纹理坐标等信息。
- 顶点着色器: 对每个顶点进行变换,将模型的局部坐标转换到世界坐标、视图坐标,最后是投影坐标(也就是我们常说的 MVP 矩阵变换)。
- 图元装配: 将顶点组装成指定的图元,如点、线或三角形。
- 光栅化: 确定哪些屏幕像素(片段)被图元覆盖,并计算出这些片段的坐标和插值后的属性(如颜色、纹理坐标)。
- 片段着色器: 对每个片段进行处理,决定其最终的颜色,这是实现光照、纹理贴图等效果的核心。
- 测试与混合: 进行深度测试(确保远处的物体不会挡住近处的)、模板测试,以及将片段颜色与屏幕上已有颜色进行混合(实现半透明效果等)。
- 帧缓冲: 最终处理好的像素被写入到帧缓冲中,然后显示在屏幕上。
关键工具:
- GLSL (OpenGL Shading Language): 用于编写顶点着色器和片段着色器的语言,你将在 C++ 代码中以字符串的形式嵌入它们。
技术路线选择:OpenGL ES vs. Vulkan
对于初学者,选择合适的 API 非常重要。
| 特性 | OpenGL ES (ES 3.0+) | Vulkan |
|---|---|---|
| 易用性 | 高,API 设计直观,封装了底层的复杂性,学习曲线较平缓。 | 低,API 非常底层和繁琐,需要开发者手动管理大量资源(如内存、命令缓冲区),学习曲线陡峭。 |
| 性能 | 良好,由驱动程序优化,但有一定开销。 | 极高,CPU 开销极低,并行度极高,能充分发挥硬件性能,适合移动高端设备和 PC 游戏。 |
| 控制力 | 较低,许多细节由驱动程序处理。 | 极高,开发者对渲染管线有完全的控制权。 |
| 适用场景 | 初学者、大多数 App、UI、中等复杂度的 3D 效果。 | 专业游戏、高性能计算、需要极致优化的 3D 应用。 |
建议:
- 如果你是初学者:从 OpenGL ES 3.0 开始,它能让你快速理解核心概念,并看到成果,建立信心。
- 如果你是经验丰富的开发者,追求极致性能:直接学习 Vulkan,虽然痛苦,但回报是巨大的性能提升。
深入 OpenGL ES
我们将通过一个 C++ 项目来演示。
1. 创建一个 OpenGL ES 项目
如前所述,使用 "Native C++" 模板创建项目,你会看到几个关键文件:
app/src/main/cpp/native-lib.cpp: C++ 代码的入口。app/src/main/cpp/CMakeLists.txt: 用于编译 C++ 代码的脚本。app/src/main/java/com/yourpackage/MainActivity.java: Java/Kotlin 主活动,用于加载 C++ 库和设置GLSurfaceView。
GLSurfaceView 是一个专门用于渲染 OpenGL 内容的 SurfaceView。
2. 绘制你的第一个三角形
这是 "Hello, World!" 的 3D 版本。
步骤 1: 设置 GLSurfaceView
在 MainActivity 中,你需要设置一个 GLSurfaceView.Renderer。Renderer 是一个接口,它定义了 OpenGL 绘制的三个关键生命周期方法:
onSurfaceCreated(): 当 Surface 创建时调用,通常在这里初始化 OpenGL 环境和加载着色器。onSurfaceChanged(int width, int height): 当 Surface 大小改变时调用,通常在这里设置视口和投影矩阵。onDrawFrame(): 每一帧都会调用,这是执行渲染指令的地方。
// In MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GLSurfaceView glView = findViewById(R.id.glview);
glView.setEGLContextClientVersion(3); // 使用 OpenGL ES 3.0
glView.setRenderer(new MyGLRenderer()); // 设置你的 Renderer
glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 当数据变化时才渲染
}
步骤 2: 编写着色器
在 native-lib.cpp 中,我们将定义 GLSL 代码字符串。
顶点着色器 (simple_vertex_shader.glsl):
attribute vec4 vPosition; // 顶点位置属性
void main() {
gl_Position = vPosition; // 直接将顶点位置设置为裁剪空间坐标
}
片段着色器 (simple_fragment_shader.glsl):
precision mediump float; // 设置浮点数精度
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 所有片段都输出红色
}
步骤 3: 加载着色器并编译链接
你需要 C++ 函数来加载这些字符串,并编译、链接它们成一个可执行的着色器程序,这涉及到 glCreateShader, glShaderSource, glCompileShader, glCreateProgram, glAttachShader, glLinkProgram 等调用。
步骤 4: 定义顶点数据 定义一个三角形的顶点坐标数组,这些坐标是在一个标准的立方体空间(-1.0 到 1.0)中定义的,称为裁剪空间。
// 定义一个三角形的顶点坐标
static const GLfloat triangleVertices[] = {
0.0f, 0.5f, 0.0f, // 顶点1
-0.5f, -0.5f, 0.0f, // 顶点2
0.5f, -0.5f, 0.0f // 顶点3
};
步骤 5: 在 onDrawFrame 中绘制
void MyGLRenderer::onDrawFrame(GL10 unused) {
// 清除颜色缓冲区,为下一帧做准备
glClear(GL_COLOR_BUFFER_BIT);
// 使用着色器程序
glUseProgram(mProgram);
// 绑定顶点数据
// 1. 启用顶点属性数组
glEnableVertexAttribArray(mPositionHandle);
// 2. 将顶点数据指针传递给着色器
glVertexAttribPointer(mPositionHandle, 3, GL_FLOAT, false, 0, triangleVertices);
// 绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3); // 从第0个顶点开始,绘制3个顶点
// 禁用顶点属性数组
glDisableVertexAttribArray(mPositionHandle);
}
现在运行你的 App,你应该能在屏幕中央看到一个红色的三角形!
3. 添加颜色与纹理
- 颜色: 修改顶点数据,为每个顶点添加一个颜色属性,然后在顶点着色器中将颜色传递给片段着色器,片段着色器接收插值后的颜色并输出。
- 纹理:
- 加载图片: 在 Java/Kotlin 中使用
BitmapFactory加载一张图片。 - 生成纹理: 在 C++ 中调用
glGenTextures创建一个纹理对象。 - 绑定并上传数据: 调用
glBindTexture,glTexImage2D将图片数据上传到 GPU。 - 修改着色器: 顶点着色器需要传递纹理坐标,片段着色器使用
sampler2D和texture2D()函数来采样纹理颜色。
- 加载图片: 在 Java/Kotlin 中使用
4. 实现 3D 效果:模型视图投影矩阵
目前我们画的是 2D 三角形,要让它看起来是 3D 的,需要使用矩阵变换。
- 模型矩阵: 将模型从自己的局部空间移动到世界空间(如旋转、平移、缩放)。
- 视图矩阵: 将世界中的所有物体移动到相机(观察者)的视角下,通常将相机放在原点,并看向 Z 轴负方向。
- 投影矩阵: 将 3D 坐影转换到 2D 屏幕坐标,有透视投影(近大远小)和正交投影。
这三个矩阵相乘 (Projection * View * Model) 得到最终的 MVP 矩阵。
在 C++ 中,你可以使用 GLM (OpenGL Mathematics) 库来方便地进行矩阵运算。
流程:
- 在 C++ 中创建一个
glm::mat4类型的 MVP 矩阵。 - 使用 GLM 函数填充这个矩阵。
- 在顶点着色器中,定义一个
uniform mat4 uMVPMatrix;变量。 - 在 C++ 中获取
uMVPMatrix的位置 (glGetUniformLocation)。 - 在
onDrawFrame中,使用glUniformMatrix4fv将计算好的 MVP 矩阵传递给着色器。 - 在顶点着色器中,将
gl_Position修改为gl_Position = uMVPMatrix * vPosition;。
通过修改模型矩阵,你就可以让三角形旋转起来!
5. 添加光照
要实现逼真的 3D 效果,光照是必不可少的,最简单的是冯氏光照,它包含三个分量:
- 环境光: 环境的基础亮度。
- 漫反射光: 来自特定方向的漫反射,取决于表面法线和光照方向的夹角。
- 镜面高光: 来自特定方向的镜面反射,取决于视线方向和反射方向的夹角。
这需要在片段着色器中进行计算,你需要为着色器提供:
- 光源位置
- 物体表面颜色(或纹理)
- 物体表面法线(每个顶点一个)
- 观察者(相机)位置
计算会变得复杂一些,但效果非常棒。
6. 加载 3D 模型
手动定义顶点数据只适合简单的几何体,加载复杂的 3D 模型(如 .obj 文件)是常见需求。
- 解析模型文件: 你需要一个解析器(如
tinyobjloader)来读取.obj文件,提取顶点位置、法线、纹理坐标和面信息(索引)。 - 使用索引缓冲区: 为了避免重复的顶点数据,模型文件通常使用索引,你需要创建一个顶点缓冲对象 和一个索引缓冲对象。
- 渲染: 使用
glDrawElements代替glDrawArrays,并传递索引缓冲区。
探索 Vulkan
如果你已经掌握了 OpenGL ES 并且对底层性能有追求,那么可以开始学习 Vulkan。
学习 Vulkan 的挑战在于它需要你做 OpenGL 驱动程序为你做的大部分事情:
- 实例和设备: 显式创建 Vulkan 实例和逻辑设备。
- 交换链: 管理前后缓冲区,防止画面撕裂。
- 命令缓冲区: 你需要自己录制一系列绘制命令,然后提交给 GPU 执行。
- 内存管理: 你需要显式地为每个资源(缓冲区、图像)分配内存,并指定其类型(如设备本地、主机可见等)。
好处是,一旦你掌握了这些繁琐的设置,你就能获得极高的性能和更可预测的帧率,Vulkan 是 3D 图形的未来,尤其是在移动平台上。
更高级的选择:Sceneform
如果你不想陷入底层的 OpenGL/Vulkan 细节,只想快速地在 AR 或普通 App 中添加 3D 内容,Sceneform 是一个绝佳的选择。
- 是什么: Sceneform 是一个由 Google 提供的高层 3D 框架,专门为 Android 设计,它使用 Vulkan 作为后端,但为你提供了简洁的 Java/Kotlin API。
- 优点:
- 纯 Java/Kotlin: 无需编写 C++。
- 基于物理的渲染: 轻松实现逼真的光照和材质。
- 易于使用: API 设计直观,几行代码就能加载和显示一个 3D 模型。
- 内置 AR 支持: 与 ARCore 无缝集成。
- 缺点:
- 灵活性较低: 不像 OpenGL/Vulkan 那样可以完全控制渲染管线。
- 社区较小: 相比 OpenGL,学习资源和社区支持较少。
适合人群: Android 应用开发者,希望快速集成 3D 内容,而不是成为图形专家。
专业级选择:Unity
如果你的目标是开发一个完整的 3D 游戏或复杂的 3D 应用,而不是在 App 中嵌入一些 3D 效果,Unity 是行业标准。
- 是什么: 一个完整的游戏引擎,它内置了强大的渲染器、物理引擎、动画系统、音频系统等。
- 工作流程:
- 在 Unity 编辑器中进行可视化场景搭建、材质编辑、动画制作。
- 使用 C# 编写游戏逻辑。
- Unity 负责将你的所有内容打包成一个 Android App。
- 优点:
- 极高效率: 无需处理底层图形 API,专注于创意和玩法。
- 庞大的生态系统: 资源商店、教程社区、工具链极其成熟。
- 跨平台: 一套代码可以发布到 Android, iOS, Windows, macOS, Console 等。
- 缺点:
- 引擎体积大: 最终的 App 包体积会比较大。
- 非原生: 对 Android 平台特性的直接控制不如原生开发灵活。
推荐学习资源
-
OpenGL ES:
- Learn OpenGL (中文版): https://learnopengl-cn.github.io/ - 必看! 从最基础的光照到高级的阴影、PBR,讲解非常清晰,有 C++ 代码示例。
- OpenGL ES 3.0 Programming Guide: 官方书籍,非常权威。
- GitHub: 搜索
opengl-es-tutorial-android可以找到很多开源项目。
-
Vulkan:
- Vulkan Tutorial (英文): https://vulkan-tutorial.com/ - 非常好的入门教程,一步步教你搭建 Vulkan 环境。
- Sascha Willems' Vulkan Examples: https://github.com/SaschaWillems/Vulkan - 优秀的 Vulkan 示例代码库。
-
Sceneform:
- Google Codelabs: 搜索 "Sceneform for Android" 有官方的动手教程。
- Sceneform Samples GitHub: https://github.com/google-ar/sceneform-samples
-
Unity:
- Unity Learn: https://learn.unity.com/ - Unity 官方学习平台,有大量免费课程。
- B站/YouTube: 无数优秀的 Unity 教程。
总结与学习路径
-
初学者/应用开发者:
- 目标: 在 App 中添加漂亮的 3D 效果或 AR 功能。
- 路径: Sceneform -> 如果需要更多控制,再学习 OpenGL ES 3.0,直接跳过 Vulkan。
-
游戏开发者/图形工程师:
- 目标: 开发高性能的 3D 游戏或应用。
- 路径: Unity (快速上手和开发) -> OpenGL ES 3.0 (理解底层原理) -> Vulkan (追求极致性能和平台控制)。
学习 3D 图形是一个循序渐进的过程,从简单的三角形开始,逐步添加颜色、纹理、光照和变换,不要害怕遇到问题,查阅官方文档和优秀的开源项目是解决问题的关键,祝你学习愉快!
