WebGL ThreeJS学习总结一、二

© Young 2016-10-18 07:57
Welcome to My GitHub

概述

在写这篇总结之前,这已经是我第二次尝试学习WebGL了,第一次是在两年前,那时候正在从事Java开发相关工作,觉得没什么意思,然后平时工作中有接触到一些前端相关的东西,刚好那时HTML5很火,就稍微了解了一下Canvas,然后自然而然的知道了WebGL,刚开始学的时候感觉很吃力,然后就买了本书《WebGL入门指南》,虽然跟着书上的例子也能照葫芦画瓢弄个正方体出来,但是总感觉实现了某个例子,依然啥也不会,没多久就放弃了。

现在已经从事前端开发工作接近两年,在前不久看到一篇博客后再次燃起了学习WebGL的热情,然后采购了一本书《WebGL编程指南》,花了一周时间看了100多页之后,差点再次放弃,思考了一下午决定改进下学习方法。

结合这两次的从准备入门到放弃的经验来看,单独学习ThreeJS容易给人造成不知其然的感觉,而单独学习WebGL又太枯燥,所以我决定把二者结合起来,先根据《WebGL编程指南》学习WebGL感觉枯燥之后就参照ThreeJS官方文档以及官方源代码中的例子学习ThreeJS并找些相对容易实现的例子实现,在这过程中写一些学习总结。

总结一、二主要是了解一些WebGL的基本概念,然后通过示例程序学习WebGL程序的基本结构、GLSL语言以及编写一些简单的WebGL程序。

概念

一、基本概念

1、WebGL起源

在个人计算机上使用最广泛的两种三维图形渲染技术是Direct3D和OpenGL,其中WebGL就是从OpenGL的一个特殊版本OpenGL ES中派生出来的,后者专用于嵌入式计算机、智能手机、家用游戏机等设备。

2、WebGL程序结构

在HTML中动态网页包括HTML和JavaScript两种语言,在引入WebGL后,还需要加入着色器语言GLSL ES。

3、WebGL坐标系

暂时认为WebGL坐标系为右手坐标系,即当你面朝计算机屏幕时,X轴是水平的(正方向为右),Y轴是垂直的(正方向为上),Z轴垂直于屏幕(正方向为外)。

4、WebGL颜色分量取值范围

在平时前端开发中我们一般使用的颜色分量取值范围是从0到255,但是由于WebGL是继承自OpenGL,所以它遵循传统OpenGL颜色分量的取值范围,即从0.0到1.0。

5、WebGL坐标取值范围

坐标取值范围与颜色分量的取值范围一样也是从0.0到1.0。

个人理解:取值范围从0.0到1.0其实可以看成从0%到100%,之所以取值范围是这样,可能是为了适应计算机硬件的发展吧,比如颜色分量取值范围如果固定为某个具体数值,随着硬件的发展能识别更多颜色之后就会有兼容性的问题。

6、齐次坐标

齐次坐标(x,y,z,w)等价于三维坐标(x/w,y/w,z/w),暂时理解为齐次坐标能提高三维数据的运算效率,在GLSL ES中使用vec4类型表示。

7、着色器

WebGL需要两种着色器;

顶点着色器,顶点着色器是用来描述顶点特性(如位置、颜色等)的程序;

片元着色器,进行逐片元处理过程(如光照)的程序。

8、片元

暂时理解成像素即可。

9、网格

绘制3D图形的方法有很多,最常用的一种方法就是使用网格(Mesh),这也是为什么下边的程序示例是绘制三角形的原因。

10、矩阵

虽然上大学时没好好学,但是网上资料挺多,就不一一拷贝了。

理解矩阵乘法

11、顶点着色器和片元着色器之间图形装配和光栅化的过程

  • 执行顶点着色器,传入缓冲区对象中的第一个顶点坐标,一旦赋值成功,该数据就进入了图形装配区域,并暂时存储在那里;

  • 重复执行顶点着色器直到所有顶点数据赋值完成;

  • 开始装配图形(按照一定的规则把所有顶点连接起来);

  • 将图形转化为片元,这个过程被称为光栅化,光栅化是三维图形学的关键技术之一,它负责将矢量的几何图形转变为栅格化的片元

补充解释下光栅化,其实就是把矢量图形转化成像素点的过程,因为最常用的一种绘制3D图形的方法就是使用网格(Mesh),也就是点线面属于矢量图形,而当前屏幕是像素渲染的,那么从矢量图形转化成用户所看到的像素图像必然需要光栅化这一步骤。

  • 光栅化结束之后,程序就开始逐片元调用片元着色器,每调用一次就处理一个片元,片元着色器会计算出该片元的颜色,并写入颜色缓冲区;

  • 当最后一个片元处理完成,浏览器就会显示最终结果。

另外这个过程如果我们使用当前流行的ThreeJS框架可以理解如下:

其中黄色区域是ThreeJS框架中使用JS实现的,绿色部分是THreeJS框架中使用GLSL ES实现的。

12、缓冲区对象

对于那些由多个顶点组成的图形,比如三角形、矩形等,你需要一次性地将图形的顶点全部传入顶点着色器,然后才能把图形画出来,WebGL提供了一种很方便的机制,即缓冲区对象(是WebGL系统中的一块内存区域),它可以一次性地向着色器传入多个顶点数据。

使用缓冲区对象时需要遵循以下五个步骤:

创建缓冲区对象(gl.createBuffer());
绑定缓冲区对象(gl.bindBuffer());
将数据写入缓冲区对象(gl.bufferData());
将缓冲区对象分配给着色器变量(gl.vertexAttriPointer());
启动着色器变量(gl.enableVertexAttriArray())。

使用多个缓冲区对象向着色器传递多种数据,比较适合数据量不大的情况,当数据量很大时这种方式很难维护,所以WebGL允许我们把不同种类的数据打包到同一个缓冲区对象中,并通过某种机制分别访问缓冲区对象中不同种类的数据。

13、纹理坐标

纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色,WebGL系统中的纹理坐标系统是二维的,为了将纹理坐标和广泛使用的x坐标和y坐标区分开来,使用s和t命名,称之为st坐标系统。

14、纹理映射

纹理映射的一般步骤:

准备好映射到几何图形上的纹理图像;
为几何图形配置纹理映射方式;
加载纹理图像,对其进行一些配置,以在WebGL中使用它;
在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋予片元。

在配置纹理映射的时候需要注意一点,图片坐标系统和WebGL纹理坐标系统的Y轴方向是相反的,这时候你需要对纹理图像进行Y轴反转;

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);// Flip the image’s y-axis

15、纹理单元

WebGL通过一种纹理单元的机制来同时使用多个纹理,每个纹理单元有一个单元编号来管理一张纹理图像,系统支持的纹理单元个数取决于硬件和浏览器的WebGL实现,默认情况下至少支持8个纹理单元。

使用纹理单元的一般步骤:

激活纹理单元(gl.activeTexture());
绑定纹理对象(gl.bindTexture());
配置纹理对象的参数(gl.texParameteri());
将纹理图像分配给纹理对象(gl.texImage2D());
将纹理单元编号传递给取样器。

16、FrameBuffer

有点类似于Canvas中的离屏渲染,一般我们使用gl.drawArrays或者gl.drawElements都是将对象绘制在了默认的窗口中,但是当我们指定一个FrameBuffer时,再用这个两个方法去绘制,则会将对象会绘制于当前指定的FrameBuffer。

一般用于纹理的多重处理,比如对于一个纹理我们可以先灰度过滤,然后再模糊过滤,但是灰度过滤后还不能直接渲染到屏幕上,则可以使用FrameBuffer过渡,直到处理完成之后再渲染到屏幕上;
使用范例可以去WebGL简单实现高斯模糊这个例子中查看。

17、组合矩阵

从视点看上去的变换后的顶点坐标 = 视图矩阵 * 变换矩阵 * 原始顶点坐标;
模型视图矩阵 = 视图矩阵 * 模型矩阵;
模型视图投影矩阵 = 投影矩阵 * 视图矩阵 * 模型矩阵。

18、隐藏面消除

WebGL在默认情况下会按照缓冲区中的顺序绘制图形,而且后绘制的图形覆盖先绘制的图形,这种做法比较高效,如果场景中的对象以及观察者状态都不发生任何变化,这种做法没有任何问题;但是如果场景中的对象或者观察者状态发生了变化,那么有可能会影响对象的显示次序,这时候还按照默认顺序绘制图形就会有问题了,为了解决这个问题,WebGL提供了隐藏面消除功能,这个功能会自动根据对象和观察者的状态计算场景中对象的显示顺序;启动该机制只需要两行代码:

    //开启隐藏面消除功能
    gl.enable(gl.DEPTH_TEST);


    在绘制之前,清除深度缓冲区
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

在绝大多数情况下,隐藏面消除功能都能很好的工作,然而当几何图形或者物体的两个表面极为接近时,就会出现新的问题,使得表面看上去斑斑驳驳,这种现象被称为深度冲突,主要原因在于两个面过于接近,深度缓冲区有限的精度已经不能区分哪个在前,哪个在后了。

针对上述情况WebGL提供一种被称为多边形偏移的机制来解决这个问题,该机制将自动在Z值上加上一个偏移量,偏移量的值由物体表面相对于观察者视线的角度来确定;启动该机制同样也只需要两行代码:

    //启动多边形偏移
    gl.enable(gl.POLYGON_OFFSET_FILL);


    //在绘制之前指定用来计算偏移量的参数
    gl.polygonOffset(1.0,1.0);

19、通过顶点索引绘制物体

通过gl.drawElements配合gl.ELEMENT_ARRAY_BUFFER可以实现通过顶点索引绘制物体,这种绘制方式相对于gl.drawArraysgl.ARRAY_BUFFER纯顶点绘制物体方式存在一定的优势,特别是当共享顶点很多时可以节约很多内存。

20、按行主序和按列主序

在编程时我们通常使用数组存储矩阵,但是矩阵是二维的,数组是一维的,所以如果在数组中按行存储矩阵元素就被称为按行主序,在数组中按列存储矩阵元素就被称为按列主序;

WebGL和OpenGL一样,矩阵元素是按列主序存储在数组中的。

21、光源类型

平行光类似于自然中的太阳光,光线是相互平行的,可以用一个方向和一个颜色来定义;
点光源类似于人造灯泡的光,光是从一个点向周围的所有方向发出的光,因此我们需要指定点光源的位置和颜色,光线的方向将根据点光源的位置和被照射之处的位置计算出来;
环境光环境光是指那些经光源发出后,被墙壁等物体多次反射,然后照到物体表面上的光,环境光从各个角度照射物体,其强度都是一致的,环境光不需要指定位置和方向,只需要指定颜色即可。

除了上述三种基本类型的光,还有很多其它更加特殊的光源类型,可以参考《OpenGL ES 2.0 Programming Guide》。

22、反射类型

漫反射是指在粗糙的物体表面,反射光以不固定的角度反射出去,因此漫反射的反射光在各个方向上是均匀的。

漫反射光颜色 = 入射光颜色 * 表面基底色 * 入射角余弦;
环境反射光颜色 = 入射光颜色 * 表面基底色。

二、GLSL

1、attribute变量和uniform变量

都是GLSL ES中的变量类型,使用这两种变量可以把数据从JavaScript程序中传给着色器程序;

attribute和uniform类似都是存储限定符,主要区别如下:attribute只能用于顶点着色器,用来表示逐顶点数据;uniform可以用于顶点着色器也可以用于片元着色器,用来表示不变的数据。

uniform表示不变的数据的意思是在GLSL程序内部不能再被改变,但是可以再次由JavaScript程序赋予新的值。

2、varying变量以及颜色值内插

暂时理解为varying变量的作用是从顶点着色器向片元着色器传输数据;

颜色值内插暂时就理解为颜色自动渐变(如果想要深入了解内插过程,可以参考《计算机图形学》)。

3、数组

示例程序

发表评论

电子邮件地址不会被公开。 必填项已用*标注