上一篇:Xcode与C++之游戏开发:带有简单AI的塔防游戏
SDL 渲染器支持 2D 图形,但是不支持 3D 图形。为了同时支持 2D 和 3D,这里使用了著名的 OpenGL。
OpenGL(Open Graphics Library)是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像(二维的亦可),是一个功能强大,调用方便的底层图形库。底层图形库提供的接口用于渲染二维或者三维图形,可以说这些接口架起了上层应用程序和底层 GPU 之间的桥梁。应用程序使用这些接口发送渲染命令,而这些接口会向显卡驱动发送渲染命令。
在前面的章节里,使用的是 SDL_Renderer
,现在要换成 OpenGL,就要把它移除掉了。在 Game.cpp
的实现中把原本的标志位 0
换成 OpenGL:
// 创建 SDL 窗体
mWindow = SDL_CreateWindow(
"OpenGL", // 标题
100, // 窗体左上角的 x 坐标
100, // 窗体左上角的 y 坐标
1024, // 窗体宽度
768, // 窗体高度
SDL_WINDOW_OPENGL // OpenGL
);
在创建 OpenGL 窗口之前,可以设置 OpenGL 相关的一些属性,比如色彩深度,版本等。要配置这些参数可以使用 SDL_GL_SetAttribute
:
// 设置 OpenGL 参数
// core OpenGL
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
// 指定版本 3.3
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
// RGBA
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
// 双缓冲
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// 强制使用硬件加速
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
将之后的 SDL_Renderer
创建部分删除。接下来就是创建一个 OpenGL 上下文(context),可以把 context 理解成 OpenGL 世界。
添加一个成员变量到 Game
中:
SDL_GLContext mContext;
并在 Initialize
中进行初始化:
mContext = SDL_GL_CreateContext(mWindow);
有了 Create 自然需要有销毁,在 Shutdown
函数里添加析构,通用声明顺序和析取顺序相反,应该在 SDL_DeleteWindow
之前添加:
void Game::Shutdown()
{
UnloadData();
IMG_Quit();
SDL_GL_DeleteContext(mContext);
SDL_DestroyWindow(mWindow);
SDL_Quit();
}
现在已经创建好了 OpenGL 的上下文,但还有一个问题。OpenGL 为了保持后向兼容,需要手动使用扩展才能使用 OpenGL 3.3。为了避免这个繁琐的过程,不妨使用 GLEW 库(OpenGL Extension Wrangler Library)。这样的话只要一个函数调用就可以获得支持的所有扩展功能,包括3.3 版本之前的功能。
GLEW 用起来还是挺方便的,就是去 GLEW官网 下载源文件,之后解压,一个
make
就自动编译了。使用 GLEW 的方式和之前添加 SDL 库一样了,添加库依赖,在Build Settings
中加上头文件的路径。
要初始化 GLEW,可以添加下面的代码到创建 context 之后:
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
SDL_Log("初始化 GLEW 失败!");
return false;
}
渲染的原理和之前的 SDL 还是一样的,清除、绘制、交换缓冲区,使用 OpenGL 本质上就是换了个渲染器:
void Game::GenerateOutput()
{
// 灰色
glClearColor(0.86f, 0.86f, 0.86f, 1.0f);
// 清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
// TODO: 绘制场景
// 交换缓冲区
SDL_GL_SwapWindow(mWindow);
}
正常来讲,应该就可以看到一个灰色背景的 OpenGL 环境了,这也意味着我们把渲染器换成了 OpenGL 了。
2D 图形和 3D 图形其实没什么区别,因为本质上 3D 图形也是 2D图形,只不过它是被压平到了 2D 屏幕上。早期的一些 2D 游戏,比如说任天堂,会简单的把画面图像复制到颜色缓冲区,这个过程被称为 blitting(印迹)。然而,现在的硬件并不擅长干这种事情,但是在多边形渲染上却非常高效。因此,可以说现在所有的游戏,无论是 2D 还是 3D,最终都是用多边形来满足图形需求。
最简单的多边形是三角形,这是现在游戏最终的底层基础(四边形可以分解成两个三角形,五边形可以分解为3个三角形…)。多边形不仅计算简单,可以具有很好的伸缩性,如果设备性能不行,那么三角形绘制得少一些,这个技术也就是所谓的 HLOD(分层 LOD)。
NDC(Normalized device coordinates )是 OpenGL 默认的坐标系统。窗口的中心是坐标原点,左下角是 (-1,-1),右上角是 (1,1)。这个规范化的坐标体系与屏幕的实际高度和宽度无关。在渲染过程中,GPU(准确来说是图形硬件)会把 NDC 的坐标转为实际的像素位置。
在 3D 中,z 坐标分量范围也是从 -1 到 +1,正轴朝向屏幕内部。对于 2D,显然 z 分量为 0。
如果有一个由三角形组成的 3D 模型,需要通过某种方式在内存中存储这些三角形的顶点。最简单粗暴就是直接存顶点,比如这样:
float vertices[] = {
-0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f