learnopengl:chap02光照

颜色

我们希望有下面的运算模型,光的照射物体的结果用点乘来表示

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);
}

漫反射光照

在这里插入图片描述
计算漫反射光照的两要素

  • 法向量:(为了方便,我们直接从顶点数组输入法向量
  • 定向的光线

会在世界空间中进行所有的光照计算,所以定向的光线的计算是lightPosFragPos(世界空间的

不要忘记绑定法向量的属性

但是我们要注意,我们在局部空间的法向量也要变换到世界空间!因为位移不改变法向量,且法向量没有齐次坐标,所以我们只处理旋转和缩放。

  • 旋转
  • 缩放

不均匀的缩放会导致出现问题,处理这个问题需要计算一个特殊的法线矩阵
在这里插入图片描述

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值