当前位置: 首页 > 文档资料 > Three.js 入门指南 >

9.2 初窥着色器

优质
小牛编辑
130浏览
2023-12-01

在这节中,我们将通过具体的着色器代码,初步理解着色器编程。至于具体如何将着色器应用于程序,将在下一节做介绍。

我们回顾一下上节内容,着色器是一段在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映射,将每个面片贴的图统一映射到一张纹理上,记录每个面片贴图在纹理上对应的位置。得到这样的效果:

图片来源

而之所以称为uv,指的就是在纹理映射后的新坐标系。我们也发现,uv变量的类型是vec2,顾名思义就是一个二维的向量,可以使用uv.xuv.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纹理的一部分,因此呈现的效果是这样的: