LearnOpenGL CN
《Unity Shader入门精要》自学笔记(三)第六章 基础光照
《Unity Shader入门精要》自学笔记(七)第九章 更复杂的光照 ——光照衰减
平行光
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
点光源光照衰减的计算
struct PointLight {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
#define MAX_N_POINT_LIGHTS 4
uniform PointLight pointLights[MAX_N_POINT_LIGHTS];
之前没有深究过光照衰减的计算,这次终于知道了
F a t t = 1.0 K c + K l ∗ d + K q ∗ d 2 F_{att} = \frac {1.0 }{K_{c} + K_{l} * d + K_{q} * d^2} Fatt=Kc+Kl∗d+Kq∗d21.0
d d d 为距离, K c , K l , K q K_{c} ,K_{l} , K_{q} Kc,Kl,Kq 分别为常数项、一次项(Linear)、二次项(Quadratic)
- 常数项:通常保持为1.0,它的主要作用是保证分母永远不会比1小
- 一次项:以线性的方式减少强度。
- 二次项:让光源以二次递减的方式减少强度
二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。
vec3 Lighting_PointLight(PointLight light, vec3 normal, vec3 viewDir, vec3 baseColor, float specularMask){
vec3 lightDir = normalize(light.position - vertexPosition);
float distance = length(light.position - vertexPosition);
float attenuation = 1 / (light.constant + light.linear * distance + light.quadratic * distance * distance);
vec3 ambient = baseColor * light.ambient;
vec3 diffuse = baseColor * light.diffuse * attenuation * max(dot(lightDir, normal), 0.0);
vec3 specular = specularMask * light.specular * attenuation * max(dot(viewDir, reflect(-lightDir, normal)), 0.0);
return vec3(ambient + diffuse + specular);
}
聚光灯范围衰减的计算
struct SpotLight{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 direction;
float cutoff;
float outerCutoff;
};
uniform SpotLight spotLight;
之前在 Unity 里看到的是一张 Mask,现在是直接计算
使用聚光灯时,整个屏幕被分为三个部分
- 处于内圆锥内:接受全部光照
- 处于外圆锥外:只接受环境光照
- 处于内外圆锥之间:漫反射和高光根据距离衰减
需要的参数:
- 内圆锥角度的余弦值 cutoff
- 外圆锥角度的余弦值 outerCutoff
- 聚光灯的正方向 direction
进行光照前,计算出光源方向与聚光灯正方向的余弦值,与 cutoff、outerCutoff 的大小关系就可以知道当前像素处于哪个区域
中间区域的衰减算法思路和 InverseLerp()
一致,Lerp()
yyds
vec3 Lighting_SpotLight(SpotLight light, vec3 normal, vec3 viewDir, vec3 baseColor, float specularMask){
vec3 lightDir = normalize(light.position - vertexPosition);
float theta = dot(light.direction, -lightDir);
float epsilon = light.cutoff - light.outerCutoff;
float coneFactor = clamp((theta - light.outerCutoff) / epsilon, 0.0, 1.0);
vec3 ambient = light.ambient * baseColor;
vec3 diffuse = coneFactor * light.diffuse * baseColor * max(dot(lightDir, normal), 0.0);
vec3 specular = coneFactor * light.specular * specularMask * max(dot(viewDir, reflect(-lightDir, normal)), 0.0);
return vec3(ambient + diffuse + specular);
}
还可以做个变体,解密灯的效果
vec3 lerp(vec3 min, vec3 max, float alpha){
return min + alpha * (max - min);
}
vec3 Lighting_SpotLight(SpotLight light, vec3 normal, vec3 viewDir, vec3 baseColor, float specularMask, vec3 pattern){
// ...
diffuse = lerp(diffuse, pattern, coneFactor);
return vec3(ambient + diffuse + specular);
}
多光源
每个光源都会对像素产生影响,所以对于每个像素来说,逐个光源根据不同的光源类型进行计算就好
void main()
{
vec3 lighting_dir = Lighting_DirLight();
vec3 lighting_point;
for(int i; i < MAX_N_POINT_LIGHTS; i++){
lighting_point += Lighting_PointLight();
}
vec3 lighting_spot = Lighting_SpotLight();
fragColor = vec4(lighting_dir + lighting_point + Lighting_SpotLight, 1.0);
}
唯一比较麻烦的是点光源,有多个光源要设置,所以还是写了个类
class PointLight {
public:
int Index;
glm::vec3 Position;
glm::vec3 Ambient;
glm::vec3 Diffuse;
glm::vec3 Specular;
float Constant;
float Linear;
float Quadratic;
PointLight(int index = 0,
glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3 ambient = glm::vec3(0.03f, 0.03f, 0.03f),
glm::vec3 diffuse = glm::vec3(0.1f, 0.1f, 0.1f),
glm::vec3 specular = glm::vec3(1.0, 1.0, 1.0),
float constant = 1.0f, float linear = 0.09f, float quadratic = 0.032f);
void SendDataToGPU(Shader shader);
};
// ...
void PointLight::SendDataToGPU(Shader shader) {
std::string name = "pointLights[";
name.append(std::to_string(Index));
name.append("]");
shader.setVec3(name + ".position", Position);
shader.setVec3(name + ".ambient", Ambient);
// ...
}