9.2 初窥着色器
在这节中,我们将通过具体的着色器代码,初步理解着色器编程。至于具体如何将着色器应用于程序,将在下一节做介绍。
我们回顾一下上节内容,着色器是一段在GPU中执行的接近C语言的代码,顶点着色器对于每个顶点调用一次,片元着色器对于每个片元调用一次。
着色器语言的调试有时候十分困难,很可能报的错让你不明所以。建议使用Chrome和Firefox调试,此外,Chrome的一个插件也可能给你提供一定帮助。另外,从我写着色器的经验来看,最常发生错误的原因就是忘记float
类型和int
类型不会自动转换的,因此,当你想表达浮点数零的时候,一定要写成0.0
而非0
。当然,即使我在这里提醒大家了,你仍然会惊讶这一错误发生的频率之高!
顶点着色器
着色器是类似C语言的代码,即便如此,下面代码仍然可能让你感到困惑:
varying vec2 vUv;
void main()
{
// passing texture to fragment shader
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
我们可以猜测到,和C语言一样,着色器程序也从main
函数开始调用。但除此之外……就有点看不懂了吧?
让我们一起来认识一下varing
。它是WebGL定义的限定符(Qualifier),限定符用于数据类型(Type)之前,表明该变量的性质。
限定符共有四个:
const
:这是我们熟悉的常量的意思attribute
:从JavaScript代码传递到顶点着色器中,每个顶点对应不同的值uniform
:每个顶点/片元对应相同的值varying
:从顶点着色器传递到片元着色器中
如果不写限定符,那么默认是只有在当前文件中能访问。
所以,varying vec2 vUv;
的意思是,声明了一个叫vUv
的变量,它的类型为vec2
,该变量是为了将顶点着色器中的信息传递到片元着色器中。那么它传递了什么信息呢?我们看到与之相关的只有vUv = uv;
,可是uv
都没声明过啊!这是哪里来的?
其实,uv
是Three.js帮你传进来的一个很有用的属性,它代表了该顶点在UV映射时的横纵坐标。简单地说,一个物体的模型可能很复杂,对其贴图的一个简单有效的方法就是UV映射,将每个面片贴的图统一映射到一张纹理上,记录每个面片贴图在纹理上对应的位置。得到这样的效果:
而之所以称为u
和v
,指的就是在纹理映射后的新坐标系。我们也发现,uv
变量的类型是vec2
,顾名思义就是一个二维的向量,可以使用uv.x
和uv.y
分别访问到uv
两个维度的值。
使用varying vec2 vUv;
将uv
信息传递到片元着色器是因为片元着色器本身不能访问到uv
信息,如果需要得到这一值的话,就需要从顶点着色器中传递过去,我们将其命名为vUv
。
那么,gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
又是在干什么呢?学过图形学的读者一定对投影矩阵、模型矩阵并不陌生,这里做的事情就是计算三维模型在二维显示屏上的坐标。这里,我们看到position
也没有预先定义过,不过通过上面的uv
应该也能猜测到position
也是Three.js为我们提供的一个方便。position
是顶点在物体坐标系(而不是世界坐标系)中的位置。这就意味着,一个正方体位于世界坐标系的(2, 0, 0)
与位于(0, 0, 0)
将不会改变任何顶点的position
,这个position
是相对于正方体的锚点而言的。
因此,这段顶点着色器的作用就是将uv
信息传递到片元着色器中,并按默认的方式计算顶点位置。
片元着色器
有了前面顶点着色器传过来的vUv
信息,我们能做些有意思的事了吧?比如来看看使用颜色表示uv
信息如何?
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv.x, vUv.y, 1.0, 1.0);
}
太好了,它看起来很简单!
你能告诉我上面代码是什么意思吗?
来看看你回答得对不对。varying vec2 vUv;
同样声明了从顶点着色器传递到片元着色器中的vUv
属性,记得要在片元着色器中再写一遍。主程序只有gl_FragColor = vec4(vUv.x, vUv.y, 1.0, 1.0);
,gl_FragColor
用来设置片元的颜色,vec4
的四个维度分别表示红、绿、蓝以及alpha通道。因此,这里我们是将vUv
的两个维度分别对应到红绿通道,得到的效果是:
现在,你是不是对UV映射有更深的理解了呢?对于正方体而言,每个面都映射到了整个UV纹理,所以呈现了如上结果。而对于正四面体而言,每个面都映射到了UV纹理的一部分,因此呈现的效果是这样的: