日期:2021年7月15日标签:ComputerGraphics

材质 #

前一篇文章中,我介绍了冯氏光照理论,冯氏光照理论主要由三个部分组成:

  • 环境光照
  • 漫反射光照
  • 镜面光照

按照冯氏光照理论,我们可以绘制出非常真实的场景,但是与现实世界相比总会差那么一点。现实中,不同的物体,它们对光产生的反应是不一样的,也就是说当光线照射在物体上,它们反射的颜色不一样,亮度也不一样,这是由物体本身的材质决定的。木头、石头、金属对同一种光线产生的反应当然是不一样的,金属反光度会强很多,石头次之,木头最弱。

一. 物体材质 #

所以我们需要给物体定义一个材质颜色属性。物体材质定义成如下几个部分:

  • 环境光照(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);

运行程序,你应该能看见如下场景。

webgl物体材质

二.光的材质 #

虽然我们添加了物体材质,在着色器中添加了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);

如果你的代码正确,应该能看到如下效果:

webgl材质

完整的代码可以在这里获得。

你可以尝试更改材质分量值,看看不同值对视觉产生的影响。

#

材质分量值表:http://devernay.free.fr/cours/opengl/materials.html

(完)

目录