从这篇起,开始进入渲染模块(Renderer),Renderer是游戏引擎中最重要的一块,需要具备良好的数学、图形学、OpenGL基础
Context抽象
要设计更通用的游戏引擎,需要将图形API抽象出来,设计成上下文-Context。不同的图形驱动对应不同的Context子类。
如下图中,windows平台支持directx和OpenGl,这里以OpenGLContext为例,讲解Context抽象是如何实现的。

基类GraphicsContext
创建基类,将驱动Init和SwapBuffers两个方法抽象到基类GraphicsContext中。 Sandbox/Hazel/src/Hazel/Renderer/GraphicsContext.h
c++
#pragma once
namespace Hazel {
class GraphicsContext {
public:
virtual void Init() = 0;
virtual void SwapBuffers() = 0;
};
}
OpenGLContext实现类
继承自GraphicsContext。 Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLContext.h
c++
#pragma once
#include "Renderer/GraphicsContext.h"
struct GLFWwindow;
namespace Hazel {
class OpenGLContext : public GraphicsContext {
public:
OpenGLContext(GLFWwindow* windowHandle);
virtual void Init() override;
virtual void SwapBuffers() override;
private:
GLFWwindow* m_WindowHandle;
};
}
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLContext.cpp
c++
#include "OpenGLContext.h"
#include "Core/Base.h"
#include <glad/glad.h>
#include <GLFW/glfw3.h>
namespace Hazel {
OpenGLContext::OpenGLContext(GLFWwindow *windowHandle):m_WindowHandle(windowHandle) {
HZ_CORE_ASSERT(windowHandle, "Window handle is null");
}
void OpenGLContext::Init() {
glfwMakeContextCurrent(m_WindowHandle);
int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
HZ_CORE_ASSERT(status, "Failed to initialize Glad!");
}
void OpenGLContext::SwapBuffers() {
glfwSwapBuffers(m_WindowHandle);
}
}
修改WindowsWindow
WindowsWindow中增加GraphicsContext,替换掉OpenGL的操作,这里只替换了gladLoadLoader、glfwSwapBuffers操作,后续会逐步将所有图形API抽象到基类中。
Sandbox/Hazel/src/Hazel/Platform/Windows/WindowsWindow.h

Sandbox/Hazel/src/Hazel/Platform/Windows/WindowsWindow.cpp

更新CMakeList.txt
css
set(SRC_LIST
...
src/Hazel/Renderer/GraphicsContext.h
src/Hazel/Platform/OpenGL/OpenGLContext.cpp
src/Hazel/Platform/OpenGL/OpenGLContext.h
)
OK,到此,Context抽象的逻辑暂告一段落。下面进入到绘制的阶段,是时展示时真正的技术了。
绘制第一个三角形
OpenGL API提供了一组查询GPU硬件、图形驱动等信息的方法。glGetString(...)
实例如下: Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLContext.cpp
c++
void OpenGLContext::Init() {
glfwMakeContextCurrent(m_WindowHandle);
int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
HZ_CORE_ASSERT(status, "Failed to initialize Glad!");
HZ_CORE_INFO("OpenGL Info:");
HZ_CORE_INFO(" Vendor: {0}", (char*)glGetString(GL_VENDOR));
HZ_CORE_INFO(" Renderer: {0}", (char*)glGetString(GL_RENDERER));
HZ_CORE_INFO(" Version: {0}", (char*)glGetString(GL_VERSION));
}
命令行能看到日志:
csharp
[00:38:19] HAZEL : OpenGL Info:
[00:38:19] HAZEL : Vendor: ATI Technologies Inc.
[00:38:19] HAZEL : Renderer: AMD Radeon Pro 5300M OpenGL Engine
[00:38:19] HAZEL : Version: 4.1 ATI-4.14.1
绘制三角形
绘制一个三角行虽然很基础,但也囊括了OpenGL操作的大部分流程。这里不讲OpenGL和图形学的基础知识,建议零基础的朋友参考LearnOpenGL教程学习。
三角形处于窗口的正中间,顶点的坐标为:
- 声明顶点数组、顶点缓冲、索引缓冲. Sandbox/Hazel/src/Hazel/Application.h
c++
private:
...
unsigned int m_VertexArray, m_VertexBuffer, m_IndexBuffer;
unsigned int shaderProgram;
- 定义顶点数组、顶点缓冲、索引缓冲.
一个顶点数组可以关连多个缓冲,包括顶点缓冲、索引缓冲。
注意!顶点数组本身并没有数据,通过绑定的缓冲间接的关连数据缓冲,使用顶点数组方便在多组缓冲之间切换状态,更方便了,不用挨个缓冲切换。当然了,不使用顶点数组也可以使用缓冲。

Sandbox/Hazel/src/Hazel/Application.cpp
c++
Application::Application() {
...
// 生成顶点数组
glGenVertexArrays(1, &m_VertexArray);
// 绑定顶点数组
glBindVertexArray(m_VertexArray);
// 生成顶点缓冲
glGenBuffers(1, &m_VertexBuffer);
// 绑定数组缓冲
glBindBuffer(GL_ARRAY_BUFFER, m_VertexBuffer);
// 定义三角形的三个点
float vertices[3 * 3] = {
-0.5f, -0.5, 0.0,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// 填充数组数据。三角形数据绑定到GPU上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// enable第一个顶点数组,对应shader中的顶点数据
glEnableVertexAttribArray(0);
// 定义顶点属性为:从零开始,三组数据,浮点型,不进行归一化,每组数据为3*float(即步幅)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), nullptr);
// 生成索引缓冲,本质都是缓冲和顶点缓冲没区别,存不同的数据而已
glGenBuffers(1, &m_IndexBuffer);
// 绑定到索引缓冲
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer);
// 定义索引
unsigned int indices[3] = {0, 1, 2};
//将indices填充索引缓冲数据(发送到GPU)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
- 调GL的draw方法进行绘制 Sandbox/Hazel/src/Hazel/Application.cpp
c++
while(m_Running) {
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(m_VertexArray);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
for (Layer* layer : m_LayerStack) {
layer->OnUpdate();
}
OpenGL绘制三角形有两类方法:glDrawArrays、glDrawArrays
c++glUseProgram(shaderProgram); glBindVertexArray(VAO); // drawArrays glDrawArrays(GL_TRIANGLES, 0, 3); // drawElements glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
到此Windows上应该就能出现三角形了,但是Mac上不行!
Mac上的OpenGL,还需要定义shader(可能Windows上的OpenGL有一个默认的shader)。
增加以下代码为了在Mac上显示三角形,其实完整的流程也应该有shader的。
Sandbox/Hazel/src/Hazel/Application.h
c++
private:
// 声明shader id
unsigned int shaderProgram;
};
增加一个简单的shader实现,如果你有OpenGL基础,很容易看懂这段代码. Sandbox/Hazel/src/Hazel/Application.cpp
c++
Application::Application() {
...
// build and compile our shader program
// ------------------------------------
// vertex shader
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// fragment shader
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// link shaders
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
绘制三角形之前,设置shader
c++
while(m_Running) {
...
glUseProgram(shaderProgram);
glBindVertexArray(m_VertexArray);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
}
实现效果&代码
如果顺利的话,能看到一个橙色的三角形

本节两段实现,完整代码参考: