图形学笔记(八)画一个可以由鼠标和键盘控制的立方体
画完自己会动的立方体,下面画一个由鼠标和键盘控制的立方体。
一、代码
- main.cpp
#include <iostream>
//GLEW
#define GLEW_STATIC
#include <GL/glew.h>
//GLFW
#include <GLFW/glfw3.h>
//Shader
#include "Shader.h"
// SOIL2
//Linux 用的是 \, 但是 / 都可以用
#include "SOIL2/SOIL2.h"
#include "SOIL2/stb_image.h"
//glm
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> //需要什么变换,就导入什么文件,具体可以去官网看
#include <glm/gtc/type_ptr.hpp>
#include "Camera.h" //当前引用,所以用""
const GLint WIDTH = 800, HEIGHT = 600; //新建窗口
//键盘回应
void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode);
//监听鼠标移动事件:xPos 和 yPos 代表当前鼠标位置
void MouseCallback(GLFWwindow *window, double xPos, double yPos);
//键盘移动
void DoMovement();
//初始化一个相机
Camera camera(glm::vec3(0.0f, 0.0f, 2.0f));
//设置初始量
GLfloat lastX = WIDTH / 2.0;
GLfloat lastY = HEIGHT / 2.0;
bool firstMouse = true;
//鼠标缩放
//double fov = 45.0;
bool keys[1024]; //存放获取的所有键盘操作,先存下来再进行操作
GLfloat deltaTime = 0.0f; //两帧之间的间隔时间
GLfloat lastTime = 0.0f; //上一帧绘制的时间
int main()
{
glfwInit();
//OpenGL 版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//窗口设置
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //用的是新版的 OpenGL 3.3
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // must for Mac
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); //改为 GL_TRUE,改变窗口,纵横比会变
GLFWwindow *window = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL B16xxxxxx", nullptr,
nullptr); //窗口名字改成自己的学号
if (nullptr == window)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
// next two lines are for mac retina display
int screenWidth, screenHeight;
glfwGetFramebufferSize(window, &screenWidth, &screenHeight); //获取窗口大小
glfwMakeContextCurrent(window); //可以新建很多 window
// Set the required callback function
//KeyCallback 是响应键盘消息的回调函数
glfwSetKeyCallback(window, KeyCallback);
//mouse_callback 是响应鼠标消息的回调函数,鼠标一移动 MouseCallback 函数就会被调用
glfwSetCursorPosCallback(window, MouseCallback);
//注册鼠标滚轮的回调函数
//glfwSetScrollCallback(window, ScrollCallback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); //不允许光标出现
//glewExperimental = GL_TRUE; //在 OpenGL 4.3 以上要加这一句:让 glewInit() 可以顺利完成所有的初始化
if (GLEW_OK != glewInit())
{
std::cout << "Failed to initialise GLEW" << std::endl;
return -1;
}
glViewport(0, 0, screenWidth, screenHeight); //从(0,0)开始画点,直到 WIDTH 和 HEIGHT
//glEnable(GL_CULL_FACE); //只显示一半
//glEnable(GL_DEPTH_TEST); //深度测试
//glDepthFunc(GL_LESS); //深度信息小于当期信息,就把进行测试
/* //启动透明度混合,固定不能改,alpha 线性混合:设置当前为 α ,其他就为 1- α glEnable(GL_BLEND); //表示把渲染的图像融合到目标区域。也就是说源的每一个像素的alpha都等于自己的alpha, //目标的每一个像素的alpha等于1减去该位置源像素的alpha。因此不论叠加多少次,亮度是不变的。 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */
//vs 是顶点调色器,frag 是边缘调色器
Shader ourShader = Shader("core1.vs", "core1.frag"); //文件相对路径
//now the verte information comes below
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
};
//the date should be transfered to the memory on the Graphics Card,传到显存
GLuint VAO, VBO; //VAO:Vertex Array Object VBO:Vertex Buffer Object传数据
glGenVertexArrays(1, &VAO); //创建 VAO
glGenBuffers(1, &VBO);
glBindVertexArray(VAO); //设当前直线
glBindBuffer(GL_ARRAY_BUFFER, VBO); //VAO 和 VBO 成对出现
// transfer the data:传数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //静态访问,几乎不修改
//set the attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
6 * sizeof(GLfloat), (GLvoid *)0); //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
6 * sizeof(GLfloat), (GLvoid *)(3 * sizeof(GLfloat))); //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glm::mat4 view = glm::mat4(1.0f); //初始化 4 * 4 单位矩阵
//计算透视投影矩阵,设置几个参数就OK了
//将视锥体的空间投影成正方体/将长方体变成立方体
//第一个:正交投影:FoV/Field of View 视域。默认焦距是 1 。
//第二个:长宽比
//第三个、第四个:视平台里的上下底的距离,人的视觉空间。设置:近:0.1f。远:1000.0f。
glm::mat4 projection = glm::perspective(glm::radians(camera.
GetZoom()), static_cast<GLfloat>(screenWidth) / static_cast<
GLfloat>(screenHeight), 0.1f, 1000.0f);
//画图
while (!glfwWindowShouldClose(window))
{
GLfloat currentFrame = glfwGetTime(); //屏幕刚画出来的时间
deltaTime = currentFrame - lastTime; //更新两帧之间的间隔时间
lastTime = currentFrame; //更新上一帧绘制的时间
glfwPollEvents(); //把所有事件系统都取过来:键盘/鼠标等操作
DoMovement(); //获取完操作之后的额外参数
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //窗口背景颜色,RGB,最后一个是透明度
glClear(GL_COLOR_BUFFER_BIT); //清除颜色缓冲,否则会保留之前的移动轨迹颜色
//Bind the shader
ourShader.Use();
glm::mat4 model = glm::mat4(1.0f); //model
model = glm::rotate(model, glm::radians(20.0f) * static_cast<GLfloat>
(glfwGetTime()), glm::vec3(1.0f, 1.0f, 1.0f));
view = camera.GetViewMatrix(); //获得相机矩阵
//glm::mat4 projection = glm::perspective(glm::radians((float)fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
//glm 从 0.9.9 版本起,默认会将矩阵类型初始化为一个零矩阵(所有元素均为 0)
//glm::mat4 transform = glm::mat4(1.0f); //初始化 4 * 4 单位矩阵
//旋转
//GLM 希望它的角度是弧度制,radians 将角度转化为弧度制
//glfwGetTime():让图形一直变换,做一个类型转换,用 static_cast<GLfloat>,设为 GLfloat 型
//glm::vec3(1.0f, 1.0f, 1.0f),分别绕 x 轴、y 轴、z 轴进行旋转,如果都为 1.0f,就是绕和向量 (1,1,1) 转
//transform = glm::rotate(transform, glm::radians(20.0f) * static_cast<GLfloat>(glfwGetTime()), glm::vec3(1.0f, 1.0f, 1.0f));
//缩放,x、y、z 都缩放到原来的 0.5 倍
//transform = glm::scale(transform, glm::vec3(0.5f, 0.5f, 0.5f));
//平移
//transform =
//将矩阵传入着色器
GLuint modelLoc = glGetUniformLocation(ourShader.Program, "model"); //到 vs 找到那个 model 变量
GLuint viewLoc = glGetUniformLocation(ourShader.Program, "view"); //到 vs 找到那个 view 变量
GLuint projectionLoc = glGetUniformLocation(ourShader.Program, "projection"); //到 vs 找到那个 projection 变量
//GLuint transLoc = glGetUniformLocation(ourShader.Program, "transform"); //到 vs 找到那个 transform 变量
//Matrix4fv:4维矩阵,fv:浮点类型
//transLoc:变量 uniform 的位置
//1:代表只传入一个矩阵
//GL_FALSE:不对矩阵进行置换,即不交换矩阵的行和列。GLM 的默认布局就是列主序,所以并不需要置换矩阵
//最后:直接给出 transform 矩阵数组,这里我们要把矩阵转换成数组的格式传递。
//glUniformMatrix4fv(transLoc, 1, GL_FALSE, glm::value_ptr(transform)); //glUniformMatrix4fv:四个坐标 glUniform4fv:三个坐标
//Matrix4fv:4维矩阵,fv:浮点类型
//modelLoc:变量 uniform 的位置
//1:代表只传入一个矩阵
//GL_FALSE:不对矩阵进行置换,即不交换矩阵的行和列。GLM 的默认布局就是列主序,所以并不需要置换矩阵
//最后:直接给出 model 矩阵数组,这里我们要把矩阵转换成数组的格式传递。
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
//Draw the triangle
glBindVertexArray(VAO); //使用 VAO,直接绑定
glDrawArrays(GL_TRIANGLES, 0, 36); //画三角形,总共有 36 个顶点
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glfwSwapBuffers(window); //调用双面进行画,显示一个,另一个在画,画面更流畅
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
//lDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
void KeyCallback(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; //键盘按下去了,就设置为 true,即为1
}
else if (action == GLFW_RELEASE)
{
keys[key] = false; //键盘松开,设为 false
}
}
}
void MouseCallback(GLFWwindow *window, double xPos, double yPos)
{
if (firstMouse) { //只有第一次才把鼠标的初始位置更新为 xPos 和 yPos 值
lastX = xPos;
lastY = yPos;
firstMouse = false;
}
GLfloat xOffset = xPos - lastX; //当前位置 - 上一个x
GLfloat yOffset = lastY - yPos; //注意这里是相反的,因为 y 坐标是从底部往顶部依次增大的
lastX = xPos;
lastY = yPos;
camera.ProcessMouseMovement(xOffset, yOffset);
}
/* void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) { if (fov >= 1.0f && fov <= 45.0f) fov -= yoffset; if (fov <= 1.0f) fov = 1.0f; if (fov >= 45.0f) fov = 45.0f; } */
void DoMovement()
{
if (keys[GLFW_KEY_W] || keys[GLFW_KEY_UP]) { //W 或者 ↑
camera.ProcessKeyboard(FORWARD, deltaTime);
}
if (keys[GLFW_KEY_S] || keys[GLFW_KEY_DOWN]) {
camera.ProcessKeyboard(BACKWARD, deltaTime);
}
if (keys[GLFW_KEY_A] || keys[GLFW_KEY_LEFT]) {
camera.ProcessKeyboard(LEFT, deltaTime);
}
if (keys[GLFW_KEY_D] || keys[GLFW_KEY_RIGHT]) {
camera.ProcessKeyboard(RIGHT, deltaTime);
}
}
- Shader.h
#pragma once
//#ifndef shader_hpp
//#define shader_hpp
//#endif /* shader_hpp */
#include<string>
#include<fstream> //可以打开文件
#include<sstream>
#include<iostream>
#include<GL/glew.h>
class Shader {
GLuint vertex, fragment;
public:
GLuint Program;
Shader(const GLchar * vertexPath, const GLchar * fragmentPath)
{
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
vShaderFile.exceptions(std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::badbit);
try {
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
//文件关闭顺序,先 v 再 f
vShaderFile.close();
fShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure a) {
std::cout <<
"ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ"
<< std::endl;
}
//类型转换
const GLchar *vShaderCode = vertexCode.c_str();
const GLchar *fShaderCode = fragmentCode.c_str();
//import and compile the shader
vertex = glCreateShader(GL_VERTEX_SHADER); //不用重新定义
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex); //编译
GLint success;
GLchar infoLog[512];
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 = 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_FAILED\n"
<< infoLog << std::endl;
}
//create the program and link the program
this->Program = glCreateProgram(); //创建着色器程序
glAttachShader(this->Program, vertex);
glAttachShader(this->Program, fragment);
glLinkProgram(this->Program); //链接
glValidateProgram(this->Program); //可省略
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;
}
}
~Shader() {
glDetachShader(this->Program, vertex);
glDetachShader(this->Program, fragment);
glDeleteShader(vertex);
glDeleteShader(fragment);
glDeleteProgram(this->Program);
}
void Use() {
glUseProgram(this->Program);
}
};
- core1.vs
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 Color;
//uniform mat4 transform;
uniform mat4 model; //模型变化
uniform mat4 view; //相机坐标系
uniform mat4 projection; //投影变换
void main(){
//gl_Position = transform * vec4(position, 1.0f);
//从右向左乘:先乘 model,最后乘 projection
gl_Position = projection * view * model * vec4(position, 1.0f);
Color = color;
}
- core1.frag
#version 330 core
in vec3 Color;
out vec4 color;
void main(){
color = vec4(Color, 1.0f);
}
- Camera.h
//
// Camera.h
// Course 3
//
// Created by rui huang on 10/18/17.
// Copyright © 2017 rui huang. All rights reserved.
//
#pragma once
#include <vector>
#define GLEW_STATIC
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
enum Camera_Movement
{
FORWARD,
BACKWARD,
LEFT,
RIGHT
};
const GLfloat YAW = -90.0f;
const GLfloat PITCH = 0.0f;
const GLfloat SPEED = 6.0f;
const GLfloat SENSITIVITY = 0.25f;
const GLfloat ZOOM = 45.0f;
// An abstract camera class that processes input and calculates the corresponding Eular Angles, Vectors and Matrices for OpenGL
class Camera
{
public:
// 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), GLfloat yaw = YAW, GLfloat pitch = PITCH) :front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM)
{
this->position = position; //相机的起点
this->worldUp = up; //向前的量
this->yaw = yaw; //仰角:飞机上下动
this->pitch = pitch; //飞机左右动
this->updateCameraVectors(); //更新:建立坐标系
}
// Constructor with scalar values
Camera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw = YAW, GLfloat pitch = PITCH) :front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM)
{
this->position = glm::vec3(posX, posY, posZ);
this->worldUp = glm::vec3(upX, upY, upZ);
this->yaw = yaw;
this->pitch = pitch;
this->updateCameraVectors();
}
void ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime)
{
//乘以 deltaTime:消除电脑性能,控制时间一样长
GLfloat velocity = this->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;
}
}
void ProcessMouseMovement(GLfloat xOffset, GLfloat yOffset, GLboolean constrainPith = true)
{
xOffset *= this->mouseSensitivity;
yOffset *= this->mouseSensitivity;
this->yaw += xOffset;
this->pitch += yOffset;
if (constrainPith) { //保证用户只能看到天空或脚下,但是不能超越这个限制
//设置界限 < 90.0f,超过 90 度就会失效,视角发生逆转,因为直角是 90 度
if (this->pitch > 89.0f) {
this->pitch = 89.0f;
}
if (this->pitch < -89.0f) {
this->pitch = -89.0f;
}
}
this->updateCameraVectors();
}
void ProcessMouseScroll(GLfloat yOffset)
{
}
glm::mat4 GetViewMatrix()
{
//lookAt:观察矩阵:摄像机位置;目标位置;上向量
return glm::lookAt(this->position, this->position + this->front, this->up);
}
GLfloat GetZoom()
{
return this->zoom;
}
private:
glm::vec3 position;
glm::vec3 front;
glm::vec3 up;
glm::vec3 right;
glm::vec3 worldUp;
GLfloat yaw;
GLfloat pitch;
GLfloat movementSpeed;
GLfloat mouseSensitivity;
GLfloat zoom;
void updateCameraVectors()
{
glm::vec3 front;
//通过俯仰角和偏航角来计算以得到真正的方向向量
//极坐标系下的:x、y、z轴
front.x = cos(glm::radians(this->pitch)) * cos(glm::radians(this->yaw)); //x 轴向前
front.y = sin(glm::radians(this->pitch)); //y 轴向上
front.z = cos(glm::radians(this->pitch)) * sin(glm::radians(this->yaw)); //z 轴向右
this->front = glm::normalize(front); //向前
this->right = glm::normalize(glm::cross(this->front, this->worldUp)); //向右:向前 × 向上
this->up = glm::normalize(glm::cross(this->right, this->front)); //向上:向右 × 向前
}
};
程序正常运行,能得到已下图形。鼠标能控制立方体移动。键盘能控制立方体移动和缩放。
二、讲解
鼠标、键盘操作回应函数。
//键盘回应
void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode);
//监听鼠标移动事件:xPos 和 yPos 代表当前鼠标位置
void MouseCallback(GLFWwindow *window, double xPos, double yPos);
//键盘移动
void DoMovement();
初始化一个相机。
//初始化一个相机
Camera camera(glm::vec3(0.0f, 0.0f, 2.0f));
//设置初始量
GLfloat lastX = WIDTH / 2.0;
GLfloat lastY = HEIGHT / 2.0;
bool firstMouse = true;
存放所有键盘操作。
bool keys[1024]; //存放获取的所有键盘操作,先存下来再进行操作
初始化间隔时间和上一帧绘制时间。
GLfloat deltaTime = 0.0f; //两帧之间的间隔时间
GLfloat lastTime = 0.0f; //上一帧绘制的时间
键盘、鼠标操作函数。
// Set the required callback function
//KeyCallback 是响应键盘消息的回调函数
glfwSetKeyCallback(window, KeyCallback);
//mouse_callback 是响应鼠标消息的回调函数,鼠标一移动 MouseCallback 函数就会被调用
glfwSetCursorPosCallback(window, MouseCallback);
//注册鼠标滚轮的回调函数
//glfwSetScrollCallback(window, ScrollCallback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); //不允许光标出现
//glewExperimental = GL_TRUE; //在 OpenGL 4.3 以上要加这一句:让 glewInit() 可以顺利完成所有的初始化
顶点位置 + 颜色坐标。
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
};
设置顶点属性指针。
//set the attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
6 * sizeof(GLfloat), (GLvoid *)0); //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
6 * sizeof(GLfloat), (GLvoid *)(3 * sizeof(GLfloat))); //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始
glEnableVertexAttribArray(1);
初始化矩阵。
glm::mat4 view = glm::mat4(1.0f); //初始化 4 * 4 单位矩阵
//计算透视投影矩阵,设置几个参数就OK了
//将视锥体的空间投影成正方体/将长方体变成立方体
//第一个:正交投影:FoV/Field of View 视域。默认焦距是 1 。
//第二个:长宽比
//第三个、第四个:视平台里的上下底的距离,人的视觉空间。设置:近:0.1f。远:1000.0f。
glm::mat4 projection = glm::perspective(glm::radians(camera.
GetZoom()), static_cast<GLfloat>(screenWidth) / static_cast<
GLfloat>(screenHeight), 0.1f, 1000.0f);
时间变化。
GLfloat currentFrame = glfwGetTime(); //屏幕刚画出来的时间
deltaTime = currentFrame - lastTime; //更新两帧之间的间隔时间
lastTime = currentFrame; //更新上一帧绘制的时间
model。
glm::mat4 model = glm::mat4(1.0f); //model
model = glm::rotate(model, glm::radians(20.0f) * static_cast<GLfloat>
(glfwGetTime()), glm::vec3(1.0f, 1.0f, 1.0f));
view = camera.GetViewMatrix(); //获得相机矩阵
将矩阵传入着色器。
//将矩阵传入着色器
GLuint modelLoc = glGetUniformLocation(ourShader.Program, "model"); //到 vs 找到那个 model 变量
GLuint viewLoc = glGetUniformLocation(ourShader.Program, "view"); //到 vs 找到那个 view 变量
GLuint projectionLoc = glGetUniformLocation(ourShader.Program, "projection"); //到 vs 找到那个 projection 变量
MVP。
//Matrix4fv:4维矩阵,fv:浮点类型
//modelLoc:变量 uniform 的位置
//1:代表只传入一个矩阵
//GL_FALSE:不对矩阵进行置换,即不交换矩阵的行和列。GLM 的默认布局就是列主序,所以并不需要置换矩阵
//最后:直接给出 model 矩阵数组,这里我们要把矩阵转换成数组的格式传递。
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
键盘操作响应函数。
void KeyCallback(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; //键盘按下去了,就设置为 true,即为1
}
else if (action == GLFW_RELEASE)
{
keys[key] = false; //键盘松开,设为 false
}
}
}
鼠标操作响应函数。
void MouseCallback(GLFWwindow *window, double xPos, double yPos)
{
if (firstMouse) { //只有第一次才把鼠标的初始位置更新为 xPos 和 yPos 值
lastX = xPos;
lastY = yPos;
firstMouse = false;
}
GLfloat xOffset = xPos - lastX; //当前位置 - 上一个x
GLfloat yOffset = lastY - yPos; //注意这里是相反的,因为 y 坐标是从底部往顶部依次增大的
lastX = xPos;
lastY = yPos;
camera.ProcessMouseMovement(xOffset, yOffset);
}
键盘移动响应函数。
void DoMovement()
{
if (keys[GLFW_KEY_W] || keys[GLFW_KEY_UP]) { //W 或者 ↑
camera.ProcessKeyboard(FORWARD, deltaTime);
}
if (keys[GLFW_KEY_S] || keys[GLFW_KEY_DOWN]) {
camera.ProcessKeyboard(BACKWARD, deltaTime);
}
if (keys[GLFW_KEY_A] || keys[GLFW_KEY_LEFT]) {
camera.ProcessKeyboard(LEFT, deltaTime);
}
if (keys[GLFW_KEY_D] || keys[GLFW_KEY_RIGHT]) {
camera.ProcessKeyboard(RIGHT, deltaTime);
}
}