OpenGL学习笔记11-Colors

Colors

Lighting/Colors

在前几章中,我们简要地使用和处理了颜色,但从未正确地定义过它们。在这里,我们将讨论什么颜色是,并开始建立场景为即将到来的照明章节。

在现实世界中,颜色可以采用任何已知的颜色值,每个对象都有自己的颜色。在数字世界中,我们需要将(无限的)真实颜色映射到(有限的)数字值,因此并不是所有真实世界的颜色都可以用数字表示。颜色用数字表示,使用红、绿、蓝组成部分,通常缩写为RGB。使用这3个值的不同组合,在[0,1]的范围内,我们可以表示几乎任何颜色。例如,要得到一个珊瑚色,我们定义一个颜色向量为:


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

我们在现实生活中看到的物体的颜色并不是它实际拥有的颜色,而是物体反射出来的颜色。没有被物体吸收(排斥)的颜色就是我们感知到的颜色。例如,太阳的光被认为是一种白光,它是许多不同颜色的总和(正如你在图片中看到的)。如果我们把这个白光照射在一个蓝色的玩具上,它会吸收除蓝色之外的所有白色的亚颜色。由于玩具不吸收蓝色部分,它被反射。反射光进入我们的眼睛,使它看起来像一个蓝色的玩具。下面的图片显示了这是一个珊瑚色的玩具,它反映了不同的颜色强度:

 

你可以看到,白色的阳光是所有可见颜色的集合,而物体吸收了这些颜色的很大一部分。它只反射那些代表物体颜色的颜色,这些颜色的组合就是我们所感知到的(在这个例子中是珊瑚色)。

从技术上讲,它有点复杂,但是我们将在PBR的章节中讨论它。

这些颜色反射规则直接应用于图形领域。当我们在OpenGL中定义一个光源时,我们想给这个光源一个颜色。在前一段中,我们使用了白色,所以我们也将光源设置为白色。如果我们将光源的颜色与物体的颜色值相乘,得到的颜色将是物体的反射颜色(因此它的感知颜色)。让我们回顾一下我们的玩具(这次使用珊瑚值),看看如何计算它在graphics-land中的感知颜色。我们通过在光和物体颜色向量之间进行组件式乘法得到结果的颜色向量:


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);

我们可以看到,玩具的颜色吸收了很大一部分白光,但反射了几个基于它自己的颜色值的红、绿、蓝值。这是在现实生活中如何使用颜色的一种表现。因此,我们可以将一个物体的颜色定义为它从光源反射的每种颜色分量的数量。如果我们用绿灯会发生什么?


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 一个照明场景

在接下来的章节中,我们将通过模拟真实世界的灯光广泛使用颜色来创建有趣的视觉效果。因为现在我们将使用光源,我们想要在场景中显示它们作为可视对象,并添加至少一个对象来模拟光源。

我们需要的第一件事是一个对象来打开灯,我们将使用前几章中臭名昭著的容器立方体。我们还需要一个光对象来显示光源在3D场景中的位置。为了简单起见,我们将用一个立方体来表示光源(我们已经有了顶点数据vertex data,对吧?)

填充一个顶点缓冲区对象,设置顶点属性指针,所有这些你现在应该很熟悉了,所以我们不会带你走过这些步骤。如果你仍然不知道发生了什么,我建议你在继续之前回顾一下前面的章节previous chapters,并在可能的情况下完成练习。

首先我们需要一个顶点着色器来绘制容器。容器的顶点位置保持不变(尽管这次我们不需要纹理坐标),所以代码应该没有什么新内容。我们将使用一个剥离版本的顶点着色器从上一章:


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

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

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

确保更新顶点数据和属性指针来匹配新的顶点着色器(如果你想,你实际上可以保持纹理数据和属性指针活动;我们只是现在不使用它们)。

因为我们还将渲染一个光源立方体,所以我们想为光源生成一个新的VAO。我们可以呈现相同的光源VAO,然后做一些光位置对模型转换矩阵,但在接下来的章节我们将改变顶点数据和属性的指针容器对象通常我们不希望这些变化传播到光源对象(我们只关心光立方体的顶点的位置),所以我们将创建一个新的VAO:


unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// we only need to bind to the VBO, the container's VBO's data already contains the data.
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// set the vertex attribute 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

代码应该相对简单。现在我们创建了容器和光源立方体,还有一件事需要定义,那就是容器和光源的片段着色器:


#version 330 core
out vec4 FragColor;
  
uniform vec3 objectColor;
uniform vec3 lightColor;

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

fragment shader从一个统一变量中接受一个对象颜色和一个浅色。这里我们将光的颜色与物体(反射)的颜色相乘,就像我们在本章开始时讨论的那样。同样,这个着色器应该很容易理解。让我们把对象的颜色设置为最后一部分的珊瑚色和白光:


// don't forget to use the corresponding shader program first (to set the uniform)
lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor",  1.0f, 1.0f, 1.0f);

还有一件事需要注意的是,当我们在接下来的章节中开始更新这些照明着色器时,光源立方体也会受到影响,这不是我们想要的。我们不希望光源对象的颜色受到光照计算的影响,而是让光源与其他光源隔离。我们希望光源有一个恒定的明亮的颜色,不受其他颜色变化的影响(这使它看起来像光源立方体真的是光源)。

为了完成这一点,我们需要创建第二套着色器,我们将使用它来绘制光源立方体,这样就可以避免任何对照明着色器的更改。顶点着色器和光照顶点着色器是一样的,所以你可以简单地复制源代码。光源立方体的fragment shader通过在灯上定义一个恒定的白色来确保立方体的颜色保持明亮:


#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0); // set all 4 vector values to 1.0
}

当我们想要渲染时,我们想要渲染容器对象(或者可能是许多其他的对象)使用我们刚刚定义的照明着色器,当我们想要绘制光源时,我们使用光源的着色器。在照明章节中,我们将逐步更新照明着色器,以慢慢实现更真实的结果。

光源立方体的主要目的是显示光从哪里来。我们通常在场景的某个地方定义光源的位置,但这只是一个没有视觉意义的位置。为了显示光源的实际位置,我们在光源的相同位置渲染一个立方体。我们使用光源立方体着色器来渲染这个立方体,以确保立方体始终保持白色,不管场景的光照情况如何。

那么让我们声明一个全局的vec3变量,它表示光源在世界空间坐标中的位置:


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

然后我们将光源立方体转换到光源的位置,并在渲染前将其缩小:


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

光源立方体的渲染结果应该是这样的:


lightCubeShader.use();
// set the model, view and projection matrix uniforms
[...]
// draw the light cube object
glBindVertexArray(lightCubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);			

将所有的代码片段注入到适当的位置将会产生一个干净的OpenGL应用程序,该应用程序经过了正确的配置,可以进行照明实验。如果一切编译,它应该是这样的:

 

现在没有太多可看的,但我保证在接下来的章节中会变得更有趣。

如果您很难找到所有代码片段在应用程序中的整体位置,请查看这里here 的源代码,并仔细查看代码/注释。

现在我们已经有了一些关于颜色的知识,并且创建了一个基本的场景来实验照明,我们可以跳到下一章next 真正魔术开始的地方。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值