英文原文:https://learnopengl.com/Lighting/Materials
在现实世界中,每个物体对光都有不同的反应。 例如,钢制物品通常比粘土花瓶更闪亮,而木制容器对光的反应与钢制容器不同。 一些物体反射光而没有太多散射,导致镜面高光较小,而其他物体散射很多,使高光半径更大。 如果我们想在 OpenGL 中模拟多种类型的对象,我们必须定义每个表面特定的材料属性。
在上一章中,我们定义了一个物体和光的颜色来定义物体的视觉输出,并结合了环境和镜面反射强度分量。 在描述表面时,我们可以为 3 种照明组件中的每一种定义材质颜色:环境光、漫射光和镜面光。 通过为每个组件指定颜色,我们可以对表面的颜色输出进行细粒度控制。 现在为这 3 种颜色添加一个光泽度组件,我们就拥有了我们需要的所有材料属性:
#version 330 core
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
在片段着色器中,我们创建一个结构来存储表面的材质属性。 我们也可以将它们存储为单独的 uniform 值,但将它们存储为结构使其更有条理。 我们首先定义结构的布局,然后简单地声明一个 uniform 变量,并将新创建的结构作为其类型。
如您所见,我们为每个 Phong 照明组件定义了一个颜色向量。 环境材质向量定义了表面在环境光照下反射的颜色; 这通常与表面的颜色相同。 漫反射材质向量定义漫反射照明下表面的颜色。 漫反射颜色(就像环境照明一样)设置为所需表面的颜色。 镜面反射材质向量设置表面镜面反射高光的颜色(或者甚至可能反映特定于表面的颜色)。 最后,反光度会影响镜面高光的散射/半径。
通过这 4 个定义对象材质的组件,我们可以模拟许多真实世界的材质。 devernay.free.fr 上的一张表格显示了模拟外部世界真实材料的材料属性列表。 下图显示了其中几个真实世界材质值对我们的立方体的影响:
如您所见,通过正确指定表面的材料属性,它似乎改变了我们对物体的感知。 效果非常明显,但为了获得更逼真的效果,我们需要用更复杂的东西替换立方体。 在模型加载章节中,我们将讨论更复杂的形状。
为一个物体找出正确的材质设置是一项艰巨的任务,主要需要实验和大量经验。 错位的材质完全破坏物体的视觉质量并不少见。
让我们尝试在着色器中实现这样一个材质系统。
材质设置
我们在片段着色器中创建了一个 uniform 的材质结构,因此接下来我们要更改光照计算以符合新的材质属性。 由于所有材质变量都存储在一个结构中,我们可以从材质 uniform 中访问它们:
void main()
{
// ambient
vec3 ambient = lightColor * material.ambient;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = lightColor * (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);
vec3 specular = lightColor * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
如您所见,我们现在可以在任何需要的地方访问材质结构的所有属性,这次在材质颜色的帮助下计算生成的输出颜色。 每个对象的材质属性都与它们各自的照明组件相乘。
我们可以通过设置合适的 uniform 来设置应用中物体的材质。 然而,在设置 uniform 时,GLSL 中的结构在任何方面都不是特别的; 结构仅真正充当 uniform 变量的命名空间。 如果我们想要填充结构,我们将不得不设置单独的 uniform ,但以结构的名称为前缀:
lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightingShader.setFloat("material.shininess", 32.0f);
我们将环境和漫反射分量设置为我们希望对象具有的颜色,并将对象的镜面反射分量设置为中等亮度的颜色; 我们不希望镜面反射分量太强。 我们还将光泽度保持在 32。
我们现在可以轻松地从应用程序中影响对象的材质。 运行程序会给你这样的结果:
虽然看起来真的不对?
光属性
物体太亮了。 物体太亮的原因是环境色、漫反射色和镜面反射色被任何光源全力反射。 光源的环境光、漫反射和镜面反射分量也分别具有不同的强度。 在上一章中,我们通过使用强度值改变环境和镜面反射强度来解决这个问题。 我们想做类似的事情,但这次是通过为每个照明组件指定强度向量。 如果我们将 lightColor 可视化为 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(1.0) 值也可以单独受到影响,这通常是我们想要的。 现在,物体的环境成分完全影响了立方体的颜色。 环境分量不应该对最终颜色产生如此大的影响,因此我们可以通过将光的环境强度设置为较低的值来限制环境颜色:
vec3 ambient = vec3(0.1) * material.ambient;
我们可以用同样的方式影响光源的漫反射和镜面反射强度。 这与我们在上一章中所做的非常相似; 你可以说我们已经创建了一些灯光属性来单独影响每个照明组件。 我们要为光属性创建类似于材质结构的东西:
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
光源的环境光、漫反射和镜面反射分量具有不同的强度。 环境光通常设置为低强度,因为我们不希望环境颜色太显眼。 光源的漫反射分量通常设置为我们希望光线具有的确切颜色; 通常是明亮的白色。 镜面反射分量通常保持在 vec3(1.0) 以全强度发光。 请注意,我们还将光的位置向量添加到结构中。
就像使用材质 uniform 一样,我们需要更新片段着色器:
vec3 ambient = light.ambient * material.ambient;
vec3 diffuse = light.diffuse * (diff * material.diffuse);
vec3 specular = light.specular * (spec * material.specular);
然后,我们要在应用程序中设置灯光强度:
lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); // 漫反射光变暗一点
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
现在我们已经调整了光如何影响对象的材质,我们得到的视觉输出看起来很像上一章的输出。
然而,这一次我们完全控制了对象的照明和材质:
现在,改变物体的视觉方面相对容易。让我们让事情变得更有趣一点吧!
不同的灯光颜色
到目前为止,通过选择从白色到灰色到黑色的颜色,我们使用了光的颜色来仅改变其单个组件的强度,而不影响对象的实际颜色(仅影响其强度)。因为我们现在可以很容易地访问灯光的属性,我们可以随着时间的推移更改它们的颜色,以获得一些真正有趣的效果。
由于已经在片段着色器中设置了所有内容,因此更改灯光的颜色很容易,并且会立即创建一些时髦的效果:
如您所见,不同的灯光颜色会极大地影响对象的颜色输出。由于灯光颜色直接影响对象可以反射的颜色(您可能还记得在颜色一章中所述),因此它对视觉输出有重大影响。
我们可以通过sin和glfwGetTime更改灯光的环境光和漫反射颜色,从而轻松地随时间更改灯光的颜色:
glm::vec3 lightColor;
lightColor.x = sin(glfwGetTime() * 2.0f);
lightColor.y = sin(glfwGetTime() * 0.7f);
lightColor.z = sin(glfwGetTime() * 1.3f);
glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f);
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f);
lightingShader.setVec3("light.ambient", ambientColor);
lightingShader.setVec3("light.diffuse", diffuseColor);
尝试并尝试几个照明和材质值,看看它们如何影响视觉输出。您可以在此处找到该应用程序的源代码。