干货|WebGL入门 把三维绘制搬上网页
Posted 西山居技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干货|WebGL入门 把三维绘制搬上网页相关的知识,希望对你有一定的参考价值。
引言
说起游戏编程,想必大家都听说过DirectX和OpenGL。DirectX是微软公司开发的一套图形接口,广泛用于Windows平台和XBox平台。OpenGL作为比DirectX更为悠久的图形接口,它在早期几乎统治着所有和图形有关的软件,并且有着不错的跨平台性,它是我们今天的主题。
尽管OpenGL目前使用的人数不如以前了,它却仍是一套优秀的图形接口。早期的3D游戏,如雷神之锤3等等,使用的图形接口都是OpenGL。OpenGL从出生开始到现在,经历了非常大的改变,由以前的固定管线流水线到了现在的可编程着色器流水线,可谓是越来越灵活,其代价是,难度也越来越大。
那么,今天我们要做的就是,通过OpenGL的网页版API WebGL来绘制下面这个色彩斑斓的三角形。由于是绘制在网页上 ,您可以用您的Pad打开,可以用您的手机打开,当然,也可以用您的电脑浏览器打开,只要您的浏览器不是假的,我相信看到的结果都是一样的。
为什么要选择WebGL呢?因为WebGL是OpenGL三维渲染中最简单的。您只要一个浏览器,一个记事本,就可以写出三维渲染的程序。
就如同要学会跑先要学会走,绘制三维图形,我们先绘制二维图形,其实二维图形是三维图形的一个特例,即每个点的z轴坐标都为0。本篇的最后,各位看官将会知道,如何在网页上绘制出一个三角形,然后举一反三,绘制出更多复杂的形状。
准备工作
如果您第一次接触OpenGL(严格点来说,是第一次接触可编程管线OpenGL),需要做好心理准备,绘制第一个图形非常麻烦,非常不直观,但是只要学会了绘制第一个图形,以后绘制多少个图形都不是问题。我将尽可能通俗地解释OpenGL的工作原理,解释其中每一步的含义。本篇着重会讲解绘制的实现原理,不会有太多数学知识。除此之外,您还需要一点点javascript的知识。
接触过WebGL的同学应该知道,现在有一个非常好用的库叫做THREE.js,它封装了WebGL的很多方法,使得我们可以用很少代码很方便绘制出自己想要渲染的图形。但是本文作者非常叛逆,并不打算使用THREE.js。我们将从最底层开始讲起,希望最后各位看官能对WebGL有一个直观的印象。
当然,如果您接触过WebGL,或者很了解OpenGL,可以直接跳到本文后端的代码编写部分。
目前主流的支持html5的浏览器都支持WebGL(换句话说,只要您不用IE,都应该是支持的)。准备一个浏览器,准备一个好看一点的文本编辑器(如Sublime),就可以开始写程序了。
绘制的基本流程
计算机绘制出来的图形是由一个个三角形组成,三角形由三个点组成,我们按照顺序连接三个点,就组成了一个三角形。
假设我们要绘制如下三角形:
三个顶点坐标如下:A(-1, 1, 0),B(1, 1, 0),C(1, -1, 0)
以下是基本绘制的流程:
1、把这些坐标保存到一块缓存中,如下所示:
我们把一个顶点包含的所有数据称为顶点数据,如顶点A的顶点数据是-1, 1, 0。
2、编写着色器处理每一组顶点数据。着色器其实就是用户自己写的一个程序,或者说是一段函数。一个WebGL程序需要包含两种着色器:顶点着色器,片元着色器。它们用途如下:
顶点着色器:决定顶点位置。在绘制每一个顶点时,顶点着色器接受一组顶点数据,并且可以设置它最终的顶点坐标。例如,在绘制A(-1, 1, 0)的时候,顶点着色器获取的一组顶点数据为(-1, 1, 0)。它可以将这个坐标设置为最终坐标,那么A最终绘制出来的坐标就是(-1, 1, 0);它也可以改变这个坐标,如在原有的基础上加1,那么A最终的输出坐标就是(0, 0, 1),即A点平移了一个单位,这些都取决于用户如何编写顶点着色器。
片元着色器:决定顶点颜色。颜色一般用(R, G, B, A)来表示,A一般取1。
WebGL在绘制每一个顶点时,将一组顶点数据先传给顶点着色器,获取最终顶点位置,然后再传给片元着色器,获取顶点的最终颜色,完成对该顶点的绘制。
3、调用绘制函数。绘制的时候,需要明确以下行为:
a. 每个顶点由几个字节来描述?本例中,一个顶点由3个浮点数来描述(因为只包含位置信息),共12字节。
b. 从缓存的哪里取数据?即从A点开始绘制,还是从B点开始绘制,还是从其它地方开始绘制?
c. 如何绘制?绘制出来的三角形是实心(填充)的呢?还是空心(线框)的呢?
d. 需要绘制多少个顶点?
只有明确以上几点,WebGL才知道如何绘制这个图元。
为了直观感受一下绘制的过程,我们用流程图的形式表示出一个三角形的大致绘制流程。
用户逻辑就是指用户需要自己编写的逻辑,如自己编写着色器、传入顶点坐标、约定格式等。WebGL的逻辑不需要用户来操心,它会根据用户逻辑来自动绘制出图元。除此之外,WebGL还会做更多的事情,比如图元装配(把两个点绘制成直线)、填充、裁剪等。由于都是封装好的流程,它的细节并不需要我们关心。
开始编程
理论知识准备就绪,现在可以开始编码,绘制出一个三角形了!
1. 创建一个网页模板
HTML5规定,WebGL的绘制必须要绘制在网页的<canvas>元素上。我们写一个简单的网页,添加一个canvas元素,其id叫作canvas,占满整个网页大小。
2. 通过Canvas拿WebGL对象
我们在网页的<body>末端添加一个<script>节点,网页会运行<script>中的js代码。下文中的js代码均位于<script>节点中。
我们需要获取WebGL对象,我们先通过getElementById拿到Canvas的DOM对象。之后,再通过canvas.getContext来拿WebGL绘制上下文。WebGL的绘制上下文有两个名字,一个叫experimental-webgl,一个叫作webgl,我们专门写个函数来拿它,并保存为全局变量。
对于不支持的浏览器,getContext返回的结果为空,此时就会弹出一个警告框。
3. 传入顶点坐标
我们需要开辟一块缓存,传入我们的3个顶点坐标,共9个浮点数。用gl.createBuffer()开辟缓存。开辟完之后,我们要调用gl.bindBuffer,表示在此之后,我们所使用的就是这一块缓存。用gl.bufferData把我们的顶点数据放入缓存中。
bindBuffer第一个参数传gl.ARRAY_BUFFER,表示我们传入的是顶点的坐标(而不是索引)。第二个参数表示我们今后使用的缓存。
bufferData的第一个参数也应该是gl.ARRAY_BUFFER,第二个参数传入我们的数据,我们定义了一个data数组,并新建了一个Float32Array对象,表示这个数组中每一个数都是32位(4字节)的浮点数,这一步必不可少,否则无法正确绘制。第三个参数表示这块缓存是用来绘制的,只写入一次,并且以后会多次使用,它的作用在于提升性能。
直观来讲,整个过程就是,我申请一块缓存,我之后将使用这块缓存,我往缓存中放数据。
4. 编写着色器
OpenGL的着色器语言叫GLSL。GLSL是一种和C语言十分类似的语言,每个着色器都有一个main函数,它是着色器的入口,每种着色器都有自己的全局变量,发挥不同的作用。下面简单来讲解一下两种着色器的编写方法。
a.顶点着色器:最简单的顶点着色器如下所示:
先解释代码中vec3、vec4的含义。
vec3、vec4是GLSL中的内置类型,表示分别一个三维、四维向量。vec3 vertex表示定义一个三维向量,变量名为vertex。
attribute关键字表示,这个变量会由外部传入,也就是这个vertex的值会从我们使用的buffer中获取。比如,在绘制A点时,A点的坐标就会被传入vertex;绘制B点时,vertex又会变成B点坐标。
gl_Position是顶点着色器的内置变量,表示此顶点最终的位置,是一个四维向量,第四维一般取1。这个例子中,我们把前3维直接从vertex中拷贝过来,第4维设置为1。
b.片元着色器
gl_FragColor是片元着色器中的内置变量,表示最终的颜色。这里我们直接赋值(1,0,0,1),表示红色。
5. 使用着色器
着色器其实就是一段字符串格式的GLSL代码,我们把两种着色器用变量保存起来:
接下来,我们用gl.createShader创建一个着色器。createShader接收一个参数,表示着色器类型。如果是顶点着色器,传gl.VERTEX_SHADER,如果是片元着色器,传gl.FRAGMENT_SHADER。创建完了之后,我们调用gl.shaderSource给着色器传入源码,也就是上面的vertexShaderSrc和fragShaderSrc。传入源码之后,我们调用gl.compileShader来编译着色器,用gl.getShaderParameter来获取着色器状态,如果GLSL中出现错误,应该及时提醒用户。
将上面过程封装成函数就是:
一个WebGL程序由若干个着色器组成,这些着色器将组成一个着色器程序(Program)。我们要调用gl.createProgram创建着色器程序,并把刚刚两个着色器通过gl.attachShader绑定进来。绑定之后,调用gl.linkProgram链接这两个着色器,并调用gl.useProgram来表明,整个绘制过程,我们将使用这个着色器程序。
6. 配置顶点数据属性
关键性的一步。还记得顶点着色器中有一行“attribute vec3 vertex;” 吗?顶点着色器在绘制每一个顶点的时候,会把缓存中的顶点赋给vertex。那么,我们是如何把顶点的3个浮点数赋给vertex的呢?
a. 我们要拿到某个attribute变量在着色器中的位置:
WebGL会在顶点着色器程序中寻找一个名称为vertex的attribute变量,把其位置赋值给vertexPositionAttribute。
b. 激活attribute功能
调用gl.enableVertexAttribArray,激活顶点属性数组功能,意味着我们可以把顶点数据传递给着色器的该attribute变量。
c. 设置传输格式
在这一步,我们要约定,我们要传递几个值给哪个attribute,它的类型是什么,是否需要单位化等。
我们通过gl.vertexAttribPointer来设置这些规则。vertexAttribPointer第一个参数表示接收的目标,在这个例子中,接收方就是着色器的vertex变量。第二个参数表示,一次传递几个值。第三个参数表示数字的类型,如浮点、无符号整形等。第四个参数表示是否需要向量标准化,第五个参数表示顶点数据之间的偏移字节。因为我们的数据是紧凑的,所以偏移字节可以写成0。最后一个参数表示每组数据的起始偏移字节。
以上操作,完成了所有绘制前的准备,接下来就可以调用绘制函数开始绘制了。
7. 开始绘制!
WebGL(OpenGL)坐标系如下,原点坐标(0,0),左下角坐标为(-1, -1),右上角为(1, 1)。
首先我们需要设置视窗,所谓视窗就是图形显示的界面。为了能在我们指定的视窗绘制图元,我们需要把狭小的OpenGL坐标系区域扩大到整个canvas,通过调用gl.viewport来设置视窗。
调用如下:
在设置完视窗大小后,通过gl.drawArrays绘制图元:
第一个参数gl.LINE_LOOP表示,三个点以直线的方式绘制(不填充)。如果要填充,可以传入gl.TRIANGLE_STRIP。第二个参数表示data数组的起始偏移,第三个参数表示绘制的顶点个数。
写完之后,运行网页,大功告成!看看是不是已经在网页上绘制出了一个三角形呢?
改进程序——改变三角形颜色
我们已经成功绘制了第一个三角形,但是整个三角形都是红色的,离我们的目标还有点差距。其原因在于,我们直接在片元着色器中返回了(1,0,0,1),导致三角形中每个点全部都是一种颜色。如果这个颜色值,可以如同顶点坐标一样传递给片元着色器,那么我们就可以从外部改变三角形的颜色了。
为此,我们要深入了解一下顶点数据,顶点数据中,不仅仅只可以有顶点的坐标,还可以有顶点的颜色。因此,我们按照以下步骤修改代码:
1. 更改createBuffer代码,给顶点数据加上颜色值
我们在每个顶点的坐标后面,增加了3个浮点数表示顶点的颜色,我们希望前3个浮点数(位置)传给顶点着色器,后3个浮点数(颜色)传给片元着色器。
2. 修改顶点着色器和片元着色器
读者可能会想,如果要在片元着色器中接收颜色,是不是在片元着色器中写上attribute vec3 color,然后激活顶点属性数组就可以了呢?遗憾的是,WebGL并不允许这么做。所有的attribute必须写在顶点着色器中。好在我们可以通过一些方法,让顶点着色器将attribute传递给片元着色器:
在顶点着色器中,我们增加了个新的attribute,叫作color。同时,我们使用关键字varying,表示这个变量将会传递到片元着色器中。片元着色器中的varying变量必须和顶点着色器中的签名保持一致,否则会引起链接错误。
如上,顶点着色器接收到vertex和color,将color拷贝到_color,然后WebGL再自动将_color传递到片元着色器中,片元着色器将_color拷贝到gl_FragColor。整段代码如下:
3. 设置顶点属性
我们再次通过以下来设置顶点属性:
gl.enableVertexAttribArray,gl.vertexAttribPointer
可以发现,除了vertexPositionAttribute之外,我们还多了个colorPositionAttribute,不要忘记激活它。
在这个步骤中,最重要的一步就是正确调用gl.vertexAttribPointer。一个浮点数占用4个字节,对于顶点A,其缓存的内存布局如下:
可见,我们用24个字节来描述一个顶点,其中偏移值0~11字节用来描述顶点位置,12~23来描述顶点颜色。
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 24, 0)
上面这一句表示vertexPositionAttribute由3个浮点数组成,不需要单位化,它和下一个顶点位置的数据偏移相差24字节,第一个字节偏移为0字节。
gl.vertexAttribPointer(colorPositionAttribute, 3, gl.FLOAT, false, 24, 12)
上面这一句表示colorPositionAttribute由3个浮点数组成,不需要单位化,它和下一个顶点颜色的数据偏移相差24字节,第一个字节偏移为12字节。
在绘制过程中,attribute vec3 vertex将收到顶点数据的第0~11字节,attribute vec3 color将收到第12~23字节。
4. 绘制
有了上面的工作,我们可以调用gl.drawArrays来进行绘制了。这次我们使用gl.TRIANGLE_STRIP来绘制实心的三角形。
由于三个顶点颜色不一样,WebGL将会自动采取平滑着色的方式来进行填充。
以上,便是本文一开始所呈现的效果(A、B、C是手动加上的)。
写在最后
万事总是开头难,OpenGL的绘制第一眼看上去总是晦涩难懂的,当跨出了第一步之后,我们将会对图形的绘制有更深刻的了解。
本文的demo你可以在以下获取源代码,也欢迎大家向我直接索要源代码。
https://github.com/froser/tech
由于是WebGL的入门,关于OpenGL的很多方面都讲得不够严谨和深入,请见谅!也欢迎大家和我一起探讨。
最后奉上一个令人叹为观止的WebGL demo:
http://madebyevan.com/webgl-water/
Brainstorm
脑洞时间
问题:甲、乙两个机器人在一条无限长的跑道上赛跑,目前甲暂时领先。如果甲保持自已的速度不变,乙不断地增加自己的速度,最终乙一定会超过甲吗?
一定会吗?为什么?在文章结尾留下答案
3名留言者可获得鲜榨果汁、咖啡或者1积分
获奖者可选择奖品类型,奖品在一周后送出
长按二维码关注西山居技术
以上是关于干货|WebGL入门 把三维绘制搬上网页的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点#愚公系列2022年12月 微信小程序-WebGL立体图形的绘制