OpenGL的官方文档中有一个例子:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
layout (location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
out vec3 ourColor; // 向片段着色器输出一个颜色
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
}
意思是在顶点着色器中,传入3个顶点的颜色,最终会生成一个有渐变色彩的三角形。
刚学到这个东西的时候感觉非常反直觉,因为顶点着色器实际上只提供了3个颜色,为什么会生成这么多的颜色并且填满整个三角形呢?
官方给的解答是:
这个图片可能不是你所期望的那种,因为我们只提供了3个颜色,而不是我们现在看到的大调色板。这是在片段着色器中进行的所谓 片段插值(Fragment Interpolation) 的结果。当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置。
基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。比如说,我们有一个线段,上面的端点是绿色的,下面的端点是蓝色的。如果一个片段着色器在线段的70%的位置运行,它的颜色输入属性就会是一个绿色和蓝色的线性结合;更精确地说就是30%蓝 + 70%绿。
这正是在这个三角形中发生了什么。我们有3个顶点,和相应的3个颜色,从这个三角形的像素来看它可能包含50000左右的片段,片段着色器为这些像素进行插值颜色。如果你仔细看这些颜色就应该能明白了:红首先变成到紫再变为蓝色。片段插值会被应用到片段着色器的所有输入属性上。
也就是说在渲染管线中,进行片段着色之前,可能会对要渲染的每一个像素进行插值。但是它是如何进行插值的呢?
起初我认为,在片段着色器里,会首先渲染这个图形每个顶点的颜色,然后进行线性插值,最后渲染每一个像素的颜色。
直到我学到漫反射之后,感觉我的理解有点不对:
如图,利用OpenGL渲染出来的漫反射图:
片段着色器代码如下:
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
uniform vec3 objColor;
uniform vec3 ambientColor;
uniform vec3 lightPos;
uniform vec3 lightColor;
void main(){
vec3 lightDir = normalize(lightPos - FragPos);
vec3 diffuse = max(dot(lightDir,Normal),0) * lightColor;//点乘
FragColor = vec4((ambientColor + diffuse) * objColor,1.0);
}
我把光源设置在第一个立方体中心前面一点的位置,渲染出来了比较真实的光照效果。
你可能会注意到,正方体中心位置会比顶点周围的像素要亮得多。如果按照我之前的理解,光栅化之后,片段着色器会先渲染每个顶点的颜色,然后插值运算每个像素的颜色,那么结果肯定和渲染出来的不同。
因为把顶点带入到片段着色器计算的话,每个顶点的颜色是用(ambientColor + diffuse) * objColor即环境光和漫反射计算出来的,由于光源和每个顶点的夹角相同,所以计算出每个顶点的颜色相同,然后进行插值运算填充像素的话,那么渲染出来的结果肯定是这个立方体正对着我们的一面都是同一种的颜色,不会出现中心要亮一点的情况。
这时候我突然发现,之前的理解肯定有问题,于是又花了些时间退回去看渲染管线的一些知识。
我认为OpenGL从顶点着色器 -> 片段着色器可能会经历这么一个过程:
这里贴上顶点着色器代码:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
layout (location = 3) 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 = (model * vec4(aPos, 1.0)).xyz;
Normal = mat3(model) * aNormal;
}
OpenGL的片段着色器在进行渲染之前,实际上会对out里面的数据,例如FragPos,Normal进行线性插值例如顶点(1,0,0,1)和(3,0,0,1),就会对中间的像素插值(2,0,0,1)。由于一个面上的顶点法向量都是相同的,所以Normal进行插值的时候,同一个面上生成的值都是相同的。
在片段着色器中,会对面一个插值出来的像素计算它的颜色,进行(ambientColor + diffuse) * objColor的计算,由于每个像素点FragPos不同,所以会计算出如上图那样立方体面中心很亮的结果,而不是像我理解那样先计算正方形面的四个顶点,然后进行插值渲染,这样就会出现一个颜色都相同的立方体面。
以上知识本人的一点理解,可能是错的,初学OpenGL,感觉它还是蛮硬核的,很多问题都要纠结很久...