这里的基础光照使用的是:
冯氏光照模型
冯氏光照模型分为三部分
在开始之前我要强调一点,在前面半章对于光照的运用都是使用固定的法线,并且在“世界坐标”下运行
之后会在观察坐标下重写我们的顶点着色器和片段着色器
观察坐标的优点在于我们可以轻松获得摄像机的位置或者说我们观察者的位置,他始终处于观察坐标下的(0,0,0)位置
再让我们复习一下坐标系统中各个空间
1环境光照(Ambient)
把环境光照添加到场景里非常简单。
我们用光的颜色乘以一个很小的常量环境因子,再乘以物体的颜色,然后将最终结果作为片段的颜色
2漫反射(diffuse)
我们知道两个单位向量的夹角越小,它们点乘的结果越倾向于1。
当两个向量的夹角为90度的时候,点乘会变为0。这同样适用于θ,θ越大,光对片段颜色的影响就应该越小。
那么此时我们就要一个法线方向和一个指向光源的方向(图形学里面光照方向指的是从每一个世界坐标出发指向光源的位置,没有物理意义只是方便计算)。
通过他们的点积就能得到漫反射效果
3镜面光照(Specular)
我们通过根据法向量翻折入射光的方向来计算反射向量。
然后我们计算反射向量与观察方向的角度差,它们之间夹角越小,镜面光的作用就越大。由此产生的效果就是,我们看向在入射光在表面的反射方向时,会看到一点高光。
观察向量是我们计算镜面光照时需要的一个额外变量,我们可以使用观察者的世界空间位置和片段的位置来计算它。之后我们计算出镜面光照强度,用它乘以光源的颜色,并将它与环境光照和漫反射光照部分加和。
但是这里我们使用的是世界坐标而没用观察矩阵
我们选择在世界空间进行光照计算,但是大多数人趋向于更偏向在观察空间进行光照计算。
在观察空间计算的优势是,观察者的位置总是在(0, 0, 0),所以你已经零成本地拿到了观察者的位置。
然而,若以学习为目的,我认为在世界空间中计算光照更符合直觉。
如果你仍然希望在观察空间计算光照的话,你需要将所有相关的向量也用观察矩阵进行变换(不要忘记也修改法线矩阵)。
//cubeShader.fs
#version 330 core
out vec4 FragColor;
uniform vec3 objectColor;//物体颜色
uniform vec3 lightColor;//光源颜色
uniform vec3 lightPos;//需要光源的位置来计算漫反射
uniform vec3 viewPos;
in vec3 Normal;
in vec3 FragPos;
void main()
{
//ambient
float ambientStrength = 0.1;//环境光照强度系数
vec3 ambient = ambientStrength * lightColor;//环境光=环境光照强度*光源颜色
// diffuse //漫反射
vec3 norm = normalize(Normal);//normalize标准化坐标使用其单位向量
vec3 lightDir = normalize(lightPos-FragPos);//方向是FragPos指向lightPos 图形学里面光照方向指的是从每一个世界坐标出发指向光源的位置,没有物理意义只是方便计算
float diff = max(dot(norm, lightDir), 0.0);//使用最大值max来保证结果不为负数
vec3 diffuse = diff * lightColor;//最后乘以光源颜色就是漫反射的效果了
//高光反射
vec3 viewDir = normalize(viewPos - FragPos);//从物体指向视线位置的向量 在这里就是从物体指向摄像机
vec3 reflectDir = reflect(-lightDir, norm);//前面我们说到lightDir 是物体指向光源的方向 但是这里我们要使用光源指向物体的方向,还需要提供一个法线来计算反射向量
float specularStrength=0.5;//光强度
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);//视线方向和反射向量的点积,就是反射角与视线方向夹角的余弦值 按照2的几次方来来划分 这里是2的五次方就是第五个度 当然也有2的8次方也就是256
//然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小
vec3 specular = specularStrength * spec * lightColor;//最后的高光反射=反光度*光强度*光源颜色
vec3 result = (ambient + diffuse+ specular) * objectColor;//最终结果=(环境光+漫反射+高光反射)*物体本身的颜色
FragColor = vec4(result, 1.0);
}
//cubeShader.vs
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;//告诉GPU位置1的属性为法线向量
out vec3 Normal;
out vec3 FragPos;
uniform vec3 viewPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));//片段的位置就是这个立方体经过模型变换之后的位置
Normal = aNormal;
}
//lightShader.fs
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0);
}
//lightShader.vs
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
#include "Shader.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#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 <iostream>
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow* window);
// settings
const unsigned int SCR_WIDTH = 800; //定义屏幕空间的大小
const unsigned int SCR_HEIGHT = 600;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
bool firstMouse = true;
float yaw = -90.0f; // 偏航初始化为-90.0度,因为偏航为0.0会导致指向右边的方向向量,所以我们最初向左旋转了一点。
float pitch = 0.0f; //初始化俯仰角
float lastX = 800.0f / 2.0;//为了把初始位置设置为屏幕中心所以取屏幕空间大小的一半
float lastY = 600.0 / 2.0;
float fov = 45.0f;//初始的视场角
// timing
float deltaTime = 0.0f; // time between current frame and last frame
float lastFrame = 0.0f;
//glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
void processInput(GLFWwindow* window);
float vertices[] = {
//顶点 //法线normal
-0.5f, -0.5f, -0.5f, 0.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, -1.0f,
0.5f, 0.5f, -0.5f, 0.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, -1.0f,
-0.5f, -0.5f, 0.5f, 0.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, 1.0f,
0.5f, 0.5f, 0.5f, 0.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, 1.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, 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, 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, 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, 0.0f,
-0.5f, -0.5f, -0.5f, 0.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, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.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, -1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.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, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.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, 1.0f, 0.0f
};
//定义一个vec3类型的数组来存位移矩阵
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)
};
int main()
{
//Glfw:初始化和配置
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
//glfw窗口创建
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);//使用定义的屏幕空间的大小
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
//首先我们要告诉GLFW,它应该隐藏光标,并捕捉(Capture)它。
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//加载所有OpenGL函数指针
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 配置全局opengl状态
// -----------------------------
glEnable(GL_DEPTH_TEST);
// 构建并编译我们的shader程序
// ------------------------------------
Shader ourShader("shaderSampler.vs", "shaderSampler.fs");
Shader modelShader("cubeShader.vs", "cubeShader.fs");
Shader lightShader("lightShader.vs", "lightShader.fs");
//建立和编译顶点数据(和缓冲区),配置顶点属性
// ------------------------------------------------------------------
unsigned int VBO, VAO, VAO2;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindVertexArray(VAO);//光源使用VAO来管理顶点属性
//位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);//我们只需要使用前面三个顶点其他就不用管了
glEnableVertexAttribArray(0);
//之后在顶点着色器layout (location = 1) in vec3 aNormal;//告诉GPU位置1的属性
glGenVertexArrays(1, &VAO2);//物体使用VAO2来管理顶点属性
glBindVertexArray(VAO2);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//法线属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 加载并创建一个纹理
// -------------------------
unsigned int texture1, texture2;
// texture 1
// ---------
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
// 设置纹理wrapping参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// 设置纹理filtering参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//加载图像,创建纹理和生成mipmaps //多级原理纹理
int width, height, nrChannels;
stbi_set_flip_vertically_on_load(true); // 告诉stb_image.h在y轴上翻转已加载的纹理。
//为什么需要翻转Y轴是因为纹理图片开始位置是右上而我们顶点的坐标(0,0)点是左下
unsigned char* data = stbi_load("container.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);
// texture 2
// ---------
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
// 设置纹理wrapping参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// 设置纹理filtering参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// load image, create texture and generate mipmaps
//data = stbi_load(("aotu.jpg"), &width, &height, &nrChannels, 0);
//data = stbi_load(("shanshui.jpg"), &width, &height, &nrChannels, 0);
data = stbi_load(("awesomeface.png"), &width, &height, &nrChannels, 0);
if (data)
{
//如果没有贴图请优先注意这个RGBA中的alpha(A)通道 如果你的贴图有alpha通道请务必使用RGBA模式否则无法显示贴图
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
// 为每个采样器告诉opengl它属于哪个纹理单元(只需要做一次)
// -------------------------------------------------------------------------------------------
ourShader.use();
ourShader.setInt("texture1", 0);
ourShader.setInt("texture2", 1);
// ourShader.setVec3("lightPos", lightPos);
//渲染循环
// -----------
while (!glfwWindowShouldClose(window))
{
float currentFrame = 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); // 清除上一帧的颜色缓冲 以及 深度测试缓冲
// bind textures on corresponding texture units
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
//激活着色器
float angle = 20.0f * 0 * (float)glfwGetTime();//给一个glfwGetTime让模型旋转起来
glm::mat4 projection = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 model = glm::mat4(1.0f);
glm::vec3 lightPos = glm::vec3(cubePositions[2]);//光源位置
ourShader.use();
projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);//投影矩阵 参数:视口大小,屏幕宽高比,以及near和far
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);//lookAt矩阵 参数:摄像机位置 ,观察目标的位置 ,竖直向上的方向
model = glm::translate(model, cubePositions[0]);//把数组传进去给每一个新建的模型在世界坐标下有不同的位移
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
lightShader.use();
lightPos.x = 1.0f + sin(glfwGetTime()) * 2.0f;
lightPos.y = sin(glfwGetTime() / 2.0f) * 1.0f;
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);//把数组传进去给每一个新建的模型在世界坐标下有不同的位移
angle = 20.0f * 2 * (float)glfwGetTime();//给一个glfwGetTime让模型旋转起来
model = glm::rotate(model, glm::radians(angle) * 0, glm::vec3(1.0f, 0.3f, 0.5f));
lightShader.setMat4("model", model);
lightShader.setMat4("projection", projection);
lightShader.setMat4("view", view);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
modelShader.use();
model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[0]);//把数组传进去给每一个新建的模型在世界坐标下有不同的位移
angle = 20.0f * 1 * (float)glfwGetTime();//给一个glfwGetTime让模型旋转起来
model = glm::rotate(model, glm::radians(angle) * 0, glm::vec3(1.0f, 0.5f, 0.5f));//rotate 模型位置 旋转角 旋转轴
modelShader.setMat4("model", model);//设置模型变换矩阵
modelShader.setMat4("projection", projection);//设置投影变化矩阵
modelShader.setMat4("view", view);//设置视图变化矩阵
modelShader.setVec3("objectColor", 1, 0.5, 0.5);//设置物体的颜色
modelShader.setVec3("lightColor", 1, 1, 1);//设置光源的颜色当然也可以设置一个uniform来设置变量进行设置
modelShader.setVec3("lightPos", lightPos);//设置光源位置
modelShader.setVec3("viewPos", cameraPos);//将相机的位置设置为观察位置
glBindVertexArray(VAO2);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
float cameraSpeed = 5.5f * deltaTime;; // adjust accordingly
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
cameraPos += cameraSpeed * cameraFront;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
cameraPos -= cameraSpeed * cameraFront;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)//这里给了一个空格键使我们可以在Y轴上移动
cameraPos += cameraUp * cameraSpeed;
//cameraPos.y = 0.0f;
//可以通过把Y方向上的向量设置为0来使他成为一个FPS类型的摄像机只能在XZ平面上移动
}
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
// 这个bool变量初始时是设定为true的
//我们在一开始的时候需要把他设为屏幕的中心点
//如果不这样干 程序一开始就会调用回调函数指向你鼠标进去的时候所在屏幕的位置
//这样就离中心点很远了
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
//然后在鼠标的回调函数中我们计算当前帧和上一帧鼠标位置的偏移量:
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // y坐标是从下到上
lastX = xpos;
lastY = ypos;
float sensitivity = 0.1f; // sensitivity这个值可以随便设置
xoffset *= sensitivity;
yoffset *= sensitivity;
yaw += xoffset;
pitch += yoffset;
// 为了保证摄像机不会整个翻车
if (pitch > 89.0f)
pitch = 89.0f;
if (pitch < -89.0f)
pitch = -89.0f;
//在xz平面上看向Y轴
//这里我们只更新了y值,仔细观察x和z分量也被影响了。从三角形中我们可以看到它们的值等于:
//direction.x = cos(glm::radians(pitch));
//direction.y = sin(glm::radians(pitch)); // 注意我们先把角度转为弧度
//direction.z = cos(glm::radians(pitch));//这里Y轴更新确实会影响到Z轴但是不是很懂为什么直接等于cos(pitch)
//
//
//
//这里我们只更新了y值,仔细观察x和z分量也被影响了。从三角形中我们可以看到它们的值等于:
//direction.x = cos(glm::radians(yaw));
//direction.y =1 // Y不变
//direction.z = sin(glm::radians(yaw));
//
//下面的等式相当于是先俯仰角的旋转变换完成之后再乘以这个偏航角
//把上面两步合起来
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));
cameraFront = glm::normalize(front);
}
// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
//yoffset就是我们滚轮竖直滚动的方向
if (fov >= 1.0f && fov <= 45.0f)
fov -= yoffset;
//为他设定一个边界 在1到45之间
if (fov < 1.0f)
fov = 1.0f;
if (fov > 45.0f)
fov = 45.0f;
}
运行成功之后光源会随时间发生改变
现在我们将会引入法线矩阵
什么是法线矩阵,以及为什么我们需要使用法线矩阵?
对于规则缩放的物体来讲,规则的缩放只会影响法线的大小,方向并不会改变,这很容易通过归一化或者标准化(normalize)来消除规则缩放的影响
但是对于不规则变化的物体来说,法线就不会再垂直于对应的表面了,这样光照就会被破坏。
如下图:
法线矩阵:
在观察坐标下就会存在这样的问题,乘以一个观察矩阵只会法线会发生改变,这样我们就需要使用法线矩阵来消除不规则变化带来的影响
这是我们希望得到的效果
我们希望切线方向始终与法线方向
这是实际上非等比缩放得到的效果
现在假设切线向量变换矩阵为M,变换后的切线向量T’ = M * T,同样的我们假设现在有一个正确法线变换矩阵G,使得变换后的法线N’ = G * N,那么变换后,N’点乘T’依然是等于零的,于是我们可以等到等式:
由于向量的点乘等价于向量的乘积(可以把转置的向量看做一个Nx1列的矩阵):
又由于乘积的转置等于转置的乘积可得:
I是单位矩阵
由此可得:
Normal = mat3(transpose(inverse(view * model))) * aNormal;
现在我们就可以使用法线矩阵在观察坐标来计算我们的基础光照了
//cubeShader.fs
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec3 LightPos; //在视图坐标下的光源位置
uniform vec3 lightColor;//光源的颜色 在主程序中设置的
uniform vec3 objectColor;//物体的颜色 在主程序中设置的
void main()
{
// ambient 环境光照
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor; //光照的影响因子乘以光源的颜色
// diffuse 漫反射
vec3 norm = normalize(Normal);//法线的单位向量
vec3 lightDir = normalize(LightPos - FragPos);//在图形学中之前计算漫反射设置的光线方向是由物体指向光源的方向 只是为了方便计算没有物理意义
float diff = max(dot(norm, lightDir), 0.0);//指向光源的方向再点成法线算出夹角 角度越小说明光源在头顶上(想象太阳在中午时候)光照最强 为什么使用max是因为余弦为负数并没用意义
vec3 diffuse = diff * lightColor;
// specular 镜面高光
float specularStrength = 0.5;
vec3 viewDir = normalize(-FragPos); // 由于在视图坐标下摄像机的位置为0 所以0-FragPos=-FragPos 方向由物体指向观察者(摄像机)
vec3 reflectDir = reflect(-lightDir, norm); //在图形学中之前计算漫反射设置的光线方向是由物体指向光源的方向 只是为了方便计算没有物理意义
//这里取反方向就是光源方向指向物体的方向
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);//点成反射光和观察者的方向 32次幂表这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}
//cubeShader.vs
#version 330 core
layout (location = 0) in vec3 aPos;//设置第一个属性为顶点位置
layout (location = 1) in vec3 aNormal;//设置第二个属性为法线
out vec3 FragPos;//输出一个物体的位置
out vec3 Normal;//输出经过法线矩阵变化后的法线向量
out vec3 LightPos;//输出一个在视图坐标下的光源的位置
uniform vec3 lightPos; //在modelShader.setVec3("lightPos", lightPos);中设置的光源位置
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(view * model * vec4(aPos, 1.0));//视图坐标下的物体的位置
Normal = mat3(transpose(inverse(view * model))) * aNormal;//视图坐标下的 经过法线矩阵变化后的法线
LightPos = vec3(view * vec4(lightPos, 1.0)); // 在视图坐标下的光源位置
}