本文将对Three.js(r105)的Sprite进行讲解,主要包括以下几个方面:介绍与使用在项目中,我主要是使用Sprite在一些3D场景中创建标签。下面是一个简单的例子来理解Sprite的基本用法:constmap=newTHREE.TextureLoader().load("sprite.png")constmaterial=newTHREE.SpriteMaterial({map})constsprite=newTHREE.Sprite(material)scene.add(sprite)效果如下(画布大小为300px*400px,灰色背景区域为画布区域,下同):Sprite有几个特点:它是一个平面Sprite是一个平面,也就是Sprite的Geometry描述是一个扁平的矩形。下面我在讲解源码的时候会讲到。永远面对镜头我们知道3D场景中的物体是由三角形组成的,每个三角形都有一条法线。法线方向和相机视角方向可以是任何关系。Sprite的特点是这个平面矩形的法线方向总是平行于相机的视线方向,方向相反。最终的渲染效果是绘制出来的Sprite始终是一个没有变形的矩形。例如,将普通平面沿X轴旋转45度:constgeometry=newTHREE.PlaneGeometry(1,1)constplaneMaterial=newTHREE.MeshBasicMaterial({color:0x00ff00})constplane=newTHREE.Mesh(geometry,planeMaterial)plane.rotation.x=Math.PI/4scene.add(plane)效果如下图:对Sprite做同样的操作(为了对比,将贴图换成纯色):constmaterial=newTHREE.SpriteMaterial({color:0x00ff00})constsprite=newTHREE.Sprite(material)sprite.rotation.x=Math.PI/4scene.add(sprite)效果如下图所示:可以设置取消相机近大远小的效果相机(PerspectiveCamera)模拟人眼看世界的效果:近的大,远的小。默认情况下,精灵也是远近小,但是你可以通过SpriteMaterial的sizeAttenuation属性来取消这个效果。后面会详细讲解sizeAttenuation的实现原理。Sprite的Geometry首先看一下Sprite的构造函数(Sprite.js)的源码:vargeometry;//注意1:全局几何函数Sprite(material){Object3D.call(this);this.type='雪碧';if(geometry===undefined){//注1:全局几何geometry=newBufferGeometry();//注1:全局几何varfloat32Array=newFloat32Array([//注2:顶点信息和纹理信息,共四个顶点-0.5,-0.5,0,0,0,0.5,-0.5,0,1,0,0.5,0.5,0,1,1,-0.5,0.5,0,0,1]);varinterleavedBuffer=newInterleavedBuffer(float32Array,5);//注2:每个顶点信息包括5条数据geometry.setIndex([0,1,2,0,2,3]);//注意2:两个三角形geometry.addAttribute('position',newInterleavedBufferAttribute(interleavedBuffer,3,0,false));//注2:顶点信息,取前三项geometry.addAttribute('uv',newInterleavedBufferAttribute(interleavedBuffer,2,3,false));//注2:纹理信息,取最后两项}this.geometry=geometry;//注1:全局几何this.material=(material!==undefined)?材质:新的SpriteMaterial();this.center=newVector2(0.5,0.5);//Note3:centerdefaultsto(0.5,0.5)}从上面的代码中,我们可以看到两条信息:Note1:所有的Sprites共享一个Geometry;注2:每个顶点信息的长度为5,前三项为顶点信息的x、y、z值,后两项为纹理信息,存储在一个数组中;一共定义了四个顶点,两个三角形的四个顶点坐标分别为A(-0.5,-0.5,0),B(0.5,-0.5,0),C(0.5,0.5,0),D(-0.5,0.5,0)。这两个三角形是T1(0,1,2)和T2(0,2,3),即T1(A,B,C)和T2(A,C,D)。这两个三角形组成的矩形的中心点O的坐标为(0,0,0)。这两个三角形形成一个1X1的正方形。如下图(Z轴全0,此处不显示):始终面向相机原理分析对于3D场景中的一个点,其最终位置的计算方式一般如下:gl_Position=projectionMatrix*viewMatrix*模型矩阵*vec4(位置,1.0);其中,position是3D场景中的坐标,这个坐标需要经过物体自身坐标系(modelMatrix)的矩阵变换(位移,旋转,缩放等)相机坐标系(viewMatrix)的矩阵变换)和投影矩阵变换(projectionMatrix)即最后使用的坐标是通过3D场景中坐标的一系列固有变换得到的。其中,上述相机坐标系的矩阵变换与相机有关,即相机的信息会影响最终的坐标。但是,精灵始终面向相机。我们可以推测,Sprite位置的计算肯定不是上面的固有变换。我们来看看Sprite是如何实现的。这块的逻辑是在着色器(sprite_vert.glsl.js)中实现的:voidmain(){//...vec4mvPosition=modelViewMatrix*vec4(0.0,0.0,0.0,1.0);//Note1:ApplicationModelandcameramatrixatpointO//Note6:Scalingrelatedvec2alignedPosition=(position.xy-(center-vec2(0.5)))*scale;//注2:center的默认值为vec2(0.5),scale为模型的缩放比例,简单情况下为1,所以这里可以简化为://vec2alignedPosition=position.xy;vec2旋转位置;rotatedPosition.x=cos(旋转)*alignedPosition.x-sin(旋转)*alignedPosition.y;rotatedPosition.y=sin(旋转)*alignedPosition.x+cos(旋转)*alignedPosition.y;//注3:应用旋转时,rotatedPosition=alignedPosition//其实旋转等于0带入上面的计算过程mvPosition.xy+=rotatedPosition;//注4:在O点的基础上,重新计算每个顶点的坐标,保持Z分量不变,保证相机视线和Sprite垂直gl_Position=projectionMatrix*mvPosition;//注5:应用投影矩阵//...}顶点坐标的计算过程如下:注1:计算O点在相机坐标系中的坐标;注2-4:以O为圆心,在垂直相机视线平面Plane1上,直接获取相机坐标系中各顶点的坐标;注5:上面获取的坐标直接在相机坐标系中,所以不需要申请modelMatrix和viewMatrix,直接申请projectionMatrix很好;ABCD在空间中的实际位置和实际绘制ABCD的位置A'B'C'D'如下图所示:SpriteMaterial的sizeAttenuation属性相机近大远小的效果这个block的实现逻辑还是在shader(sprite_vert.glsl.js)中实现:vec2规模;//注1:计算缩放比例scale.x=length(vec3(modelMatrix[0].x,modelMatrix[0].y,modelMatrix[0].z));scale.y=length(vec3(modelMatrix[1].x,modelMatrix[1].y,modelMatrix[1].z));#ifndefUSE_SIZEATTENUATIONboolisPerspective=(projectionMatrix[2][3]==-1.0);//注2:判断是否为透视相机if(isPerspective)scale*=-mvPosition.z;//注意2:根据相机距离应用不同的缩放值#endifvec2alignedPosition=(position.xy-(center-vec2(0.5)))*scale;//注2:顶点信息的计算考虑了比例因子。这里也没有考虑中心的影响。简化如下://vec2alignedPosition=position.xy*scale;//...注3:同上计算顶点位置的过程#include
