说明:跟着learnopengl的内容学习,不是纯翻译,只是自己整理记录。
强烈推荐原文,无论是内容还是排版。 原文链接
本文地址:http://blog.csdn.net/aganlengzi/article/details/50479585

颜色 Colors

我们在前面的教程中简单说过一些过于颜色设置的内容,还记得那个三角形吗?就是那个三个点是三原色,三角形的其它点是三个顶点按照位置插值结果的那个三角形。之后我们就再也没有提过关于颜色设置的事情了(实际上是因为我们有了纹理…)。本次教程中,我们将首先介绍颜色相关的内容,一方面是补充一下,另一方面是为了后面具体讲光照做准备。(还记得吗,我们已经完成了初识阶段,现在我们在光照阶段啦)。

在真实世界中,颜色是无穷的,而在数字世界中,颜色 好像都是需要数字来表示的,那么就不会是无穷的,数字总会有大小,而能表示的颜色种类也就取决于存储这些颜色值的物理空间。在数字世界中,为了表示真实世界中的场景(很大程度上决定于颜色),我们需要做一些折中。我们虽然不能表示尽真实世界中的所有颜色,但是我们可以表示足够多的颜色并且使用相近的颜色来表示真实世界中的颜色值而不被人眼所察觉。在计算机中,我们用颜色的三种分量(红,绿,蓝)RGB的组合来表示某种特定的颜色,实际上它们的组合可以表示足够我们使用的颜色。例如:我们用之前已经熟悉的方式定义一种颜色:

glm::vec3 coral(1.0f, 0.5f, 0.31f);   

根据物理学知识,我们看到的颜色实际上不是物体本身的颜色,而是物体反射的颜色。未被物体吸收的光才能够被我们看到,也就是我们平常说的物体的颜色。我们知道,太阳光被认为是白色的,起始它是所有光的组合,其中包括红橙黄绿蓝靛紫各种分量。当白色的太阳光照射在一个蓝色的玩具上的时候,这个蓝色的玩具吸收除自己本身颜色之外的所有其他分量。而和它本身一致的颜色被反射出去也就是我们人眼能够看到的光。所以我们认为这只玩具是蓝色的。下面这张图显示的是人眼观察太阳光照射在珊瑚色的物体上的情形。

可以看到,太阳光中的其它颜色被这个珊瑚色的物体吸收,而一些组成自身颜色分量的颜色并没有被吸收而是被反射到了人眼中。所以我们认为它是珊瑚色的。

上面说的原理在图形中同样适用。当我们在OpenGL中定义一个光源,我们需要为这个光源指定一个光源颜色。在上面的举例中,我们的光源是太阳,它的光源颜色是白色。而物体的最终颜色是由它反射光源的颜色决定的(与自身相同的颜色被吸收)。让我们利用向量相乘的方式模拟上面的过程:我们先定义一个光源颜色lightColor,这里定义为白色光,然后定义物体颜色toyColor,定义为珊瑚色,然后将这两个向量按照组件相乘的方式看一下得到的反射结果:

glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

我们可以看到,最终的反射结果是这个物体的颜色本身!而白色光中的很大部分都被吸收掉了。这和现实世界中是一致的。这样,我们就可以定义对象的颜色为它反射RGB不同颜色分量值了,也就是利用如上所示的方式和值来表示。现在,让我们把白色光源换成是绿色光源,看一下效果是怎样的呢:

glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);

正如我们猜想的,经过物体的吸收之后,反射的颜色中只有绿色光分量。而且颜色值和物体本社的颜色值相当。那么这个物体的颜色值就只是暗绿色了。让我们再试一个橄榄绿色的光源看看是什么效果:

glm::vec3 lightColor(0.33f, 0.42f, 0.18f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);

原理和上面的举例一致,我们得到了不同的物体颜色,原来,只要改变光源的颜色,我们就可以改变我们看到的物体的颜色。看来创建颜色也不是一件难事。

对于颜色值,以上的只是基本上就已经够了,现在让我们先放一放,我们来创建一个能够进行实验的场景吧!在其中,我们可以来感受颜色值和光照的其妙。

一个光照场景 A lighting scene

在后面的教程中,我们将会模仿真实世界中关于光照和颜色值的情形,创建有趣的颜色值来充分使用颜色。因为我们将要使用到光源,所以我们想把光源弄成一个真实可见的对象,而不是一个抽象的概念。这样可以更加真实模拟出真实世界中的情形。

我们首先要做的是找一个我们把各种光投射到它身上的物体——一个深度光污染受害者,找什么呢?就用前面教程中使用的那个立方体吧!其次,我们还需要一个物体来扮演光源(比如一个立方体灯泡?^_^),我们还是使用前面教程中用到的立方体吧!

那么,开始干吧!我们需要用我们的坐标数据填充顶点缓冲对象(VBO),设置顶点属性指针等等,这些步骤对你来说应该十分熟悉和简单了。如果在创建的时候不知道怎么做,就先看看前面的教程吧。

我们真正需要做的是首先需要一个顶点处理程序来绘制那个立方体,这和前面的教程是一致的,因为前面我们已经绘制过类似的立方体:

#version 330 core
layout (location = 0) in vec3 position;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0f);
} 

需要注意的是,之前我们绘制立方体箱子的时候使用了纹理,而在这儿是不需要纹理的,所以我们可以选择在坐标中删除那些纹理坐标并且修改相应的指针,否则OpenGL解读数据就会出现问题。或者也可以保留但是不使用它们。

我们还需要绘制一个立方体灯泡,所以我们会它新创建一个VAO来存储相关的属性和设置,这样做的好处是,光源和被光照的物体是分离的:

GLuint lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// We only need to bind to the VBO, the container's VBO's data already contains the correct data.
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// Set the vertex attributes (only position data for our lamp)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0); 

上面的代码是比较简单直接的。现在我们相当于创建了两个立方体,一个是光源,一个是被光源照射的物体。接下来让我们来创建相应的片段处理程序:

#version 330 core
out vec4 color;

uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
    color = vec4(lightColor * objectColor, 1.0f);
}

如代码所示,片段处理程序接收箱子对象和立方体光源的颜色值作为参数,这两个参数是uniform类型的变量。在其中,像是上面举例的那样按照向量相乘的方式模拟物体的官员之间的作用效果。这应该是比较好理解的,接下来让我们来设置物体的颜色并且传递参数:

// Don't forget to 'use' the corresponding shader program first (to set the uniform)
GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");
GLint lightColorLoc  = glGetUniformLocation(lightingShader.Program, "lightColor");
glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);
glUniform3f(lightColorLoc,  1.0f, 1.0f, 1.0f); // Also set light's color (white)

有一件事情需要注意的是:如果只使用上面的方式,当我们进行改变顶点和片段处理程序的时候,立方体“灯泡”也会像箱子一样改变(因为它们使用的是相同的顶点和片段处理程序),这显然并不是我们想要的。我们想要的结果是:灯泡是恒定的颜色值(比如设置为白色),而且不会受其他的颜色值的影响,这样显得更加真实。

为了达到我们期望的效果,我们需要做的是创建一个新的片段处理程序来绘制这个光源立方体。片段处理程序是一致的,因为都是从点来绘制对象结构的,只需要改变片段处理器就可以了,因为片段处理器决定一个对象的表面颜色。在下面的绘制光源的片段处理程序中,我们将光源的颜色设置为白色。

#version 330 core
out vec4 color;

void main()
{
    color = vec4(1.0f); // Set alle 4 vector values to 1.0f
}

有了上面的shaders,当我们绘制物体的时候,比如说绘制箱子或者其它物体,我们使用前面定义的shader或者根据要绘制的物体新创建的shader;而当我们绘制光源的时候,我们就利用我们刚刚创建的这个片段处理程序就可以了。这样的话可以将物体的绘制和光源的绘制分开来,方便各自的修改也相互不影响。

其实,绘制这个立方体光源的主要作用是让我们实际看到光源的位置,知道光是从哪里发出来的。在实际应用中,我们只是将光源的位置定义在场景中的某个位置处,这个位置实际上并不需要可以看见。在这个例子中,我们在只是在定义的光源位置处绘制了一个光源来让我们有直观的认识。

Ok,我们来利用一个vec3类型的全局向量来表示世界坐标系中光源的位置:

glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

为了在这个位置上绘制立方体光源,我们需要在绘制之前将这个小立方体转换到这个位置,用我们之前学到的转换方法就可以了。当然,还有就是光源大小的事情(在前面我还一直担心绘制出来一个巨大无比的立方体光源呢^_^):

model = glm::mat4();
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f)); 

最终,和绘制光源相关的代码应该是如下所示的样子:

lampShader.Use();
// Set the model, view and projection matrix uniforms
...
// Draw the lamp object
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);          
glBindVertexArray(0);

让我们把上面的步骤合起来,运行一下看看效果吧,我的效果是这样的:

这是代码:main.cpp

看上去并没有想象得那么真实啊,因为我们后面还要学到好多关于光源和颜色的知识,这个教程算是颜色和光照学习的环境搭建和知识准备吧。后面才是真正能够体验到光照的其妙之处的地方~