颜色,基础光照,材质(一)

本片blog主要是为了整理脑内一些混杂的知识。


  • 颜色:

大自然中的物体都有着自己独一无二的颜色,我们需要用电脑去模拟这种颜色(尽管是用有限的电脑颜色去模拟无限的自然界存在的颜色,但是你基本看不出来区别)。

严格地说,平时生活中我们看到的某一个物体的颜色并不是它本身的颜色,而是它所反射(reflected)出来的颜色:想象一下,你在黑暗中看到自己的手基本是黑乎乎的一片,这就是它在弱光条件下所反射出来的颜色。也就是说,在不同的光源条件下,物体会反射出不同的颜色(也就是物体不吸收的光)。这也就是我们需要去处理的东西。

首先我们先定义一个物体自身的颜色:

//珊瑚红
glm::vec3 coral(1.0f, 0.5f, 0.31f);

接着我们定义一下白色的光源(也就是太阳光),然后把光源与物体本身的颜色相乘,得到的值为这个物体所反射出来的光:

//定义光源
glm::vec3 light(1.0f, 1.0f, 1.0f);
//相乘:反射出来的光
glm::vec3 result = coral * light;  

由此我们可以定义物体的颜色为物体从一个光源反射各个颜色分量的大小。自然而然,换个别的光源,反射出来的光也就变了:

glm::vec3 light2(0.51f, 0.23f, 0.87f);
glm::vec3 result2 = coral * light2;

并且显然,如果我们在OpenGL中要显示出物体的颜色的话,需要将颜色值结果赋值给FragColor。


 

  • 基础光照:现实世界的光很复杂,想要全部模拟出来基本不可能,因此我们会使用一些简化的光照模型,其中一个叫做冯氏光照模型(Phong Lighting Model)。冯氏光照模型由三个分量组成:环境 (ambient), 漫反射(diffuse),镜面(specular)光照
  1. 先说环境(ambient),我们知道即便在黑暗环境下,也或多或少地存在一些光亮(这里不说直接的光源,有时候光亮由其他物体反射光源形成),为了模拟这个,就出现了环境光照:
//fragment shader
void main()
{
    float ambientStrength = 0.1f;
    float ambient = light * ambientStrength;
    
    glm::vec3 result = ambient * coral;
    FragColor = vec4(result, 1.0f);
}

:这里引入了ambientStrength,是考虑到环境光不像直接的光源那么强。在后面的材质篇章会提出新的解决方法:光源分量(方便我们在主程序函数更改参数)。

  2. 接下来是漫反射(diffuse),我们知道在不光滑的平面上会出现漫反射这一现象,我们也同样需要去模拟它,那么怎么模拟呢?很简单,计算出光线与平面法线向量(normal vector)的夹角大小就行了,夹角越大,说明片段距离光源越远,影响也就越小(而这个夹角的大小我们可以通过使用点乘向量来展现,因为 a x b = |a||b|cos α。

//fragment shader
//the data of normal vector and the fragment position(world position) come from vertex shader,
// the source data is vertices array,and the light position come from uniform variable
void main() { vec3 norm = glm::normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos); float diff = max(dot(norm, lightDir), 0.0f); vec3 diffuse = diffuse * light;
   vec3 result = diffuse * coral;
   FragColor = vec4(result, 1.0f);
}

:这里的diff可以看作是同第一部分环境光强度差不多的东西,后面也可以通过光源的漫反射分量来改变。

我们使用的是标准化之后的向量来简化运算,因为我们并不需要除了方向之外的东西。

还有一个值得注意的地方,就是法线向量(normal vector),在进行点乘运算之前,我们需要将法向向量转化为世界坐标(其他的各种坐标运算也是在世界坐标进行的),那么我们是直接乘以一个简单的模型矩阵(model matrix)就好了吗?非也。首先法向向量只是一个方向向量,不能表示空间中的位置,因此我们必须保证位移不能影响到它,其次,如果有不等比缩放的问题,也会影响到法向向量。

为了应对上述的问题,我们需要使用一个专门定制的模型矩阵,称之为法向矩阵(Normal Vector),它定义为“模型矩阵左上角的逆矩阵的转置矩阵“,具体如何计算出来的参照这篇文章。我们可以在顶点着色器(你要在片段着色器中运算也Ok,不过还是提前计算好比较方便)中自己生成这个矩阵(当顶点个数多的时候,这个运算建议在cpu运行,也就是主程序中,因为开销比较大)

//aNormal is the normal vector in local space

Normal = mat3(transpose(inverse(model))) * aNormal;

  3.镜面光照:第三部分,也就是最后一部分,也称之为高光反射(heightlight reflection)。当我们看一些物体的时候,总有一些相对刺眼的光,称之为高光,镜面光照既是要处理这个东西。

  同漫反射一样,镜面光照也依赖于法向向量和光线位置(光线到片段的向量),但同时它也依赖于观察者的位置(就是camera的位置了)。有了这些数据以后我们要怎么计算呢?很简单,我们计算出光线的反射向量,然后同观察者的位置(向量)进行点乘(夹角越小,镜面光影响程度越大)。

//view position is the camera's position
void main()
{
    float specularStrength = 0.5f;
    vec3 viewDir = normallize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
  
  float spec = pow(max(dot(viewDir, reflectDir), 0.0f), 32);
  vec3 specular = specularStrength * spec * light;
  vec3 result = specular * coral;
  FragColor = (result, 1.0f);
}

:计算光线地反射向量那里,reflect函数要求第一个参数是从光源指向片段,所以再原来的lightDir基础上取反了。接着spec的表达式里面还有一个指数32,这个指数称之为反光度(shininess),一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。

并且同样的,我们这里有个specularStrength变量,后面可通过光源的镜面光分量来改写(同样方便我们改写强度值)。

 

posted @ 2019-10-10 21:53  jckcoenf  阅读(521)  评论(0编辑  收藏  举报