光照,在计算机图形当中一直都是很难得话题,在一些大的游戏公司里面有专门研究光照算法得计算机工程师,笔者在计算机图形方面还只是一直菜鸟,只能给大家介绍一些比较简单的常用的光照模型,此篇文章为大家介绍风氏光照模型。
颜色的互相影响
在平时的生活当中我们知道,使用不同颜色的灯光对物体颜色的表现有相当程度上的影响,那么这种灯光的颜色对物体颜色的影响我们通常采用将两个颜色向量相乘的方式来表示。
//灯光的颜色
glm::vec3 lightColor(1.0f,1.0f,1.0f);
//被照射物体的颜色
glm::vec3 objectColor(1.0f,0.5f,0.31f);
//物体最终表现出来的颜色
glm::vec3 result = lightColor * objectColor;
在该文章当中我们使用白色来作为光源的颜色,如果说读者对某种颜色的光源情有独钟拿到此案例的代码后可以自行修改。当然光有这么一个公式肯定不够的,如果我们使用的是白色的光源对于被着色的物体来讲可以说是没有影响,这没有办法反应我们现实生活当中的光照情况,因为在生活当中哪怕就是被同一光源照射,光照的角度和强度都会让物体表现出不同的颜色。光照的角度其实也是影响光照的强度,所以我们下面就要讨论如何合理的修改光照的强度。
风氏光照模型
该模型就是计算光线照射到物体上的强度的一个模型,风氏光照模型的光线强度主要由以下三个部分,环境光照,漫反射光照,镜面光照。
环境光照
即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。(这也就解释了,为什么有的游戏场景中没有任何光源,照样能看清周围环境)环境光照非常的复杂,读者有兴趣可以去查找相关的资料来进行学习,我们在这里只设置一个简单的公式来表示环境光照。
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
漫反射光照
模拟光源对物体的方向性影响(Directional Impact)。它是风氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。如下图所示
图中的是物体表面的法线,这个法线一般都是垂直于物体表面朝外的,(如果是一个多面不规则物体,那么这些表面的法线就需要单独的公式来进行计算了)当此法线与光源方向的夹角越小,那么该表面就越是正对光源,光照强度越强。我们令物体表面中点到光源的向量为
,使用余弦
来表示光照的强度,由于三角函数在计算机中的计算相率很差所以我们对此公式需要做一些优化。
,我们在这里将向量
进行标准化,所以我么得到最后的公式
,有前面的公式推论可知,我们可以使用两个标准向量的点乘来表示光线的强弱。具体的代码如下所示:
//表面法线方向
vec3 norm = normalize(Normal);
//表面中点指向光源的方向向量
vec3 lightDir = normalize(lightPos - FragPos);
//为负数的反射光线没有意义
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
镜面光照
模拟有光泽物体上面出现的亮点,镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。你可以在下图中看到效果:
我们通过根据法向量翻折入射光的方向来计算反射向量。然后我们计算反射向量与观察方向的角度差,它们之间夹角越小,镜面光的作用就越大。由此产生的效果就是,我们看向在入射光在表面的反射方向时,会看到一点高光,其光线强度的计算和漫反射计算的原理也是相同的,不过两个计算的量变成了反射向量,以及观察向量
。具体代码如下
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
有读者可能心中有疑问了,为什么这里要做一个指数运算,其实就是为了增加一个变换梯度,让亮点地方看起来更亮,暗的的地方看来更暗,有个一比较大的视觉差效果,这样才会显得这个物体是被某一个光源照亮的。
风氏光照模型公式
结合前面三种反光的强度我们就可以得到最后的物体表面在此光源下的颜色
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
法线矩阵
从前面的计算公式中我们可以看出,光照的强度和物体表面的法线方向都是有紧密的关系的,在这个案例当中我们并没有对我们的模型进行复杂的变换操作,但是我们的法线方向是一定要随着我们对物体的变换而进行变换的,我们下面看一个不等比例缩放例子:
我们可以看到,因为缩放不等比,物体表面的法线不再垂直于物体的表面,那么我们应该如何避免这样的情况发生了?很多教材都是推荐使用专门的法线矩阵,具体计算公式如下
Normal = mat3(transpose(inverse(model))) * aNormal;
光照着色器
为了表现风氏光照模型,我们需要新写一个着色器,具体代码如下 LightShader.glsl
#type vertex
#version 450 core
layout(location = 0) in vec4 position;
layout(location = 1) in vec3 color;
layout(location = 2) in vec3 normal;
layout(location = 0) out vec4 v_Position;
layout(location = 1) out vec3 v_Color;
layout(location = 2) out vec3 v_Normal;
uniform mat4 u_ViewProject;
void main(){
gl_Position = u_ViewProject * position;
v_Position = position;
v_Color = color;
v_Normal = normal;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 0) in vec4 v_Position;
layout(location = 1) in vec3 v_Color;
layout(location = 2) in vec3 v_Normal;
uniform vec3 u_LightPos;
uniform vec3 u_LightColor;
uniform vec3 u_ViewPos;
void main(){
vec3 v_position3 = vec3(v_Position.x,v_Position.y,v_Position.z);
//环境亮度
float ambientStrength = 0.1f;
vec3 ambient = ambientStrength * u_LightColor;
//漫反射亮度
vec3 vertex2light = normalize(u_LightPos - v_position3);
float diff = max(dot(vertex2light,normalize(v_Normal)),0.0f);
vec3 diffuse = diff * u_LightColor;
//镜面反射
float specularStrength = 0.5f;
vec3 viewDir = normalize(u_ViewPos - v_position3);
vec3 reflectDir = reflect(-vertex2light,v_Normal);
float spec = pow(max(dot(viewDir,reflectDir),0.0f),32);
vec3 specular = specularStrength * spec * u_LightColor;
o_Color = vec4((ambient + diffuse + specular) * v_Color,1.0f);
}
演示以下最后的效果
主函数最后贴在这里,希望能帮助到大家。如果没有看笔者前面的文章,不知到其中的一些代码的片段是怎么回事,可以在此网站下载此项目,该项目有此案例的所有的代码https://gitee.com/HonyOrange_227/opengl-light-lab
#include<glad/glad.h>
#include<GLFW/glfw3.h>
#include<iostream>
#include<glm/gtc/matrix_transform.hpp>
#include"Shader.h"
#include"Camera.h"
static Camera camera(glm::vec3(0.0f, 0.0f, 5.0f));
static bool run = true;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
int main() {
glfwInit();
GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);
glfwMakeContextCurrent(window);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetKeyCallback(window, key_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//需要初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
float lightVertexes[] = {
//front surface
-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //0
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //1
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //2
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //3
//back surface
-0.5f, -0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //4
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //5
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //6
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //7
//up surface
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //8
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //9
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //10
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //11
//down surface
-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //12
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //13
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //14
-0.5f, -0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //15
//left surface
-0.5f, -0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //16
-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //17
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //18
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //19
//right surface
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f, //20
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //21
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,1.0f,1.0f, //22
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,1.0f,1.0f //23
};
float vertexes[] = {
//front surface
-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,1.0f,//0
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,1.0f,//1
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,1.0f,//2
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,1.0f,//3
//back surface
-0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,-1.0f,//4
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,-1.0f,//5
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,-1.0f,//6
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,0.0f,-1.0f,//7
//up surface
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,1.0f,0.0f, //8
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,1.0f,0.0f, //9
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,1.0f,0.0f, //10
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,1.0f,0.0f, //11
//down surface
-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,-1.0f,0.0f, //12
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,-1.0f,0.0f, //13
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,-1.0f,0.0f, //14
-0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 0.0f,-1.0f,0.0f, //15
//left surface
-0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, -1.0f,0.0f,0.0f,//16
-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, -1.0f,0.0f,0.0f,//17
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, -1.0f,0.0f,0.0f,//18
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, -1.0f,0.0f,0.0f,//19
//right surface
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 1.0f,0.0f,0.0f, //20
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 1.0f,0.0f,0.0f, //21
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f,0.2f, 1.0f,0.0f,0.0f, //22
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f,0.2f, 1.0f,0.0f,0.0f //23
};
glm::vec4 originVertexes[24] = {
{-0.5f, -0.5f, 0.5f, 1.0f},
{0.5f, -0.5f, 0.5f, 1.0f},
{0.5f, 0.5f, 0.5f, 1.0f},
{-0.5f, 0.5f, 0.5f, 1.0f},
//back surface
{-0.5f, -0.5f, -0.5f, 1.0f},
{0.5f, -0.5f, -0.5f, 1.0f},
{0.5f, 0.5f, -0.5f, 1.0f},
{-0.5f, 0.5f, -0.5f, 1.0f},
//up surface
{-0.5f, 0.5f, 0.5f, 1.0f},
{0.5f, 0.5f, 0.5f, 1.0f},
{0.5f, 0.5f, -0.5f, 1.0f},
{-0.5f, 0.5f, -0.5f, 1.0f},
//down surface
{-0.5f, -0.5f, 0.5f, 1.0f},
{0.5f, -0.5f, 0.5f, 1.0f},
{0.5f, -0.5f, -0.5f, 1.0f},
{-0.5f, -0.5f, -0.5f, 1.0f},
//left surface
{-0.5f, -0.5f, -0.5f, 1.0f},
{-0.5f, -0.5f, 0.5f, 1.0f},
{-0.5f, 0.5f, 0.5f, 1.0f} ,
{-0.5f, 0.5f, -0.5f, 1.0f},
//right surface
{0.5f, -0.5f, -0.5f, 1.0f},
{0.5f, -0.5f, 0.5f, 1.0f},
{0.5f, 0.5f, 0.5f, 1.0f},
{0.5f, 0.5f, -0.5f, 1.0f}
};
unsigned int indexes[] = {
//front surface
0,1,2,
2,3,0,
//back surface
4,5,6,
6,7,4,
//up surface
8,9,10,
10,11,8,
//down surface
12,13,14,
14,15,12,
//left surface
16,17,18,
18,19,16,
//right surface
20,21,22,
22,23,20
};
glEnable(GL_DEPTH_TEST);
unsigned int buffer = 0, lightbuffer = 0,vertexArray = 0, lightVertexArray = 0,indexBuffer = 0;
glCreateVertexArrays(1, &vertexArray);
glBindVertexArray(vertexArray);
glCreateBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 10 * sizeof(float), NULL);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 10 * sizeof(float), (const void*)(4 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 10 * sizeof(float), (const void*)(7 * sizeof(float)));
glCreateVertexArrays(1, &lightVertexArray);
glBindVertexArray(lightVertexArray);
glCreateBuffers(1, &lightbuffer);
glBindBuffer(GL_ARRAY_BUFFER, lightbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(lightVertexes), lightVertexes, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), NULL);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (const void*)(4 * sizeof(float)));
glCreateBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");
Shader* pLightShader = new Shader("assets/shaders/LightShader.glsl");
glm::mat4 transform = glm::translate(glm::mat4(1.0), glm::vec3(1.5f, 1.5f, 1.5f));
glm::vec3 lightPosition(1.0f, 1.0f, 1.0f), lightColor(1.0f, 1.0f, 1.0f);
glm::vec4 orginCenter(0.0f, 0.0f, 0.0f, 1.0f);
while (!glfwWindowShouldClose(window) && run) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 project = glm::perspective(glm::radians(camera.GetCameraZoom()), 640.0f / 480.0f, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 ViewProject = project * view;
for (int i = 0; i < 24; i++) {
glm::vec4 originPoint = originVertexes[i];
originPoint = transform * glm::scale(glm::mat4(1.0f), glm::vec3(0.5f, 0.5f, 0.5f)) * originPoint;
lightVertexes[i * 7] = originPoint.x;
lightVertexes[i * 7 + 1] = originPoint.y;
lightVertexes[i * 7 + 2] = originPoint.z;
lightVertexes[i * 7 + 3] = originPoint.w;
}
glBindBuffer(GL_ARRAY_BUFFER, lightbuffer);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(lightVertexes), lightVertexes);
pShader->Bind();
pShader->UploadUniformat4("u_ViewProject", ViewProject);
glBindVertexArray(lightVertexArray);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, NULL);
glm::vec4 centerMove = transform * orginCenter;
for (int i = 0; i < 24; i++) {
glm::vec4 originPoint = originVertexes[i];
originPoint = glm::scale(glm::mat4(1.0f), glm::vec3(1.5f, 1.5f, 1.5f)) * originPoint;
vertexes[i * 10] = originPoint.x;
vertexes[i * 10 + 1] = originPoint.y;
vertexes[i * 10 + 2] = originPoint.z;
vertexes[i * 10 + 3] = originPoint.w;
}
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertexes), vertexes);
pLightShader->Bind();
pLightShader->UploadUniformat4("u_ViewProject", ViewProject);
pLightShader->UploadUnifromFloat3("u_LightPos", glm::vec3(centerMove.x,centerMove.y,centerMove.z));
pLightShader->UploadUnifromFloat3("u_LightColor", lightColor);
pLightShader->UploadUnifromFloat3("u_ViewPos",camera.GetPosition());
glBindVertexArray(vertexArray);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, NULL);
glfwSwapBuffers(window);
glfwPollEvents();
}
delete pShader;
glfwDestroyWindow(window);
glfwTerminate();
}
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {
static float lastX = 320.0f, lastY = 240.0f;
static bool firstMouse = true;
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos, lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffsetIn, double yoffsetIn) {
camera.ProcessMouseScroll(static_cast<float>(yoffsetIn));
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
run = false;
}