asp做网站主要技术,360帝国模板网欢迎大家来访_济南网站建设,推广_济南 去114网,手机网站打开速度,cgi做网站绘制三角形 
前言 
三角形是一个最简单、最稳定的面#xff0c;webgl 中的三维模型都是由三角面组成的。咱们这一篇就说一下三角形的绘制方法。 
课堂目标 
理解多点绘图原理。可以绘制三角形#xff0c;并将其组合成多边形。 
知识点 
缓冲区对象点、线、面图形 
第一章 web…绘制三角形 
前言 
三角形是一个最简单、最稳定的面webgl 中的三维模型都是由三角面组成的。咱们这一篇就说一下三角形的绘制方法。 
课堂目标 
理解多点绘图原理。可以绘制三角形并将其组合成多边形。 
知识点 
缓冲区对象点、线、面图形 
第一章 webgl 的绘图方式 
我们先看一下webgl是怎么画图的。 绘制多点   如果是线就连点成线   如果是面那就在图形内部逐片元填色   
webgl 的绘图方式就这么简单接下咱们就说一下这个绘图方式在程序中是如何实现的。 
第二章 绘制多点 
在webgl 里所有的图形都是由顶点连接而成的咱们就先画三个可以构成三角形的点。 
这里大家还要注意一下我现在要画的多点是可以被webgl 加工成线、或者面的这和我们上一篇单纯的想要绘制多个点是不一样的。 
1-绘制多点的整体步骤 建立着色器源文件 script idvertexShader typex-shader/x-vertexattribute vec4 a_Position;void main(){gl_Position  a_Position;gl_PointSize  20.0;}
/script
script idfragmentShader typex-shader/x-fragmentvoid main(){gl_FragColorvec4(1.0,1.0,0.0,1.0);}
/script获取webgl 上下文 const canvas  document.getElementById(canvas);
canvas.widthwindow.innerWidth;
canvas.heightwindow.innerHeight;
const gl  canvas.getContext(webgl);初始化着色器 const vsSource  document.getElementById(vertexShader).innerText;
const fsSource  document.getElementById(fragmentShader).innerText;
initShaders(gl, vsSource, fsSource);设置顶点点位 const verticesnew Float32Array([0.0,  0.1,-0.1,-0.1,0.1, -0.1
])
const vertexBuffergl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
const a_Positiongl.getAttribLocation(gl.program,a_Position);
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(a_Position);清理画布 gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);绘图 gl.drawArrays(gl.POINTS, 0, 3);实际效果 上面的步骤主要是先给大家一睹为快其具体原理咱们后面细说。 
2-绘制多点详解 
首先咱们先从概念上疏通一下。 
我们在用js定点位的时候肯定是要建立一份顶点数据的这份顶点数据是给谁的呢肯定是给着色器的因为着色器需要这份顶点数据绘图。 
然而我们在js中建立顶点数据着色器肯定是拿不到的这是语言不通导致的。 
为了解决这个问题webgl 系统就建立了一个能翻译双方语言的缓冲区。js 可以用特定的方法把数据存在这个缓冲区中着色器可以从缓冲区中拿到相应的数据。 
接下来咱们就看一下这个缓冲区是如何建的着色器又是如何从其中拿数据的。 
建立顶点数据两个浮点数构成一个顶点分别代表x、y 值。 
const verticesnew Float32Array([//x    y0.0,  0.1, //顶点-0.1,-0.1, //顶点0.1, -0.1  //顶点
])现在上面的这些顶点数据是存储在js 缓存里的着色器拿不到所以咱们需要建立一个着色器和js 都能进入的公共区。 
建立缓冲对象。 
const vertexBuffergl.createBuffer();现在上面的这个缓冲区是独立存在的它只是一个空着的仓库和谁都没有关系。接下来咱们就让其和着色器建立连接。 
绑定缓冲对象。 
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);gl.bindBuffer(target,buffer) 绑定缓冲区 
target 要把缓冲区放在webgl 系统中的什么位置buffer 缓冲区 
着色器对象在执行initShaders() 初始化方法的时候已经被写入webgl 上下文对象gl 中了。 
当缓冲区和着色器建立了绑定关系我们就可以往这块空间写入数据了 
往缓冲区对象中写入数据 
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);bufferData(target, data, usage) 将数据写入缓冲区 
target 要把缓冲区放在webgl 系统中的什么位置data 数据usage 向缓冲区写入数据的方式咱们在这里先知道 gl.STATIC_DRAW 方式即可它是向缓冲区中一次性写入数据着色器会绘制多次。 
现在着色器虽然绑定了缓冲区可以访问里面的数据了但是我们还得让着色器知道这个仓库是给哪个变量的比如咱们这里用于控制点位的attribute 变量。这样做是为了提高绘图效率。 
将缓冲区对象分配给attribute 变量 
const a_Positiongl.getAttribLocation(gl.program,a_Position);
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0);gl.vertexAttribPointer(local,size,type,normalized,stride,offset) 将缓冲区对象分配给attribute 变量 
local attribute变量size 顶点分量的个数比如我们的vertices 数组中两个数据表示一个顶点那咱们就写2type 数据类型比如 gl.FLOAT 浮点型normalized 是否将顶点数据归一stride 相邻两个顶点间的字节数我的例子里写的是0那就是顶点之间是紧挨着的offset 从缓冲区的什么位置开始存储变量我的例子里写的是0那就是从头开始存储变量 
到了这里着色就知道缓冲区的数据是给谁的了。因为咱们缓冲区里的顶点数据是数组里面有多个顶点。所以我们得开启一个让着色器批量处理顶点数据的属性。默认着色器只会一个一个的接收顶点数据然后一个一个的绘制顶点。 
开启顶点数据的批处理功能。 
gl.enableVertexAttribArray(a_Position);location attribute变量 
好啦现在已经是万事俱备只欠绘图了。 
绘图 
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 3);drawArrays(mode,first,count) 
mode 绘图模式比如 gl.POINTS 画点first 从哪个顶点开始绘制count 要画多少个顶点 
关于绘制多点我就说到这接下来咱们说一下基于多点绘制图形。 
第三章 绘制图形 
在数学中我们知道三个点可以确定一个唯一的三角面。接下来咱们画一下。 
1-绘制三角面 
我们在之前绘制多点的基础上做一下修改。 
顶点着色器中的gl_PointSize  20.0 不要因为这个属性是控制顶点大小的咱们已经不需要显示顶点了。 
script idvertexShader typex-shader/x-vertexattribute vec4 a_Position;void main(){gl_Position  a_Position;//gl_PointSize  20.0;}
/script在js 中修改绘图方式 
// gl.drawArrays(gl.POINTS, 0, 3);
gl.drawArrays(gl.TRIANGLES, 0, 3);上面的gl.TRIANGLES 就是绘制三角面的意思。 
看一下效果 webgl 既然可以画面了那它是否可以画线呢这个是必须可以我们可以在gl.drawArrays() 方法的第一个参数里进行设置。 
2-基本图形 
gl.drawArrays(mode,first,count) 方法可以绘制一下图形 
POINTS 可视的点LINES 单独线段LINE_STRIP 线条LINE_LOOP 闭合线条TRIANGLES 单独三角形TRIANGLE_STRIP 三角带TRIANGLE_FAN 三角扇 
上面的POINTS 比较好理解就是一个个可视的点。 
线和面的绘制方式各有三种咱们接下来就详细说一下。 
2-1-点的绘制 
POINTS 可视的点 上面六个点的绘制顺序是v0, v1, v2, v3, v4, v5 
2-2-线的绘制 
LINES 单独线段 上面三条有向线段的绘制顺序是 
v0v1 
v2v3 
v4v5 
LINE_STRIP 线条 上面线条的绘制顺序是v0v1v2v3v4v5 
LINE_LOOP 闭合线条 上面线条的绘制顺序是v0v1v2v3v4v5v0 
2-3-面的绘制 
对于面的绘制我们首先要知道一个原理 
面有正反两面。面向我们的面如果是正面那它必然是逆时针绘制的面向我们的面如果是反面那它必然是顺时针绘制的 
接下来咱们看一下面的三种绘制方式 
TRIANGLES 单独三角形 上面两个面的绘制顺序是 
v0v1v2 
v3v4v5 
TRIANGLE_STRIP 三角带 上面四个面的绘制顺序是 
v0v1v2 
以上一个三角形的第二条边下一个点为基础以和第二条边相反的方向绘制三角形 
v2v1v3 
以上一个三角形的第三条边下一个点为基础以和第二条边相反的方向绘制三角形 
v2v3v4 
以上一个三角形的第二条边下一个点为基础以和第二条边相反的方向绘制三角形 
v4v3v5 
规律 
第一个三角形v0v1v2 
第偶数个三角形以上一个三角形的第二条边下一个点为基础以和第二条边相反的方向绘制三角形 
第奇数个三角形以上一个三角形的第三条边下一个点为基础以和第二条边相反的方向绘制三角形 
TRIANGLE_FAN 三角扇 上面四个面的绘制顺序是 
v0v1v2 
以上一个三角形的第三条边下一个点为基础按照和第三条边相反的顺序绘制三角形 
v0v2v3 
以上一个三角形的第三条边下一个点为基础按照和第三条边相反的顺序绘制三角形 
v0v3v4 
以上一个三角形的第三条边下一个点为基础按照和第三条边相反的顺序绘制三角形 
v0v4v5 
关于webgl 可以绘制的基本图像就说到这接下来咱们画个矩形面练一下手。 
3-实例绘制矩形面 
首先我们要知道webgl 可以绘制的面只有三角面所以咱们要绘制矩形面的话只能用两个三角形去拼。 
接下咱们就说一下如何用三角形拼矩形。 
4-1-三角形拼矩形的方法 
我们可以用TRIANGLE_STRIP 三角带拼矩形。 
下面的两个三角形分别是 
v0v1v2 
v2v1v3 4-2-代码实现 
建立顶点数据 
const verticesnew Float32Array([-0.2, 0.2,-0.2,-0.2,0.2, 0.2,0.2,-0.2,
])上面两个浮点代表一个顶点依次是v0、v1、v2、v3如上图所示。 
绘图 
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);上面参数的意思分别是三角带、从第0个顶点开始画、画四个。 
效果如下 关于矩形的绘制就这么简单接下来咱们可以去尝试其它的图形。 
比如把TRIANGLE_STRIP 三角带变成TRIANGLE_FAN 扇形 
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);画出了一个三角带的样子 其绘图顺序是 
v0v1v2 
v0v2v3 关于基本图形的绘制咱们就说到这。 
第四章 异步绘制多点 
在项目实战的时候用户交互事件是必不可少的。因为事件是异步的所以我们在绘图的时候必须要考虑异步绘图。 
接下来我通过一个例子来说一下异步绘制多点的方法。 
1-异步绘制线段 
1.先画一个点 2.一秒钟后在左下角画一个点 3.两秒钟后我再画一条线段 接下来看一下代码实现 
1.顶点着色器和片元着色器 
!-- 顶点着色器 --
script idvertexShader typex-shader/x-vertexattribute vec4 a_Position;void main(){gl_Positiona_Position;gl_PointSize20.0;}
/script
!-- 片元着色器 --
script idfragmentShader typex-shader/x-fragmentvoid main(){gl_FragColorvec4(1,1,0,1);}
/script2.初始化着色器 
import { initShaders } from ../jsm/Utils.js;const canvas  document.querySelector(#canvas);
canvas.width  window.innerWidth;
canvas.height  window.innerHeight;// 获取着色器文本
const vsSource  document.querySelector(#vertexShader).innerText;
const fsSource  document.querySelector(#fragmentShader).innerText;//三维画笔
const gl  canvas.getContext(webgl);//初始化着色器
initShaders(gl, vsSource, fsSource);3.建立缓冲对象并将其绑定到webgl 上下文对象上然后向其中写入顶点数据。将缓冲对象交给attribute变量并开启attribute 变量的批处理功能。 
//顶点数据
let points[0, 0.2]
//缓冲对象
const vertexBuffer  gl.createBuffer();
//绑定缓冲对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
//写入数据
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(points),gl.STATIC_DRAW)
//获取attribute 变量
const a_Positiongl.getAttribLocation(gl.program, a_Position)
//修改attribute 变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
//赋能-批处理
gl.enableVertexAttribArray(a_Position)4.刷底色并绘制顶点 
//声明颜色 rgba
gl.clearColor(0, 0, 0, 1);
//刷底色
gl.clear(gl.COLOR_BUFFER_BIT);
//绘制顶点
gl.drawArrays(gl.POINTS, 0, 1);5.一秒钟后向顶点数据中再添加的一个顶点修改缓冲区数据然后清理画布绘制顶点 
setTimeout((){points.push(-0.2,-0.1)gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(points),gl.STATIC_DRAW)gl.clear(gl.COLOR_BUFFER_BIT);gl.drawArrays(gl.POINTS, 0, 2);
},1000)6.两秒钟后清理画布绘制顶点绘制线条 
setTimeout((){gl.clear(gl.COLOR_BUFFER_BIT);gl.drawArrays(gl.POINTS, 0, 2);gl.drawArrays(gl.LINE_STRIP, 0, 2);
},2000)总结一下上面的原理当缓冲区被绑定在了webgl 上下文对象上后我们在异步方法里直接对其进行修改即可顶点着色器在绘图的时候会自动从其中调用数据。 
WebGLBuffer缓冲区中的数据在异步方法里不会被重新置空。 
理解了异步绘图原理后我们还可以对这种图形的绘制进行一个简单的封装。 
2-封装多边形对象 
建立一个Poly 对象这个对象是辅助我们理解这一篇的知识的没做太深层次的考量因为有的知识点我们还没有讲到。 
const defAttr()({gl:null,vertices:[],geoData:[],size:2,attrName:a_Position,count:0,types:[POINTS],
})
export default class Poly{constructor(attr){Object.assign(this,defAttr(),attr)this.init()}init(){const {attrName,size,gl}thisif(!gl){return}const vertexBuffer  gl.createBuffer()gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)this.updateBuffer()const a_Positiongl.getAttribLocation(gl.program,attrName)gl.vertexAttribPointer(a_Position, size, gl.FLOAT, false, 0, 0)gl.enableVertexAttribArray(a_Position)}addVertice(...params){this.vertices.push(...params)this.updateBuffer()}popVertice(){const {vertices,size}thisconst lenvertices.lengthvertices.splice(len-size,len)this.updateCount()}setVertice(ind,...params){const {vertices,size}thisconst iind*sizeparams.forEach((param,paramInd){vertices[iparamInd]param})}updateBuffer(){const {gl,vertices}thisthis.updateCount()gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(vertices),gl.STATIC_DRAW)}updateCount(){this.countthis.vertices.length/this.size}updateVertices(params){const {geoData}thisconst vertices[]geoData.forEach(data{params.forEach(key{vertices.push(data[key])})})this.verticesvertices}draw(typesthis.types){const {gl,count}thisfor(let type of types){gl.drawArrays(gl[type],0,count);}}
}属性 
gl webgl上下文对象vertices 顶点数据集合在被赋值的时候会做两件事 更新count 顶点数量数据运算尽量不放渲染方法里向缓冲区内写入顶点数据 geoData 模型数据对象数组可解析出vertices 顶点数据size 顶点分量的数目positionName 代表顶点位置的attribute 变量名count 顶点数量types 绘图方式可以用多种方式绘图 
方法 
init() 初始化方法建立缓冲对象并将其绑定到webgl 上下文对象上然后向其中写入顶点数据。将缓冲对象交给attribute变量并开启attribute 变量的批处理功能。addVertice() 添加顶点popVertice() 删除最后一个顶点setVertice() 根据索引位置设置顶点updateBuffer() 更新缓冲区数据同时更新顶点数量updateCount() 更新顶点数量updateVertices() 基于geoData 解析出vetices 数据draw() 绘图方法 
接下来就可以用Poly 对象实现之前的案例了。 
const polynew Poly({gl,vertices:[0, 0.2]
})
poly.draw([POINTS])setTimeout((){poly.addVertice(-0.2,-0.1)gl.clear(gl.COLOR_BUFFER_BIT);poly.draw([POINTS])
},1000)setTimeout((){gl.clear(gl.COLOR_BUFFER_BIT);poly.draw([POINTS,LINE_STRIP])
},2000)异步绘图原理跑通了我们也就可以用鼠标绘制线条了。 
//实例化多边形
const polynew Poly({gl,types:[POINTS,LINE_STRIP]
})// 鼠标点击事件
canvas.addEventListener(click, (event)  {const {x,y}getMousePosInWebgl(event,canvas)poly.addVertice(x,y)gl.clear(gl.COLOR_BUFFER_BIT);poly.draw()
});当前我们所能画的是一条线条如果我们想要绘制多条线呢就比如我们要画一个狮子座。 3-绘制多线 
既然是多线那就需要有个容器来承载它们这样方便管理。 
3-1-建立容器对象 
建立一个Sky 对象作为承载多边形的容器。 
export default class Sky{constructor(gl){this.glglthis.children[]}add(obj){obj.glthis.glthis.children.push(obj)}updateVertices(params){this.children.forEach(ele{ele.updateVertices(params)})}draw(){this.children.forEach(ele{ele.init()ele.draw()})}
}属性 
gl webgl上下文对象children 子级 
方法 
add() 添加子对象updateVertices() 更新子对象的顶点数据draw() 遍历子对象绘图每个子对象对应一个buffer 对象所以在子对象绘图之前要先初始化。 
3-2-示例 
想象一个场景鼠标点击画布绘制多边形路径。鼠标右击取消绘制。鼠标再次点击绘制新的多边形。 
//夜空
const skynew Sky(gl)
//当前正在绘制的多边形
let polynull//取消右击提示
canvas.oncontextmenu  function(){return false;
}
// 鼠标点击事件
canvas.addEventListener(mousedown, (event)  {if(event.button2){popVertice()}else{const {x,y}getMousePosInWebgl(event,canvas)if(poly){poly.addVertice(x,y)}else{crtPoly(x,y)}}render()
});
//鼠标移动
canvas.addEventListener(mousemove, (event)  {if(poly){const {x,y}getMousePosInWebgl(event,canvas)poly.setVertice(poly.count-1,x,y)render()}
});//删除最后一个顶点
function popVertice(){poly.popVertice()polynull
}
//创建多边形
function crtPoly(x,y){polynew Poly({vertices:[x,y,x,y],types:[POINTS,LINE_STRIP]})sky.add(poly)
}
// 渲染方法
function render(){gl.clear(gl.COLOR_BUFFER_BIT)sky.draw()
}最后用一个完整的例子结束这一篇的知识。 
案例-狮子座 
接下来我要在下图中绘制狮子座。 效果如下 1-产品需求 
1-1-基本绘图需求 
鼠标第1次点击画布时 创建多边形绘制2个点 鼠标移动时 当前多边形最后一个顶点随鼠标移动 鼠标接下来点击画布时 新建一个点 鼠标右击时 删除最后一个随鼠标移动的点  
1-2-优化需求 
顶点要有闪烁动画建立顶点的时候如果鼠标点击了其它顶点就不要再显示新的顶点 
对于上面的基本需求我们在用鼠标画多线的时候已经实现了接下来我们直接实现优化需求。 
2-代码实现 
1.建立顶点着色器 
script idvertexShader typex-shader/x-vertexattribute vec4 a_Attr;varying float v_Alpha;void main(){gl_Positionvec4(a_Attr.x,a_Attr.y,0.0,1.0);gl_PointSizea_Attr.z;v_Alphaa_Attr.w;}
/scripta_Attr() 是一个4维向量其参数结构为(x,y,z,w) x,y代表位置z代表顶点尺寸w代表顶点透明度w会通过 varying 变量v_Alpha 传递给片元  
2.建立片元着色器 
script idfragmentShader typex-shader/x-fragmentprecision mediump float;varying float v_Alpha;void main(){float distdistance(gl_PointCoord,vec2(0.5,0.5));if(dist0.5){gl_FragColorvec4(0.87,0.91,1.0,v_Alpha);}else{discard;}}
/script通过v_Alpha接收透明度然后设置片元的颜色。 
3.建立夜空对象用于承载多边形 
const skynew Sky(gl)4.建立合成对象用于对顶点数据做补间运算 
const compose  new Compose();5.声明两个变量用于表示当前正在绘制的多边形和鼠标划上的点 
//当前正在绘制的多边形
let polynull
//鼠标划上的点
let pointnull6.取消右击提示 
//取消右击提示
canvas.oncontextmenu  function(){return false;
}7.鼠标按下事件 
// 鼠标按下事件
canvas.addEventListener(mousedown, (event)  {if(event.button2){//右击删除顶点polypopVertice()}else{const {x,y}getMousePosInWebgl(event,canvas)if(poly){//连续添加顶点addVertice(x,y)}else{//建立多边形crtPoly(x,y)}}
});getMousePosInWebgl() 方法是用于获取鼠标在webgl 画布中的位置我们之前说过。crtPoly() 创建多边形 
function crtPoly(x,y){let o1point?point:{x,y,pointSize:random(),alpha:1}const o2{x,y,pointSize:random(),alpha:1}polynew Poly({size:4,attrName:a_Attr,geoData:[o1,o2],types:[POINTS,LINE_STRIP]})sky.add(poly)crtTrack(o1)crtTrack(o2)
}建立两个顶点数据o1,o2如果鼠标点击了其它顶点o1的数据就是此顶点的数据。 
顶点的尺寸是一个随机数random() 
function random(){return Math.random()*8.03.0
}基于两个顶点数据建立多边形对象和两个时间轨对象。 
crtTrack() 建立时间轨 
function crtTrack(obj){const {pointSize}objconst track  new Track(obj)track.start  new Date()track.timeLen  2000track.loop  truetrack.keyMap  new Map([[pointSize,[[500, pointSize],[1000, 0],[1500, pointSize],],],[alpha,[[500, 1],[1000, 0],[1500, 1],],],]);compose.add(track)
}addVertice() 添加顶点 
function addVertice(x,y){const {geoData}polyif(point){geoData[geoData.length-1]point}let obj{x,y,pointSize:random(),alpha:1}geoData.push(obj)crtTrack(obj)
}如果鼠标点击了其它顶点就让多边形的最后一个顶点数据为此顶点。 
建立下一个顶点的顶点数据添加新的顶点建立新的时间轨。 
popVertice() 删除最后一个顶点 
function popVertice(){poly.geoData.pop()compose.children.pop()polynull
}8.鼠标移动事件 
canvas.addEventListener(mousemove, (event)  {const {x,y}getMousePosInWebgl(event,canvas)pointhoverPoint(x,y)if(point){canvas.style.cursorpointer}else{canvas.style.cursordefault}if(poly){const objpoly.geoData[poly.geoData.length-1]obj.xxobj.yy}
});基于鼠标是否划上顶点设置鼠标的视觉状态。 
设置正在绘制的多边形的最后一个顶点点位。 
hoverPoint() 检测所有顶点的鼠标划入返回顶点数据 
function hoverPoint(mx,my){for(let {geoData} of sky.children){for(let obj of geoData){if(polyobjpoly.geoData[poly.geoData.length-1]){continue}const delta{x:mx-obj.x,y:my-obj.y}const {x,y}glToCssPos(delta,canvas)const distx*xy*y;if(dist100){return obj}}}return null
}遍历sky 中的所有顶点数据 
忽略绘图时随鼠标移动的点 
获取鼠标和顶点的像素距离 
若此距离小于10像素返回此点否则返回null。 
glToCssPos() webgl坐标系转css坐标系将之前说过的getMousePosInWebgl() 方法逆向思维即可 
function glToCssPos({x,y},{width,height}){const [halfWidth, halfHeight]  [width / 2, height / 2]return {x:x*halfWidth,y:-y*halfHeight}
}9.连续渲染方法 
!(function ani() {compose.update(new Date())sky.updateVertices([x,y,pointSize,alpha])render()requestAnimationFrame(ani)
})();更新动画数据更新Vertices 数据render() 渲染 
function render(){gl.clear(gl.COLOR_BUFFER_BIT)sky.draw()
}扩展-图形转面 
1-webgl三种面的适应场景 
之前咱们说过webgl 可以绘制三种面 
TRIANGLES 单独三角形TRIANGLE_STRIP 三角带TRIANGLE_FAN 三角扇 
在实际的引擎开发中TRIANGLES 是用得最多的。 
TRIANGLES 的优势是可以绘制任意模型缺点是比较费点。 
适合TRIANGLES 单独三角形的的模型 TRIANGLE_STRIP 和TRIANGLE_FAN 的优点是相邻的三角形可以共用一条边比较省点然而其缺点也太明显因为它们只适合绘制具备相应特点的模型。 
适合TRIANGLE_STRIP三角带的模型 适合TRIANGLE_FAN三角扇的模型 three.js 使用的绘制面的方式就是TRIANGLES我们可以在其WebGLRenderer 对象的源码的renderBufferImmediate 方法中找到 
_gl.drawArrays( _gl.TRIANGLES, 0, object.count );2-图形转面的基本步骤 
在three.js 里有一个图形几何体ShapeGeometry可以把图形变成面。 我们学到这里只要有数学支撑也可以实现这种效果。 
接下来我要使用TRIANGLES 独立三角形的方式将图形转成面。 
我使用的方法叫做“砍角”其原理就是从起点将多边形中符合特定条件的角逐个砍掉然后保存到一个集合里直到把多边形砍得只剩下一个三角形为止。这时候集合里的所有三角形就是我们想要的独立三角形。 
举个例子 已知逆时针绘图的路径G 
求将其变成下方网格的方法 解 
1.寻找满足以下条件的▲ABC 
▲ABC的顶点索引位置连续如012,123、234点C在向量AB的正开半平面里可以理解为你站在A点面朝B点点C要在你的左手边▲ABC中没有包含路径G 中的其它顶点 
2.当找到▲ABC 后就将点B从路径的顶点集合中删掉然后继续往后找。 
3.当路径的定点集合只剩下3个点时就结束。 
4.由所有满足条件的▲ABC构成的集合就是我们要求的独立三角形集合。 
3-绘制路径G 
1.路径G的顶点数据 
const pathData  [0, 0,0, 600,600, 600,600, 200,200, 200,200, 400,300, 400,300, 300,500, 300,500, 500,100, 500,100, 100,600, 100,600, 0];在pathData里两个数字为一组分别代表顶点的位和位。 
pathData里的数据是我以像素为单位画出来的在实际项目协作中UI给我们的svg文件可能也是以像素为单位画出来的这个我们要做好心理准备。 
因为webgl画布的宽和高永远都是两个单位。 
所以我们要将上面的点画到webgl 画布中就需要做一个数据映射。 
2.在webgl 中绘制正方形。 
从pathData 数据中我们可以看出路径G的宽高都是600是一个正方形。 
所以我可以将路径G映射到webgl 画布的一个正方形中。 
这个正方形的高度我可以暂且定为1那么其宽度就应该是高度除以canvas画布的宽高比。 
//宽高比
const ratio  canvas.width / canvas.height;
//正方形高度
const rectH  1.0;
//正方形宽度
const rectW  rectH / ratio;3.正方形的定位把正方形放在webgl画布的中心。 
获取正方形尺寸的一半然后求出其x、y方向的两个极值即可。 
//正方形宽高的一半
const [halfRectW, halfRectH]  [rectW / 2, rectH / 2];
//两个极点
const minX  -halfRectW;
const minY  -halfRectH;
const maxX  halfRectW;
const maxY  halfRectH;我想把 
4.利用之前的Poly对象绘制正方形测试一下效果。 
const rect  new Poly({gl,vertices: [minX, maxY,minX, minY,maxX, minY, maxX, maxY,],
});
rect.draw();先画了4个点效果没问题。 
5.建立x轴和y轴比例尺。 
const scaleX  ScaleLinear(0, minX, 600, maxX);
const scaleY  ScaleLinear(0, minY, 600, maxY);
function ScaleLinear(ax, ay, bx, by) {const delta  {x: bx - ax,y: by - ay,};const k  delta.y / delta.x;const b  ay - ax * k;return function (x) {return k * x  b;};
}ScaleLinear(ax, ay, bx, by) 方法使用的就是点斜式用于将x轴和y轴上的数据像素数据映射成 webgl数据 
ax 像素数据的极小值ay webgl数据的极小值bx 像素数据的极大值by webgl数据的极大值 
6.将路径G中的像素数据解析为webgl 数据 
const glData  [];
for (let i  0; i  pathData.length; i  2) {glData.push(scaleX(pathData[i]), scaleY(pathData[i  1]));
}画一下看看 
const path  new Poly({gl,vertices: glData,types: [POINTS, LINE_LOOP],
});
path.draw();效果没有问题。 
4.将图形网格化 
1.我自己建立了一个ShapeGeo 对象用于将图形网格化。 
const shapeGeo  new ShapeGeo(glData)属性 
pathData 平展开的路径数据geoData 由路径数据pathData 转成的对象型数组triangles 三角形集合对象型数组vertices 平展开的对立三角形顶点集合 
方法 
update() 更新方法基于pathData 生成verticesparsePath() 基于路径数据pathData 转成对象型数组findTriangle(i) 寻找符合条件的三角形 i 顶点在geoData 中的索引位置表示从哪里开始寻找三角形 includePoint(triangle) 判断三角形中是否有其它顶点inTriangle(p0, triangle) 判断一个顶点是否在三角形中cross([p0, p1, p2]) 以p0为基点对二维向量p0p1、p0p2做叉乘运算upadateVertices() 基于对象数组geoData 生成平展开的vertices 数据 
2.绘制G形面 
const face  new Poly({gl,vertices: shapeGeo.vertices,types: [TRIANGLES],
});
face.draw();效果如下 填坑 
我之前在用鼠标绘制线条的时候还留了一个系统兼容性的坑。 
线条在mac电脑中是断的 这种效果是由片元着色器导致的 
precision mediump float;    
void main(){ float distdistance(gl_PointCoord,vec2(0.5,0.5));if(dist0.5){gl_FragColorvec4(1,1,0,1);}else{discard;}
}我们在用上面的片元着色器绘图的时候把线给过滤掉了。 
因此我需要告诉着色器当前绘图的方式如果是POINTS 方式绘图的话就过滤一下圆圈以外的片元否则就正常绘图。 
接下来咱们就看一下代码实现。 
1.给片元着色器添加一个uniform 变量。 
precision mediump float;
uniform bool u_IsPOINTS;
void main(){if(u_IsPOINTS){float distdistance(gl_PointCoord,vec2(0.5,0.5));if(dist0.5){gl_FragColorvec4(1,1,0,1);}else{discard;}}else{gl_FragColorvec4(1,1,0,1);}
}2.给Poly 对象添加两个属性。 
const defAttr()({circleDot:false,u_IsPOINTS:null,……
})circleDot 是否是圆点u_IsPOINTS uniform变量 
3.在初始化方法中如果是圆点就获取一下uniform 变量 
init(){……if (circleDot) {this.u_IsPOINTS  gl.getUniformLocation(gl.program, u_IsPOINTS);}
}4.在渲染的时候如果是圆点就基于绘图方式修改uniform 变量 
draw(typesthis.types){const {gl,count,u_IsPOINTS,circleDot}thisfor (let type of types) {circleDotgl.uniform1f(u_IsPOINTS,typePOINTS);gl.drawArrays(gl[type],0,count);}
}总结 
在实际项目中我们绘制完了图形往往还会对其进行修改变换下一篇咱们会说一下如何变换图形。