在前一篇文章中,我介绍了冯氏光照理论,冯氏光照理论主要由三个部分组成:
按照冯氏光照理论,我们可以绘制出非常真实的场景,但是与现实世界相比总会差那么一点。现实中,不同的物体,它们对光产生的反应是不一样的,也就是说当光线照射在物体上,它们反射的颜色不一样,亮度也不一样,这是由物体本身的材质决定的。木头、石头、金属对同一种光线产生的反应当然是不一样的,金属反光度会强很多,石头次之,木头最弱。
所以我们需要给物体定义一个材质颜色属性。物体材质定义成如下几个部分:
环境光照(ambient)
漫反射光照(diffuse)
镜面光照(specular)
反光度(shininess)
在glsl中这样定义:
// 物体材质struct
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material; // 使用Material类型,定义一个材质uniform
通过上一篇的学习,物体Material
的几个分量,我们应该并不会感觉到陌生。这几个分量的概念,均在上一篇文章中讲解过,并通过例子说明。这里只不过是将其封装成一个结构体,构成一个材质的概念, 每个分量对冯氏光照模型的某个环节产生一定的作用。
接下来在之前代码的基础上修改。 修改片段着色器如下:
#version 300 es
precision highp float;
in vec3 v_normal;
in vec3 v_pos;
out vec4 FragColor;
uniform vec3 u_lightColor; // 光照颜色
uniform vec3 u_lightDirection; // 光照方向
uniform vec3 viewPos; // 摄像机位置
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
void main() {
// 环境光照
vec3 ambient = material.ambient * u_lightColor;
// 漫反射
vec3 lightDirectionReverse = normalize(-u_lightDirection);
vec3 normal = normalize(v_normal);
float diffuseStrength = max(dot(normal, lightDirectionReverse), 0.0);
vec3 diffuse = u_lightColor * (diffuseStrength * material.diffuse);
// 镜面光照
vec3 viewDir = normalize(viewPos - v_pos);
vec3 reflectDir = reflect(u_lightDirection, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = u_lightColor * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
设置属性材质,向着色器传递数据:
shader.setFloat3("material.ambient", 1, 0.5, 0.31);
shader.setFloat3("material.diffuse", 1, 0.5, 0.31);
shader.setFloat3("material.specular", 0.5, 0.5, 0.5);
shader.setFloat("material.shininess", 32);
运行程序,你应该能看见如下场景。
虽然我们添加了物体材质,在着色器中添加了Material Struct, 但是最后的结果有点过于亮?物体过亮的原因是环境光、漫反射和镜面光这三个颜色对任何一个光源都会去全力反射,假设光的颜色是vec3(1.0)
,那么之前我们计算不同分量的光如下:
vec3 ambient = vec3(1.0) * material.ambient;
vec3 diffuse = vec3(1.0) * (diff * material.diffuse);
vec3 specular = vec3(1.0) * (spec * material.specular);
其实光源对环境光、漫反射和镜面反射光的分量也具有着不同的强度的。所以我们需要为光也定义一个属性,也就是光的材质。以环境光为例,环境光一般会对物体的颜色产生很小的影响,我们希望其计算如下:
vec3 ambient = vec3(0.1) * material.ambient; // 使用vec3(0.1)而不是vec3(1.0), 否则过亮
可以使用同样的方式定义光的材质:
struct Light {
vec3 direction; // 如果需要点光源,可以设置成vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
完整的片段着色器代码如下:
#version 300 es
precision highp float;
in vec3 v_normal;
in vec3 v_pos;
out vec4 FragColor;
// uniform vec3 u_lightDirection; // 光照方向
uniform vec3 viewPos; // 摄像机位置
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
struct Light {
vec3 direction; // 如果是点光源,需设置成vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Material material;
uniform Light light;
void main() {
// 环境光照
vec3 ambient = material.ambient * light.ambient;
// 漫反射
vec3 lightDirectionReverse = normalize(-light.direction);
vec3 normal = normalize(v_normal);
float diffuseStrength = max(dot(normal, lightDirectionReverse), 0.0);
vec3 diffuse = light.diffuse * (diffuseStrength * material.diffuse);
// 镜面光照
vec3 viewDir = normalize(viewPos - v_pos);
vec3 reflectDir = reflect(light.direction, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
给着色器传递light
数据:
shader.setFloat3("light.ambient", 0.2, 0.2, 0.2);
shader.setFloat3("light.diffuse", 0.5, 0.5, 0.5); // 将光照调暗了一些以搭配场景
shader.setFloat3("light.specular", 1.0, 1.0, 1.0);
shader.setFloat3("light.direction", 1.0, -0.5, -1.0);
如果你的代码正确,应该能看到如下效果:
完整的代码可以在这里获得。
你可以尝试更改材质分量值,看看不同值对视觉产生的影响。
材质分量值表:http://devernay.free.fr/cours/opengl/materials.html
(完)