颜色

计算机如何表达现实世界中五彩滨纷的颜色?答案是用红,绿,蓝(缩写RGB)三个分量来数字化颜色。但我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色,而是它反射的(Reflected)颜色。也就是说,那些不能被物体所吸收的颜色就是我们能够感知到的物体颜色。例如,太阳光能被看见的白光其实是由许多不同的颜色组合而成的。如果我们将白光照在一个红色的衣服上,这个红色的衣服会吸收白光中除了红色以外的所有子颜色,而不被吸收的红色光被反射到我们的眼中,让这件衣服看起来是红色的。

颜色反射的定律被直接运用到图形领域。当我们在OpenGL中创建一个光源时,我们希望给光源一个颜色。当我们把光源的颜色与物体的颜色值相乘,所得到的就是这个物体所反射的颜色。在OpenGL中,我们将两个颜色向量作分量相乘,结果就是最终的颜色向量了:

vec3 lightColor(1.0,1.0,1.0);
vec3 clothingColor(0.0,0.5,0.0);
vec3 result =  lightColor * clothingColor; // (0.0, 0.5, 0.0);

我们定义物体的颜色为物体从一个光源反射各个颜色分量的大小.我们可以使用不同的光源颜色来让物体显现出意想不到的颜色。

##基础光照

现实世界的光照极其复杂,而且受诸多因素影响,计算机有限的计算能力无法模拟。因此在OpenGL的光照使用的是简化的模型,对现实的情况进行近似模拟。这些光照模型是基于我们对光的物理特性的理解。冯氏光照模型是其中一个模型,冯氏光照模型主要由3个分量组成:环境(Ambient),漫反射(Diffuse)和镜面(Specular)光照。如下图:

  • 环境光照(Ambient Lighting):在黑暗的情况下,现实世界通常也仍有一些光亮,所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
  • 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响。它是冯氏光照模型中视觉上最显著的分量。物体的莫一部分越是正对着光源,它就会越亮。
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。

环境光照

我们使用一个很小的常量(光照)颜色,添加到物体片段的最终颜色中,如此即使场景中没有直接的光源也能看起来存在有些发散的光。

void main()
{
	float ambientStrength = 0.1;
	vec3 ambient = ambientStrength * lightColor;
	vec3 result = ambient * objectColor;
	gl_fragColor = vec4(result, 1.0);
}

漫反射光照

漫反射对物体产生显著的视觉影响。漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。

光源发出的光线落在物体的一个片段上,我们需要计算这个光线是以什么角度接触到这个片段的。为了测量光线和片段的角度,需用到一个叫做法向量(Normal Vector)的东西,它是垂直于片段表面的一个向量。点乘返回一个标量,我们可以用它计算光线对片段颜色的影响。不同片段朝向光源的方向不同,这些片段被照亮的情况也不同。所以,计算漫反射光照需要:

  • 法向量:一个垂直于顶点表面的向量
  • 定向的光线:作为光源的位置与片段的位置之间向量差的方向向量。

法向量

法向量是一个垂直于顶点表面的(单位)向量。由于顶点本身并没有表面,我们利用它周围的顶点来计算出这个顶点的表面。一个三角形的法线是一个长度为1并且垂直于这个三角形的向量。而顶点的法线,是包含该顶点的所有三角形的法线均值。

###漫反射光照 每个顶点有了法向量,但我们仍然需要光源额度位置向量和片段的位置向量。由于光源的位置是一个静态变量,故简单在片段着色器中声明为uniform:

uniform vec3 lightPos;

渲染的时候,设置lightPos就可以了。 最后,我们还需要片段的位置。我们会在世界空间中进行所有的光照计算,因此我们需要一个在世界空间中的顶点位置。我们可以通过把顶点位置属性乘以模型矩阵来把它变换到世界坐标系。如下:

varying vec3 fragPos;
varying vec3 normal;

void main() {
	gl_Position = projection * view * model * vec4(aPos, 1.0);
	fragPos = vec3 (model * vec4(aPos, 1.0));
	normal = aNormal;
}

在片段着色器中,我们需要计算光源和片段位置之间的方向向量。通过让两个向量相减的方式计算向量差。

vec3 norm = normalize (normal);
vec3 lightDir = normalize (lightPos - fragPos);

然后我们对 norm 和 lightDir 向量进行点乘,计算光源对当前片段实际的漫发射影响。结果值再乘以光的颜色,得到漫发射分量。两个向量之间的角度越大,漫反射分量就会越小:

float diff = max (dot(norm, lightDir), 0.0); // 如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致漫反射分量变为负数,为此,我们使用max函数返回两个参数之间较大的参数,从而保证漫反射分量不会变成负数。
vec3 diffuse = diff * lightColor;

vec3 result = (ambient + diffuse) * objectColor;
gl_FragColor =  vec4(result, 1.0);

镜面光照(Specular Hightlight)

和漫反射光照一样,镜面光照是依据光的方向向量和物体的法向量来决定的,但是它还依赖于观察方向。镜面光照是基于光的反射特性。如果我们想象物体表面像一面镜子一样,那么无论我们从哪里去看那个表面所反射的光,镜面光照都会达到最大化。如下图:

我们通过反射法向量周围光的方向来计算反射向量。然后我们计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大。 观察向量是镜面光照附加的一个变量,我们可以使用观察者世界空间位置和片段的位置来计算它。之后,我们计算镜面光强度,用它乘以光源的颜色,再将它加上环境光和漫反射分量。

uniform vec3 viewPos;
float specularStrength = 0.5;
float shininess  = 32; // 反光度,一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(-lightDir, norm)
float spec = pow (max (dot (viewDir, reflectDir), 0.0), shininess);
vec3 specular = specularStrength * spec * lightColor;

vec3 result = (ambient + diffuse + specular) * objectColor;
gl_FragColor = vec4(result, 1.0);

以上就是基础光照的相关知识了。