OpenGL入门007——摄像机

本节将用opengl实现一个Camera类,学习如何在顶点/片段着色器中应用模型/视图/投影矩阵,以及理解坐标变换和渲染流程。

一些概念

坐标转换

关于坐标转换,模型矩阵,视图矩阵,投影矩阵,可以看这篇的概念说明

欧拉角

偏航角:

  • 概述:Yaw是绕世界坐标系的Y轴旋转的角度
  • 作用:控制摄像机左右旋转
  • 实现:在摄像机类中,Yaw用于计算摄像机的前向量,从而影响摄像机的视图方向

俯仰角:

  • 概述:Pitch是绕摄像机自身的X轴旋转的角度
  • 作用:控制摄像机上下旋转
  • 实现:在摄像机类中,Pitch同样用于计算摄像机的前向量,从而影响摄像机的视图方向

滚转角:

  • 概述:Roll是绕摄像机自身的Z轴的角度
  • 作用:控制摄像机的左右倾斜
  • 实现:在摄像机类中,滚转角用于计算摄像机的右向量,从而影响摄像机的上向量和右向量

实战

简介

怎么在vscode上使用cmake构建项目,具体可以看这篇Windows上如何使用CMake构建项目 - 凌云行者的博客

目的: 实现Camera类

  • 编译工具链:使用msys2安装的mingw-gcc
  • 依赖项:glfw3:x64-mingw-static,glad:x64-mingw-static(通过vcpkg安装)

dependencies

shader.fs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 指定OpenGL着色器语言的版本为3.30
#version 330 core
// 输入变量,表示纹理坐标
in vec2 TexCoord;
// 输出变量,表示片段的最终颜色
out vec4 FragColor;
// uniform变量,表示第一个纹理
uniform sampler2D texture0;
// uniform变量,表示第二个纹理
uniform sampler2D texture1;

void main() {
// 在两个纹理之间进行线性插值
FragColor = mix(texture(texture0, TexCoord), texture(texture1, TexCoord), 0.2);
}

shader.vs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTex;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0f);
TexCoord = aTex;
}

teenager.png

tex.png

utils

camera.h

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#ifndef CAMERA_H
#define CAMERA_H

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <vector>

// 定义摄像机移动的几种可能选项
enum Camera_Movement {
FORWARD, // 向前
BACKWARD, // 向后
LEFT, // 向左
RIGHT, // 向右
UP, // 向上
DOWN, // 向下
ROLL_LEFT, // 左旋转
ROLL_RIGHT, // 右旋转
PITCH_UP, // 向上俯仰
PITCH_DOWN, // 向下俯仰
YAW_LEFT, // 向左偏航
YAW_RIGHT, // 向右偏航
};

// 摄像机默认参数
// 偏航角
const float YAW = -90.0f;
// 俯仰角
const float PITCH = 0.0f;
// 滚转角
const float ROLL = 0.0f;
// 移动速度
const float SPEED = 12.5f;
// 鼠标灵敏度
const float SENSITIVITY = 0.1f;
// 缩放
const float ZOOM = 45.0f;

// 摄像机类
class Camera {
public:
// 位置
glm::vec3 Position;
// 前向量
glm::vec3 Front;
// 上向量
glm::vec3 Up;
// 右向量
glm::vec3 Right;
// 世界上向量
glm::vec3 WorldUp;

// 偏航角,控制摄像机左右旋转
float Yaw;
// 俯仰角,控制摄像机上下旋转
float Pitch;
// 滚转角,控制摄像机的左右倾斜
float Roll;

// 移动速度
float MovementSpeed;
// 鼠标灵敏度
float MouseSensitivity;
// 缩放
float Zoom;

// 使用向量的构造函数
Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH, float roll = ROLL) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) {
Position = position;
WorldUp = up;
Yaw = yaw;
Pitch = pitch;
Roll = roll;
updateCameraVectors();
}
// 使用标量的构造函数
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch, float roll = ROLL) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) {
Position = glm::vec3(posX, posY, posZ);
WorldUp = glm::vec3(upX, upY, upZ);
Yaw = yaw;
Pitch = pitch;
Roll = roll;
updateCameraVectors();
}

// 返回使用前向量和上向量以计算的视图矩阵
glm::mat4 GetViewMatrix() {
return glm::lookAt(this->Position, this->Position + this->Front, this->Up);
}

// 处理键盘输入
void ProcessKeyboard(Camera_Movement direction, float deltaTime) {
float velocity = MovementSpeed * deltaTime;
if (direction == FORWARD)
this->Position += this->Front * velocity;
if (direction == BACKWARD)
this->Position -= this->Front * velocity;
if (direction == LEFT)
this->Position -= this->Right * velocity;
if (direction == RIGHT)
this->Position += this->Right * velocity;
if (direction == UP)
this->Position += this->Up * velocity;
if (direction == DOWN)
this->Position -= this->Up * velocity;
if (direction == ROLL_LEFT)
this->Roll -= velocity;
if (direction == ROLL_RIGHT)
this->Roll += velocity;
if (direction == PITCH_UP)
this->Pitch += velocity;
if (direction == PITCH_DOWN)
this->Pitch -= velocity;
if (direction == YAW_LEFT)
this->Yaw -= velocity;
if (direction == YAW_RIGHT)
this->Yaw += velocity;

updateAngles();
updateCameraVectors();
}

// 处理鼠标的移动
void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) {
xoffset *= MouseSensitivity;
yoffset *= MouseSensitivity;

this->Yaw += xoffset;
this->Pitch += yoffset;

// 确保当前俯仰角超出范围时,屏幕不会翻转
if (constrainPitch) {
if (this->Pitch > 89.0f)
this->Pitch = 89.0f;
if (this->Pitch < -89.0f)
this->Pitch = -89.0f;
}

// 使用更新的欧拉角更新前向量、右向量和上向量
updateCameraVectors();
}

// 处理鼠标滚轮事件,只处理在垂直滚轮轴上的输入
void ProcessMouseScroll(float yoffset) {
this->Zoom -= (float)yoffset;
if (this->Zoom < 1.0f)
this->Zoom = 1.0f;
if (this->Zoom > 45.0f)
this->Zoom = 45.0f;
}

private:
// 更新角度,确保俯仰角在合理范围内
void updateAngles() {
if (this->Pitch > 89.9f)
this->Pitch = 89.9f;
if (this->Pitch < -89.9f)
this->Pitch = -89.9f;
}
// 更新前向量,上向量和右向量
void updateCameraVectors() {
// 计算新的前向量
glm::vec3 front;
front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
front.y = sin(glm::radians(Pitch));
front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
this->Front = glm::normalize(front);

// 计算新的右向量
this->Right = glm::normalize(glm::cross(Front, WorldUp));
glm::mat4 rollMatrix = glm::rotate(glm::mat4(1.0f), glm::radians(this->Roll), this->Front);
this-> Right = glm::vec3(rollMatrix * glm::vec4(this->Right, 0.0f));

// 计算新的上向量
Up = glm::normalize(glm::cross(this->Right, this->Front));
}
};
#endif

windowFactory.h

这个文件有改动!!!

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#pragma once
#include "camera.h"
#include <glad/glad.h> // gald前面不能包含任何opengl头文件
#include <GLFW/glfw3.h>
#include <functional>
#include <iostream>

using std::cout;
using std::endl;

class GLFWWindowFactory {
public:
// 默认构造函数
GLFWWindowFactory() {}
// 构造函数,初始化窗口
GLFWWindowFactory(int width, int height, const char* title) {
// 初始化glfw
glfwInit();
// 设置opengl版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 使用核心模式:确保不使用任何被弃用的功能
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

// 创建glfw窗口
this->window = glfwCreateWindow(width, height, title, NULL, NULL);
if (this->window == NULL) {
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
exit(-1);
}
// 设置当前窗口的上下文
glfwMakeContextCurrent(this->window);
// 设置窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(this->window, framebuffer_size_callback);
// 加载所有opengl函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
cout << "Failed to initialize GLAD" << endl;
}
// 再次设置当前窗口的上下文,确保当前上下文仍然是刚刚创建的窗口,是一个安全措施
glfwMakeContextCurrent(this->window);
// 设置窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(this->window, framebuffer_size_callback);
// 设置鼠标移动的回调函数
glfwSetCursorPosCallback(this->window, mouse_callback);
// 设置鼠标滚轮滚动的回调函数
glfwSetScrollCallback(this->window, scroll_callback);
// 告诉GLFW捕获鼠标
glfwSetInputMode(this->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
}

// 获取窗口对象
GLFWwindow* getWindow() {
return this->window;
}

// 运行窗口,传入一个自定义的更新函数
void run(std::function<void()> updateFunc) {
// 启用深度测试,opengl将在绘制每个像素之前比较其深度值,以确定该像素是否应该被绘制
glEnable(GL_DEPTH_TEST);

// 循环渲染
while (!glfwWindowShouldClose(this->window)) { // 检查是否应该关闭窗口
// 清空屏幕所用的颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 清空颜色缓冲,主要目的是为每一帧的渲染准备一个干净的画布
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

float currentFrame = glfwGetTime();
this->deltaTime = currentFrame - this->lastFrame;
this->lastFrame = currentFrame;
this->timeElapsed += this->deltaTime;
this->frameCount++;

// 处理输入
GLFWWindowFactory::process_input(this->window);

// 初始化投影矩阵和视图矩阵
this->projection =
glm::perspective(glm::radians(camera.Zoom),
(float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
this->view = this->camera.GetViewMatrix();

// 执行更新函数
updateFunc();

// 交换缓冲区
glfwSwapBuffers(this->window);
// 处理所有待处理事件,去poll所有事件,看看哪个没处理的
glfwPollEvents();
}

// 终止GLFW,清理GLFW分配的资源
glfwTerminate();
}

// 窗口大小改变的回调函数
static void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
// 确保视口与新窗口尺寸匹配,注意在视网膜显示器上,宽度和高度会显著大于指定值
glViewport(0, 0, width, height);
}

// 鼠标移动的回调函数
static void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);

if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}

float xoffset = xpos - lastX;
// 反转y坐标,因为y坐标的范围是从上到下,我们需要从下到上
float yoffset = lastY - ypos;

lastX = xpos;
lastY = ypos;

camera.ProcessMouseMovement(xoffset, yoffset);
}

// 鼠标滚轮的回调函数
static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
camera.ProcessMouseScroll(static_cast<float>(yoffset));
}

// 处理输入
static void process_input(GLFWwindow* window) {
// 按下ESC键时进入if块
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
// 关闭窗口
glfwSetWindowShouldClose(window, true);

if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
camera.ProcessKeyboard(UP, deltaTime);
if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
camera.ProcessKeyboard(DOWN, deltaTime);
if (glfwGetKey(window, GLFW_KEY_I) == GLFW_PRESS)
camera.ProcessKeyboard(PITCH_UP, deltaTime);
if (glfwGetKey(window, GLFW_KEY_K) == GLFW_PRESS)
camera.ProcessKeyboard(PITCH_DOWN, deltaTime);
if (glfwGetKey(window, GLFW_KEY_J) == GLFW_PRESS)
camera.ProcessKeyboard(YAW_LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_L) == GLFW_PRESS)
camera.ProcessKeyboard(YAW_RIGHT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_U) == GLFW_PRESS)
camera.ProcessKeyboard(ROLL_LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_O) == GLFW_PRESS)
camera.ProcessKeyboard(ROLL_RIGHT, deltaTime);
}

// 获取投影矩阵
const glm::mat4 getProjectionMatrix() {
return this->projection;
}

// 获取视图矩阵
glm::mat4 getViewMatrix() {
return this->view;
}

public:
// 投影矩阵
glm::mat4 projection;
// 视图矩阵
glm::mat4 view;
// 摄像机
static Camera camera;
// 屏幕宽度
static const unsigned int SCR_WIDTH = 800;
// 屏幕高度
static const unsigned int SCR_HEIGHT = 600;

private:
// 窗口对象
GLFWwindow* window;

// 经过的时间
float timeElapsed;
// 帧计数
int frameCount;

// 上一次鼠标的X坐标
static float lastX;
// 上一次鼠标的Y坐标
static float lastY;
// 是否第一次鼠标移动
static bool firstMouse;

// 时间间隔
static float deltaTime;
// 上一帧的时间
static float lastFrame;
};

windowFactory.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "windowFactory.h"

Camera GLFWWindowFactory::camera = Camera(glm::vec3(0.0f, 0.0f, 3.0f));

// 初始化鼠标的最后X位置为屏幕宽度的一半
float GLFWWindowFactory::lastX = GLFWWindowFactory::SCR_WIDTH / 2.0f;
// 初始化鼠标的最后Y位置为屏幕高度的一半
float GLFWWindowFactory::lastY = GLFWWindowFactory::SCR_HEIGHT / 2.0f;
// 标记是否为第一次鼠标输入
bool GLFWWindowFactory::firstMouse = true;
// 初始化帧间隔时间
float GLFWWindowFactory::deltaTime = 0.0f;
// 初始化上一帧的时间
float GLFWWindowFactory::lastFrame = 0.0f;

shader.h

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>
#include <glm/glm.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

using std::string;
using std::ifstream;
using std::stringstream;
using std::cout;
using std::endl;

class Shader {
public:
// 默认构造函数
Shader() {}
// 着色器程序ID
unsigned int ID;

// 构造函数
Shader(const char* vertexPath, const char* fragmentPath) {
string vertexCode;
string fragmentCode;
ifstream vShaderFile;
ifstream fShaderFile;

// 确保ifstream对象可以抛出异常
vShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
fShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
try {
// 打开文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
// 读取文件缓冲区内容到stream中
stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理器
vShaderFile.close();
fShaderFile.close();
// 将stream转换为字符串
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
} catch (ifstream::failure& e) {
cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 编译着色器
unsigned int vertex, fragment;
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// 片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// 着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// 删除着色器
glDeleteShader(vertex);
glDeleteShader(fragment);
}

// 激活着色器
void use() {
glUseProgram(ID);
}

// 实用的uniform工具函数
// 用于在着色器程序中设置uniform值
// 设置一个布尔类型的uniform变量
void setBool(const std::string& name, bool value) const {
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}

// 设置一个整型的uniform变量
void setInt(const std::string& name, int value) const {
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

// 设置一个浮点类型的uniform变量
void setFloat(const std::string& name, float value) const {
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}

// 设置一个vec2类型的uniform变量
void setVec2(const std::string& name, const glm::vec2& value) const {
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec2类型的uniform变量
void setVec2(const std::string& name, float x, float y) const {
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}

// 设置一个vec3类型的uniform变量
void setVec3(const std::string& name, const glm::vec3& value) const {
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec3类型的uniform变量
void setVec3(const std::string& name, float x, float y, float z) const {
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}

// 设置一个vec4类型的uniform变量
void setVec4(const std::string& name, const glm::vec4& value) const {
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec4类型的uniform变量
void setVec4(const std::string& name, float x, float y, float z, float w) {
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}

// 设置一个mat2类型的uniform变量
void setMat2(const std::string& name, const glm::mat2& mat) const {
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

// 设置一个mat3类型的uniform变量
void setMat3(const std::string& name, const glm::mat3& mat) const {
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

// 设置一个mat4类型的uniform变量
void setMat4(const std::string& name, const glm::mat4& mat) const {
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

private:
// 检查着色器编译/链接错误
void checkCompileErrors(GLuint shader, string type) {
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM") {
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << endl;
}
}
else {
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << endl;
}
}
}
};
#endif

RectangleModel.h

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#pragma once
#include <glad/glad.h>
#include "shader.h"

#include <vector>
#include <chrono>
#include <cmath>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

class RectangleModel {
public:
// 构造函数
RectangleModel(const Shader& shader);
// 析构函数
~RectangleModel();

// 绘制矩形
void draw();

private:
unsigned int VAO;
unsigned int VBO;
unsigned int EBO;
Shader shader;

// 纹理
std::vector<unsigned int>texture;


// 编译着色器
void compileShaders();
// 设置缓冲区
void setElements();
// 加载纹理
void loadTexture();
// 绑定纹理
void bindTexture(GLuint& textured, const char* path);
};

RectangleModel.cpp

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include "RectangleModel.h"
#include <iostream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

using std::cout;
using std::endl;

// 构造函数
RectangleModel::RectangleModel(const Shader& shader) : shader(shader) {
// 加载纹理
loadTexture();
// 设置缓冲区
setElements();
}

// 析构函数
RectangleModel::~RectangleModel() {
// 删除VAO
glDeleteVertexArrays(1, &this->VAO);
// 删除VBO
glDeleteBuffers(1, &this->VBO);
// 删除EBO
glDeleteBuffers(1, &this->EBO);
}

/// public
// 绘制矩形
void RectangleModel::draw() {
// 使用着色器程序
this->shader.use();
// 绑定VAO
glBindVertexArray(VAO);

// 获取当前时间
auto now = std::chrono::system_clock::now();
// 计算毫秒数
auto duration = now.time_since_epoch();
double milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();

// 设置模型矩阵
glm::mat4 model = glm::mat4(1.0f);
shader.setMat4("model", model);

// 绘制矩形,即绘制两个三角形,GL_UNSIGNED_INT表示索引数组中的每个元素都是一个无符号整数
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

/// private
void RectangleModel::setElements() {
//顶点数据
float vertices[] = {
-0.75f, -0.75f,0.0f, 0.0f, 0.0f,
-0.1f, -0.75f,0.0f, 1.0f, 0.0f,
-0.75f, -0.1f,0.0f, 0.0f, 1.0f,
-0.1f, -0.1f,0.0f, 1.0f, 1.0f,
};
// 索引数据
int indices[] = {
0, 1, 2,
1, 2, 3,

};

// 生成一个VAO
glGenVertexArrays(1, &this->VAO);
// 绑定VAO,使其成为当前操作的VAO
glBindVertexArray(this->VAO);
// 生成一个VBO
glGenBuffers(1, &this->VBO);
// 绑定VBO, 使其成为当前操作的VBO,GL_ARRAY_BUFFER表示顶点缓冲区
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
// 为当前绑定的VBO创建并初始化数据存储,GL_STATIC_DRAW表示数据将一次性提供给缓冲区,并且在之后的绘制过程中不会频繁更改
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 生成一个EBO
glGenBuffers(1, &this->EBO);
// 绑定EBO,使其成为当前操作的EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// 传递索引数据
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// 定义顶点属性的布局
// - index:顶点属性的索引
// - size:每个顶点属性的数量
// - type:数据类型
// - normalized:是否将非浮点数值归一化
// - stride:连续顶点属性之间的间隔
// - pointer:数据在缓冲区中的偏移量
// 设置顶点属性指针,位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
// 启用顶点属性
glEnableVertexAttribArray(0);
// 设置顶点属性指针,颜色属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
// 启用顶点属性
glEnableVertexAttribArray(1);
}

// 加载纹理
void RectangleModel::loadTexture() {
std::vector<std::string> path = { "teenager.png", "tex.png" };
texture.resize(path.size());
for (int i = 0; i < path.size(); i++) {
bindTexture(texture[i], path[i].c_str());
}
// 参数传入指定要激活的纹理单元,例如GL_TEXTURE0/1/*
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture[1]);

// 使用着色器程序
shader.use();
// 设置uniform变量
shader.setInt("texture0", 0);
shader.setInt("texture1", 1);
}

// 绑定纹理
void RectangleModel::bindTexture(GLuint& textureId, const char* path) {
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
// 设置纹理环绕和过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
stbi_set_flip_vertically_on_load(true);
int width, height, nrChannels;
unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0);
if (data) {
GLenum format;
if (nrChannels == 4)
format = GL_RGBA;
else if (nrChannels == 3)
format = GL_RGB;
else
format = GL_RED;

glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else {
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "utils/RectangleModel.h"
#include "utils/windowFactory.h"

int main() {
// 创建一个窗口Factory对象
GLFWWindowFactory myWindow(800, 600, "This is Title");
// 创建一个着色器对象
Shader shader("shader.vs", "shader.fs");
// 创建一个矩形模型对象
RectangleModel rectangle(shader);

// 运行窗口,传入一个lambda表达式,用于自定义渲染逻辑
myWindow.run([&]() {
// 设置投影矩阵
shader.setMat4("projection", myWindow.getProjectionMatrix());
// 设置视图矩阵
shader.setMat4("view", myWindow.getViewMatrix());
// 绘制矩形
rectangle.draw();
});

return 0;
}

CMakeLists.txt

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
28
29
30
31
32
33
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(Camera)

# vcpkg集成, 这里要换成你自己的vcpkg工具链文件和共享库路径
set(VCPKG_ROOT D:/software6/vcpkg/)
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
set(CMAKE_PREFIX_PATH ${VCPKG_ROOT}/installed/x64-mingw-static/share)

# 查找所需的包
find_package(glad CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)
find_package(assimp CONFIG REQUIRED)
find_package(yaml-cpp CONFIG REQUIRED)

# 搜索并收集utils文件夹下的所有源文件
file(GLOB UTILS "utils/*.cpp", "utils/*.h")

# 添加可执行文件(还要加入utils文件夹下的源文件)
add_executable(Camera main.cpp ${UTILS})

# 链接所需的库
target_link_libraries(Camera PRIVATE glad::glad glfw glm::glm assimp::assimp yaml-cpp::yaml-cpp)

# 检查项目是否有dependeicies目录,如果存在,则在使用add_custom_command命令在构建后将dependencies目录中的文件复制到项目的输出目录
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dependencies")
if(EXISTS ${SOURCE_DIR})
add_custom_command(TARGET Camera POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${SOURCE_DIR} $<TARGET_FILE_DIR:Camera>)
endif()

最终效果


OpenGL入门007——摄像机
http://example.com/2024/11/06/OpenGL入门007——摄像机/
作者
凌云行者
发布于
2024年11月6日
许可协议