1.我们在说什么?我们谈两件事:1、WebGL背后的工作原理是什么?2、以Three.js为例,框架在其背后起到什么作用?二、为什么要了解原理?我们假设你已经对WebGL有了一定的了解,或者对Three.js有所了解。这时候你可能会遇到一些问题:1、很多事情还做不到,甚至没有想法;2.遇到BUG无法解决,甚至没有方向;3.有性能问题,完全不知道怎么优化。这个时候,我们需要了解更多。三、先了解一个基本概念1、什么是矩阵?简单点说就是用矩阵进行坐标变换,如下图:2.具体是怎么变换的,如下图:3.以坐标平移2为例,如下图所示:如果这时候你还不明白,没关系,你只需要知道矩阵是用来做坐标变换的。4、WebGL4.1的工作原理。WebGLAPI在了解一项新技术之前,我们会先看看它的开发文档或API。查看Canvas的绘图API,我们会发现它可以绘制直线、矩形、圆、圆弧和贝塞尔曲线。于是,我们查看了WebGL绘图API,发现:它只能理解点、线、三角形?我一定是错的。不,你没有看错。就连这么复杂的模型,也是一张一张画出来的。4.2.WebGL绘制过程简单来说,WebGL绘制过程包括以下三个步骤:1.获取顶点坐标2.图元组装(即绘制三角形)3.光栅化(生成片段,即像素)接下来我们对每个步骤进行说明一步步。4.2.1.获取顶点坐标顶点坐标从何而来?立方体还好,但如果是机器人呢?没错,这些坐标我们就不一一写了。往往来自于3D软件导出,或者帧生成,如下图所示:什么是writebuffer?没错,为了简化流程,之前没有介绍。由于顶点数据往往是数以万计的,所以在获取到顶点坐标后,我们通常会将其存放在显存中,也就是缓存区中,以便于GPU更快的读取。4.2.2.图元组装我们已经知道,图元组装就是从顶点生成图元(即三角形)。这个过程是自动完成的吗?答案并不尽然。为了给我们更多的可控性,也就是可以自由控制顶点的位置,WebGL给了我们这种能力,这就是可编程渲染管线(不用懂)。WebGL要求我们先处理顶点,那么怎么处理呢?先看下图:我们引入了一个新名词叫做“vertexshader”,它是由opengles编写的,由javascript以字符串的形式定义,传递给GPU生成。例如,下面是一段顶点着色器代码:attributevec4position;voidmain(){gl_Position=position;}属性修饰符用于声明浏览器(javascript)传递给顶点着色器的变量值;position是我们定义的顶点坐标;gl_Position是一个内置的传出变量。这段代码什么都不做。如果是绘制2D图形,没有问题,但是如果是绘制3D图形,即传入的顶点坐标是三维坐标,我们需要将其转换成屏幕坐标。例如:v(-0.5,0.0,1.0)转换为p(0.2,-0.4)。这个过程类似于用相机拍照。4.2.2.1.顶点着色器处理流程回到刚才的话题,顶点着色器是如何处理顶点坐标的呢?如上图所示,vertexshader会先转换坐标,再由GPU组装图元。有多少顶点,这个顶点着色器程序就会运行多少次。你可能已经注意到顶点着色器变成了:attributevec4position;uniformmat4matrix;voidmain(){gl_Position=position*matrix;}这是应用matrix矩阵将三维世界坐标转换成屏幕坐标,这个矩阵称为投影矩阵,由javascript传入。至于如何生成这个矩阵,我们暂时不讨论。4.2.3.光栅化类似于图元组装,光栅化也是可控的。图元生成后,我们需要给模型“上色”,这部分工作由运行在GPU上的“片段着色器”来完成。也是一个opengles的程序,模型长什么样的贴图(颜色,漫反射贴图等),光照等都是由fragmentshader计算出来的。下面是一段简单的片段着色器代码:precisionmediumpfloat;voidmain(void){gl_FragColor=vec4(1.0,1.0,1.0,1.0);}gl_FragColor是输出颜色值。4.2.3.1.片段着色器处理流程片段着色器是如何控制颜色生成的?如上图所示,vertexshader是它有多少顶点,运行了多少次,而fragmentshader是它产生了多少片元(像素),运行了多少次。4.3.WebGL的完整工作流程至此,本质上,WebGL经历了以下处理流程:1.数据准备阶段在这个阶段,我们需要提供顶点坐标,index(三角形绘制顺序),uv(确定纹理坐标),normal(决定光照效果),以??及各种矩阵(比如投影矩阵)。其中,顶点数据存放在缓冲区中(因为数量巨大),通过modifier属性传递给vertexshader;矩阵被传递给带有修饰符uniform的顶点着色器。2.生成vertexshader根据我们的需要,Javascript定义一串vertexshader(opengles)程序,生成并编译成shader程序传递给GPU。3、图元组装GPU根据顶点个数,逐一执行顶点着色器程序,生成顶点的最终坐标,完成坐标转换。4.生成的fragmentshader模型是什么颜色,是什么样的贴图,光照效果,阴影(过程比较复杂,需要先渲染到贴图,暂时可以忽略),都处理了在这个阶段。5.光栅化可以通过fragmentshader。我们已经确定了每个片段的颜色,根据深度缓冲区判断哪些片段被挡住了。不需要渲染。最后将分片信息存入颜色缓冲区。完成整个渲染。5.Three.js到底是做什么的?我们知道three.js为我们做了很多事情,但是它到底做了什么,在整个过程中起到了什么样的作用呢?简单看一下three.js中涉及的流程:黄色和绿色部分是three.js中涉及的部分,其中黄色部分是javascript部分,绿色部分是opengles部分。我们发现能做什么,three.js基本都帮我们做了。协助我们导出模型数据;自动生成各种矩阵;生成的顶点着色器;协助我们生成材料和配置灯光;根据我们设置的材质生成片段着色器。而且,webGL光栅化的2DAPI被封装成了我们人类可以理解的3DAPI。5.1.Three.js顶点处理流程从WebGL工作原理一章我们已经知道顶点着色器会将三维世界坐标转换为屏幕坐标,但实际上坐标转换并不仅限于投影矩阵。如下图所示:之前WebGL经过primitive组装后的结果,因为我们认为模型是固定在坐标原点的,相机在x轴和y轴上的坐标都是0,其实正常的结果是这样的:5.1.1,模型矩阵现在,如果我们将模型顺时针旋转Math.PI/6,所有的顶点肯定都改变了位置。box.rotation.y=Math.PI/6;但是,如果我们直接用javascript计算顶点位置,性能会很低(顶点通常有几万个),而且这些数据也很不利于维护。因此,我们使用矩阵modelMatrix来记录这个旋转信息。5.1.2.ViewMatrix然后,我们将相机向上偏移30.camera.position.y=30;同样,我们使用矩阵viewMatrix来记录运动信息。5.1.3.投影矩阵这个就是我们之前介绍过的,我们用projectMatrix来记录。5.1.4.Applymatrix然后,我们编写顶点着色器:gl_Position=position*modelMatrix*viewMatrix*projectionMatrix;通过这种方式,我们计算出最终顶点在GPU中的位置。其实three.js已经帮我们完成了以上所有步骤。5.2.片段着色器处理流程我们已经知道片段着色器负责处理材质、灯光等信息,但是具体如何处理呢?如下图所示:5.3.three.js的完整运行流程:当我们选择材质后,three.js会根据我们选择的材质选择对应的vertexshader和fragmentshader。我们常用的着色器已经内置到three.js中了。
