0%

开发环境配置

Android

在 Android 平台有两种方法可以构建 OpenGL 环境:通过系统自带的 GLSurfaceView 组件和通过 EGL 。GLSurfaceView 内部自带了 OpenGL 的渲染线程,通过 EGL 需要自己创建渲染线程。

Android 平台通过 OpenGL 渲染图像的本质是通过一系列的 GPU 指令将绘制的图形或者获得的图像帧(本地/网络视频解码或摄像头采集)渲染到设备屏幕上的过程,这个屏幕在系统中被抽象为 Surface ,而图形和图像则被抽象为 Texture (纹理),纹理通过顶点和片段着色器映射到 Surface 上。顶点着色器可以简单理解为确定每个点在屏幕上的坐标,而片段着色器可以简单理解为确定每个点的颜色,即一个计算位置,一个计算颜色。OpenGL 的编程模式为线性编程,即上一段程序的输出是下一段程序的输入。

以绘制三角形为例:

GLSurfaceView

在 Android 的 OpenGL 环境中,用纹理 ID 来标识纹理 ,它实际上指向的是 GPU 中分配的一块内存区域,用于存储纹理数据, SurfaceTexture 将视频帧数据存储在这个内存区域中。

初始设置

1
2
3
4
5
6
// 创建一个OpenGL ES 2.0的上下文
setEGLContextClientVersion(2);
// 仅在绘制数据发生变化时才进行绘制
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
// 设置渲染器
setRenderer(...);

创建 Renderer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyGLRenderer implements GLSurfaceView.Renderer {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置清除屏幕所用的颜色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

@Override
public void onDrawFrame(GL10 gl) {
// 清除屏幕
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置视口
GLES20.glViewport(0, 0, width, height);
}
}

编写着色器

1
2
3
4
5
6
7
8
9
10
11
12
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";

private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";

编译着色器

1
2
3
4
5
6
7
8
9
10
public static int loadShader(int type, String shaderCode){
// 创建一个着色器类型(GLES20.GL_VERTEX_SHADER 或 GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);

// 将源码添加到着色器并编译它
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);

return shader;
}

链接着色器

1
2
3
4
5
6
7
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

int mProgram = GLES20.glCreateProgram(); // 创建一个空的OpenGL程序
GLES20.glAttachShader(mProgram, vertexShader); // 将顶点着色器添加到程序中
GLES20.glAttachShader(mProgram, fragmentShader); // 将片段着色器添加到程序中
GLES20.glLinkProgram(mProgram); // 创建可执行的OpenGL程序

定义图形数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static float triangleCoords[] = {   // 按逆时针方向顺序
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};

private final FloatBuffer vertexBuffer;

// 设置颜色
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

public Triangle() {
// 初始化顶点字节缓冲区
ByteBuffer bb = ByteBuffer.allocateDirect(
// (每个浮点数占用4个字节)
triangleCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);
}

绘制图形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void draw() {
// 将程序添加到OpenGL环境
GLES20.glUseProgram(mProgram);

// 获取顶点着色器的位置
int positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

// 启用顶点着色器的句柄
GLES20.glEnableVertexAttribArray(positionHandle);

// 准备三角形的坐标数据
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);

// 获取片段着色器的颜色句柄
int colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

// 设置绘制三角形的颜色
GLES20.glUniform4fv(colorHandle, 1, color, 0);

// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

// 禁用顶点数组
GLES20.glDisableVertexAttribArray(positionHandle);
}

EGL

EGL (Embedded-System Graphics Library) 是一个接口层,连接了 OpenGL ES 等图形库和显示设备(Surface),GLSurfaceView 就是对 EGL 的封装。它有几个关键概念:

EGLDisplay:表示与本地窗口系统的连接。

EGLConfig:描述绘图表面的格式和类型的配置,包括颜色深度、缓冲区类型等。

EGLContext:表示OpenGL ES等图形库的渲染上下文。

EGLSurface:表示一个可以渲染的表面,例如窗口表面、Pixmap表面或Pbuffer表面。

它们的关系可以简单理解为 EGLSurface 和 EGLContext 通过 EGLDisplay 连接。

创建 EGL 显示和初始化

1
2
3
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
EGL14.eglInitialize(eglDisplay, version, 0, version, 1);

配置 EGL

1
2
3
4
5
6
7
8
9
10
11
12
int[] configAttribs = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, configs.length, numConfigs, 0);
EGLConfig eglConfig = configs[0];

创建 EGL 上下文

1
2
3
4
5
int[] contextAttribs = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribs, 0);

创建 EGL Surface

1
2
3
4
5
6
Surface surface = ...; // 获取或创建一个 Android Surface
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
// 创建一个 EGLSurface,关联到一个 Android Surface。
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);

编写着色器

编译和链接着色器

定义图形数据

均同上

连接 EGL 上下文和显示设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 绑定 EGL 上下文和 Surface
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

// 获取属性位置
int positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
int colorHandle = GLES20.glGetUniformLocation(program, "vColor");

// 启用顶点数组
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);

// 设置颜色
GLES20.glUniform4fv(colorHandle, 1, color, 0);

// 清屏并绘制三角形
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);

// 交换显示缓冲区。GLSurfaceView 自动处理了缓冲区的交换。在渲染循环的每一帧结束时,它会调用 EGL 函数来交换前后缓冲区。这些操作是通过 GLSurfaceView
// 的内部类 GLThread 来完成的。
EGL14.eglSwapBuffers(eglDisplay, eglSurface);

// 禁用顶点数组
GLES20.glDisableVertexAttribArray(positionHandle);

释放 EGL 和 OpenGL 资源

1
2
3
4
5
6
GLES20.glDeleteProgram(program);
GLES20.glDeleteShader(vertexShader);
GLES20.glDeleteShader(fragmentShader);
EGL14.eglDestroySurface(eglDisplay, eglSurface);
EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglTerminate(eglDisplay);

使用 GLSurfaceView 和 EGL 渲染本地视频帧的 Demo 地址:

https://github.com/cmder/Demos/tree/main/Mp4Player

Windows

IDE

下载安装 Visual Studio 2022

安装 OpenGL / GLSL

不需要安装,系统自带,可使用 GLView 查看版本。

准备 GLFW

用途:用于创建和管理 OpenGL 上下文、窗口和输入事件的库。

  1. 下载 GLFW 源代码:https://www.glfw.org/download.html
  2. 下载并安装 Cmake:https://cmake.org/
  3. 运行 CMake 的图形界面并选择GLFW源代码所在位置和期望的构建目标文件夹
  4. 单击Configure
  5. 单击Generate

CMake会在之前指定的“构建目标”文件夹中生成多个文件,该文件夹中有一个文件名为GLFW.sln,用Visual Studio打开并构建。构建生成后,在\build\src\Debug下找到glfw3.lib文件,在\include\GLFW下找到glfw3.h和glfw3native.h。

准备 GLEW

用途:用于加载 OpenGL 扩展函数的库。解决了不同平台、不同硬件上 OpenGL 版本和功能不一致的问题。确保能够使用最新的 OpenGL 功能。

从官网https://glew.sourceforge.net/下载64位二进制文件,解压后找到以下项目:

  1. lib/Release/x64 下的glew32.lib
  2. bin/Release/x64 下的glew32.dll
  3. include文件夹下的头文件

准备 GLM

用途:用于 OpenGL 程序中的数学计算库,特别是矩阵和向量操作。

从官网https://github.com/icaven/glm?tab=readme-ov-file下载,glm文件夹下的全部内容。

准备SOIL2

用途:用于加载图像纹理的库,主要用于将图片文件加载为 OpenGL 纹理。

  1. 下载并解压缩 premake :https://premake.github.io/

  2. 下载SOIL2:https://github.com/SpartanJ/SOIL2(使用界面中Code下的Download ZIP超链接),然后解压缩。

  3. 将premake5.exe文件复制到soil2文件夹中。

  4. 在soil2文件夹下打开命令提示符,运行命令:

    1
    premake5 --platform=x64 vs2012
  5. 在soil2文件夹中,打开make文件夹,然后打开windows文件夹。双击SOIL2.sln。

  6. 使用界面顶部的下拉菜单,从x86(或Win32)切换到x64,然后在“解决方案资源管理器”面板中,右键单击soil2-static-lib并选择“生成(Build)”。\lib\windows下可以发现soil2-debug.lib

准备共享的lib和include文件夹

创建库文件夹,放入glew32.dll,再创建两个子文件夹:lib 和 include,lib下放glew32.lib,glew32s.lib,glfw3.lib,glm.lib,soil2-debug.lib,include文件夹下创建glew,glfw,glm和SOIL2(\src\SOIL2)文件夹,放入各自头文件。

创建Visual Studio自定义项目模板

启动Visual Studio(假设为2019版本)。创建一个新的C++空项目。在界面顶部中心,菜单栏下方有两个相邻的下拉菜单。选择x64。先在Debug模式下(然后在Release模式下)进入“项目属性”并进行以下更改。

  1. 在“VC++目录”下(也可能写成“C/C++”),单击“常规”,然后在“包含目录”中添加你之前创建的include文件夹。

  2. 在“链接器”下,有以下两个更改:

    单击“常规”,然后在“附加库目录”下添加先前创建的lib文件夹。

    单击“输入”,然后在“附加依赖项”下添加以下4个文件名:glfw3.lib、glew32.lib、soil2-debug.lib和opengl32.lib(最后一个应该已经作为标准Windows SDK的一部分提供)。

对Debug和Release模式的项目属性都进行上述更改后,就可以开始创建模板了。

创建模板通过进入“项目”菜单并选择“导出模板”来完成。选择“项目模板”,并为模板提供有意义的名称,例如“OpenGL project”。

新建项目

安装库并设置好自定义模板后,创建一个新的OpenGL C++项目就很简单了。

  1. 启动Visual Studio,单击“新建项目”。
  2. 在左上方选择OpenGL模板,然后单击“下一步”。
  3. 给项目起一个名称,然后单击“下一步”。
  4. 使用界面顶部的下拉菜单,从x86切换到x64。
  5. 回到Windows中,导航到VS创建的和你新建项目名称相同的文件夹,其中应该还有另一个同名的子文件夹。
  6. 将应用程序文件复制到子文件夹中,包括应用程序用到的任何.cpp源文件、.h头文件、.glsl着色器文件、纹理图像文件和.obj 3D模型文件。不需要再指定任何在模板里已经内置的头文件。
  7. 将glew32.dll也放入同一个子文件夹。
  8. 在解决方案资源管理器中,右键单击“源文件”,选择“添加►现有项”加载main.cpp。重复这个过程添加其他的.cpp文件。
  9. 在解决方案资源管理器中,右键单击“头文件”,选择“添加►现有项”加载应用程序的头文件(.h文件)。
  10. 现在就准备好构建和执行应用程序了。

部署

在开发、测试和调试应用程序之后,程序可以作为一个独立的可执行文件进行部署。部署时需要在Release模式下构建项目,然后将以下文件放在同一个文件夹中:

  1. 项目生成的.exe文件;
  2. 应用程序使用的所有着色器文件;
  3. 应用程序使用的所有纹理图像和模型文件;
  4. glew32.dll。