OpenGL 学习实录6: 进阶光照(光照纹理 & 投光物)
文章目录
系列文章
- OpenGL 学习实录1: 基于 MacOS + Clion 配置 OpenGL 运行环境
- OpenGL 学习实录2: 基础绘制初试
- OpenGL 学习实录3: 深入着色器 - 纹理
- OpenGL 学习实录4: 坐标系统 & 摄像机
- OpenGL 学习实录5: 基础光照 & 材质
正文
1. 材质纹理
前一篇我们学会了根据参数调整光源反射来表现材质,现在我们更进一步加上纹理,并在纹理上表现出材质效果
我们只需要把材质对象关于漫反射和高亮反射的属性改成纹理对象就可以了
struct Material {
sampler2D diffuse;// 光照纹理
sampler2D specular;// 镜面纹理
float shininess;
};
in vec2 TexCoords;
接下来计算三种光线分量的时候要乘上纹理对象
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));
2. 多种光源
上一节OpenGL 学习实录5: 基础光照 & 材质使用的光源为点光源系统,现实场景还存在多种光照系统(定向光ex太阳、聚光ex手电筒)
2.1 定向光源
定向光只需要方向与三个参数
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;// 定向光
接下来将定向光的计算封装成一个方法
vec3 CalcDirLight(DirLight dirLight, vec3 normal, vec3 viewDir);
首先计算光照方向
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) {
vec3 lightDir = normalize(-light.direction);
计算漫反射和镜面光的分量
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
// 镜面光
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
最后得出最终光源颜色
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);
}
2.2 点光源
点光源也是类似
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform PointLight pointLights[NR_POINT_LIGHTS];// 点光源
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
首先是两种光线的分量计算
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
// 镜面光
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
接下来是点光源还需要计算光线衰减
// 衰减
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance);
最终结果再乘上衰减效果
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) * attenuation;
}
2.3 聚光源
聚光灯基本就跟点光源一致
struct SpotLight {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform SpotLight spotLight;// 聚光
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
// 镜面光
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// 衰减
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance);
与点光源不同的是,我们还需要再多计算一个光照角度(theta),并计算光照强度(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);
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) * attenuation * intensity;
}
2.4 合成光线
有了三种光源,片段着色器的主方法就是计算所有光源的结果并加总
void main() {
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 result = CalcDirLight(dirLight, norm, viewDir);
for (int i = 0; i < NR_POINT_LIGHTS; i++) {
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
}
result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
3. 布置场景 & 结果
main.cpp
中进行顶点设置、物体模型、光源模型的配置,着色器的各个光源属性配置,光照纹理配置等前几篇都有了,对代码细节有兴趣的可以看下面的源代码,最终效果如下
- 正面
- 侧面
- 聚光效果
其他资源
参考连接
Title | Link |
---|---|
多光源 - LearnOpenGL CN | https://learnopengl-cn.github.io/02%20Lighting/06%20Multiple%20lights/ |
完整代码示例
https://github.com/superfreeeee/Blog-code/tree/main/others/open_gl/open_gl_advanced_light