scenery.png

光的模拟

“神说要有光,这世界便有了光”,这是旧约创世纪里面的第一句话,在西方世界几乎家喻户晓。光在人类的生活中扮演了极其重要的角色。虽然有些海底或者地底生物不需要光线也能生存,但是对于人类和绝大多数的生物来说,光线是日常生活中的一部分,就像空气和水一样。我们需要光来感知这个世界,在我们每个人身上甚至有一个器官是用来适应这个充满着光的世界——我们的眼睛。我们生活在五彩斑斓的世界,即追逐璀璨耀眼的阿波罗,也习惯于暗影交织的阿尔忒弥斯,即沉醉于郁郁葱葱的花草世界,也畏惧于无边黑暗的宇宙。在沉浸在虚拟的世界中时,我们同样也会以这样的标准看计算机呈现的世界。

真实的世界

我们这个世界上充满了光线,有人造光,太阳光,还有生物发出来的光线。光驱散了黑夜,温暖照亮了人类以及其他生物的世界。我们很难想像一个缺少光线的世界是什么样子,茫无边际的黑暗和寒冷。现实世界中的光照又是那么自然,仿佛重亘古的年代就存在一样。“看见”东西对人类或者其他生物来说是天生的,不需要去可以学习。而在计算机的屏幕上,一切都将是全新的开始。

虚拟世界

在计算的图形中不像现实的世界一样。你把一个物体放到屏幕面前,它不会自动出现光照的效果。实际上我们为了是所绘制的物体变得真实,就需要为他们“人为地”为物体加上光照的效果。如果你看过之前的文章,你可以看到,我们绘制的所有物体所绘制的物体都只有一种单独的非常单调颜色。而实际上在现实的生活中,物体的每一个面的亮暗程度是不一样的,呈现出来的是忽明忽暗的表象,这种现象被我们的眼睛所习惯,我们称职未显示。图形学光照就是需要模拟出在真实的世界中的光照效果。

光照类型

局部光照

局部关照是只计算光线作用在当个物体上的作用效果。我们知道,在现实世界中会再不同的物体上发生跃迁的,而且跃迁的次数越多,光照强度就会越小。局部光照是不考虑物体之间的光照反馈和交互,也就是不会对光线的跃迁进行追踪。例如在A物体上的光线效果就只作用于A,而在附近的B,是不会被A反射的光线照亮的。局部光照因为计算的要素比较少,但是也能表现出非常逼真的光照模拟效果。局部关照模型最基本的就是冯式关照模型,是由计算机科学家冯布同(Phong Bui-Tuong)在1974年提出来的一种对现实世界的的计算模拟理论,我们在后面对其进行详细讲解。
【image】

全局光照

全局光照是对光线的完全追踪,完全地去计算模拟整个现实的世界。实现全局光照需要计算物体之间的距离,材质的反射特性,以及对光线的全程追踪。全局的光照对于计算机的性能要求非常高,但是它能创造出无与伦比的或奇幻或逼真的虚拟世界。

冯式光照

冯式光照又称之ADS光照模型,ADS即:A(ambien)环境光、D(diffuse)漫反射光、S(specular)镜面光的英文字母缩写。冯式光照虽然是局部光照,但对于现实世界也是有非常好的还原。

法线

法线,又称法向量,是指物体表面的朝向。法线在光照模型中是重要的计算因子。我们真实的世界中物体是会运动的,或者我们作为观察者在观察物体时的角度会变化。在运动过程中,我们就会以不同的角度在看到同一个物体的不同面,而不同的角度去看一个物体的面时,它们的暗亮程度是不一样的。对于一个面来说,法线的方向都是一样的,我们可以认为一个面只有一个法线,或者有无数条同一个方向平行的法线。
normals.jpg

环境光

环境光是指光在世界中被其他地方反射的光线。我都知道,即使是在非常黑暗的环境下,物体也并非是不可见的。从四周反射到物体上的光线在反射到我们眼睛肿,这种环境光线物体一定的光线。环境光线在webgl中的计算是最简单的,我只需要确定两个变量:物体的基底颜色以及环境光的颜色,让它们相乘,就可以模拟计算出物体表面的环境光照效果。公式如下:
基础光照 - 图3

  1. v_Color = vec4(1.0, 0.0, 0.0, 1.0); //红色基底
  2. u_Ambient = vec4(0.1, 0.1, 0.1); //白色的环境光线
  3. ambientColor = (v_Color.rgb * u_Ambient, v_Color.a);// 最终物体的颜色

漫反射光

漫反射我们在高中的物理课中都应该学习过:由于物体的表面是凹凸不平的,所以导致了照射到物体表面上的光线会朝着不通的方向散射出去。这些光线进入眼睛本我们感知到的时候也是会在不同的地方出现或明或暗的效果的。
【image】
我们现在一束光线方向是X,现在假设它是平行的太阳光,颜色是A,物体某个面的颜色是C,朝向即法线是N,那么我们可以通过光线的方向与法线的夹角来计算光的反射情况。XN·cos(a)。这个结果就是我们算出来的,光线在物体该表面上造成的光线强度效果。在opengl中,计算法线和光线的效果有内置的函数dot,现在我们来写出这个公式:
基础光照 - 图4
转换成shader如下表示:

  1. diffuse = u_LightColor * v_Color * max(dot(lightDirection, v_Normal), 0.0);

如果光源是点光源,那么我们需要计算每个面到点的差值,就可以计算某个面上的光线方向。我们知道,只需要将两个矢量相减,就能得到另外一个矢量。那么我们只需要知道光源的坐标A点,和当前的坐标X点,通过X - B就能知道光线的方向。公式如下所示:
基础光照 - 图5
转换成公式如下所示:

  1. world_position = matrix * a_Position;
  2. lightDirection = normalize(lightSourcePosition - world_position);
  3. diffuse = u_LightColor * v_Color * max(dot(lightDirection, normalize(v_Normal)), 0.0);

这样我们就能计算出一个物体的表面漫反射的基本效果了。

镜面光照

镜面光照是假设物体的表面是光滑的,随着光线与物体表面夹角的变化,反射到人的眼睛中光线会非常常明显的明暗变化。我们可以在真实世界上看到物体的表面的光斑就是镜面反射的结果:
【reflect light】
镜面光照的要素有几点:镜面反射的光滑程度,物体与光线的夹角。在反射光照模型中,光滑程度决定了我们见到的效果的真实性。在一个绝对平面的镜子上,我们只能在一个角度看到光线的反射。这是因为光滑程度的因子决定了反射光线的颜色。我们可以找一下这个公式来计算反射因素:
基础光照 - 图6a
I表示的是光线的方向,

镜面光线的公式也如下所示:

  1. // surface to light
  2. vec3 lightDirection = normalize(u_LightPostion - v_WorldPosition.xyz);
  3. // surface to eye
  4. vec3 eyeDirection = normalize(u_EyesPosition - v_WorldPosition.xyz);
  5. vec3 halfVector = normalize(lightDirection + eyeDirection);
  6. float light = max(dot(lightDirection, v_Normal), 0.0);
  7. float specularLightWeight = 0.0;
  8. if (light > 0.0) {
  9. specularLightWeight = pow(dot(v_Normal, halfVector), shininess);
  10. }
  11. vec3 specularReflection = u_LightColor * specularLightWeight;

光的衰减

在现实的世界中,光线会在一定的条件下衰减,这就早成了同一束光线照射在远近不一的物体上会造成亮度不一的效果。我们在webgl中也需要模拟这种效果。

聚焦光

折射光线

光照源头

平行光线

平行管线指的是在无线远的地方返回的光线。所以对于物体来说,这些管线是平行的,例如我们在实际生活中接触到太阳光就可以被认为是一种平行的管线。平行管线在计算光照效果中是最简单的。直接制动光线的方向即可:

  1. const lightDirectionLocation = webgl.getUniformLocation(program, "u_LightDirection");
  2. const value = vec3.fromValues(0, 0, 1);
  3. vec3.normalize(value);
  4. webgl.uniform3fv(lightDirectionLocation, value);

上面标示了一束从x正反向照射出来因为他是矢量,我需要将它归一化处理,这点一定要记得。

点光源

点光源是只所有的光线从一个点散发出来,管线的方向是发散的。相对于平行管线来说,点光源的每一束光线与物体表面的夹角都是不一样的,因此在计算光线与表面的夹角的是时候,我用到了矢量的减法。我们都知道如果两个矢量相减,会得到另外一个矢量,这个矢量就是光线在某个面上的入射方向:

  1. const lightPositionLocation = webgl.getUniformLocation(program, "u_LightPosition");
  2. const value = vec3.fromValues(0, 0, 1);
  3. vec3.normalize(value);
  4. webgl.uniform3fv(lightPositionLocation, value);
  5. // shader
  6. ...
  7. vec3 lightDirection = normalize(u_worldPostion - u_LightPsiotn);

聚光灯

我们都在舞台的聚光的下看过表演,聚光灯在凸显物体的时候显得十分重要,我们在webgl中也可以模拟这种管线,思路是给定数值范围内的片元着色。我们的关键在于计算给定的范围。一般来说由于探照灯的模型是圆的,所以我们的方位也是一个圆。假设管线中央的方向是M,照射灯的外边距未N那么我们计算照射方位只需要采用圆形的公式来处理。即:MNcos(a); 我们一级知道opengl内置了dot函数用啦计算两个矢量的点积。

  1. vec3 surfaceToLight = normalize(lightPosition - worldPosition);
  2. vec3 dotFromPosition = dot(spotLightDirection, surfaceToLight);
  3. if(dotFromPosition > 0.0) {
  4. // ...
  5. // 开始着色
  6. }

光晕