知识点归纳
现实世界中有无数种颜色,每一个物体都有它们自己的颜色。我们要做的工作是使用(有限的)数字来模拟真实世界中(无限)的颜色,因此并不是所有的现实世界中的颜色都可以用数字来表示。然而我们依然可以用数字来代表许多种颜色,并且你甚至可能根本感觉不到他们与真实颜色之间的差异。颜色可以数字化的由红色(Red)、绿色(Green)和蓝色(Blue)三个分量组成,它们通常被缩写为RGB。这三个不同的分量组合在一起几乎可以表示存在的任何一种颜色。
我们在现实生活中看到某一物体的颜色并不是这个物体的真实颜色,而是它所反射(Reflected)的颜色。换句话说,那些不能被物体吸收(Absorb)的颜色(被反射的颜色)就是我们能够感知到的物体的颜色。例如,太阳光被认为是由许多不同的颜色组合成的白色光。如果我们将白光照在一个蓝色的玩具上,这个蓝色的玩具会吸收白光中除了蓝色以外的所有颜色,不被吸收的蓝色光被反射到我们的眼中,使我们看到了一个蓝色的玩具。
白色的阳光是一种所有可见颜色的集合,物体吸收了其中的大部分颜色,它仅反射了那些代表这个物体颜色的部分,这些被反射颜色的组合就是我们感知到的颜色。
当我们把光源的颜色与物体的颜色相乘,所得到的就是这个物体所反射该光源的颜色(也就是我们感知到的颜色)。
来看一下光源色和物体颜色的反射运算:
// 光源的颜色值lightColor(RGB每个分量值可取范围为0到1)
glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
// 物体的颜色值toyColor
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
// 当光源直射物体时,反射后的反射光颜色值result
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);
我们可以看到玩具在进行反射时吸收了白色光源颜色中的大部分颜色,但它对红、绿、蓝三个分量都有一定的反射,反射量是由物体本身的颜色所决定的。这也代表着现实中的光线原理。由此,我们可以定义物体的颜色为这个物体从一个光源反射各个颜色分量的多少。
我们可以通过物体对不同颜色光的反射来的得到意想不到的不到的颜色,从此创作颜色已经变得非常简单。
教程原文
程序归纳
该章节程序的最终运行结果:
通用的顶点着色器:
#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);
}
普通物体的片段着色器
#version 330 core
out vec4 color;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
color = vec4(lightColor * objectColor, 1.0f);
}
光源的片段着色器
#version 330 core
out vec4 color;
void main()
{
color = vec4(1.0f); //设置四维向量的所有元素为 1.0f
}
主程序
// 标准输出
#include <string>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// 着色器类和摄像机类
#include "Shader.h"
#include "Camera.h"
// GLM 数学库
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// 图片数据读取库
#include <SOIL.h>
// 定义窗口大小
GLuint screenWidth = 800, screenHeight = 600;
// GLFW窗口需要注册的函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
// 处理摄像机位置移动的函数
void Do_Movement();
// 定义摄像机
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// 定义数组存储按键信息
bool keys[1024];
//
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
glm::vec3 lightPos(1.2f, 0.0f, -2.0f);
// The MAIN function, from here we start our application and run our Game loop
int main()
{
// 初始化GLFW
glfwInit();
// 设置glfw使用的OpenGL版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 设置使用OpenGL的核心模式
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 设置窗口大小可改变性 为不可变
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// GLFW会自动创建一个每像素4个子采样点的深度和样本缓冲。这也意味着所有缓冲的大小都增长了4倍。
glfwWindowHint(GLFW_SAMPLES, 4);
// 创建GLFW的窗口
GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr);
// 设置window窗口线程为主线程
glfwMakeContextCurrent(window);
// 注册窗口的事件
glfwSetKeyCallback(window, key_callback); // 按键检测
glfwSetCursorPosCallback(window, mouse_callback); // 鼠标移动检测
glfwSetScrollCallback(window, scroll_callback); // 滚轮滑动检测
// 设置隐藏光标模式
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// 设置GLEW为更现代的模式
glewExperimental = GL_TRUE;
// 初始化GLEW
glewInit();
// 设置视口的位置和大小(位置是相对于窗口左下角的)
glViewport(0, 0, screenWidth, screenHeight);
// 开启深度测试功能
glEnable(GL_DEPTH_TEST);
// 根据顶点着色器和片段着色器位置创建着色器程序
Shader lightingShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");
Shader lampShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");
// 顶点数据(位置+纹理坐标)
GLfloat vertices[] = {
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f
};
// 设置VBO和VAO
GLuint VBO, VAO;
// 先申请VAO和VBO的显存
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 绑定VAO
glBindVertexArray(VAO);
// 绑定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 将顶点数据传输到显存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 位置数据解析
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0); // 解绑VAO
GLuint lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// 只需要绑定VBO不用再次设置VBO的数据,因为容器(物体)的VBO数据中已经包含了正确的立方体顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 设置灯立方体的顶点属性指针(仅设置灯的顶点数据)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
// 游戏循环
while (!glfwWindowShouldClose(window))
{
// 计算帧相差时间
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// 窗口的事件检测
glfwPollEvents();
// 根据事件移动摄像机
Do_Movement();
// 设置清空屏幕颜色缓存所使用颜色
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
// 清空 屏幕颜色缓存、深度缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 被光源照亮的普通物体的着色器
lightingShader.Use();
GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");
GLint lightColorLoc = glGetUniformLocation(lightingShader.Program, "lightColor");
glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色
// 创建观察矩阵( 只要创建矩阵记得初始化= glm::mat4(1.0f) )
glm::mat4 view = glm::mat4(1.0f);
// 获取观察矩阵(根据摄像机的状态)
view = camera.GetViewMatrix();
// 创建投影矩阵
glm::mat4 projection = glm::mat4(1.0f);
// 计算投影矩阵(fov视野为摄像机的属性camera.Zoom)
projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 1000.0f);
// 计算顶点着色器中矩阵的位置值
GLint modelLoc = glGetUniformLocation(lightingShader.Program, "model");
GLint viewLoc = glGetUniformLocation(lightingShader.Program, "view");
GLint projLoc = glGetUniformLocation(lightingShader.Program, "projection");
// 将观察矩阵和投影矩阵传入对应的位置(记得转换)
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
// 定义模型矩阵
glBindVertexArray(VAO);
glm::mat4 model = glm::mat4(1.0f);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
// 使用光源着色器
lampShader.Use();
modelLoc = glGetUniformLocation(lampShader.Program, "model");
viewLoc = glGetUniformLocation(lampShader.Program, "view");
projLoc = glGetUniformLocation(lampShader.Program, "projection");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(1.0f)); // Make it a smaller cube
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
// 交换前后缓冲区
glfwSwapBuffers(window);
}
// 释放VAO和VBO的显存
glDeleteVertexArrays(1, &VAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
// 释放glfw的内存
glfwTerminate();
return 0;
}
// 根据按键信息移动摄像机
void Do_Movement()
{
// 如果某个键按下,就执行摄像机对应的方法(更新摄像机的位置)
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);
}
// 按键回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
// 如果按下ESE则设置窗口应该关闭
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key < 1024)
{
// 如果按下某一个键,则设置其keys为true,如果松开则设置回false
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
keys[key] = false;
}
}
// 鼠标移动的回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
// 第一次进窗口鼠标坐标很大,所以第一次调用函数我们需要把它设置为一个正常的值
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
// 计算鼠标在竖直方向和水平方向的偏移(屏幕坐标系往右x大,但往下是y大,所以运算顺序不同)
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)
{
// 调用摄像机的鼠标滚动函数(根据位置偏移更新摄像机fov视野大小)
camera.ProcessMouseScroll(yoffset);
}
如果你想运行以上程序,我为你提供着色器头文件和摄像机头文件的源代码。记得更改程序中创建着色器时的路径。
着色器头文件(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):
#pragma once
// Std. Includes
#include <vector>
// GL Includes
#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 = 3.0f;
// 摄像机旋转速度
const GLfloat SENSITIVTY = 0.25f;
// 摄像机角度(关乎远近)
const GLfloat ZOOM = 45.0f;
// An abstract camera class that processes input and calculates the corresponding Eular Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
// 摄像机的位置、方向、上向量、右向量、世界上向量
glm::vec3 Position;
glm::vec3 Front;
glm::vec3 Up;
glm::vec3 Right;
glm::vec3 WorldUp;
// 旋转角度
GLfloat Yaw;
GLfloat Pitch;
// 移动速度和旋转速度
GLfloat MovementSpeed;
GLfloat MouseSensitivity;
// 摄像机的视野(fov)
GLfloat Zoom;
// 无参构造函数将使用定义的全局变量值对摄像机对象的所有属性进行初始化
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(SENSITIVTY), Zoom(ZOOM)
{
this->Position = position;
this->WorldUp = up;
this->Yaw = yaw;
this->Pitch = pitch;
this->updateCameraVectors();
}
// 有参构造函数接收的参数:摄像机位置、摄像机的上向量、摄像机的旋转角度
Camera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw, GLfloat pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVTY), Zoom(ZOOM)
{
this->Position = glm::vec3(posX, posY, posZ);
this->WorldUp = glm::vec3(upX, upY, upZ);
this->Yaw = yaw;
this->Pitch = pitch;
this->updateCameraVectors();
}
// 根据摄像机位置、朝向、上向量生成其对应的观察矩阵
glm::mat4 GetViewMatrix()
{
return glm::lookAt(this->Position, this->Position + this->Front, this->Up);
}
// 当按下某一个键时,根据按键和帧时间进行移动(移动摄像机的位置)
void ProcessKeyboard(Camera_Movement direction, GLfloat 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 constrainPitch = true)
{
xoffset *= this->MouseSensitivity;
yoffset *= this->MouseSensitivity;
this->Yaw += xoffset;
this->Pitch += yoffset;
// Make sure that when pitch is out of bounds, screen doesn't get flipped
if (constrainPitch)
{
if (this->Pitch > 89.0f)
this->Pitch = 89.0f;
if (this->Pitch < -89.0f)
this->Pitch = -89.0f;
}
// 更新摄像机状态
this->updateCameraVectors();
}
// 根据鼠标滚轮的缩放信息调整视野fov的大小
void ProcessMouseScroll(GLfloat yoffset)
{
// 鼠标滚动数值太大因此缩小一些
yoffset = yoffset * 0.1f;
if (this->Zoom >= 1.0f && this->Zoom <= 45.0f)
this->Zoom -= yoffset;
if (this->Zoom <= 1.0f)
this->Zoom = 1.0f;
if (this->Zoom >= 45.0f)
this->Zoom = 45.0f;
}
private:
// 更新摄像机状态(使用Yaw、Pitch、WorldUp,得到Front、Right、Up)
void updateCameraVectors()
{
// 根据旋转信息Yaw和Pitch计算摄像机的朝向
glm::vec3 front;
front.x = cos(glm::radians(this->Yaw)) * cos(glm::radians(this->Pitch));
front.y = sin(glm::radians(this->Pitch));
front.z = sin(glm::radians(this->Yaw)) * cos(glm::radians(this->Pitch));
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));
}
};