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\)标明这个矩形铺满了整个屏幕(视口)范围.

总结

  1. Main程序主要利用GLFW+OpenGL+GLAD搭建了运行框架,提供初始化+主循环+回调的运行入口;

  2. 界面显示、人机交互交由ViewManager负责;

  3. 与窗口的交互,由一组GLFW回调接口负责;

  4. 着色器程序交由ProgramGLSL负责;

posted @ 2025-05-20 00:36  明明1109  阅读(74)  评论(0)    收藏  举报