OpenGL学习笔记13-Materials
Materials 材料
Lighting/Materials
在现实世界中,每个物体对光有不同的反应。例如,钢制物品通常比陶土花瓶更闪亮,而木制容器对光线的反应与钢制容器不同。一些物体反射光没有太多散射导致小高光和其他散射很多给高光一个更大的半径。如果我们想要在OpenGL中模拟几种类型的对象,我们必须定义特定于每个表面的材质属性。
在前一章中,我们定义了一个物体和光的颜色来定义物体的视觉输出,并结合了环境光和镜面光的强度。当描述一个表面时,我们可以为三个照明组件分别定义一个材质颜色:环境光,漫射光和高光。通过为每个组件指定颜色,我们可以对表面的颜色输出进行细粒度控制。现在给这三种颜色添加一个光泽组件,我们就有了我们需要的所有材质属性:
#version 330 core
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
在fragment shader中,我们创建了一个struct来存储表面的材质属性。我们也可以将它们存储为单独的uniform值,但是将它们存储为结构可以使其更有组织。我们首先定义结构的布局,然后简单地声明一个uniform变量,将新创建的结构作为其类型。
正如你所看到的,我们为Phong照明的每个组件定义了一个颜色矢量。环境材质向量定义了在环境光照下表面反射的颜色;这通常与表面的颜色相同。漫反射材质向量定义漫反射光线下表面的颜色。漫反射颜色(就像环境光一样)设置为所需表面的颜色。高光材质向量设置表面高光的颜色(或者甚至可能反射特定于表面的颜色)。最后,亮度影响镜面高光的散射/半径。
通过这4个组件定义一个对象的材质,我们可以模拟许多真实世界的材质。在devernay.free.frdevernay.free.fr 上有一个表,显示了模拟真实材料的材料属性列表。下面的图片展示了几个真实世界的材质值对我们的立方体的影响:
正如你所看到的,通过正确地指定一个表面的材料属性,它似乎改变了我们对这个物体的感知。效果很明显,但是为了得到更真实的结果,我们需要用更复杂的东西来替换立方体。在模型加载Model Loading章节中,我们将讨论更复杂的形状。
为一个物体弄清楚正确的材质设置是一个困难的壮举,大部分需要实验和大量的经验。由于材料错位而完全破坏物品的视觉效果,这种情况并不少见。
让我们尝试在着色器中实现这样一个材质系统。
Setting materials
我们在fragment shader中创建了一个uniform的材质结构,所以接下来我们要根据新的材质属性改变光照计算。由于所有的材质变量都存储在一个结构体中,我们可以从material 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中的struct在任何方面都不特殊;结构实际上只充当统一变量的名称空间。如果我们想填充结构,我们将不得不设置单独的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。
我们现在可以很容易地从应用程序中影响对象的材质。运行程序会得到如下结果:
看起来不太对吧?
Light properties
这个物体太亮了。物体太亮的原因是环境色、漫射色和高光色从任何光源都被充分反射。光源的环境光、漫射光和镜面光的强度也各不相同。在前一章中,我们通过改变环境强度和镜面强度值来解决这个问题。我们想做一些类似的事情,但是这一次通过指定每个照明组件的强度向量。如果我们将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一样,我们需要更新fragment shader:
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); // darken diffuse light a bit
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
现在我们调整了光线如何影响物体的材质,我们得到了一个视觉输出,看起来很像前一章的输出。但是这一次我们完全控制了灯光和物体的材质:
现在改变物体的视觉方面是相对容易的。让我们把事情变得有趣一点吧!
Different light colors
到目前为止,我们使用的是浅色,通过选择从白色到灰色到黑色的颜色来改变单个组件的强度,不影响物体的实际颜色(只影响它的强度)。因为我们现在可以很容易地获得光的属性,我们可以随着时间的推移改变它们的颜色来获得一些真正有趣的效果。因为所有的东西都已经在fragment shader中设置好了,所以改变光线的颜色是很容易的,并且可以立即创建一些时髦的效果:
正如你所看到的,不同的光色会极大地影响物体的颜色输出。因为光的颜色直接影响到物体反射的颜色(你可能还记得颜色那一章),它对视觉输出有很大的影响。
通过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);
尝试和实验几种照明和材质值,看看它们如何影响视觉输出。您可以在这里here. 找到应用程序的源代码。
Exercises
- Can you make it so that changing the light color changes the color of the light's cube object?
- Can you simulate some of the real-world objects by defining their respective materials like we've seen at the start of this chapter? Note that the table's ambient values are not the same as the diffuse values; they didn't take light intensities into account. To correctly set their values you'd have to set all the light intensities to
vec3(1.0)
to get the same output: solution of cyan plastic container.
推荐阅读