【OpenGL】谢尔宾斯基(Sierpinski)地毯 OpenGL分形(七)
效果展示
https://www.bilibili.com/video/BV1Ri4y1g7Gg
阴影效果不是怎么好。
原理简介
大致流程:
使用的是现代OpenGL。
主要思路是首先递归(已改为迭代)生成每个正方体的偏移位置,保存到数组里面。总共迭代了5次,产生了3200000个正方体(每次正方体*20,再迭代一次的话,GPU渲染跟不上)。
然后根据OpenGL多实例渲染的特性,传入刚刚生成的偏移位置到顶点着色器(当然这些数据在代码渲染循环前已经设置为VBO传入到显存了,所以应该是着色器从显存里取)。通过在顶点着色器里面构造平移矩阵,在绘制正方体之前都平移到相应位置。
然后就是光照了,这里用的冯氏光照模型(着色处理在片段着色器)。最终产生该效果。
关键点:
- 内存解决: 因为VS x86运行最多给2G内存,然后迭代6次的话光存储偏移就用了接近1G的内存,所以并没有在主函数里面计算好模型矩阵后传入,而是直接传入正方体的偏移位置在着色器里面构造平移矩阵。但是这样虽然内存可以支持6次迭代,但是我的GTX1050渲染跟不上。
- 这里产生谢尔宾斯基(Sierpinski)地毯的迭代方法是将一个基本的谢尔宾斯基(Sierpinski)地毯分解为20个正方体(下面一层和上面一层各8个,中间一层4个),即每增加一次迭代都将上一次的谢尔宾斯基(Sierpinski)地毯构造20个。
- 使用了背面剔除,OpenGL中使用顶点绕序(winding order)来确定。所谓绕序就是当几何对象细分为三角形时,三角形顶点相对于中心的定义顺序。左侧的三角形顶点顺序为1->2->3,右侧的三角形顶点顺序为1->2->3。当观察者在右侧时,则右边的三角形方向为逆时针方向为正面,而左侧的三角形为顺时针则为背面;当观察者转到左侧时,左侧的三角形为逆时针绕序判定为正面,而右侧的三角形为顺时针绕序判定为背面。可以看出正面和背面是由三角形的顶点定义顺序和观察者的观察方向共同决定的,而且随着观察方向的改变,正面和背面将会跟着改变。
最后,talk is cheap, show me the code,我将代码都给出了,然后在关键地方添加了注释!
欢迎大家批判指正,如果有任何优化的地方都欢迎指出!
最终代码
主函数代码
- main.cpp
#include <iostream>
#include <vector>
#include <cmath>
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Shader.h"
#include "Camera.h"
bool stop = true; // 空格键 控制是否旋转
const GLfloat sizeCube = 0.1f; // 正方体大小
const int pos[] = { // 20个位置,表示一个谢尔宾斯基(Sierpinski)地毯
0, 0, 0,
0, 1, 0,
0, 2, 0,
1, 0, 0,
2, 0, 0,
1, 2, 0,
2, 1, 0,
2, 2, 0,
0, 0, 1,
0, 2, 1,
2, 0, 1,
2, 2, 1,
0, 0, 2,
0, 1, 2,
0, 2, 2,
1, 0, 2,
2, 0, 2,
1, 2, 2,
2, 1, 2,
2, 2, 2
};
GLfloat vertices[] = {
// 正方体局部坐标 法向量(用于光照)
// 正方体局部坐标 法向量(用于光照)
// Back face
-sizeCube, -sizeCube, -sizeCube, 0.0f, 0.0f, -1.0f,
sizeCube, -sizeCube, -sizeCube, 0.0f, 0.0f, -1.0f,
sizeCube, sizeCube, -sizeCube, 0.0f, 0.0f, -1.0f,
sizeCube, sizeCube, -sizeCube, 0.0f, 0.0f, -1.0f,
-sizeCube, sizeCube, -sizeCube, 0.0f, 0.0f, -1.0f,
-sizeCube, -sizeCube, -sizeCube, 0.0f, 0.0f, -1.0f,
// Front face
-sizeCube, -sizeCube, sizeCube, 0.0f, 0.0f, 1.0f,
sizeCube, sizeCube, sizeCube, 0.0f, 0.0f, 1.0f,
sizeCube, -sizeCube, sizeCube, 0.0f, 0.0f, 1.0f,
sizeCube, sizeCube, sizeCube, 0.0f, 0.0f, 1.0f,
-sizeCube, -sizeCube, sizeCube, 0.0f, 0.0f, 1.0f,
-sizeCube, sizeCube, sizeCube, 0.0f, 0.0f, 1.0f,
// Left face
-sizeCube, sizeCube, sizeCube, -1.0f, 0.0f, 0.0f,
-sizeCube, -sizeCube, -sizeCube, -1.0f, 0.0f, 0.0f,
-sizeCube, sizeCube, -sizeCube, -1.0f, 0.0f, 0.0f,
-sizeCube, -sizeCube, -sizeCube, -1.0f, 0.0f, 0.0f,
-sizeCube, sizeCube, sizeCube, -1.0f, 0.0f, 0.0f,
-sizeCube, -sizeCube, sizeCube, -1.0f, 0.0f, 0.0f,
// Right face
sizeCube, sizeCube, sizeCube, 1.0f, 0.0f, 0.0f,
sizeCube, sizeCube, -sizeCube, 1.0f, 0.0f, 0.0f,
sizeCube, -sizeCube, -sizeCube, 1.0f, 0.0f, 0.0f,
sizeCube, -sizeCube, -sizeCube, 1.0f, 0.0f, 0.0f,
sizeCube, -sizeCube, sizeCube, 1.0f, 0.0f, 0.0f,
sizeCube, sizeCube, sizeCube, 1.0f, 0.0f, 0.0f,
// Bottom face
-sizeCube, -sizeCube, -sizeCube, 0.0f, -1.0f, 0.0f,
sizeCube, -sizeCube, sizeCube, 0.0f, -1.0f, 0.0f,
sizeCube, -sizeCube, -sizeCube, 0.0f, -1.0f, 0.0f,
sizeCube, -sizeCube, sizeCube, 0.0f, -1.0f, 0.0f,
-sizeCube, -sizeCube, -sizeCube, 0.0f, -1.0f, 0.0f,
-sizeCube, -sizeCube, sizeCube, 0.0f, -1.0f, 0.0f,
// Top face
-sizeCube, sizeCube, -sizeCube, 0.0f, 1.0f, 0.0f,
sizeCube, sizeCube, -sizeCube, 0.0f, 1.0f, 0.0f,
sizeCube, sizeCube, sizeCube, 0.0f, 1.0f, 0.0f,
sizeCube, sizeCube, sizeCube, 0.0f, 1.0f, 0.0f,
-sizeCube, sizeCube, sizeCube, 0.0f, 1.0f, 0.0f,
-sizeCube, sizeCube, -sizeCube, 0.0f, 1.0f, 0.0f
};
/**
* 把递归改成了迭代,step表示当前步数,k表示目标步数
*/
void func(std::vector<GLfloat>& vec, int step, int k) {
while (step < k) {
int stepLen = pow(3, step);
std::vector<GLfloat> temp = vec;
int len = temp.size() / 3;
for (int i = 1; i < 20; i++) {
int alphaX = pos[i * 3] * stepLen;
int alphaY = pos[i * 3 + 1] * stepLen;
int alphaZ = pos[i * 3 + 2] * stepLen;
for (int j = 0; j < len; j++) {
vec.push_back(temp[j * 3] + alphaX);
vec.push_back(temp[j * 3 + 1] + alphaY);
vec.push_back(temp[j * 3 + 2] + alphaZ);
}
}
step++;
}
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void do_movement();
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
GLfloat lastX = WIDTH / 2.0;
GLfloat lastY = HEIGHT / 2.0;
bool keys[1024];
glm::vec3 lightPos;
// Deltatime
GLfloat deltaTime = 0.0f; // Time between current frame and last frame
GLfloat lastFrame = 0.0f; // Time of last frame
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// 全屏
/*bool isFullScreen = true;
GLFWmonitor* pMonitor = isFullScreen ? glfwGetPrimaryMonitor() : NULL;
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", pMonitor, nullptr);*/
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// GLFW Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, WIDTH, HEIGHT);
// OpenGL options
glEnable(GL_DEPTH_TEST);
// 未开启面剔除、好像会影响渲染效果
Shader lightingShader("lighting.vs", "lighting.frag"); // 正方体的着色器
Shader lampShader("lamp.vs", "lamp.frag"); // 中心灯的着色器
const int k = 5; // 迭代次数
std::vector<GLfloat> vec(pos, pos + sizeof(pos) / sizeof(float)); // 初始赋值为pos
vec.reserve(3 * pow(20, k)); // 提前计算并分配好空间
func(vec, 1, k);
for (auto& item : vec) item *= sizeCube * 2; // 每一项移动为正方体边长比例
lightPos.x = lightPos.y = lightPos.z = pow(3, k) * sizeCube; // 光源位置初始化为中央
GLuint amount = vec.size() / 3; // 正方体个数
//std::cout << amount;
// containerVAO 正方体VAO,使用一个顶点坐标和方向量的VBO和一个表示正方体偏移的instanceVBO
GLuint VBO, instanceVBO, containerVAO;
glGenVertexArrays(1, &containerVAO);
glBindVertexArray(containerVAO);
{
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// Normal attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, vec.size() * sizeof(float), &vec[0], GL_STATIC_DRAW);
// 偏移(用于绘制多个正方体)
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(3);
glVertexAttribDivisor(3, 1); // 第一个参数2表示layout索引,第二个参数指定顶点属性的更新方式
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
glBindVertexArray(0);
// lightVAO 中心光源VAO, 同containerVAO共用VBO
GLuint lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
{
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
glBindVertexArray(0);
glCullFace(GL_BACK); // 设置剔除的面是背面(默认剔除背面)
glFrontFace(GL_CW); // 使用顺时针表示正面。clockwise(默认逆时针表示正面)
glEnable(GL_CULL_FACE); // 开启面剔除(默认不开启)
while (!glfwWindowShouldClose(window)) {
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
glfwPollEvents();
do_movement();
// Clear the colorbuffer
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Use cooresponding shader when setting uniforms/drawing objects
lightingShader.Use();
GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");
GLint lightColorLoc = glGetUniformLocation(lightingShader.Program, "lightColor");
GLint lightPosLoc = glGetUniformLocation(lightingShader.Program, "lightPos");
GLint viewPosLoc = glGetUniformLocation(lightingShader.Program, "viewPos");
glUniform3f(objectColorLoc, 0.75f, 0.75f, 0.75f);
glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f);
glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);
glUniform3f(viewPosLoc, camera.Position.x, camera.Position.y, camera.Position.z);
// Create camera transformations
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(camera.Zoom, (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);
glm::mat4 model = glm::mat4(); // 让正方体旋转
static float angle = 0;
if (!stop) angle += 0.1f;
model = glm::rotate(model, angle, glm::vec3(1, 1, 1));
// Get the uniform locations
GLint modelLoc = glGetUniformLocation(lightingShader.Program, "model");
GLint viewLoc = glGetUniformLocation(lightingShader.Program, "view");
GLint projLoc = glGetUniformLocation(lightingShader.Program, "projection");
// Pass the matrices to the shader
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
// Draw the container (using container's vertex attributes)
glBindVertexArray(containerVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, amount);
glBindVertexArray(0);
// Also draw the lamp object, again binding the appropriate shader
lampShader.Use();
model = glm::mat4();
model = glm::translate(model, glm::vec3(lightPos.x, lightPos.y, lightPos.z)); // 光源移动到中心
// Get location objects for the matrices on the lamp shader (these could be different on a different shader)
modelLoc = glGetUniformLocation(lampShader.Program, "model");
viewLoc = glGetUniformLocation(lampShader.Program, "view");
projLoc = glGetUniformLocation(lampShader.Program, "projection");
// Set matrices
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
// Draw the light object (using light's vertex attributes)
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
// Swap the screen buffers
glfwSwapBuffers(window);
}
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) {
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key < 1024) {
if (action == GLFW_PRESS) {
keys[key] = true;
if (key == GLFW_KEY_SPACE)
stop = !stop;
}
else if (action == GLFW_RELEASE)
keys[key] = false;
}
}
void do_movement() {
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}
bool firstMouse = true;
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos; // Reversed since y-coordinates go from bottom to left
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
camera.ProcessMouseScroll(yoffset);
}
着色器代码
- lighting.vs
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 3) in vec3 pos; // 正方体偏移
out vec3 Normal;
out vec3 FragPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
mat4 instanceMatrix = mat4( // 平移矩阵的构造
1.0, 0, 0, 0,
0, 1.0, 0, 0,
0, 0, 1.0, 0,
pos.x, pos.y, pos.z, 1.0
);
gl_Position = projection * view * model * instanceMatrix * vec4(position, 1.0f); // 先平移到指定位置,再进行视图变换
FragPos = vec3(model * instanceMatrix * vec4(position, 1.0f)); // 需要顶点位置属性乘以模型矩阵(Model Matrix,只用模型矩阵不需要用观察和投影矩阵)来把它变换到世界空间坐标
Normal = mat3(transpose(inverse(model * instanceMatrix))) * normal; // 修复不等比缩放,正规矩阵被定义为“模型矩阵左上角的逆矩阵的转置矩阵”
}
- lighting.frag
#version 330 core
out vec4 color;
in vec3 Normal;
in vec3 FragPos;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main()
{
// Ambient
float ambientStrength = 0.1f;
vec3 ambient = ambientStrength * lightColor;
// Diffuse
vec3 norm = normalize(Normal); // 保证自身为单位向量
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0); // dot 点乘 为0表示垂直,a1b1+a2b2 第一个向量投影到第二个向量上
vec3 diffuse = diff * lightColor;
// Specular
float specularStrength = 0.5f; // 镜面强度(Specular Intensity)变量specularStrength,给镜面高光一个中等亮度颜色,这样就不会产生过度的影响了。
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm); // norm 标准化的法向量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); // 32是高光的发光值(Shininess)。一个物体的发光值越高,反射光的能力越强,散射得越少,高光点越小。
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor; // 冯氏光照模型
color = vec4(result, 1.0f);
}
- lamp.vs
#version 330 core
layout (location = 0) in vec3 position;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
}
- lamp.frag
#version 330 core
out vec4 color;
void main()
{
color = vec4(1.0f); //设置四维向量的所有元素为 1.0f
}
包含头文件
- Shader.h
#ifndef SHADER_H
#define SHADER_H
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <GL/glew.h>
class Shader {
public:
GLuint Program;
// Constructor generates the shader on the fly
Shader(const GLchar* vertexPath, const GLchar* fragmentPath) {
// 1. Retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// ensures ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::badbit);
try {
// Open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// Read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// Convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure e) {
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const GLchar* vShaderCode = vertexCode.c_str();
const GLchar* fShaderCode = fragmentCode.c_str();
// 2. Compile shaders
GLuint vertex, fragment;
GLint success;
GLchar infoLog[512];
// Vertex Shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// Print compile errors if any
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Fragment Shader
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
// Print compile errors if any
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Shader Program
this->Program = glCreateProgram();
glAttachShader(this->Program, vertex);
glAttachShader(this->Program, fragment);
glLinkProgram(this->Program);
// Print linking errors if any
glGetProgramiv(this->Program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(this->Program, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// Delete the shaders as they're linked into our program now and no longer necessery
glDeleteShader(vertex);
glDeleteShader(fragment);
}
// Uses the current shader
void Use() {
glUseProgram(this->Program);
}
};
#endif
- Camera.h
#ifndef CAMERA_H
#define CAMERA_H
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vector>
// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
RIGHT
};
// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 5;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;
// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
// Camera Attributes
glm::vec3 Position;
glm::vec3 Front;
glm::vec3 Up;
glm::vec3 Right;
glm::vec3 WorldUp;
// Euler Angles
float Yaw;
float Pitch;
// Camera options
float MovementSpeed;
float MouseSensitivity;
float Zoom;
// Constructor with vectors
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) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
{
Position = position;
WorldUp = up;
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
// Constructor with scalar values
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : 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;
updateCameraVectors();
}
// Returns the view matrix calculated using Euler Angles and the LookAt Matrix
glm::mat4 GetViewMatrix()
{
return glm::lookAt(Position, Position + Front, Up);
}
// Processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
void ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = MovementSpeed * deltaTime;
if (direction == FORWARD)
Position += Front * velocity;
if (direction == BACKWARD)
Position -= Front * velocity;
if (direction == LEFT)
Position -= Right * velocity;
if (direction == RIGHT)
Position += Right * velocity;
}
// Processes input received from a mouse input system. Expects the offset value in both the x and y direction.
void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
{
xoffset *= MouseSensitivity;
yoffset *= MouseSensitivity;
Yaw += xoffset;
Pitch += yoffset;
// Make sure that when pitch is out of bounds, screen doesn't get flipped
if (constrainPitch)
{
if (Pitch > 89.0f)
Pitch = 89.0f;
if (Pitch < -89.0f)
Pitch = -89.0f;
}
// Update Front, Right and Up Vectors using the updated Euler angles
updateCameraVectors();
}
// Processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
void ProcessMouseScroll(float yoffset)
{
if (Zoom >= 1.0f && Zoom <= 45.0f)
Zoom -= yoffset;
if (Zoom <= 1.0f)
Zoom = 1.0f;
if (Zoom >= 45.0f)
Zoom = 45.0f;
}
private:
// Calculates the front vector from the Camera's (updated) Euler Angles
void updateCameraVectors()
{
// Calculate the new Front vector
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));
Front = glm::normalize(front);
// Also re-calculate the Right and Up vector
Right = glm::normalize(glm::cross(Front, WorldUp)); // Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
Up = glm::normalize(glm::cross(Right, Front));
}
};
#endif