冯氏光照模型
Notes
- 冯氏光照模型
主要由三部分构成:
环境光照:世界上通常有的一些光亮
漫反射光照:模拟光源对物体的方向性影响
镜面光照:模拟有光泽物体上面的亮点
人眼看到物体的颜色为:光颜色*物体颜色
- 冯氏光照模型表达式
I
p
=
k
a
i
a
+
∑
m
∈
l
i
g
h
t
s
(
k
d
(
L
m
→
⋅
N
→
)
i
m
,
d
+
k
s
(
R
m
→
⋅
V
→
)
α
i
m
,
s
)
{{I}_{p}}={{k}_{a}}{{i}_{a}}+\sum\limits_{m\in lights}{({{k}_{d}}(\overrightarrow{{{L}_{m}}}\centerdot \overrightarrow{N}){{i}_{m,d}}+{{k}_{s}}{{(\overrightarrow{{{R}_{m}}}\centerdot \overrightarrow{V})}^{\alpha }}{{i}_{m,s}})}
Ip=kaia+m∈lights∑(kd(Lm⋅N)im,d+ks(Rm⋅V)αim,s)
环境光:
k
a
{{k}_{a}}
ka: 环境光常数
i
a
{{i}_{a}}
ia:环境光强度
漫反射光:
k
d
{{k}_{d}}
kd:漫反射光常数
L
m
→
\overrightarrow{{{L}_{m}}}
Lm: 光源-物体方向
N
→
\overrightarrow{N}
N: 物体法线方向
i
m
,
d
{{i}_{m,d}}
im,d: 漫反射光强度
镜面光:
k
s
{{k}_{s}}
ks:镜面光常数
R
m
→
\overrightarrow{{{R}_{m}}}
Rm: 物体-光源反射方向
V
→
\overrightarrow{V}
V: 视角-物体视野方向
α
^{\alpha }
α: 高光反光度
i
m
,
s
{{i}_{m,s}}
im,s: 镜面光强度
- 法线矩阵
在片段着色器中世界坐标系下计算冯氏光照模型,而法线方向在顶点着色器中在物体坐标下,因此需要转换到世界坐标下即用Model矩阵作用后。而非等比缩放就会发生法线不垂直于物体,因此为法线再作用一个矩阵,使其能够垂直于物体,该矩阵即为法线矩阵。
N ˋ → ⋅ T ˋ → = ( G ⋅ N ) ⋅ ( M T ) = 0 \overrightarrow{{{N}^{\grave{\ }}}}\centerdot \overrightarrow{{{T}^{\grave{\ }}}}=(G\centerdot N)\centerdot (MT)=0 N ˋ⋅T ˋ=(G⋅N)⋅(MT)=0
G = ( M − 1 ) T G={{({{M}^{-1}})}^{T}} G=(M−1)T - 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
TexCoords = aTexCoords;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
- 片段着色器
#version 330 core
out vec4 FragColor;
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient
float ambientStrength = 0.2;
vec3 ambient = ambientStrength * light.ambient * vec3(texture(material.diffuse, TexCoords));
// diffuse
float diffuseStrength = 1.0;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diffuseStrength * diff * light.diffuse * vec3(texture(material.diffuse, TexCoords));
// specular
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = specularStrength * spec * light.specular * vec3(texture(material.specular, TexCoords));
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
- 平行光
平行光在漫反射光和镜面反射光中不需要计算光源-物体的方向 - 点光源
点光源在漫反射光和镜面反射光中需要计算光源-物体的方向 - 聚光源
聚光源光照强度,外光切->内光切之间逐渐增强,内光切之内完全增强
ϕ {\phi} ϕ: 内光切
γ {\gamma} γ: 外光切
θ {\theta } θ: 角度
i n t e n s i t y = cos θ − cos γ cos ϕ − cos γ intensity=\frac{\cos \theta -\cos \gamma }{\cos \phi -\cos \gamma } intensity=cosϕ−cosγcosθ−cosγ
intensity=clamp(intensity,0,1);
- 光线衰减
随着光线传播距离原来越远,光的强度逐渐减小
代码示例
https://gitee.com/NiMiKiss/opengl-notes.git
cubetexture.fs
#version 400 core
out vec4 FragColor;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
uniform vec3 lightColor;
uniform vec3 objectColor;
uniform vec4 lightPos;
uniform vec3 cameraPos;
uniform vec3 cameraFront;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
struct PointLight {
vec4 lightDir;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
struct SportLight {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
Material material;
vec3 CalcPointLight(PointLight pointLight)
{
// ambient
float ambientStrength = 1.0;
vec3 ambient = ambientStrength * pointLight.ambient * material.ambient;
// diffuse
vec3 norm = normalize(Normal), lightDir;
float distance = 0.0f;
if (0 == pointLight.lightDir.w)
lightDir = normalize(vec3(pointLight.lightDir));
if (1 == pointLight.lightDir.w)
{
lightDir = vec3(pointLight.lightDir) - FragPos;
distance = length(lightDir);
lightDir = normalize(lightDir);
}
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * pointLight.diffuse * material.diffuse;
// specular
float specularStrength = 1.0;
vec3 cameraDir = normalize(cameraPos - FragPos);
vec3 reflectLightDir = reflect(-lightDir, norm);
float spec = pow(max(dot(cameraDir, reflectLightDir), 0.0), material.shininess);
vec3 specular = specularStrength * spec * pointLight.specular * material.specular;
float attenuation = 1.0 / (pointLight.constant + pointLight.linear * distance + pointLight.quadratic * distance * distance);
return vec3(ambient + diffuse + specular) * attenuation;
}
vec3 CalSpotLight(SportLight sportLight)
{
float theta = dot(normalize(sportLight.position - FragPos), normalize(-sportLight.direction));
float epsilon = cos(radians(sportLight.cutOff))-cos(radians(sportLight.outerCutOff));
float spotI = clamp((theta-cos(radians(sportLight.outerCutOff)))/epsilon,0.0,1.0);
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(-sportLight.direction);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * sportLight.diffuse * material.diffuse;
// specular
float specularStrength = 1.0;
vec3 cameraDir = normalize(sportLight.position - FragPos);
vec3 reflectLightDir = reflect(-lightDir, norm);
float spec = pow(max(dot(cameraDir, reflectLightDir), 0.0), material.shininess);
vec3 specular = specularStrength * spec * sportLight.specular * material.specular;
float distance = length(sportLight.position - FragPos);
float attenuation = 1.0 / (sportLight.constant + sportLight.linear * distance + sportLight.quadratic * distance * distance);
return vec3(diffuse + specular) * attenuation * spotI;
}
void main()
{
material.ambient = vec3(texture(ourTexture1, TexCoords));
material.diffuse = vec3(texture(ourTexture1, TexCoords));
material.specular = vec3(texture(ourTexture2, TexCoords));
material.shininess = 32;
PointLight pointLight;
pointLight.lightDir = lightPos;
pointLight.ambient = vec3(0.2f, 0.2f, 0.2f);
pointLight.diffuse = vec3(0.6f, 0.6f, 0.6f);
pointLight.specular = vec3(1.0f, 1.0f, 1.0f);
pointLight.constant = 1.0;
pointLight.linear = 0.09;
pointLight.quadratic = 0.032;
SportLight sportLight;
sportLight.position = cameraPos;
sportLight.direction = cameraFront;
sportLight.cutOff = 5.5;
sportLight.outerCutOff = 12.5;
sportLight.ambient = vec3(0.0f, 0.0f, 0.0f);
sportLight.diffuse = vec3(1.0f, 1.0f, 1.0f);
sportLight.specular = vec3(1.0f, 1.0f, 1.0f);
sportLight.constant = 1.0;
sportLight.linear = 0.09;
sportLight.quadratic = 0.032;
vec3 light = CalcPointLight(pointLight) + CalSpotLight(sportLight) ;
FragColor=vec4(light,1.0f);
}
cubetexture.vs
#version 400 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
void main()
{
Normal = mat3(transpose(inverse(model))) * aNormal;
TexCoords = aTexCoords;
FragPos = vec3(model * vec4(aPos,1.0f));
gl_Position = projection * view * vec4(FragPos,1.0f);
}
light.fs
#version 400 core
out vec4 FragColor;
uniform vec3 lightColor;
void main()
{
FragColor = vec4(lightColor,1.0f);
}
light.vs
#version 400 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.0f);
}
OpenGL.cpp
#include <stb_image.h>
#include <camera.h>
#include <shader.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#pragma region 顶点数据
float vertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
glm::vec3 lightPos[] = {
glm::vec3(1.2f, 1.0f, 2.0f)
};
glm::vec3 cubePos[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(-3.0f, 1.0f, -2.0f),
};
#pragma endregion
GLFWwindow* Init();
void SetVAO();
void SetTexture(Shader& shader);
void LoadTexture(char const* path);
void processinput_callback(GLFWwindow* window);
void mouse_callback(GLFWwindow* window, double xpose, double ypose);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
Camera camera(glm::vec3(0.0f, 0.0f, 5.0f));
unsigned int cubeVAO, lightVAO;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
bool firstMouse = true;
static float lastX = 0.0f, lastY = 0.0f;
float deltaTime = 0.0f;
float lastFrame = 0.0f;
void SetVAO()
{
unsigned int VBO;
glGenVertexArrays(1, &cubeVAO);
glGenVertexArrays(1, &lightVAO);
glGenBuffers(1, &VBO);
glBindVertexArray(cubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBindVertexArray(lightVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
}
void SetTexture(Shader& shader)
{
stbi_set_flip_vertically_on_load(true);
unsigned int texture[2];
glGenTextures(2, texture);
glBindTexture(GL_TEXTURE_2D, texture[0]);
LoadTexture("../OpenGL/resources/textures/container2.png");
glBindTexture(GL_TEXTURE_2D, texture[1]);
LoadTexture("../OpenGL/resources/textures/container2_specular.png");
glActiveTexture(GL_TEXTURE0); //先激活再绑定
glBindTexture(GL_TEXTURE_2D, texture[0]);
glActiveTexture(GL_TEXTURE1); //先激活再绑定
glBindTexture(GL_TEXTURE_2D, texture[1]);
shader.use();
shader.setInt("ourTexture1", 0);
shader.setInt("ourTexture2", 1);
}
void LoadTexture(char const* path)
{
int width(0), height(0), nrChannels(0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0);
GLenum format;
if (1 == nrChannels)
{
format = GL_RED;
}
else if (3 == nrChannels)
{
format = GL_RGB;
}
else if (4 == nrChannels)
{
format = GL_RGBA;
}
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data);
}
int main()
{
auto window = Init();
Shader lightShader("../OpenGL/shaders/light.vs", "../OpenGL/shaders/light.fs");
Shader cubeShader("../OpenGL/shaders/cubetexture.vs", "../OpenGL/shaders/cubetexture.fs");
SetVAO();
SetTexture(cubeShader);
glEnable(GL_DEPTH_TEST);
while (!glfwWindowShouldClose(window))
{
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
processinput_callback(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 trans(glm::mat4(1.0f)), view(glm::mat4(1.0f)), projection(glm::mat4(1.0f)), model(glm::mat4(1.0f));;
view = camera.GetViewMatrix();
projection = glm::perspective(glm::radians(camera.Zoom), 800.0f / 600.0f, 0.1f, 100.0f);
//Draw Cube
for (size_t i = 0; i < 2; ++i)
{
model = glm::mat4(1.0f);
model = glm::translate(model, cubePos[i]);
model = glm::rotate(model, currentFrame, glm::vec3(1.5f, 3.0f, 0.0f));
cubeShader.use();
cubeShader.setMat4("model", model);
cubeShader.setMat4("view", view);
cubeShader.setMat4("projection", projection);
cubeShader.setVec3("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
cubeShader.setVec3("objectColor", glm::vec3(1.0f, 0.5f, 0.31f));
cubeShader.setVec4("lightPos", glm::vec4(lightPos[0], 1.0f));
cubeShader.setVec3("cameraPos", camera.Position);
cubeShader.setVec3("cameraFront", camera.Front);
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
//Draw light1
for (size_t i = 0; i < 1; ++i)
{
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos[i]);
model = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f));
lightShader.use();
lightShader.setMat4("model", model);
lightShader.setMat4("view", view);
lightShader.setMat4("projection", projection);
lightShader.setVec3("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
glfwSwapBuffers(window);
glfwPollEvents();
}
}
GLFWwindow* Init()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
auto window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
}
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
return window;
}
void processinput_callback(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
{
camera.ProcessKeyboard(FORWARD, deltaTime);
}
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
{
camera.ProcessKeyboard(LEFT, deltaTime);
}
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
{
camera.ProcessKeyboard(BACKWARD, deltaTime);
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
{
camera.ProcessKeyboard(RIGHT, deltaTime);
}
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
}
void mouse_callback(GLFWwindow* window, double xpose, double ypose)
{
if (firstMouse)
{
lastX = xpose;
lastY = ypose;
firstMouse = false;
}
auto xoffset = xpose - lastX;
lastX = xpose;
auto yoffset = lastY - ypose;
lastY = ypose;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
输出结果,两个不同位置的木箱,两个光源(一个太阳光,一个手电筒)