SoftGLRender源码:main程序
简介
文件:Viewer/Main.cpp
SoftGLRender是一个基于OpenGL + GLFW + GLAD的现代图形渲染程序,主要用于显示加载了2D纹理、天空盒的3D模型,支持鼠标交互(旋转、平移、缩放)和UI面板控制.
从main程序来看,主要功能:
1)初始化GLFW、OpenGL上下文;
2)编译顶点/片元着色器;
3)创建一个纹理缓冲作为渲染目标;
4)用ViewManager类渲染图形;
5)将结果绘制到屏幕;
6)支持鼠标交互(旋转、平移、缩放),键盘控制,UI面板控制.
全局数据
Main文件包含的数据:
- 视图管理器ViewManager类,用于管理窗口显示的内容,包括菜单面板、画面
- 屏幕尺寸:SCR_WIDTH(屏幕宽度)、SCR_HEIGHT(屏幕高度)
- 鼠标数据:上一次位置
- 2个着色器
// 视图管理器
std::shared_ptr<SoftGL::View::ViewerManager> viewer = nullptr;
const int SCR_WIDTH = 1000; // Screen width (pixel)
const int SCR_HEIGHT = 800; // Screen height (pixel)
// last mouse position
double lastX = SCR_WIDTH / 2.f;
double lastY = SCR_HEIGHT / 2.f;
bool firstMouse = true; // 避免鼠标跳变
2个OpenGL着色器字符串:VS存放顶点着色器字符串,FS存放片元着色器字符串.
const char* VS = R"(
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(aPos, 1.0);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}
)";
const char* FS = R"(
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D uTexture;
void main()
{
FragColor = texture(uTexture, TexCoord);
}
)";
顶点着色器(Vertex Shader)
layout (location = 0) in vec3 aPos; // 输入:顶点位置(属性位置 0), from VBO
layout (location = 1) in vec2 aTexCoord; // 输入:纹理坐标(属性位置 1), from VBO
out vec2 TexCoord; // 输出:传递给片元着色器的纹理坐标
void main()
{
gl_Position = vec4(aPos, 1.0); // 设置顶点位置(齐次坐标)
TexCoord = vec2(aTexCoord.x, aTexCoord.y); // 直接传递纹理坐标
}
-
R"(...)"
避免转义. -
layout(location = 0)
显式指定顶点属性位置(Attribute Location)的语法,用于将顶点输入变量和VBO中顶点数据关联起来.
e.g.
layout(location = 0) in vec3 aPos; // 位置属性绑定到 location 0
in
表明这是个输入变量(从VBO接收数据)
OpenGL代码中,location
需要与顶点属性指针配置一致:
// 配置 location = 0 的属性(aPos)
glVertexAttribPointer(
0, // 对应 layout(location = 0)
3, // 分量数(vec3)
GL_FLOAT, // 数据类型
GL_FALSE, // 是否归一化
5 * sizeof(float), // 步长(Stride)
(void*)0 // 偏移量(Offset)
);
glEnableVertexAttribArray(0); // 启用 location 0
为什么步长(Stride)是5?
因为代码中,每个顶点由 5个float值(3个位置 + 2个纹理坐标).
gl_Position
vec3 -> vec4,将3D顶点坐标aPos
扩展为4D齐次坐标;
gl_Position
是OpenGL内置变量,表示顶点在裁剪空间(Clip Space)的位置.
gl_Position = vec4(aPos, 1.0);
aTexCoord
:顶点自带的原始纹理坐标. 直接将aTexCoord
坐标传递给TexCoord
.
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
片元着色器(Fragment Shader)
根据纹理坐标从纹理中采样颜色,并输出像素颜色.
in vec2 TexCoord; // 输入:插值后的纹理坐标, from 顶点着色器
out vec4 FragColor; // 输出:当前像素的最终颜色(RGBA)
uniform sampler2D uTexture; // 声明一个2D纹理采样器
void main()
{
FragColor = texture(uTexture, TexCoord); // 采样纹理
}
TexCoord
:当前片元的 纹理坐标,来源于顶点着色器的插值输出,纹理坐标(u,v)
,范围:[0.0, 1.0]
.FragColor
:当前像素的最终输出颜色(RGBA),传给后续OpenGL管线.sampler2D
:表示绑定的2D纹理对象(如PNG/JPG图片).uniform
:CPU向GPU传递数据的限定符,是一种全局只读变量.
uniform sampler2D uTexture;
从主程序传入的 2D 纹理采样器,可通过它访问GPU中绑定的纹理数据.
后面,可以通过texture()
从uTexture绑定的2D纹理采样,得到当前像素最终颜色.
GLFW、OpenGL
要看GLFW、OpenGL初始化,可先看GLFW、OpenGL标准使用流程.
GLFW使用流程
1. 初始化 GLFW
if (!glfwInit()) {
// 初始化失败
return -1;
}
2. 配置 OpenGL 版本(可选)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL 版本号主版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // 次版本
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式
// 对于macOS
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 兼容苹果平台
#endif
3. 创建窗口对象
GLFWwindow* window = glfwCreateWindow(800, 600, "My Window", NULL, NULL);
if (!window) {
glfwTerminate();
return -1;
}
4. 创建 OpenGL 上下文
glfwMakeContextCurrent(window);
注意:必须在调用任何 OpenGL 函数之前执行.
5. 加载OpenGL函数(需要GLAD/GLEW)
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
return -1;
}
GLAD 与 GLFW 结合使用. 让GLAD加载GLFW的glfwGetProcAddress
函数指针,用来在当前OpenGL上下文中 查找某个OpenGL函数的地址.
6. 设置回调函数(可选)
如果窗口大小变化时调整视口:
glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
7. 主循环:
- 处理输入
- 渲染
- 交换缓冲区
- 处理事件
while (!glfwWindowShouldClose(window)) {
// 处理输入
processInput(window);
// 渲染
glColor(0.f, 0.f, 0.f, 0.f); // 设置当前颜色
glClear(GL_COLOR_BUFFER_BIT); // 用当前颜色清空颜色缓冲区
// 渲染内容...
render();
// 交换前后缓冲区
glfwSwapBuffers(window);
// 处理事件
glfwPollEvents();
}
8. 释放资源并终止
glfwDestroyWindow(window); // 销毁创建的 GLFWwindow 窗口
glfwTerminate(); // 终止GLFW库,释放内部占用的所有资源
OpenGL使用流程
OpenGL的初始化,通常在用GLAD gladLoadGLLoader
加载OpenGL函数后:
1. 初始化窗口系统(GLFW)
同GLFW
2. 加载OpenGL函数(GLAD)
同GLFW
3. 配置OpenGL状态
glEnable(GL_DEPTH_TEST); // 启用深度测试
glViewport(0, 0, width, height); // 设置视口
4. 编写并编译着色器程序(Shader)
// 顶点着色器和片段着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 片元着色器类似
// 链接为 shader program
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
5. 准备顶点数据(VBO, VAO, EBO)
// 创建并绑定VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// 创建并绑定VBO
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置顶点属性指针,告诉VAO怎么解释VBO中数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, offset); // 位置:layout(location = 0)
glEnableVertexAttribArray(0); // 启用位置为0的顶点属性数组
// 创建并绑定EBO(可选)
GLuint ebo;
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 解绑VAO和VBO(可在配置完成后解绑)
glBindVertexArray(0); // 解绑VAO
glBindBuffer(GL_ARRAY_BUFFER, 0); // 解绑VBO
// 注:GL_ELEMENT_ARRAY_BUFFER 不需要解绑,它与VAO绑定,除非不再使用VAO
-
VBO(Vertex Buffer Object):顶点缓冲对象,存储顶点原始数据(如位置、法线、纹理坐标等). GPU缓冲区,用于批量传输和存储顶点信息.
-
VAO(Vertex Array Object):顶点数组对象,存储顶点属性的配置,如何解释VBO中的数据,如格式、步长等. 记录顶点输入状态的“快照”,使得绘制时快速切换渲染状态.
-
EBO(Element Buffer Object):索引缓冲对象,也叫IBO(Index Buffer Object),OpenGL中一种缓冲对象,用来存储“顶点索引”数据. 如果用VBO存储每个三角形顶点,会有很多重复数据,而用EBO引用已有顶点,能有效避免重复.
6. 加载纹理(可选)
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 设置纹理参数 + 加载图像 + glTexImage2D
7. 渲染循环
while (!glfwWindowShouldClose(window)) {
processInput(window); // 处理键盘/鼠标输入
glClearColor(0, 0, 0, 1); // 设置清屏颜色
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除颜色和深度缓存
glUseProgram(shaderProgram); // 使用着色器程序
glBindVertexArray(VAO); // 绑定 VAO
glDrawArrays(...); 或 glDrawElements(...); // 绘制图形
glfwSwapBuffers(window); // 交换前后帧缓冲
glfwPollEvents(); // 处理窗口事件
}
8. 释放资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
glfwDestroyWindow(window);
glfwTerminate();
SoftGLRender中使用GLFW、OpenGL
SoftGLRender中用GLFW、OpenGL构建主程序框架.
初始化GLFW:
// View/Main.cpp
int main() {
/* Initial the library */
// 设置glfw错误回调
glfwSetErrorCallback(glfwErrorCallback);
// 1. 初始化GLFW
if (!glfwInit()) {
LOGE("Failed to initialize GLFW");
return -1;
}
// 2. 配置OpenGL版本
// OpenGL 3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 使用OpenGL核心模式(而非兼容模式),仅支持现代OpenGL特性, 移除已废弃的固定管线函数(e.g. glBegin/glEnd)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 禁止窗口缩放
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 兼容苹果平台
#endif
// 3. 创建窗口对象
// create a windowed mode window and its OpenGL context
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "SoftGLRender", nullptr, nullptr);
if (!window) {
LOGE("Failed to create GLFW window");
// 清理所有 GLFW 资源并终止库的运行
glfwTerminate();
return -1;
}
// 4. 创建OpenGL上下文
// Make the window's context current
glfwMakeContextCurrent(window);
// 5. 设置回调(可选)
glfwSetFramebufferSizeCallback(window, framebufferSizeCallback); // 窗口尺寸变化时回调,需调整视视口
glfwSetCursorPosCallback(window, mouseCallback); // 鼠标移动时回调
glfwSetScrollCallback(window, scrollCallback); // 鼠标滚轮滚动回调
// tell GLFW to capture our mouse
// glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// 6. 加载OpenGL函数
// Load all OpenGL function pointers
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
LOGE("Failed to initialize GLAD");
glfwTerminate();
return -1;
}
...
除了几个回调,其他与标准流程基本一致.
编译顶点、片元着色器:
没有直接使用OpenGL函数如glCreateProgram
,而是将其封装到SoftGL::ProgramGLSL
类中,通过loadSource
一次性加载VS
,FS
对应的顶点着色器、片元着色器.
// View/Main.cpp
// 7. 编写并编译着色器程序(Shader)
SoftGL::ProgramGLSL program;
if (!program.loadSource(VS, FS)) {
LOGE("Failed to initialize Shader");
glfwTerminate();
return -1;
}
准备顶点数据(VAO,VBO,EBO):
// 8. 准备顶点数据(VBO, VAO, EBO)
// set up vertex data (and buffer(s)) and configure vertex attributes
float vertices[] = {
// positive(3 float) | texture coords(2 float)
1.f, 1.f, 0.f, 1.f, 1.f, // top right
1.f, -1.f, 0.f, 1.f, 0.f, // bottom right
-1.f, -1.f, 0.f, 0.f, 0.f, // bottom left
-1.f, 1.f, 0.f, 0.f, 1.f, // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
GLuint VAO, VBO, EBO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr); // 启用location 0
glEnableVertexAttribArray(0); // 启用location 0的顶点属性数组
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float))); // 启用location 1
glEnableVertexAttribArray(1); // 启用location 1的顶点属性数组
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindVertexArray(GL_NONE); // 解绑VAO
加载创建纹理:
// 9. 创建纹理
// load and create texture
// -------------------------
unsigned int texture;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0); // 激活纹理单元0, 后续的 glBindTexture 将绑定到这个纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
// 设置2D纹理参数,主要控制纹理映射中的 坐标环绕(Wrapping)行为
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // S方向平铺
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // T方向平铺
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 分配显存空间给 2D 纹理,但不填充实际像素数据(因为最后一个参数nullptr)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
program.use(); // 激活shader:glUseProgram
// 告诉着色器:texture0使用纹理单元 0
glUniform1i(glGetUniformLocation(program.getId(), "uTexture"), 0);
glGetUniformLocation(program.getId(), "uTexture")
:获取指定ID(program.getId()
)的着色器(VS/FS)中,名为"uTexture"的uniform
变量的位置.
创建视图管理器ViewerManager
:
//
// init Viewer
viewer = std::make_shared<SoftGL::View::ViewerManager>();
bool initSuccess = viewer->create(window, SCR_WIDTH, SCR_HEIGHT, (int) texture);
if (!initSuccess) {
LOGE("Failed to create Viewer");
}
else {
...
}
用ViewerManager
管理视图界面,包括管理菜单、绘制当前帧、管理鼠标、键盘资源等.
渲染循环:
if (!initSuccess) {
...
}
else {
// real frame buffer size
int frameWidth, frameHeight;
glfwGetFramebufferSize(window, &frameWidth, &frameHeight);
// loop until the user closes the window
while (!glfwWindowShouldClose(window)) {
// check exit app
processInput(window);
// draw frame
int outTex = viewer->drawFrame();
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDepthMask(true);
glDisable(GL_CULL_FACE);
// 设置 多边形的渲染模式,控制填充方式
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// 解绑当前帧缓冲区对象(FBO),切换回默认的窗口系统提供的帧缓冲区(直接渲染到屏幕)
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 设置 视口(Viewport)
glViewport(0, 0, frameWidth, frameHeight);
glClearColor(0.f, 0.f, 0.f, 0.f);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, outTex);
program.use(); // 使用着色器程序
glBindVertexArray(VAO);
// 通过索引绘制几何体:告诉GPU用当前绑定的VBO、EBO渲染三角形
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
viewer->drawPanel(); // 绘制面板
glfwSwapBuffers(window); // 交换前后缓冲区
glfwPollEvents(); // 处理事件
}
}
上面代码中,用到的OpenGL中几个功能开关:
-
GL_BLEND
,混合(Blending)功能开关,用于控制像素颜色在渲染时的混合方式. 启用时,新绘制的像素颜色(源颜色)会与帧缓冲区中已有的像素颜色(目标颜色)按照设定的混合方程进行混合,从而实现透明混合效果. -
GL_DEPTH_TEST
,深度测试(Depth Testing)功能开关. 作用:确保在渲染3D场景时,物体能按照正确的远近关系显示,即近处的物体会遮挡远处的物体. -
GL_CULL_FACE
,面剔除(Face Culling)功能开关. 作用:跳过渲染某些不可见的几何面(通常是背对摄像机的面),提升渲染性能.
回收资源:
// 销毁ViewManager
viewer->destroy();
viewer = nullptr;
// 销毁着色器
program.destroy();
// 释放OpenGL的VAO,VBO,EBO, 纹理资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteBuffers(1, &texture);
// 回收GLFW资源
glfwDestroyWindow(window);
glfwTerminate();
输入事件
processInput
: 在每一帧起始调用,不是回调,用于处理用户输入的事件(键盘).
// process all input: query GLFW whether relevant keys are pressed/released this frame
// and react accordingly
void processInput(GLFWwindow* window) {
if (!viewer || viewer->wantCaptureKeyboard()) {
return;
}
// 按ESC键关闭glfw窗口
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
return;
}
// 按H键 切换面板显示状态
static bool keyPressed_H = false;
int state = glfwGetKey(window, GLFW_KEY_H);
if (state == GLFW_PRESS) {
if (!keyPressed_H) {
keyPressed_H = true;
viewer->togglePanelState();
}
else if (state == GLFW_RELEASE) {
keyPressed_H = false;
}
}
}
窗口事件
main程序中,向GLFW库注册了几个回调,用于处理窗口事件.
// 处理帧缓冲区尺寸改变事件
void framebufferSizeCallback(GLFWwindow* window, int width, int height);
// 处理鼠标事件
void mouseCallback(GLFWwindow *window, double xPos, double yPos);
// 处理鼠标滚轮事件
void scrollCallback(GLFWwindow* window, double xOffset, double yOffset);
// 处理窗口状态事件(ESC退出、切换UI面板),每一帧被调用
void processInput(GLFWwindow* window);
framebufferSizeCallback
:当窗口的帧缓冲区(即实际渲染区域)大小发生变化时(如用户调整窗口、全屏切换或拖动到不同DPI的显示器),回调该函数,用于适应新尺寸.
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// --------------------------------------------------------------------------------------------
void framebufferSizeCallback(GLFWwindow* window, int width, int height) {
// make sure the viewport matches the new window dimensions; note that width and height
// will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height); // 为3D渲染,设置视口
if (!viewer) {
return;
}
viewer->updateSize(width, height); // 更新ViewManager的窗口尺寸
}
mouseCallback
: 移动鼠标时,回调该函数,用于处理鼠标滑动事件.
// glfw: whenever the mouse moves, this callback is called
void mouseCallback(GLFWwindow* window, double xPos, double yPos) {
if (!viewer || viewer->wantCaptureMouse()) { // 如果viewer尚未初始化,或者正在处理鼠标事件,则放弃
return;
}
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { // 按下鼠标左键
if (firstMouse) {
lastX = xPos;
lastY = yPos;
firstMouse = false;
}
// 相较于上次的移动距离
double xOffset = xPos - lastX;
double yOffset = yPos - lastY;
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) { // 按下左侧shift
viewer->updateGesturePan((float)xOffset, (float)yOffset); // 平移相机
}
else {
viewer->updateGestureRotate((float)xOffset, (float)yOffset); // 旋转相机
}
lastX = xPos;
lastY = yPos;
}
else {
firstMouse = true;
}
}
scrollCallback
: 鼠标滚轮滚动时,回调该函数,缩放视距.
其原理是通过调整相机(eye)位置,确保相机与注视点距离符合放缩特性,从而形成视觉上的缩放效果.
// glfw: whenever the mouse scroll wheel scrolls, this callback is called
void scrollCallback(GLFWwindow* window, double xOffset, double yOffset) {
if (!viewer || viewer->wantCaptureMouse()) {
return;
}
viewer->updateGestureZoom((float)xOffset, (float)yOffset);
}
绘制图形内容
1)步骤 准备顶点数据(VAO,VBO,EBO)中,提供了一个顶点数组和一个索引数组,是什么图形?
// set up vertex data (and buffer(s)) and configure vertex attributes
float vertices[] = {
// positive | texture coords
1.f, 1.f, 0.f, 1.f, 1.f, // top right
1.f, -1.f, 0.f, 1.f, 0.f, // bottom right
-1.f, -1.f, 0.f, 0.f, 0.f, // bottom left
-1.f, 1.f, 0.f, 0.f, 1.f, // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
根据索引数组(indices
),
- 第一个三角形顶点:右上,右下,左上
- 第二个三角形顶点:右下,左下,左上
图形如下,可知是一个由2个三角形组成的矩形:
(-1,1) ┌───────┐ (1,1)
│ \ │
│ \ │
(-1,-1)└───────┘ (1,-1)
注:y坐标翻转前.
归一化坐标\([-1,1]^2\)标明这个矩形铺满了整个屏幕(视口)范围.
总结
-
Main程序主要利用GLFW+OpenGL+GLAD搭建了运行框架,提供初始化+主循环+回调的运行入口;
-
界面显示、人机交互交由ViewManager负责;
-
与窗口的交互,由一组GLFW回调接口负责;
-
着色器程序交由ProgramGLSL负责;