Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Web Development
Code

WebGL基础教程:第一部分

by
Length:LongLanguages:
This post is part of a series called WebGL Essentials.
WebGL Essentials: Part II

Chinese (Simplified) (中文(简体)) translation by Wentao Wang (王文涛) (you can also view the original English article)

WebGL是一种基于OpenGL的浏览器内置3D渲染器,它可以让你在HTML5页面中直接显示3维内容。 在本教程中,我会介绍你使用此框架所需的所有基础内容。


介绍

开始学习之前,有几件事你是需要了解的。 WebGL是将3D内容渲染到HTML5的canvas元素上的一种JavaScript API。 它是利用"3D世界"中称为着色器的两种脚本来实现这一点的。 这两种着色器分别是:

  • 顶点着色器
  • 片元着色器

听到这些名词时也不要过于惊慌;它们只不过是"位置计算器"和"颜色选择器"的另一种说法罢了。 片元着色器容易理解;它只是告诉WebGL,模型上的指点定应该是什么颜色。 而顶点着色器解释起来就需要点技术了,不过基本上它起到将3维模型转变为2维坐标的作用。 因为所有的计算机显示器都是2维平面,当你在屏幕上看3维物体时,它们只不过是透视后的幻象。

如果你想完整地理解这个计算过程,你最好是问一个数学家,因为这个过程中用到了高级的4x4矩阵乘法,实在是有点超过我们这个"基础"教程的范围呀。 幸运的是,你并不需要知道它所有的工作原理,因为WebGL会处理背后大部分的细节。 那么,我们开始吧。


第一步:设置WebGL

WebGL有许多细微的设置,而且每次你要在屏幕画什么东西之前都要设置一遍。 为了节约时间,并使代码整洁一些,我们把所有"幕后"的代码包装成一个JavaScript对象,并存于一个独立的文件中。 现在我们要开始了,先创建一个新文件'WebGL.js',并写入如下代码:

这个构造函数的参数是canvas无形的ID,以及两个着色器对象。 首先,我们要获得canvas元素,并确保它是支持WebGL的。 如果支持WebGL,我们就将WebGL上下文赋值给一个局部变量,称为"GL"。 清除颜色(clearColor)其实就是设置背景颜色,值得一提的是,WebGL中大部分参数的取值范围都是0.0到1.0,所以我们需要让通常的rgb值除以255。 所以,我们的示例中,1.0,1.0,1.0,1.0表示背景为白色,且100%可见 (即无透明)。 接下来两行要求WebGL计算深度和透视,这样离你近的对象会挡住离你远的对象。 最后,我们设置宽高比,即canvas的宽度除以它的高度。

继续前行之前,我们要准备好两个着色器。 我把这些着色器写到HTML文件里去,这个HTML文件里还包含了我们的画布元素 (canvas)。 创建一个HTML文件,并将下面的两个script元素放在body标签之前。

先来看顶点着色器,我们定义了两个属性 (attributes):

  • 顶点位置,它存储了当前顶点 (你的模型上的点) 的位置,包括x,y,z坐标。
  • 纹理坐标,即赋给这个点的纹理在纹理图像中的位置

接下来,我们创建变换和透视矩阵等变量。 它们被用于将3D模型转化为2D图像。 下一行是创建一个与片元着色器共享的变量vTextureCoord,在主函数中,我们计算gl_Position (即最终的2D位置)。 然后,我们将'当前纹理坐标'赋给这个共享变量vTextureCoord。

在片元着色器中,我们取出定义在顶点着色器中的这个坐标,然后用这个坐标来对纹理进行'采样'。 基本上,通过这个过程,我们得到了我们几何体上的当前点处的纹理的颜色。

现在写完了着色器,我们可回过头去在JS文件中加载这些着色器。 将"//Load Shaders Here"换成如下代码:

你的纹理必须是偶数字节大小,否则会出错。。。比如2x2,4x4,16x16,32x32。。。

首先,我们要确保这些着色器是存在的,然后,我们逐一地加载它们。 这个过程基本上是:得到着色器源码,编译,附着到核心的着色程序上。 从HTML文件中提取着色器源码的代码,封装到了一个函数中,称为LoadShader;稍后会讲到。 我们使用这个'着色器程序'将两个着色器链接起来,通过它,我们可以访问到着色器中的变量。 我们将数据储存到定义在着色器中的属性;然后,我们就可以将几何体输入到着色器中了。

现在,让我们看一下LoadShader函数,你应该将它置于WebGL函数之外。

基本上,这个函数通过遍历着色器来收集源码。


第二步:“简单”立方体

为了在WebGL中画出对象,你需要如下三个数组:

  • 顶点 (vertices):构造你的对象的那些点
  • 三角形 (triangles):告诉WebGL如何将顶点连接成面
  • 纹理坐标 (texture coordinates):定义顶点如何被映射到纹理图像上

这个过程称为UV映射。 我们的例子是构造一个简单的立方体。 我将这个立方体分成4个顶点一组,每一组又连成两个三角形。 我们可以用一个变量来存储立方体的这些数组。

这样一个简单的立方体用到的数据似乎有点过多,不过,在我们教程的第二部分中,我们写一个导入3D模型的脚本,所以你现在不必计较这些。

你可能还在想,为什么需要24个顶点 (每一面4个) 呢,实际上只有8个呀? 我这样做是因为,你可以只用为每个顶点指定一个纹理坐标;而如果你用8个顶点,则整个立方体将看起来一样,因为它会将一个纹理值传播到顶点接触的所有面上。 通过我们的方式,每个面都有它独有的点,所以我们可以在每一面上指定不同的纹理区域。

现在,我们有了这样一个立方体变量 cube,然后,我们可以准备画它了。 我们还是回到WebGL方法中,并添加一个Draw函数。


第三步:Draw函数

WebGL中绘制对象的过程有许多步骤;所以最好是将每个步骤写成函数,来简化这个过程的代码。 基本的想法是将三个数组加载到WebGL的缓存中去。 然后,我们将这些缓存连接到着色器中定义的属性,以及变换和透视矩阵。 接下来,我们需要将纹理加载到内存中,并且最后调用draw命令。 那么,我们开始吧。

接下来的代码进入到WebGL函数中:

顶点着色器对你的对象进行放置,旋转和缩放时,依据的都是变换和透视矩阵。 在本教程第二部分中,我们会更深入地介绍变换。

我已经添加了两个函数:MakePerspective()MakeTransform()。 它们只不过生成了WebGL所需的4x4矩阵。 MakePerspective()函数接受几个参数:视场竖直高度,宽高比,最近和最远点。 任何比1个单位近或比10000个单位远的对象都不会被显示,但是你可以调整这些值,以得到你所期望的效果。 现在,让我们看一看这两个函数:

这些矩阵都会影响到你的对象的最终视觉效果,但透视矩阵影响的是你的“3维世界”,比如视场和可见对象,而变换矩阵影响的是单个对象,比如它们的旋转和位置。 完成这些之后,我们几何可以开始画了,剩下的工作只是将一个图像转变为一个WebGL纹理。


第四步:加载纹理

加载一个纹理分两步。 首先,我们要用JavaScript的标准做法来加载一幅图像,然后,我们将其转化为一个WebGL纹理。 所以,我们先从第二步开始吧,毕竟我们正在讨论的是JS文件。 将下面的代码加到WebGL函数的底部,恰好在Draw命令之后。

值得一提的是,你的纹理大小必须是偶数字节,否则你会得到错误信息;比如它们可能的维度包括:2x2,4x4,16x16,32x32,等等。 我另加了一行来翻转Y坐标,只是因为我的3D应用的Y坐标是朝后的,但是否这样做完全取决于你。 这是因为一些程序取Y的零点为左上角,而其它则为左下角。 我设置的这些缩放性质只是告诉WebGL,图像应该如何向上采样和向下采样。 你可以使用其它的选项来得到不同的效果,不过我认为这个组合效果最佳。

现在,我们完成了JS文件,我们可以回到HTML文件,来完成最后一步了。


第五步:合起来

如前所述,WebGL是在canvas元素上画画。 因此,在body部分里,我们所需要的就只是一个canvas画布。 在添加canvas元素之后,你的html页面看起来像下面这样:

这个页面相当简单。 在head区域,我链接了JS文件。 现在,让我们实现Ready函数,它在页面加载时调用。

所以,我们创建一个新的WebGL对象,并将canvas和着色器的ID传递进去。 接下来,我们加载纹理图像。 一旦加载完成,我们对立方体Cube和纹理Texture调用Draw()方法。 如果你一路跟下来,你的屏幕上应该有一个覆盖有纹理的静止立方体。

虽然我说了下一次再讲变换,但我们不可能只丢给你一个静止矩形,这还不够三维。 让我们回过头去,再添加一个小小的旋转吧。 在HTML文件中,修改onload函数,使之如下面的代码:

这会使得每隔33毫秒调用一个称为Update()的函数,因而我们得到约30fps的帧率。 下面是这个更新函数:

这个函数相当简单;它只不过清除屏幕,然后绘制更新后的立方体。 现在,让我们进入JS文件,添加旋转代码。


第六步:添加一些旋转

我们不会完全实现变换的代码,因为我说了要等到下次现说,这次我们只是加一个绕Y轴的旋转。 要做的第一件事就是在Cube对象中加一个Rotation变量。 它会跟踪当前的角度,并让我们可以递增地保持旋转。 所以你的Cube变量的顶部代码应该如下面这样:

现在,让我们修改MakeTransform()函数,添加旋转功能:


结论

搞定! 在下一个教程中,我们会介绍加载模型和执行变换。 我希望你喜欢这个教程;欢迎在后面提出任何问题或留言。

Envato qr branded
关注我们的公众号
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.