颜色
我们希望有下面的运算模型,光的照射物体的结果用点乘来表示
glm::vec3 lightColor(1.0f, 1.0f, 1.0f); //表示光源的颜色是太阳光
glm::vec3 toyColor(1.0f, 0.5f, 0.31f); //表示珊瑚红的颜色
glm::vec3 result = lightColor * toyColor; //太阳光照射后返回的结果的模型= (1.0f, 0.5f, 0.31f);
//同上面的原理,但是光源不是太阳光,是绿色光
glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);
创建光照场景
基础光照
现实世界的光照是极其复杂的,我们采用简化的冯氏光照模型(Phong Lighting Model)。
有三种光照:环境光照(Ambient Lighting),漫反射光照(Diffuse Lighting),镜面光照(Specular Lighting)。
环境光照
在片段着色器加入
void main()
{
float ambientStrength = 0.1; //环境因子
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
漫反射光照
计算漫反射光照的两要素
- 法向量:(为了方便,我们直接从顶点数组输入法向量)
- 定向的光线
会在世界空间中进行所有的光照计算,所以定向的光线的计算是lightPos
和FragPos
(世界空间的
不要忘记绑定法向量的属性
但是我们要注意,我们在局部空间的法向量也要变换到世界空间!因为位移不改变法向量,且法向量没有齐次坐标,所以我们只处理旋转和缩放。
- 旋转
- 缩放
不均匀的缩放会导致出现问题,处理这个问题需要计算一个特殊的法线矩阵
Normal = mat3(transpose(inverse(model))) * aNormal;
尽量不要使用矩阵求逆,或者预先计算好。
*着色器光照处理
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
//由VAO处理得到的传递给fragment着色器
out vec3 FragPos; //在世界空间顶点的坐标,因为光照是在世界空间处理的
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0)); //世界空间里的顶点坐标
Normal = mat3(transpose(inverse(model))) * aNormal; //世界空间的法向坐标,对缩放进行了修正
gl_Position = projection * view * vec4(FragPos, 1.0);
}
我们的光照实在片段着色器处理的,但是也可以在顶点着色器做处理,只需要处理顶点,但是需要插值,效果就不好。
片段着色器
#version 330 core
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
uniform vec3 lightPos;
uniform vec3 viewPos;
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); //算点积求cos,不能为负值
vec3 diffuse = diff * lightColor;
// specular //镜面反射
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);
}
这些着色器到底是怎么工作的!
作业
记得完成作业
把每次作业所做的工作封装成函数
材质
上一节的时候,定义了描述表面的时候用三个光照来定义材质的颜色,现在再加上反光度(Shininess)分量。
也要定义不同光照分量的光照。
*着色器
materials.vs
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
materials.fs
#version 330 core
out vec4 FragColor;
//定义材质struct,shiniess很重要
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
//定义光照struct
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
//世界空间的片段位置
in vec3 FragPos;
//世界空间的法向量,已经在vs中定义并修正了
in vec3 Normal;
uniform vec3 viewPos;
//对struct的赋值方式不同于以往
uniform Material material;
uniform Light light;
void main()
{
// ambient
vec3 ambient = light.ambient * material.ambient;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * material.diffuse);
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); //shininess的作用
vec3 specular = light.specular * (spec * material.specular); //light的struct的作用
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
光照贴图
上次我们对整个物体的材质是整体定义的,但是这是不现实的。要引入漫反射和镜面反射贴图。
环境光颜色在几乎所有情况下都等于漫反射颜色,所以定义材质的时候移除了环境光。
纹理和漫反射贴图是一个意思,都是物体的颜色,所以要理解这一点!
下面是由上面获得的镜面光贴图:
因为木头的镜面光反射为0,而黑色颜色向量是vec3(0.0)
,旁边的金属边框是由镜面光反射,就是灰色。也就是`vec3(0.5)。
进阶的还有法线贴图和反射贴图
*着色器
lighting_maps.vs
#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);
}
lighting_maps.fs
#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
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;//环境光和漫反射相同
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
投光物
平行光
定向光可以视为无穷远的光,我们可以就不需要光照的位置,只需要方向就可以了。
struct Light {
// vec3 position; // 使用定向光就不再需要了
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
...
void main()
{
vec3 lightDir = normalize(-light.direction); //注意要取反,因为direction的定义是以光源为起点
...
}
我们一直将光的位置和位置向量定义为
vec3
,但一些人会喜欢将所有的向量都定义为vec4
,以w分量为0或1来区分是向量还是位置。
点光源
点光源会衰减!
- K c K_{c} Kc是常数项,通常为1,保证 F a t t F_{att} Fatt始终小于1
- K l K_{l} Kl是线性项
- K q K_{q} Kq是二次项
实现衰减
struct Light {
//点光源
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
//定义点光源的强度
float constant;
float linear;
float quadratic;
};
//计算
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
聚光
类似于手电筒和路灯,OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的。
Phi
ϕ:指定了聚光半径的切光角
现在我们要做的是求LightDir向量和SpotDir向量之间的点积,并将它与切光角ϕ值对比。
手电筒
struct Light {
vec3 position;
vec3 direction;
float cutOff;
...
};
传入合适的值,这里选择的是观察的视角
lightingShader.setVec3("light.position", camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
确定是否在聚光内部
float theta = dot(lightDir, normalize(-light.direction));
if(theta > light.cutOff)
{
// 执行光照计算
}
else // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗
color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
注意上面的会有一些假,应为在边缘没有过度,我们要模拟聚光的内圆锥(原来的聚光范围)和外圆锥。
有了这个公式就不用使用if_else判断了:只要限制在[0,1]之内就好了,应为内部I都大于1,过度介于0,1之间,外部I小于0。
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); //约束到0,1之内就可实现聚光和过度
...
// 将不对环境光做出影响,让它总是能有一点光
diffuse *= intensity;
specular *= intensity;
...
注意添加
light.outerCutOff
多光源
创建六个光源的场景,我们还要把光照计算封装到GLSL函数中去。
*着色器
multiple_lights.vs
#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);
}
multiple_lights.fs
#version 330 core
out vec4 FragColor;
//材质
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
//平行光
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
//点光源
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
//聚光
struct SpotLight {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
//四个点光源
#define NR_POINT_LIGHTS 4
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 viewPos;
uniform DirLight dirLight;
uniform PointLight pointLights[NR_POINT_LIGHTS]; //设置也很简单
uniform SpotLight spotLight;
uniform Material material;
// function prototypes
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
void main()
{
// properties
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
// == =====================================================
// Our lighting is set up in 3 phases: directional, point lights and an optional flashlight
// For each phase, a calculate function is defined that calculates the corresponding color
// per lamp. In the main() function we take all the calculated colors and sum them up for
// this fragment's final color.
// == =====================================================
// phase 1: directional lighting
vec3 result = CalcDirLight(dirLight, norm, viewDir);
// phase 2: point lights
for(int i = 0; i < NR_POINT_LIGHTS; i++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
// phase 3: spot light
result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
// calculates the color when using a directional light.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
return (ambient + diffuse + specular);
}
// calculates the color when using a point light.
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
// calculates the color when using a spot light.
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
// spotlight intensity
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
// combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation * intensity;
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
return (ambient + diffuse + specular);
}