OpenGL—基于四元数的摄像机系统
---------------------------------------------------------博主:mx
基础概念:
摄象机:
OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。
当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。
摄像机坐标系:
我们只需要求出摄象机的前向(Front)向量,然后根据世界坐标系的y轴的向上的向量(0.0f,1.0f,0.0f),就可以通过叉乘求出与Front垂直的右向(Right)向量,知道Front向量和Right向量之后再根据叉乘就能够求出向上(Up)向量,由此摄像机的xyz三个轴都求出来了。
由此我们再根据摄像机坐标和这三个轴,我们就能够求出来一个view矩阵,这个矩阵乘以任何向量都能够将这个向量变换到摄像机坐标系下。

(其中R是右向量,U是上向量,D是方向向量PP是摄像机位置向量。注意,位置向量是相反的,因为我们最终希望把世界平移到与我们自身移动的相反方向。)
对应opengl的api为
glm::lookAt(glm::vec3 Position, glm::vec3 Target, glm::vec3 Up);
glm::LookAt函数需要一个位置、目标和上向量。它会创建一个观察矩阵。
相机移动
只要改变相机的Position ,对应的view矩阵就会改变,物体和相机的相对距离也会改变。

相机缩放
视野(FOV)定义了我们可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。

相机旋转
现在旋转常见的方法有:欧拉角(Euler angles)和四元数(Quaternion)
欧拉角:
基础概念:
欧拉角是表示朝向的最简方法,只需存储绕X、Y、Z轴旋转的角度,分别叫PitchYawRoll(俯仰角偏航角滚转角)
俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。
这三个旋转是依次施加的,通常的顺序是:Y-Z-X(但并非一定要按照这种顺序)。顺序不同,产生的结果也不同。
欧拉角实现的摄象机旋转:
对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。给定一个俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。
对于Pitch:

direction.y = sin(glm::radians(pitch)); // 注意我们先把角度转为弧度
direction.x = cos(glm::radians(pitch));
direction.z = cos(glm::radians(pitch));
对于Yaw:

direction.x = cos(glm::radians(yaw));
direction.z = sin(glm::radians(yaw));
把这两个合起来:
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
得到基于欧拉角的旋转

欧拉角的缺点:
- 对两个朝向进行插值比较困难。简单地对X、Y、Z角度进行插值得到的结果不太理想。
- 实施多次旋转很复杂且不精确:必须计算出最终的旋转矩阵,然后据此推测书欧拉角。
- “臭名昭著”的“万向节死锁”(Gimbal Lock)问题有时会让旋转“卡死”。(无论什么样的旋转顺序如xyz,在某个轴旋转之后有可能使得另两个轴的旋转等效)其他一些奇异状态还会导致模型方向翻转。
- 不同的角度可产生同样的旋转(例如-180°和180°)
- 容易出错——如上所述,一般的旋转顺序是YZX,如果用了非YZX顺序的库,就有麻烦了。
- 某些操作很复杂:如绕指定的轴旋转N角度。
四元数:
四元数能够轻松的解决上述欧拉角的问题
基本概念:
四元数由4个数[x y z w]构成,表示了如下的旋转:
// RotationAxis,顾名思义即旋转轴。RotationAngle是旋转的角度。
x = RotationAxis.x * sin(RotationAngle / 2)
y = RotationAxis.y * sin(RotationAngle / 2)
z = RotationAxis.z * sin(RotationAngle / 2)
w = cos(RotationAngle / 2)
---------------------------------------------------------博主:mx

根据w分量我们可以清除的知道旋转的角度,然后再根据xyz可以推出旋转轴
四元数的使用:
四元数的创建:
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
// Creates an identity quaternion (no rotation) quat MyQuaternion;
// Direct specification of the 4 components
// You almost never use this directly MyQuaternion = quat(w,x,y,z);
// Conversion from Euler angles (in radians) to Quaternion
vec3 EulerAngles(90, 45, 0);
MyQuaternion = quat(EulerAngles);
// Conversion from axis-angle
// In GLM the angle must be in degrees here, so convert it.
MyQuaternion=gtx::quaternion::angleAxis(degrees(RotationAngle), RotationAxis);
注意如果是在shader中,应该把四元数转换成旋转矩阵,顶点会一如既往地随着MVP矩阵的变化而旋转。
mat4 RotationMatrix = quaternion::toMat4(quaternion);
...
mat4 ModelMatrix = TranslationMatrix * RotationMatrix * ScaleMatrix;
// You can now use ModelMatrix to build the MVP matrix
旋转向量/点:
rotated_point = orientation_quaternion * point;
//向量也一样
对两个四元数进行插值:
SLERP意为球面线性插值(Spherical Linear intERPolation)、可以用GLM中的mix函数进行SLERP:
glm::quat interpolatedquat = quaternion::mix(quat1, quat2, 0.5f); // or whatever factor
两个旋转的累计:
只需将两个四元数相乘即可。顺序和矩阵乘法一致。亦即逆序相乘:
quat combined_rotation = second_rotation * first_rotation;
两个向量之间的旋转:
基本思路很简单:
- 两向量间的夹角很好找:由点积可知其cos值。
- 旋转轴很好找:两向量的叉乘积
---------------------------------------------------------博主:mx
quat RotationBetweenVectors(vec3 start, vec3 dest){
start = normalize(start);
dest = normalize(dest);
float cosTheta = dot(start, dest);
vec3 rotationAxis;
if (cosTheta < -1 + 0.001f){
// special case when vectors in opposite directions:
// there is no "ideal" rotation axis
// So guess one; any will do as long as it's perpendicular to start
rotationAxis = cross(vec3(0.0f, 0.0f, 1.0f), start);
if (gtx::norm::length2(rotationAxis) < 0.01 ) // bad luck, they were parallel, try again!
rotationAxis = cross(vec3(1.0f, 0.0f, 0.0f), start);
rotationAxis = normalize(rotationAxis);
return gtx::quaternion::angleAxis(180.0f, rotationAxis);
}
rotationAxis = cross(start, dest);
float s = sqrt( (1+cosTheta)*2 );
float invs = 1 / s;
return quat(
s * 0.5f,
rotationAxis.x * invs,
rotationAxis.y * invs,
rotationAxis.z * invs
);
}
---------------------------------------------------------博主:mx
四元数实现摄象机旋转:
正确的做法:
先基于完整最开始的世界坐标系,我们假设屏幕空间是在上面,然后我们求出原生front向量,到我们鼠标点击的点到原点的向量,求出这两个向量需要的旋转四元数。然后将这个四元数应用于目前的front向量上。

求出两个向量之间的旋转
glm::quat rot1 = RotationBetweenVectors(glm::vec3(0.0f, 0.0f, 1.0f),normalize(glm::vec3(0.008f * MouseSensitivity*xoffset, 0.008f * MouseSensitivity * yoffset, 1.0f)) );
将这个旋转四元数应用于当前的Front
Front = glm::normalize(glm::rotate(rot1, Front));
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));

完整代码:
因为我尝试过三个方案所以旋转的代码有点多,上面四元数实现的旋转的函数名为updateCameraVectorsByQuat2
---------------------------------------------------------博主:mx
Shader:
#version460
layout(location =0) in vec3 aPos;
layout(location =1) in vec2 aTexCoord;
uniformfloat offset1;
uniformmat4 model;
uniformmat4 view;
uniformmat4 projection;
out vec2 TexCoord;
voidmain()
{
gl_Position =projection*view*model*vec4(aPos.x+offset1,-aPos.y,aPos.z,1.0f);
TexCoord = aTexCoord;
}
#version460
in vec2 TexCoord;
out vec4 FragColor;
uniformfloat offset2;
uniformsampler2D texture1;
uniformsampler2D texture2;
voidmain()
{
FragColor =mix(texture(texture1, TexCoord),texture(texture2,vec2(1-TexCoord.x,TexCoord.y)),0.8);
}
头文件:
#pragma once
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "glm/gtc/quaternion.hpp"
#include "glm/gtx/quaternion.hpp"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
代码:
#pragma once
#ifndef CAMERA_H
#define CAMERA_H
#include <glad/glad.h>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/quaternion.hpp"
#include "glm/gtx/quaternion.hpp"
#include <vector>
enum class Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
RIGHT
};
// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 2.5f;
const float SENSITIVITY = 0.1f;
const float FOV = 45.0f;
class Camera
{
public:
// camera Attributes
glm::vec3 Position;
glm::vec3 Front;
glm::vec3 Up;
glm::vec3 Right;
glm::vec3 WorldUp;
glm::quat FrontQuaternion;
// euler Angles
float Yaw;
float Pitch;
// camera options
float MovementSpeed;
float MouseSensitivity;
float Fov;
// 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);
// constructor with scalar values
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch);
// returns the view matrix calculated using Euler Angles and the LookAt Matrix
glm::mat4 GetViewMatrix();
// 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);
// 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);
// processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
void ProcessMouseScroll(float yoffset);
void updateCameraVectorsByQuat1(float xoffset, float yoffset);
void updateCameraVectorsByQuat2(float xoffset, float yoffset);
glm::quat RotationBetweenVectors(glm::vec3 start, glm::vec3 dest);
void ResetYawAndPitch();
private:
// calculates the front vector from the Camera's (updated) Euler Angles
void updateCameraVectors();
};
#endif
#include "Camera.h"
Camera::Camera(glm::vec3 position, glm::vec3 up, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Fov(FOV)
{
Position = position;
WorldUp = up;
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
Camera::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), Fov(FOV)
{
Position = glm::vec3(posX, posY, posZ);
WorldUp = glm::vec3(upX, upY, upZ);
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
glm::mat4 Camera::GetViewMatrix()
{
return glm::lookAt(Position, Position + Front, Up);
}
void Camera::ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = MovementSpeed * deltaTime;
if (direction == Camera_Movement::FORWARD)
Position += Front * velocity;
if (direction == Camera_Movement::BACKWARD)
Position -= Front * velocity;
if (direction == Camera_Movement::LEFT)
Position -= Right * velocity;
if (direction == Camera_Movement::RIGHT)
Position += Right * velocity;
}
void Camera::ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch)
{
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();
}
void Camera::ProcessMouseScroll(float yoffset)
{
Fov -= (float)yoffset;
if (Fov < 1.0f)
Fov = 1.0f;
if (Fov > 45.0f)
Fov = 45.0f;
}
/// <summary>
/// upadate CameraVector by Euler
/// </summary>
void Camera::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.x = cos(glm::radians(Yaw));
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));
FrontQuaternion = glm::quat(Front);
}
/// <summary>
/// update CameraVector by Quat(Maybe have some question)
/// </summary>
/// <param name="xoffset"></param>
/// <param name="yoffset"></param>
/// <param name="constrainPitch"></param>
void Camera::updateCameraVectorsByQuat1( float xoffset, float yoffset)
{
Yaw += 90;
xoffset *= 0.008f*MouseSensitivity;
yoffset *= 0.008f * MouseSensitivity;
Yaw = xoffset;
Pitch= yoffset;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (Pitch > 90.0f)
Pitch -= 90.0f;
if (Pitch < -90.0f)
Pitch += 90.0f;
if (Yaw > 90.0f)
Yaw -= 90.0f;
if (Yaw < -90.0f)
Yaw += 90.0f;
glm::vec3 axis = glm::cross(Front, Up);
glm::quat pitchQuat = glm::angleAxis(Pitch, axis);
//determine heading quaternion from the camera up vector and the heading angle
axis = glm::cross(Front, axis);
glm::quat yawQuat = glm::angleAxis(Yaw, Up);
//add the two quaternions
glm::quat combinedRotation = pitchQuat*yawQuat;
Front = glm::rotate(combinedRotation, Front);
Front = glm::normalize(Front);
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));
Yaw -= 90;
}
/// <summary>
/// update CameraVector by Quat (right)
/// </summary>
/// <param name="xoffset"></param>
/// <param name="yoffset"></param>
void Camera::updateCameraVectorsByQuat2(float xoffset, float yoffset)
{
// Find the rotation between the front of the object (that we assume towards +Z,
// but this depends on your model) and the desired direction
glm::quat rot1 = RotationBetweenVectors(glm::vec3(0.0f, 0.0f, 1.0f),normalize(glm::vec3(0.008f * MouseSensitivity*xoffset, 0.008f * MouseSensitivity * yoffset, 1.0f)) );
Front = glm::normalize(glm::rotate(rot1, Front));
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));
}
/// <summary>
/// get rotation between two vectors
/// </summary>
/// <param name="start"></param>
/// <param name="dest"></param>
/// <returns></returns>
glm::quat Camera::RotationBetweenVectors(glm::vec3 start, glm::vec3 dest)
{
start = normalize(start);
dest = normalize(dest);
float cosTheta = dot(start, dest);
glm::vec3 rotationAxis;
if (cosTheta < -1 + 0.001f) {
// special case when vectors in opposite directions:
// there is no "ideal" rotation axis
// So guess one; any will do as long as it's perpendicular to start
rotationAxis = glm::cross(glm::vec3(0.0f, 0.0f, 1.0f), start);
if (glm::length2(rotationAxis) < 0.01) // bad luck, they were parallel, try again!
rotationAxis = glm::cross(glm::vec3(1.0f, 0.0f, 0.0f), start);
rotationAxis = normalize(rotationAxis);
return glm::angleAxis(180.0f, rotationAxis);
}
rotationAxis = cross(start, dest);
float s = sqrt((1 + cosTheta) * 2);
float invs = 1 / s;
return glm::quat(
s * 0.5f,
rotationAxis.x * invs,
rotationAxis.y * invs,
rotationAxis.z * invs
);
}
void Camera::ResetYawAndPitch()
{
Yaw = YAW;
Pitch = PITCH;
}
#pragma once
#ifndef SHADER_H
#define SHADER_H
#include <glad/glad.h>; // 包含glad来获取所有的必须OpenGL头文件
#include "glm/glm.hpp"
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader
{
public:
// 程序ID
unsigned int ID;
// 构造器读取并构建着色器
Shader(const GLchar* vertexPath, const GLchar* fragmentPath, const char* geometryPath=nullptr);
~Shader();
// 使用/激活程序
void use(); // uniform工具函数
void setBool(const std::string& name, bool value)const;
void setInt(const std::string& name, int value)const;
void setFloat(const std::string& name, float value)const;
void setVec2(const std::string& name, const glm::vec2& value) const;
void setVec2(const std::string& name, float x, float y) const;
void setVec3(const std::string& name, const glm::vec3& value) const;
void setVec3(const std::string& name, float x, float y, float z) const;
void setVec4(const std::string& name, const glm::vec4& value) const;
void setVec4(const std::string& name, float x, float y, float z, float w) const;
void setMat2(const std::string& name, const glm::mat2& mat) const;
void setMat3(const std::string& name, const glm::mat3& mat) const;
void setMat4(const std::string& name, const glm::mat4& mat) const;
};
#endif
#include "Shader.h"
Shader::Shader(const GLchar* vertexPath, const GLchar* fragmentPath, const char* geometryPath)
{
// 1. 从文件路径中获取顶点/片段着色器
std::string vertexCode;
std::string fragmentCode;
std::string geometryCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
std::ifstream gShaderFile;
// 保证ifstream对象可以抛出异常:
vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
gShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
// 打开文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream; // 读取文件的缓冲内容到数据流中
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理器
vShaderFile.close();
fShaderFile.close();
// 转换数据流到string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
unsigned int geometry;
if (geometryPath != nullptr)
{
gShaderFile.open(geometryPath);
std::stringstream gShaderStream;
gShaderStream << gShaderFile.rdbuf();
gShaderFile.close();
geometryCode = gShaderStream.str();
}
}
catch (std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 2. 编译着色器
unsigned int vertex, fragment;
int success;
char infoLog[512];
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// 打印编译错误(如果有的话)
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertex, 512, NULL, infoLog); std::cout << "ERROR::SHADER::vertex::COMPILATION_FAILEDn" << infoLog << std::endl;
};
// 片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
// 打印编译错误(如果有的话)
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment, 512, NULL, infoLog); std::cout << "ERROR::SHADER::fragment::COMPILATION_FAILEDn" << infoLog << std::endl;
};
//几何着色器
unsigned int geometry;
if (geometryPath != nullptr)
{
const char* gShaderCode = geometryCode.c_str();
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &gShaderCode, NULL);
glCompileShader(geometry);
glGetShaderiv(geometry, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(geometry, 512, NULL, infoLog); std::cout << "ERROR::SHADER::fragment::COMPILATION_FAILEDn" << infoLog << std::endl;
};
}
// 着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
if (geometryPath != nullptr)
glAttachShader(ID, geometry);
glLinkProgram(ID);
// 打印连接错误(如果有的话)
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if(!success)
{
glGetProgramInfoLog(ID, 512, NULL, infoLog); std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILEDn" << infoLog << std::endl;
}
// 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
if (geometryPath != nullptr)
glDeleteShader(geometry);
}
Shader::~Shader()
{
glDeleteProgram(ID);
}
void Shader::use()
{
glUseProgram(ID);
}
void Shader::setBool(const std::string& name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
void Shader::setInt(const std::string& name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
void Shader::setFloat(const std::string& name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void Shader::setVec2(const std::string& name, const glm::vec2& value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void Shader::setVec2(const std::string& name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void Shader::setVec3(const std::string& name, const glm::vec3& value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void Shader::setVec3(const std::string& name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void Shader::setVec4(const std::string& name, const glm::vec4& value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void Shader::setVec4(const std::string& name, float x, float y, float z, float w) const
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void Shader::setMat2(const std::string& name, const glm::mat2& mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void Shader::setMat3(const std::string& name, const glm::mat3& mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void Shader::setMat4(const std::string& name, const glm::mat4& mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
#pragma once
#include"header.h"
#include"Shader.h"
#include"Camera.h"
#include <iostream>
using namespace std;
const unsigned int SCR_WIDTH = 1920;
const unsigned int SCR_HEIGHT = 1080;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;
// timing
float deltaTime = 0.0f; // time between current frame and last frame
float lastFrame = 0.0f;
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);//mac下使用
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
cout << " Failed to create GLFW window" << endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glViewport(0, 0, 1920, 1080);
glEnable(GL_DEPTH_TEST);
Shader ourShader("D:\LearnOpenGL\OpenGLProject\Learning1\Shader\Vertex.txt", "D:\LearnOpenGL\OpenGLProject\Learning1\Shader\Fragment.txt");
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
unsigned int VBO, EBO;
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//glGenBuffers(1, &EBO);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 位置属性
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);
// texture coord attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//生成纹理
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char* data = stbi_load("D:\LearnOpenGL\Texture\wall.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
unsigned int texture2;
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
// 为当前绑定的纹理对象设置环绕、过滤方式
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
data = stbi_load("D:\LearnOpenGL\Texture\ganyu.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
ourShader.use();
ourShader.setInt("texture1", 0);
ourShader.setInt("texture2", 1);
float timeValue = 0;
int UniformHandle1;
int UniformHandle2;
// 渲染循环
while (!glfwWindowShouldClose(window))
{
float currentFrame = static_cast<float>(glfwGetTime());
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// 输入
processInput(window);
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
ourShader.use();
ourShader.setFloat("offset2", abs(cos(glfwGetTime())));
//glm::mat4 model = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first
glm::mat4 view = camera.GetViewMatrix();
//glm::mat4 view = glm::toMat4(camera.FrontQuaternion);
glm::mat4 projection = glm::mat4(1.0f);
//model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
projection = glm::perspective(glm::radians(camera.Fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
//ourShader.setMat4("model", model);
ourShader.setMat4("view", view);
ourShader.setMat4("projection", projection);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glBindVertexArray(VAO);
for (unsigned int i = 0; i < 10; i++)
{
// calculate the model matrix for each object and pass it to shader before drawing
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * (i+1);
model = glm::rotate(model, glm::radians(angle*(float)glfwGetTime()), glm::vec3(1.0f, 0.3f, 0.5f));
ourShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
// 检查并调用事件,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteBuffers(1, &VBO);
glDeleteVertexArrays(1, &VAO);
//glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(Camera_Movement::FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(Camera_Movement::BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(Camera_Movement::LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(Camera_Movement::RIGHT, deltaTime);
}
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
if (glfwGetMouseButton(window, 0))
{
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = lastX - xpos;
float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
lastX = xpos;
lastY = ypos;
// camera.updateCameraVectorsByQuat1(-xoffset, -yoffset);
camera.updateCameraVectorsByQuat2(-xoffset, yoffset);
}
else
{
lastX = SCR_WIDTH / 2.0f;
lastY = SCR_HEIGHT / 2.0f;
}
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(static_cast<float>(yoffset));
}
学习资料:
---------------------------------------------------------博主:mx