OpenGL自制3D游戏引擎入门XI
Materials材质&LightingMaps光照贴图
一、材质
如果想在OpenGL中模拟多种类型的物体,我们必须为每个物体分别定义一个材质属性。在上一章中,我们指定了一个物体和光的颜色,以及结合环境光和镜面强度分量,来定义物体的视觉输出。当描述一个物体的时候,我们可以用这三个分量来定义一个材质颜色:环境光照、漫反射光照、镜面光照,最后再添加反光度这个分量,就得到我们所需要的所有材质属性
#version 330 core struct Material { vec3 ambient; vec3 diffuse; vec3 specular; float shininess; }; uniform Material material;已经为冯氏光照模型的分量都定义一个颜色向量
- ambient材质向量定义了在环境光照下这个物体反射的是什么颜色,通常是和物体颜色相同
- diffuse材质向量定义了在漫反射光照下物体的颜色(与环境光照一样设置为我们需要的物体颜色)
- specular材质向量设置的是镜面光照对物体颜色的影响
- shininess影响镜面高光的散射/半径
二、设置材质
因为我们创建了一个结构体来整合材质属性,因此需要修改光照的计算来适应材质属性,因为变量都存储在结构体中,我们可以从uniform变量material中访问
void main() { // 环境光 vec3 ambient = lightColor * material.ambient; // 漫反射 vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = lightColor * (diff * material.diffuse); // 镜面光 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对物体设置材质
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);
三、光的属性
此时我们发现一个问题,物体太亮,原因是环境光、漫反射和镜面光这三个颜色对任何一个光源都会全力去反射。光源对环境光、漫反射和镜面反射分量也具有不同的强度。因此需要为每个光照分量指定一个强度向量
如果我们假设lightColor是vec3(1.0),代码如下,所以物体的每个材质属性对每个光照分量都返回了最大的强度,实际上这些vec3的值可以分别设置
vec3 ambient = vec3(1.0) * material.ambient; vec3 diffuse = vec3(1.0) * (diff * material.diffuse); vec3 specular = vec3(1.0) * (spec * material.specular);那么我们同样创建一个结构体来整合这些光照属性
struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; }; uniform Light light;之后更新片段着色器并设置光照强度
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);
四、光照贴图
- 前面我们将整个物体的材质定义为一个整体,但现实世界中的物体通常不会只包含一种材质而是由多种材质所组成,不同的物体在不同部件上都会有不同的材质属性。因此我们需要引入漫反射和镜面光贴图,这允许我们对物体的漫反射分量和镜面光分量有着更精确的控制。
五、漫反射贴图
实际上就是纹理,不过在光照场景中它通常叫做一个漫反射贴图(Diffuse Map),它是表现了物体所有的漫反射颜色的纹理图像。因此在着色器中使用漫反射贴图的方法和纹理是一样的,但这次将纹理存储为Material结构体中的一个sampler2D
移除环境光材质颜色向量是因为环境光颜色几乎在所有情况下都等于漫反射颜色
struct Material { sampler2D diffuse; vec3 specular; float shininess; }; ... in vec2 TexCoords;
Tip:sampler2D是所谓的不透明类型(Opaque Type),也就是说我们不能实例化,只能通过uniform定义它。如果使用除uniform以外的方法比如函数的参数实例化这个结构体,GLSL会抛出奇怪的错误,当然同样适用于封装了不透明类型的结构体
六、镜面光贴图
同样,我们想让物体的某部分以不同的强度显示镜面高光,我们也可以使用一个专门用于镜面高光的纹理贴图,这就意味着我们需要生成一个黑白的纹理,来定义物体每部分的镜面光强度,镜面高光的强度可以通过图像每个像素的亮度来获取,镜面光贴图上的每个像素都可以由一个颜色向量来表示,比如说黑色代表颜色向量vec(0.0),灰色代表颜色向量vec3(0.5)。在片段着色器中,我们接下来取样对应的颜色值并将其乘以光源的镜面强度,一个像素越白,乘积就会越大,物体的镜面光分量就会越亮
使用漫反射和镜面光贴图我们可以给相对简单的物体添加大量的细节,我们还可以使用法线/凹凸贴图(Normal/Bump Map)或者反射贴图来给物体添加更多的细节。
添加一个叫做放射光贴图(Emission Map),它是一个储存了每个片段的发光值(Emission Value)的贴图。发光值是一个包含光源的物体发光是可能显现的颜色,这样的话物体就能够忽略光照条件进行发光。具体效果如下:

(下接 OpenGL入门XII)
