OpenGL Light 1: basic colors and lighting

This article is a personal record of learning, learning is recommended to see the tutorial https://learnopengl-cn.github.io/
very grateful to the original author JoeyDeVries and quality tutorials, mostly Chinese translation provided by

The content insert comments, you can skip

Foreword

We briefly mentioned how to use color (Color) in OpenGL, but we still come into contact with are very superficial knowledge, and now we want more in-depth discussion of what is the color, and also for the study of light (Lighting ) Creating a scene

colour

First you have to know that we have been using a limited value to simulate the real world in infinite colors, so not all the colors of the real world can be represented by numeric values, but we can still be a lot of color to show through numerical , you probably will not even notice any difference in color and reality

Color can be digitized by a red (Red), green (Green), and blue (Blue) of three components, which are usually abbreviated as RGB, using only three values ​​can be combined arbitrary color

For example, to get a Orange (Coral) color, we can define a color vector:

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

We see the color of the color of an object is not the object really have in real life, but it reflected (Reflected) color, in other words, those colors can not be absorbed by the object (Absorb) is (are rejected color) is the color we can perceive objects

White sunlight can be seen in fact is a combination of many different colors made, see the figure, if we white light in an orange object, the object absorbs the white light orange addition to orange in all sub-colors, not It absorbed the orange light is reflected to our eyes, so that the object appears to be orange

These laws reflected color is directly used in the graphics field, when we create a light source in OpenGL, we have to give a light color, for the time being the light source to sunlight white, then calculate the graphics out of it reflection color?

As long as the color value of the object light source color is multiplied (cross product), the resultant is reflected by an object color:

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

We can see the color of the toy absorbs the white light source of a large part of the color, but the color of it according to their own values to red, green, and blue components have made a certain amount of reflection, which also showed the reality of colors working principle

Thus, we can define the color of the object is object size from each color component of a light source and reflected


Now, if we use the green light what will happen then?

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

It can be seen, and no red and blue light to make our toys to absorb or reflect. The toy absorbs green light value in half, but still also reflected half of the green values, toys now appears to be dark green, we can see that if we use the green light to illuminate the toy, then only the green component can be reflection and perception, red and blue is not to be perceived by us

Theory of these colors is enough, let's construct an experiment with the bar scene

Build lighting scene

Next we will extensively use color to simulate real-world lighting effects, create some interesting visual effect
because we will now use a light source, and we want them to be displayed as visible objects in the scene and add at least a test object to simulate lighting effects

First, we need an object as the object is cast light (Cast the light), where it used to do before the box it
we need a body to represent the position of the light source in a 3D scene, simplicity, we still use a cube to It represents the light source

Fill a vertex buffer object (VBO), vertex attributes pointer and set about some other mess of things now should be very easy for you, so we will not repeat those steps, and if you still think this is very difficult, it is recommended you then looking through the previous blog

Well started

First, we need a vertex shader to draw the box, compared with the previous vertex shader, the vertex position of the container is maintained (although this time we do not have the texture coordinates), and therefore no new vertex shader code we will simplify the code written before:

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

Remember to update your data and vertex attributes pointer to make it with the new vertex shader consistent (you can keep the texture data and attribute pointer, but this one will not use them in the blog)

Because we need to create a representation cube lamp (light source), so we have to create a special lamp for the VAO

Of course, we can also make the lights and other objects using the same VAO, simply its model (model) transformation matrix to do some good, but the next tutorial we will vertex attribute data and pointers to make frequent changes, we do not want these changes affect the lamp (we only care about the vertex position of the lamp), so we need to create a new VAO lamp

unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// 只需要绑定VBO不用再次设置VBO的数据,因为箱子的VBO数据中已经包含了正确的立方体顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 设置灯立方体的顶点属性(对我们的灯来说仅仅只有位置数据)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

This code should be very intuitive for you, and now we have created a means light boxes and illuminated objects, we just need to define a fragment shader on the line:

#version 330 core
out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

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

This fragment shader acceptable color and the color of the object from the light source uniform variables, we object and the color of the light (reflected) color is multiplied

The shader should be easy to understand, we set the color of the object to coral mentioned before, and the white light source is disposed.

// 在此之前不要忘记首先 use 对应的着色器程序(来设定uniform)
lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor",  1.0f, 1.0f, 1.0f);

It is noted that, when we modified vertex or fragment shader, position or color of the lamp will change, this is not what we want, we do not want color light by lighting calculations in the next tutorial results affected, but to it can be separated from other computing, we want to keep bright lights, the color change is not affected by the other (so that it was more like a real source)

To achieve this goal, we need to create another set of shader to draw the lamp , thereby ensuring that it can not be affected when the other light changes the shader, vertex shader our current vertex shader is the same so you can now directly to the vertex shader Used on lights

Lamp to the lamp fragment shader defines a constant white constant, to ensure that the color of the lamp was lit:

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0); // 将向量的四个分量全部设置为1.0
}

When we want to draw our object, we need to use the definition of light shaders just to draw a box (or possibly other objects). When we want to draw light, we will use the lamp shader, after the tutorial we will gradually update this light shaders, which can achieve a more realistic effect slowly

The main purpose of using this light cube is to let us know the specific location of the light source in the scene. We usually define the position of a light source in the scene, but this is only one location, it does not have the visual sense. In order to show the real lamp, we will represent the cube light source is plotted on the same position. We will use our new fragment shader for it to draw it, it has been in the state of a white, free from the influence of light in the scene.

We declare a global vec3variable to represent the position of the light source in the world space coordinates of a scene:

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

Then we shift to light here, then it will shrink a little, it is not so obvious:

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

Lamp cube drawing code should look like the following:

lampShader.use();
// 设置模型、视图和投影矩阵uniform
lampShader.setMat4("projection", projection);
lampShader.setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f)); // 小点的立方体
lampShader.setMat4("model", model);
// 绘制灯立方体对象
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);

Please put all the above code snippet in your program in the right position, so that we can have a clean experimental site of the light. If all goes well, the effect will run as shown (the pull-back camera)

Light foundation

Light of the real world is extremely complex and affected by many factors, we have a computer with limited computing power can not be completely simulated, so OpenGL lighting using a simplified model of reality is approximated to deal with them will easier, and it seems almost as

These models are based on illumination of our understanding of the physical properties of light, wherein a model is called Phong lighting model (the Model Lighting Phong) , lighting model Feng main structure consists of three components: the environment (the Ambient) , Man reflection (the Diffuse) , a mirror (the specular) light

1565256379160

  • Ambient light (Ambient Lighting) : even in the night, often there are still some bright (moon, distant light) in the world, so the object is almost never completely dark, in order to simulate this situation, we will use an environment constant light, it will always object "color"
  • 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact),它是冯氏光照模型中视觉上最显著的分量,物体的某一部分越是正对着光源,它就会越亮
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点,镜面光照的颜色相比于物体的颜色会更倾向于光的颜色

为了创建较为真实和丰富的视觉场景,我们希望模拟至少这三种光照分量,现在我们将以最简单的一个开始:环境光照

环境光照 Ambient Lighting

首先你要知道,生活中的光通常都不是来自于同一个光源,而是来自于我们周围分散的很多光源,很多光源并不显而易见,生活中的光可以向很多方向发散并反弹,从而到达较远的点

总的来说,光能够在其它的表面上反射,从而对一个物体产生间接的影响,考虑到以上情况的算法叫做全局照明(Global Illumination)算法,但是这种算法既开销高昂又极其复杂,所以,我们现在会先使用一个简化的全局照明模型,即环境光照

我们之前使用一个很小的常量(光照)颜色,添加到物体片段的最终颜色中,这样子的话即便场景中没有直接的光源也能看起来存在有一些发散的光,那么把环境光照添加到场景里也差不多:我们用光的颜色乘以一个很小的常量(环境因子),再乘以物体的颜色,然后将最终结果作为片段的颜色:

// 片段着色器
void main()
{
    float ambientStrength = 0.2;
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}

现在运行你的程序,你会注意到冯氏光照的第一个阶段已经应用到你的物体上了,这个物体非常暗,但由于应用了环境光照(光源立方体没受影响是因为我们对它使用了另一个着色器),也不是完全黑的

如你所见环境光照本身不能提供特别强的效果,但是加入漫反射光照就能开始对物体产生显著的视觉影响了

漫反射光照 Diffuse Lighting

漫反射光照简单来说就是,使物体上与光线方向越接近的片段能从光源处获得更多的亮度

我们需要测量这个光线是以什么角度接触到这个片段的,如果光线垂直于物体表面,这束光对物体的影响会达到最大化(也就是最亮)

为了测量光线和片段的角度,我们使用一个叫做法向量(Normal Vector)的东西,它是垂直于片段表面的一个向量(这里以黄色箭头表示),这两个向量之间的角度很容易就能够通过点乘计算出来(两个单位向量的夹角越小,它们点乘的结果越倾向于1,当两个向量的夹角为90度的时候,点乘会变为0)

点乘返回一个标量,我们可以用它计算光线对片段颜色的影响,这样不同片段朝向光源的方向的不同,这些片段被照亮的情况也不同

现在我们知道了,计算漫反射光照需要:

  • 法向量:一个垂直于顶点表面的向量
  • 定向的光线:作为光源的位置片段的位置之间向量差的方向向量,为了计算这个光线,我们需要光的位置向量和片段的位置向量

法向量(Normal Vector)

法向量是一个垂直于顶点表面的(单位)向量,由于顶点本身并没有表面(它只是空间中一个独立的点),我们要利用它周围的顶点来计算出这个顶点的表面

我们要用到一个小技巧,使用叉乘对立方体所有的顶点计算法向量,但是由于3D立方体不是一个复杂的形状,所以我们可以简单地把法线数据手工添加到顶点数据中,更新后的顶点数据数组如下:

float vertices[] = {
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 

    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,

    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
};

这些法向量真的是垂直于立方体各个平面的表面的

然后更新:

//位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//法向量属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

由于我们向顶点数组添加了额外的数据,所以我们应该更新光照的顶点着色器:

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

现在我们已经向每个顶点添加了一个法向量并更新了顶点着色器,我们还要更新顶点属性指针,注意,灯使用同样的顶点数组作为它的顶点数据,然而灯的着色器并没有使用新添加的法向量,我们不需要更新灯的着色器或者是属性的配置,但是至少要修改一下顶点属性指针来适应新的顶点数组的大小:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

我们只想使用每个顶点的前三个float,并且忽略后三个float,所以我们只需要把步长参数改成float大小的6倍就行了

虽然对灯的着色器使用不能完全利用的顶点数据看起来不是那么高效,但这些顶点数据已经从箱子对象载入后开始就储存在GPU的内存里了,所以我们并不需要储存新数据到GPU内存中,对比给灯专门分配一个新的VBO更高效

所有光照的计算都是在片段着色器里进行,所以我们需要将法向量由顶点着色器传递到片段着色器:

// 顶点着色器
out vec3 Normal; //新增

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    Normal = aNormal; //新增
}

在片段着色器中也要加入定义相应的输入变量:

// 片段着色器
in vec3 Normal;

我们现在对每个顶点都有了法向量,但是我们仍然需要光源的位置向量和片段的位置向量

光线方向向量

光源的位置向量

由于光源的位置是一个静态变量,我们可以简单地在片段着色器中把它声明为uniform:

// 片段着色器
uniform vec3 lightPos;

然后在渲染循环中(渲染循环的外面也可以,因为它不会改变)更新uniform。我们使用在前面声明的lightPos向量作为光源位置:

// 渲染循环内
lightingShader.setVec3("lightPos", lightPos);
片段的位置向量

最后还需要片段的位置,我们会在世界空间中进行所有的光照计算,因此我们需要一个在世界空间中的顶点位置。我们可以通过把顶点位置属性乘以模型矩阵(不是观察和投影矩阵)来把它变换到世界空间坐标,这个在顶点着色器中很容易完成,所以我们声明一个输出变量,并计算它的世界空间坐标:

// 顶点着色器
out vec3 FragPos;  
out vec3 Normal;

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

最后,在片段着色器中添加相应的输入变量

// 片段着色器
in vec3 FragPos;

现在,所有需要的变量都设置好了,我们可以在片段着色器中添加光照计算了

计算漫反射光照

我们的第一件事是计算光线的方向向量,也就是是光源位置向量片段位置向量之间的向量差,同时我们要把结果转化为单位向量:

// 片段着色器
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

当计算光照时,我们通常不关心一个向量的模长或它的位置,我们只关心它们的方向,所以,几乎所有的计算都使用单位向量完成,这样可以简化大部分的计算(比如点乘)

下一步,我们对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫发射影响,结果值再乘以光的颜色,得到漫反射分量

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
//两个向量之间的角度越大,漫反射分量就会越小

但是,如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致漫反射分量变为负数,为此,我们使用max函数返回两个参数之间较大的参数,从而保证漫反射分量不会变成负数(负数颜色的光照是没有定义的,所以最好避免它,除非你是那种古怪的艺术家)

现在我们有了环境光分量和漫反射分量,我们把它们相加,然后把结果乘以物体的颜色,来获得片段最后的输出颜色

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

好了,终于可以运行试试看了:

现在有了漫反射光照,立方体看起来就真的像个立方体了

完整着色器代码:

//片段着色器
#version 330 core

in vec3 Normal;
in vec3 FragPos;

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;

void main()
{
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    float ambientStrength = 0.2;
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = (ambient + diffuse) * objectColor;
    FragColor = vec4(result, 1.0);
}
//顶点着色器
#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 FragPos;  
out vec3 Normal;

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

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

最后一件事

现在我们已经把法向量从顶点着色器传到了片段着色器,可是,目前片段着色器里的计算都是在世界空间坐标中进行的,那我们是不是应该把法向量也转换为世界空间坐标?对,但如果你直接用它乘以模型矩阵就可能会出现下图的情况

img

为什么呢?

首先,法向量只是一个方向向量,方向向量不能表达空间中的特定位置,同时,法向量没有齐次坐标(顶点位置中的w分量),也就是说位移操作不应该影响到法向量

对于法向量,我们只希望对它实施缩放和旋转变换

因此,如果我们打算把法向量乘以一个模型矩阵,我们就要从矩阵中移除位移部分:只选用模型矩阵左上角3×3的矩阵,或者把法向量的w分量设置为0再乘以4×4矩阵

其次,如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了,因此,我们不能用这样的模型矩阵来变换法向量

修复的方法是使用一个为法向量专门定制的模型矩阵,这个矩阵称之为法线矩阵(Normal Matrix),它使用了一些线性代数的操作来移除对法向量错误缩放的影响

法线矩阵被定义为「模型矩阵左上角 的逆矩阵 的转置矩阵」,如果你不明白这是什么意思,别担心,我们还没有讨论逆矩阵(Inverse Matrix)和转置矩阵(Transpose Matrix),注意,大部分的资源都会将法线矩阵定义为应用到模型-观察矩阵(Model-view Matrix)上的操作,但是由于我们只在世界空间中进行操作(不是在观察空间),我们只使用模型矩阵

现在,在顶点着色器中,我们可以使用inverse和transpose函数自己生成这个法线矩阵,这两个函数对所有类型矩阵都有效。注意我们还要把被处理过的矩阵强制转换为3×3矩阵,来保证它失去了位移属性以及能够乘以vec3的法向量

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

在漫反射光照部分,光照表现没有出现问题,是因为我们没有对物体本身执行任何缩放操作

即使是对于着色器来说,逆矩阵也是一个开销比较大的运算,因此,只要可能就应该避免在着色器中进行逆矩阵运算,它们必须为你场景中的每个顶点都进行这样的处理,当然我们用作学习目这样做是可以的,但是对于一个对效率有要求的应用来说,在绘制之前你最好用CPU计算出法线矩阵,然后通过uniform把值传递给着色器(像模型矩阵一样)

镜面光照 Specular Lighting

如果你还没晕,我们就再把镜面高光(Specular Highlight)加进来,这样冯氏光照才算完整

和漫反射光照一样,镜面光照也是依据光的方向向量和物体的法向量来决定的,但是它也依赖于观察方向,例如玩家是从什么方向看着这个片段的

镜面光照是基于光的反射特性,如果我们想象物体表面像一面镜子一样,那么,无论我们从哪里去看那个表面所反射的光,镜面光照都会达到最大化

img

我们通过反射法向量周围光的方向来计算反射向量,然后我们计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大

它的作用效果就是,当我们去看光被物体所反射的那个方向的时候,我们会看到一个高光

观察向量是镜面光照附加的一个变量,我们可以使用观察者世界空间位置和片段的位置来计算它,之后,我们计算镜面光强度,用它乘以光源的颜色,再将它加上环境光和漫反射分量

我们选择在世界空间进行光照计算,但是大多数人趋向于在观察空间进行光照计算,在观察空间计算的好处是,观察者的位置总是(0, 0, 0),所以这样你直接就获得了观察者位置,可是我发现在学习的时候在世界空间中计算光照更符合直觉,如果你仍然希望在观察空间计算光照的话,你需要将所有相关的向量都用观察矩阵进行变换(记得也要改变法线矩阵)

为了得到观察者的世界空间坐标,我们简单地使用摄像机对象的位置坐标代替(它当然就是观察者),所以我们把另一个uniform添加到片段着色器,把相应的摄像机位置坐标传给片段着色器:

//片段着色器
uniform vec3 viewPos;
//渲染循环
lightingShader.setVec3("viewPos", camera.Position);

现在我们已经获得所有需要的变量,可以计算高光强度了

首先,我们定义一个镜面强度(Specular Intensity)变量,给镜面高光一个中等亮度颜色,让它不要产生过度的影响

float specularStrength = 0.5;

如果我们把它设置为1.0f,我们会得到一个非常亮的镜面光分量,这对于一个珊瑚色的立方体来说有点太多了(之后我们再讨论如何合理设置这些光照强度,以及它们是如何影响物体的)

下一步,我们计算视线方向向量,和对应的沿着法线轴的反射向量:

vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);

需要注意的是我们对lightDir向量进行了取反,reflect函数要求第一个向量是光源指向片段位置的向量,但是lightDir当前正好相反,是从片段指向光源(由先前我们计算lightDir向量时,减法的顺序决定),为了保证我们得到正确的reflect向量,我们通过对lightDir向量取反来获得相反的方向
第二个参数要求是一个法向量,所以我们提供的是已标准化的norm向量。

剩下要做的是计算镜面分量

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

我们先计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的32次幂,这个32是高光的反光度(Shininess),一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小

img

我们不希望镜面成分过于显眼,所以我们把指数保持为32,剩下的最后一件事情是把它加到环境光分量和漫反射分量里,再用结果乘以物体的颜色:

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

我们现在为冯氏光照计算了全部的光照分量。根据你的视角,你可以看到类似下面的画面:

源码:

//片段着色器

#version 330 core

in vec3 Normal;
in vec3 FragPos;

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;

float specularStrength = 0.5;

void main()
{
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    float ambientStrength = 0.2;
    vec3 ambient = ambientStrength * lightColor;

    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;

    vec3 result = (ambient + diffuse + specular) * objectColor;
    FragColor = vec4(result, 1.0);
}
//顶点着色器

#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 FragPos;  
out vec3 Normal;

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

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    Normal = mat3(transpose(inverse(model))) * aNormal;
    FragPos = vec3(model * vec4(aPos, 1.0));
}

在光照着色器的早期,开发者曾经在顶点着色器中实现冯氏光照模型,在顶点着色器中做光照的优势是,相比片段来说,顶点要少得多,因此会更高效,所以(开销大的)光照计算频率会更低。然而,顶点着色器中的最终颜色值是仅仅只是那个顶点的颜色值,片段的颜色值是由插值光照颜色所得来的,结果就是这种光照看起来不会非常真实,除非使用了大量顶点

img

在顶点着色器中实现的冯氏光照模型叫做Gouraud着色(Gouraud Shading),而不是冯氏着色(Phong Shading)

由于插值,这种光照看起来有点逊色,而冯氏着色能产生更平滑的光照效果

现在你应该能够看到着色器的强大之处了,只用很少的信息,着色器就能计算出光照如何影响到所有物体的片段颜色,之后我们还会更深入的研究光照模型

Guess you like

Origin www.cnblogs.com/zhxmdefj/p/11360712.html