菜单

用webgl创设一款轻松第3人称FPS游戏

2019年4月28日 - 皇家前端

切切实实达成

用webgl构建一款轻巧第三个人称RTS游戏

2016/11/03 · HTML5 · 1
评论 ·
WebGL

原稿出处:
AlloyTeam   

背景:不明白大家还记不记得上次不胜3D迷宫游戏,有同事调侃说游戏个中有1个十字瞄准器,就以为少了一把枪。好吧,那本次就带来一款第二个人称RAC游戏。写demo训练,所以依然用的原生webgl,这一次主要会说一下webgl中关于录制头相关的学识,点开全文在线试玩~~

 

simpleFire在线试玩:

simpleFire源码地址:

说明:

游玩相比较轻便(所以叫simpleFire)……不过也勉强算是一款首位称GAL游戏啊~

由于岁月格外轻易,此番真正不是懒!!相信本身!!所以分界面非常不美观,见谅见谅(讲良心说,那比3D迷宫真的走心多了……)

上次3D迷宫小说首要介绍了迷宫的二种算法,webgl没怎么讲,那篇小说会重要讲下webgl中录制机相关的学问,webgl基础知识会轻松带一下

终极贴一下上次3D迷宫的地方:

 

壹、游戏妄图:

做一款游戏和做1个类型是一样的,不能够刚有主见了就径直开始撸代码。贰个前端项目大概要思量框架选型、采取何种营造、设计情势等等;而一款游戏,在规定游戏项目之后,要思考游戏玩的方法,游戏场景,游戏关卡,游戏建立模型版画等等,而那个洋洋都以非代码手艺层面包车型地铁,在真正的玩耍支付中会有特意那3个世界的人去承担,所以1款好的二十二日游,每3个环节都不可或缺。

位置是有关游戏支付的碎碎念,上边初叶真正的讲明simpleFire那款游戏。

试玩之后大家应该会意识游戏整个场景极度轻松,1把枪,四面墙,墙上边有对象,将装有的靶子都打掉则游戏甘休,最后的游戏分数是:
击中目标数 +
剩余时间转变。此时读者或然内心感受:那尼玛在逗我呢,那也太简单了吗。先别急,接下去说下游戏盘算进程中相见的坑点

因为是3D游戏,而且涉及到了不一样的实体在3D空间中设有(枪、靶子、墙),此前那3D迷宫准备职业之所以简单是空间中持久就只有“墙”那3个事物。

要让枪、靶子、墙那一个东西同处一个上空内比一点也不细略,把他们顶点讯息写进shader就行了嘛

(在此间考虑到大概有没接触过webgl的同室,所以简要介绍一下,canvas是目的品级的画板操作,drawImage画图片,arc画弧度等,那些皆以目标品级操作。而webgl是片元级操作,片元在此处能够先轻松明了为像素,只是它比像素含有更加多的新闻。上边所说的把顶点新闻写进shader,能够知晓为把枪、靶子、墙这么些事物的坐标地点画进canvas。先就那样通晓着往下看吗~借使canvas也不知情那就不能够了。。。)

极端新闻从哪来?一般是设计员建立模型弄好了,导成相关文件给开荒者,地方、颜色等等都有。可是……小编那里未有任何有关新闻,全体得和煦来做。

投机左右又不曾正式的建立模型工具,那该怎么转移顶点信息?用脑补 +
代码生成……事先注脚,那是1种很不对很难堪的方法,自个儿写点demo能够那样玩,不过生产中千万别那样。

这边就用生成枪来比喻,大家掌握普通制式手枪长180mm到220mm左右,在那里取20cm,并将其尺寸稍微小于视锥体近平面包车型地铁长短,视锥体近平面也看作为荧屏中webgl画布的宽度。所以大家转移的枪理论上应当是那样的,如图所示:

图片 1

好了,枪的百分比分明之后将在组成webgl坐标系生成顶点新闻了,webgl坐标系和canvas贰D坐标系有异常的大的两样,如图:

图片 2

因为是代码手动生成顶点音讯,用-一~一写起来有点难受,所以那边大家先放开十倍,后边在把除回去,蛋疼吧,那便是不走正途的代价……

代码该怎么变卦顶点音信吗?用代码画1把枪听起来很难,可是用代码画一条线、画叁个圆、画3个正方体等,这一个信手拈来吧,因为那么些是基本图形,有数学公式能够博得。叁个参差不齐的模型,我们没办法直接规定顶点新闻,那就只可以通过各样轻巧模型去拼凑了,上边那几个页面就是轻巧的拆分了下枪的模子,能够看来是逐一简单子模型拼凑而成的(表达:建立模型形成的也是拼接,可是它的1块块子模型不是靠不难图形函数方法生成)。

手枪生成呈现:

那种格局有怎么着坏处:职业量大并且倒霉看、扩充性差、可控性差

那种方法有如何利润:磨炼空间想象力与数学函数应用吧……

介绍了这么多,其实就想说:这么恶心且吃力不讨好的活笔者都干下去了,真的走心了!

具体怎么用简易图形函数生成的子模型能够看代码,代码看起来如故比较轻易,有自然立体几何空间想象力就好,那里不细讲,终归尤其尤其不推荐那样玩。

枪建模相关代码地址:

 

二、游戏视角

先是人称生活模拟游戏玩的是什么样?正是什么人开枪开的准,那几个是永远不改变的,就到底OW,在豪门套路都了然、能够见招拆招的场地下,最后也是比枪法什么人更加准。那么枪法准是什么彰显的呢?正是通过移动鼠标的速度与正确度来反映(那里未有何样IE叁.0……),对于游戏发烧友来讲,手中移动的是鼠标,映射在荧屏上的是准心,对于开采者来说,移动的是观点,也正是3D世界中的录像头!

先说下摄像头的基本概念和学识,webgl中暗许的摄像头方向是通向Z轴的负方向,随手画了图表示下(已知丑,轻吐槽)

图片 3

录像头地方不改变,同3个实体在分歧职责能给大家差异的感受,如下

图片 4 图片 5

摄像头地方变动,同3个物体地点不改变,也能给大家不一致的感受,如下

图片 6 图片 7

等等!那犹如并不曾什么分别啊!以为上就是实体发掘了转移啊!确实那样,就就像你在车上,看窗外飞驰而过的山水那般道理。

摄像头的效益也正是改动物体在视锥体中的地方,物体移动的法力也是退换其在视锥体中的地点!

熟谙webgl的中的同学了解

JavaScript

gl_Position = uPMatrix * uVMatrix * uMMatrix * aPosition;

1
gl_Position = uPMatrix * uVMatrix * uMMatrix * aPosition;

对此不领会的同校,能够如此敞亮

gl_Position是终极显示屏上的极限,aPosition是早期大家转移的模子顶点

uMMatrix是模型转换矩阵,比如大家想让实体移动、旋转等等操作,能够重新开始展览

uPMatrix是投影调换矩阵,就知晓为叁维物体能在二D显示屏上突显最为重要的一步

uVMatrix是视图调换矩阵,正是主演!我们用它来更换摄像头的职分

咱俩的第二约等于玩转uVMatrix视图矩阵!在那里,用过threejs也许glMatrix的同窗明确就很愕然了,那里有哪些好探究的,直接lookAt不就不解决了么?

的确lookAt正是用来操作视图矩阵的,思索到没用过的用户,所以那边先说一下lookAt那几个方法。

lookAt成效如其名,用来认同3D世界中的录制机方向(操作视图矩阵),参数有三个,第贰个是双眼的岗位,第三个是肉眼看向目标的职位,第四个是坐标的正上方向,能够想像成脑部的朝上方向。

用图来展现的话正是如下图(已知丑,轻戏弄):

图片 8

掌握了lookAt的用法,接下去我们来看一下lookAt的原理与得以完毕。lookAt既然对应着视图矩阵,将它的结果想象成矩阵VM

世家明白webgl中早期的坐标系是那样的

图片 9

那就是说①旦我们知道最终的坐标系,就足以逆推出矩阵VM了。这些轻便总括,结果如下

图片 10

来,重播一下lookAt第二个和第叁个参数,肉眼的职责肉眼看向目的的地方,有了那八个坐标,最后坐标系的Z是否明确了!,最后多个参数是正上方向,是或不是Y也分明了!

机敏的同室见状有了Z和Y,立马想到能够用叉积算出X,不知底如何是叉积的能够查找一下(学习webgl一定要对矩阵熟习,这几个文化是基础)

如此这般我们就很轻松欢娱的搜查捕获了VM,不过!就如不怎么语无伦次

自身VM是从未有过难题的,关键在于这么使用它,比方说作者直接lookAt(0,0,0, 一,0,0,
0,壹,0)使用,能够领略那时候大家的视界是X轴的正方向,但假诺自身鼠标随意晃二个任务,你能高效的理解那四个参数该怎么样传么?

为此现在的对象正是通过鼠标的舞狮,来测算出lookAt的多个参数,先上代码~

JavaScript

var camera = {     rx: 0,     ry: 0,     mx: 0,     my: 0,     mz: 0,
    toMatrix: function() {         var rx = this.rx;         var ry =
this.ry;         var mx = this.mx;         var my = this.my;         var
mz = this.mz;           var F =
normalize3D([Math.sin(rx)*Math.cos(ry), Math.sin(ry), -Math.cos(rx) *
Math.cos(ry)]);           var x = F[0];         var z = F[2];  
        var angle = getAngle([0, -1], [x, z]);             var R =
[Math.cos(angle), 0, Math.sin(angle)];           var U = cross3D(R,
F);           F[0] = -F[0];         F[1] = -F[1];         F[2]
= -F[2];           var s = [];           s.push(R[0], U[0],
F[0], 0);         s.push(R[1], U[1], F[1], 0);
        s.push(R[2], U[2], F[2], 0);           s.push(
            0,             0,             0,             1         );  
        return s;     } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var camera = {
    rx: 0,
    ry: 0,
    mx: 0,
    my: 0,
    mz: 0,
    toMatrix: function() {
        var rx = this.rx;
        var ry = this.ry;
        var mx = this.mx;
        var my = this.my;
        var mz = this.mz;
 
        var F = normalize3D([Math.sin(rx)*Math.cos(ry), Math.sin(ry), -Math.cos(rx) * Math.cos(ry)]);
 
        var x = F[0];
        var z = F[2];
 
        var angle = getAngle([0, -1], [x, z]);
 
 
        var R = [Math.cos(angle), 0, Math.sin(angle)];
 
        var U = cross3D(R, F);
 
        F[0] = -F[0];
        F[1] = -F[1];
        F[2] = -F[2];
 
        var s = [];
 
        s.push(R[0], U[0], F[0], 0);
        s.push(R[1], U[1], F[1], 0);
        s.push(R[2], U[2], F[2], 0);
 
        s.push(
            0,
            0,
            0,
            1
        );
 
        return s;
    }
};

此地封装了四个差不多的camera对象,里面有rx对应鼠标在X方向上的移动,ry对应鼠标在Y方向上的活动,这些我们可以因此监听鼠标在canvas上的轩然大波轻巧得出。

JavaScript

var mouse = {     x: oC.width / 2,     y: oC.height / 2 };  
oC.addEventListener(‘mousedown’, function(e) {     if(!level.isStart) {
        level.isStart = true;         level.start();     }
    oC.requestPointerLock(); }, false);  
oC.addEventListener(“mousemove”, function(event) {  
    if(document.pointerLockElement) {           camera.rx +=
(event.movementX / 200);         camera.ry += (-event.movementY / 200);
    }       if(camera.ry >= Math.PI/2) {         camera.ry =
Math.PI/2;     } else if(camera.ry <= -Math.PI/2) {         camera.ry
= -Math.PI/2;     }      }, false);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var mouse = {
    x: oC.width / 2,
    y: oC.height / 2
};
 
oC.addEventListener(‘mousedown’, function(e) {
    if(!level.isStart) {
        level.isStart = true;
        level.start();
    }
    oC.requestPointerLock();
}, false);
 
oC.addEventListener("mousemove", function(event) {
 
    if(document.pointerLockElement) {
 
        camera.rx += (event.movementX / 200);
        camera.ry += (-event.movementY / 200);
    }
 
    if(camera.ry >= Math.PI/2) {
        camera.ry = Math.PI/2;
    } else if(camera.ry <= -Math.PI/2) {
        camera.ry = -Math.PI/2;
    }
    
}, false);

lockMouse+momentX/Y对于游戏开垦来讲是实在好用啊!!不然本人来写一流蛋疼还大概会稍稍难点,安利1大家壹波,用法也一点也不细略。

鼠标在X方向上的运动,在3D空间中,其实就是围绕Y轴的转动;鼠标在Y方向上的移位,其实正是围绕X轴的旋转,那些应该可以脑补出来吧

那正是说难点来了,围绕Z轴的旋转呢??那里小编从没惦念围绕Z轴的团团转啊,因为游戏没用到嘛,第贰个人称射击的游乐很少会有围绕Z轴旋转的情景吧,那些一般是临床风湿性关节炎用的。尽管不驰念,但是原理都以同壹的,能够推出去,有意思味的伴儿能够和睦查研讨究下。

大家将rx和ry拆看来看,首先就只看rx对起来视野(0, 0,
-一)的震慑,经过三角函数的改造之后应该是( Math.sin(rx), 0,
-Math.cos(rx) )
,那里就不画图解释了,三角函数基本知识

接下来再思考( Math.sin(rx), 0, -Math.cos(rx)
)
经过了ry的调换会怎样,其实正是将( Math.sin(rx), 0, -Math.cos(rx)
)
与ry的改造映射到y-z坐标系上边,再用三角函数知识得出( Math.sin(rx)*Math.cos(ry),
Math.sin(ry), -Math.cos(rx) * Math.cos(ry) )

一代精晓不了的同学可以闭上眼睛好好脑部一眨眼调换的镜头……

通过那两步最后大家获得了经过转变之后的视界方向F(少了Z轴方向的旋转,其实就是再多一步),也正是lookAt函数中的前多少个函数得出去的值,然后再总括3个值就ok了,代码中我们求的是X轴的正方向

代码在刚刚封装的camera中是这几行

JavaScript

var x = F[0]; var z = F[2];   var angle = getAngle([0, -1], [x,
z]);

1
2
3
4
var x = F[0];
var z = F[2];
 
var angle = getAngle([0, -1], [x, z]);

angle得出了最终的观点方向(-Z)和最初视界方向在x-z坐标系中的偏转角,因为是x-z坐标系,所以最初的X正方向和终极的X正方向偏移角也是angle

JavaScript

function getAngle(A, B) {     if(B[0] === 0 && A[0] === 0) {
        return 0;     }       var diffX = B[0] – A[0];     var diffY
= B[1] – A[1];       var a = A[0] * B[0] + A[1] * B[1];
    var b = Math.sqrt(A[0] * A[0] + A[1] * A[1]);     var c =
Math.sqrt(B[0] * B[0] + B[1] * B[1]);       return (B[0] /
Math.abs(B[0])) *  Math.acos(a / b / c); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getAngle(A, B) {
    if(B[0] === 0 && A[0] === 0) {
        return 0;
    }
 
    var diffX = B[0] – A[0];
    var diffY = B[1] – A[1];
 
    var a = A[0] * B[0] + A[1] * B[1];
    var b = Math.sqrt(A[0] * A[0] + A[1] * A[1]);
    var c = Math.sqrt(B[0] * B[0] + B[1] * B[1]);
 
    return (B[0] / Math.abs(B[0])) *  Math.acos(a / b / c);
}

透过简单的三角函数获得了最后X轴的四方向中华V(注意:没思量围绕Z轴的团团转,否则要麻烦一些)

再用叉积获得了最后Z轴的方框向U,然后不要忘记,此前F是视界方向,相当于Z轴正方向的反倒方向,所以取反操作不要忘了

福特Explorer、U、-F都获得了,也就得到了最后的VM视图矩阵!

其实呢,在未有运动的情景下,视图矩阵和模型转变矩阵也正是旋转方向不等同,所以上述的知识也足以用在演绎模型调换矩阵里面。固然带上了移动也不麻烦,牢记模型转变矩阵供给先活动、再旋转,而视图调换矩阵是先旋转、再平移

打闹中录制机相关的学识就先讲到那里了,假如有不明了的同校能够留言切磋。

本来这不是唯1的章程,simpleFire那里未有思索平移,不思虑平移的状态下,其实正是终极就是要生成贰个三维旋转矩阵,只然而使用的是一种逆推的情势。别的还有局地欧拉角、依次2维旋转等等方式,都能够获得结果。但是这个都比较重视矩阵和三角函数数学知识,是否现行反革命最为的怀想当年的数学老师……

 

3、命中检查实验

我们玩转了录像头,然后即是枪击了,开枪本人很简短,可是得挂念到枪有未有打中人啊,这只是关于到用户得分乃至是敌笔者的死活。

笔者们要做的干活是决断子弹有未有击中目的,听起来像是碰撞检测有未有!来,纪念一下在2D中的碰撞检验,大家的检查测试都以遵从AABB的方法检查评定的,相当于据他们说对象的重围框(对象top、left、width、height)形成,然后坐标(x,
y)与其总计来判别碰撞景况。那种方法有一个败笔,正是非矩形的检查评定大概有模型误差,举个例子圆、三角形等等,毕竟包围框是矩形的呗。dntzhang所支付出的AlloyPage游戏引擎中有歌唱家算法完美的缓慢解决了那些毛病,将检验粒度由对象形成了像素,感兴趣的同桌能够去钻探一下~那里临时不提,大家说的是3D检查评定

精心考虑3D世界中的物体也有包围框啊,更贴切的正是包围盒,这样说来应该也得以用贰D中AABB方式来检查测试啊。

诚然能够,只要大家将触发鼠标事件获得的(x,
y)坐标经过各类转换矩阵转变为3D世界中的坐标,然后和模型举办李包裹围盒检验,也能够拿走碰撞的结果。对开荒者来讲挺费力的,对CPU来讲就更麻烦了,那里的总计量实在是太大了,假诺世界中只有壹七个物体辛亏,假如有一大票物体,那检验的总括量实在是太大了,很不可取。有未有越来越好的艺术?

有,刚刚那种方式,是将二D中(x,
y)经过矩阵转产生3D世界,还有壹种方式,将3D世界中的东西调换来贰D平面中来,那正是帧缓冲才具。帧缓冲不过二个好东西,3D世界中的阴影也得靠它来促成。

这里用一句话来直观的牵线帧缓冲给不打听的校友:将必要绘制在荧屏上的图像,更为灵活管理的后制图在内部存款和储蓄器中

如图相比较一下simpleFire中的帧缓冲图像是什么的

图片 11常规游玩画面

图片 12帧缓冲下的镜头

察觉壹切世界中只有靶子有颜色对不对!那样大家读取帧缓冲图像中有个别点的rgba值,就理解对应的点是还是不是在目的上了!实现了坐标碰撞检查评定!

事先说的愈益灵敏的管理,正是指渲染时对一一模型颜色的管理

检查评定代码如下:

JavaScript

oC.onclick = function(e) {     if(gun.firing) {         return ;     }
    gun.fire();       var x = width / 2;     var y = height / 2;     
    webgl.uniform1i(uIsFrame, true);
    webgl.bindFramebuffer(webgl.FRAMEBUFFER, framebuffer);
    webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);  
    targets.drawFrame();       var readout = new Uint8Array(1*1*4);  
    // webgl.bindFramebuffer(webgl.FRAMEBUFFER, framebuffer);
    webgl.readPixels(x, y, 1, 1, webgl.RGBA, webgl.UNSIGNED_BYTE,
readout);     webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);  
    targets.check(readout);       webgl.uniform1i(uIsFrame, false); };  
/* targets下的check方法 */ check: function(arr) {     var r = ” +
Math.floor(arr[0] / 255 * 100);     var g = ” + Math.floor(arr[1]
/ 255 * 100);     var b = ” + Math.floor(arr[2] / 255 * 100);
    var i;     var id;       for(i = 0; i < this.ids.length; i++) {
        if(Math.abs(this.ids[i][0] – r) <= 1 &&
Math.abs(this.ids[i][1] – g) <= 1 && Math.abs(this.ids[i][2]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
oC.onclick = function(e) {
    if(gun.firing) {
        return ;
    }
    gun.fire();
 
    var x = width / 2;
    var y = height / 2;
    
    webgl.uniform1i(uIsFrame, true);
    webgl.bindFramebuffer(webgl.FRAMEBUFFER, framebuffer);
    webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
 
    targets.drawFrame();
 
    var readout = new Uint8Array(1*1*4);
 
    // webgl.bindFramebuffer(webgl.FRAMEBUFFER, framebuffer);
    webgl.readPixels(x, y, 1, 1, webgl.RGBA, webgl.UNSIGNED_BYTE, readout);
    webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);
 
    targets.check(readout);
 
    webgl.uniform1i(uIsFrame, false);
};
 
/* targets下的check方法 */
check: function(arr) {
    var r = ” + Math.floor(arr[0] / 255 * 100);
    var g = ” + Math.floor(arr[1] / 255 * 100);
    var b = ” + Math.floor(arr[2] / 255 * 100);
    var i;
    var id;
 
    for(i = 0; i < this.ids.length; i++) {
        if(Math.abs(this.ids[i][0] – r) <= 1 && Math.abs(this.ids[i][1] – g) <= 1 && Math.abs(this.ids[i][2] – b) <= 1) {
            console.log(‘命中!’);
            id = this.ids[i][0] + this.ids[i][1] + this.ids[i][2];
            this[id].leave();
            score.add(1);
            level.check();
            break ;
        }
    }
}

并且这一个法子火速,计算量都在GPU里面,那种数学总括的频率GPU是比CPU快的,GPU依旧并行的!那守旧的AABB法还有存在的意思么?

其实是一对,因为精确,能够在包围盒中总结得到具体的碰撞点地点,那是帧缓冲法所达不到的

举个例证,第一人称GAL游戏中的爆头行为,能够在帧缓冲元帅人物模型中躯体和头用不相同颜色区分出来,那样能够检查测试出碰撞的是头依然肌体。那种现象下帧缓冲方法还hold住

那如假若想赢得打靶中具体的职分,留下子弹的印痕呢?那里帧缓冲方法就死也做不到了。

一流实行正是在亟需高精度复杂气象下的碰撞检验能够将二种办法结合使用:用帧缓冲去掉多余的实体,裁减古板AABB法的总括量,最后收获具体地方。

simpleFire那里就没这么折腾了……只要射到靶上打哪都是得分~~~

 

4、碎碎念

至于simpleFire想讲的事物也就讲完了,自身也未曾什么才能困难,文章的最终1节也聊一聊关于webgl

此前早已说了与canvas之间的分别,是从计算机层面包车型客车界别,那里说一下对于开拓者的区分:

canvas贰D是壹块画布,在画布上描绘,画中的东西自然是虚拟的

webgl是二个社会风气,你要在世界中开创,但也要满意世界的规则

那比喻有点夸大,都牵扯到了社会风气的条条框框。但实际正是这么,webgl比canvas二D扑朔迷离,而异常的大学一年级块复杂的地点正是社会风气的条条框框
—— 光与影子

那两块知识3D迷宫和simpleFire都未有用上,因为那应当是静态3D中最难啃的骨头了啊。说难啊,知道原理之后也轻便,但固然恶意麻烦,加上光和阴影得多多数广大的代码。前面会详细讲解光和阴影相关文化的,也是用小游戏的措施。写一篇纯原理的稿子认为没啥意思,知识点壹搜能搜到大多了

不看卡通,纯看静态渲染方面的东西,二D和3D也就大多,须求地方音信、颜色消息,平移旋转等等,3D也正是拉长了光和影子那样的社会风气规则,比2D还多了部分数学知识的要求

所以webgl并不难~应接越来越多的人过来webgl的坑中来吧,不过推荐入坑的同桌不要伊始就过度依赖three、oak3D、PhiloGL等图形库,依然从原生入手比较好

小说对simpleFire代码解说的不是无数,源码也贴出来了,百分之百原生webgl的写法,看起来应当也不是很难

 

结语:

下次带来的不自然是3D小游戏,3D小游戏写起来仍然挺累的,素材什么的比二D麻烦众多

那篇小说也就到此停止啦,写的好累T_T。。有标题和建议的伴儿应接留言一齐搜求~

1 赞 5 收藏 1
评论

图片 13

            case ‘html’:

结语

亟待关怀的是此处小编用了其它壹对shader,此时就涉嫌到了有关是用八个program
shader依旧在同一个shader中行使if
statements,那两边品质如何,有啥样不一致,那里将放在下壹篇webgl相关优化中去说。


初稿链接:

二、读取分析obj文件

JavaScript

var regex = { // 那上卿则只去相称了我们obj文件中用到数量
    vertex_pattern:
/^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
// 顶点     normal_pattern:
/^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
// 法线     uv_pattern:
/^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, //
纹理坐标     face_vertex_uv_normal:
/^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/,
// 面信息     material_library_pattern:
/^mtllib\s+([\d|\w|\.]+)/, // 正视哪3个mtl文件
    material_use_pattern: /^usemtl\s+([\S]+)/ };   function
loadFile(src, cb) {     var xhr = new XMLHttpRequest();  
    xhr.open(‘get’, src, false);       xhr.onreadystatechange =
function() {         if(xhr.readyState === 4) {  
            cb(xhr.responseText);         }     };       xhr.send(); }  
function handleLine(str) {     var result = [];     result =
str.split(‘\n’);       for(var i = 0; i < result.length; i++) {
        if(/^#/.test(result[i]) || !result[i]) { // 注释部分过滤掉
            result.splice(i, 1);               i–;         }     }  
    return result; }   function handleWord(str, obj) {     var firstChar
= str.charAt(0);     var secondChar;     var result;       if(firstChar
=== ‘v’) {           secondChar = str.charAt(壹);           if(secondChar
=== ‘ ‘ && (result = regex.vertex_pattern.exec(str)) !== null) {
            obj.position.push(+result[1], +result[2], +result[3]);
// 插足到3D对象顶点数组         } else if(secondChar === ‘n’ && (result
= regex.normal_pattern.exec(str)) !== null) {
            obj.normalArr.push(+result[1], +result[2],
+result[3]); // 到场到3D对象法线数组         } else if(secondChar ===
‘t’ && (result = regex.uv_pattern.exec(str)) !== null) {
            obj.uvArr.push(+result[1], +result[2]); //
参与到3D对象纹理坐标数组         }       } else if(firstChar === ‘f’) {
        if((result = regex.face_vertex_uv_normal.exec(str)) !== null)
{             obj.addFace(result); // 将顶点、开采、纹理坐标数组形成面
        }     } else if((result =
regex.material_library_pattern.exec(str)) !== null) {
        obj.loadMtl(result[1]); // 加载mtl文件     } else if((result =
regex.material_use_pattern.exec(str)) !== null) {
        obj.loadImg(result[1]); // 加载图片     } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var regex = { // 这里正则只去匹配了我们obj文件中用到数据
    vertex_pattern: /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, // 顶点
    normal_pattern: /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, // 法线
    uv_pattern: /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, // 纹理坐标
    face_vertex_uv_normal: /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, // 面信息
    material_library_pattern: /^mtllib\s+([\d|\w|\.]+)/, // 依赖哪一个mtl文件
    material_use_pattern: /^usemtl\s+([\S]+)/
};
 
function loadFile(src, cb) {
    var xhr = new XMLHttpRequest();
 
    xhr.open(‘get’, src, false);
 
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4) {
 
            cb(xhr.responseText);
        }
    };
 
    xhr.send();
}
 
function handleLine(str) {
    var result = [];
    result = str.split(‘\n’);
 
    for(var i = 0; i < result.length; i++) {
        if(/^#/.test(result[i]) || !result[i]) { // 注释部分过滤掉
            result.splice(i, 1);
 
            i–;
        }
    }
 
    return result;
}
 
function handleWord(str, obj) {
    var firstChar = str.charAt(0);
    var secondChar;
    var result;
 
    if(firstChar === ‘v’) {
 
        secondChar = str.charAt(1);
 
        if(secondChar === ‘ ‘ && (result = regex.vertex_pattern.exec(str)) !== null) {
            obj.position.push(+result[1], +result[2], +result[3]); // 加入到3D对象顶点数组
        } else if(secondChar === ‘n’ && (result = regex.normal_pattern.exec(str)) !== null) {
            obj.normalArr.push(+result[1], +result[2], +result[3]); // 加入到3D对象法线数组
        } else if(secondChar === ‘t’ && (result = regex.uv_pattern.exec(str)) !== null) {
            obj.uvArr.push(+result[1], +result[2]); // 加入到3D对象纹理坐标数组
        }
 
    } else if(firstChar === ‘f’) {
        if((result = regex.face_vertex_uv_normal.exec(str)) !== null) {
            obj.addFace(result); // 将顶点、发现、纹理坐标数组变成面
        }
    } else if((result = regex.material_library_pattern.exec(str)) !== null) {
        obj.loadMtl(result[1]); // 加载mtl文件
    } else if((result = regex.material_use_pattern.exec(str)) !== null) {
        obj.loadImg(result[1]); // 加载图片
    }
}

代码宗旨的地点都进展了疏解,注意那里的正则只去相配大家obj文件中包括的字段,其余新闻未有去相配,假如有对obj文件全数希望含有的音讯成功相称的同校能够去看下Threejs中objLoad部分源码

三-四次来数组(字符串)出现最多的五回成分和出现次数 ###

3、将obj中多少真正的行使3D对象中去

Text3d.prototype.addFace = function(data) {
    this.addIndex(+data[1], +data[4], +data[7], +data[10]);
    this.addUv(+data[2], +data[5], +data[8], +data[11]);
    this.addNormal(+data[3], +data[6], +data[9], +data[12]);
};

Text3d.prototype.addIndex = function(a, b, c, d) {
    if(!d) {
        this.index.push(a, b, c);
    } else {
        this.index.push(a, b, c, a, c, d);
    }
};

Text3d.prototype.addNormal = function(a, b, c, d) {
    if(!d) {
        this.normal.push(
            3 * this.normalArr[a], 3 * this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,
            3 * this.normalArr[b], 3 * this.normalArr[b] + 1, 3 * this.normalArr[b] + 2,
            3 * this.normalArr[c], 3 * this.normalArr[c] + 1, 3 * this.normalArr[c] + 2
        );
    } else {
        this.normal.push(
            3 * this.normalArr[a], 3 * this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,
            3 * this.normalArr[b], 3 * this.normalArr[b] + 1, 3 * this.normalArr[b] + 2,
            3 * this.normalArr[c], 3 * this.normalArr[c] + 1, 3 * this.normalArr[c] + 2,
            3 * this.normalArr[a], 3 * this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,
            3 * this.normalArr[c], 3 * this.normalArr[c] + 1, 3 * this.normalArr[c] + 2,
            3 * this.normalArr[d], 3 * this.normalArr[d] + 1, 3 * this.normalArr[d] + 2
        );
    }
};

Text3d.prototype.addUv = function(a, b, c, d) {
    if(!d) {
        this.uv.push(2 * this.uvArr[a], 2 * this.uvArr[a] + 1);
        this.uv.push(2 * this.uvArr[b], 2 * this.uvArr[b] + 1);
        this.uv.push(2 * this.uvArr[c], 2 * this.uvArr[c] + 1);
    } else {
        this.uv.push(2 * this.uvArr[a], 2 * this.uvArr[a] + 1);
        this.uv.push(2 * this.uvArr[b], 2 * this.uvArr[b] + 1);
        this.uv.push(2 * this.uvArr[c], 2 * this.uvArr[c] + 1);
        this.uv.push(2 * this.uvArr[d], 2 * this.uvArr[d] + 1);
    }
};

那边我们思量到包容obj文件中f(ace)行中陆个值的意况,导出obj文件中能够强行选拔唯有三角面,可是我们在代码中相配一下相比较妥贴

归纳分析一下以此obj文件

图片 14
前两行看到#标志就明白这几个是注释了,该obj文件是用blender导出的。Blender是一款很好用的建立模型软件,最重大的它是免费的!

图片 15
Mtllib(material library)指的是该obj文件所运用的材质库文件(.mtl)
唯有的obj生成的模型是白模的,它只含有纹理坐标的新闻,但并未有贴图,有纹理坐标也没用

图片 16
V 顶点vertex
Vt 贴图坐标点
Vn 顶点法线

图片 17
Usemtl 使用材料库文件中现实哪贰个材料

图片 18
F是面,后边分别对应 顶点索引 / 纹理坐标索引 / 法线索引

此处抢先四陆%也都以大家丰盛常用的性质了,还有局部任何的,那里就不多说,能够google搜一下,繁多介绍很详细的篇章。
万1有了obj文件,那我们的专业也正是将obj文件导入,然后读取内容还要按行解析就能够了。
先放出最后的结果,三个效仿银系的3D文字效果。
在线地址查看:

在此间顺便说一下,二D文字是可以通过分析获得3D文字模型数据的,将文字写到canvas上之后读取像素,获取路线。大家这边未有利用该方法,因为就算如此辩驳上别的2D文字都能转3D,还能够做出像样input输入文字,3D显示的职能。然则本文是教我们火速搭建三个小世界,所以大家依然选拔blender去建立模型。

browserInfo:function(type) {    switch (type)
{case’android’:returnnavigator.userAgent.toLowerCase().indexOf(‘android’)
!==
-1case’iphone’:returnnavigator.userAgent.toLowerCase().indexOf(‘iphone’)
!== -1case’ipad’:returnnavigator.userAgent.toLowerCase().indexOf(‘ipad’)
!==
-1case’weixin’:returnnavigator.userAgent.toLowerCase().indexOf(‘micromessenger’)
!== -1        default:returnnavigator.userAgent.toLowerCase()    }}

轻巧易行解析一下这些obj文件

图片 19

前两行看到#标记就驾驭这一个是注释了,该obj文件是用blender导出的。Blender是1款很好用的建立模型软件,最重大的它是无偿的!

图片 20

Mtllib(material library)指的是该obj文件所使用的质感库文件(.mtl)

仅仅的obj生成的模型是白模的,它只含有纹理坐标的新闻,但向来不贴图,有纹理坐标也没用

图片 21

V 顶点vertex

Vt 贴图坐标点

Vn 顶点法线

图片 22

Usemtl 使用材质库文件中现实哪3个材质

图片 23

F是面,后边分别对应 顶点索引 / 纹理坐标索引 / 法线索引

那边大多数也都以大家那一个常用的天性了,还有1部分别样的,那里就不多说,能够google搜一下,很多介绍很详细的稿子。

只要有了obj文件,那大家的做事也正是将obj文件导入,然后读取内容还要按行解析就足以了。

先放出最后的结果,二个仿照银系的3D文字效果。

在线地址查看:

在此间顺便说一下,贰D文字是能够通过分析得到3D文字模型数据的,将文字写到canvas上从此读取像素,获取路线。大家这里未有采纳该方式,因为就算这么辩白上其余二D文字都能转3D,还能够做出像样input输入文字,3D展示的功能。可是本文是教我们飞速搭建贰个小世界,所以我们照旧使用blender去建立模型。

一、首先建模生成obj文件

此处大家应用blender生成文字
图片 24

                break;

何以说webgl生成物体麻烦

我们先稍微比较下中央图形的开创代码

矩形:canvas2D

ctx1.rect(50, 50, 100, 100);
ctx1.fill();

webgl(shader和webgl情状代码忽略)

var aPo = [
    -0.5, -0.5, 0,
    0.5, -0.5, 0,
    0.5, 0.5, 0,
    -0.5, 0.5, 0
];

var aIndex = [0, 1, 2, 0, 2, 3];

webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo), webgl.STATIC_DRAW);
webgl.vertexAttribPointer(aPosition, 3, webgl.FLOAT, false, 0, 0);

webgl.vertexAttrib3f(aColor, 0, 0, 0);

webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint16Array(aIndex), webgl.STATIC_DRAW);

webgl.drawElements(webgl.TRIANGLES, 6, webgl.UNSIGNED_SHORT, 0);

一体化代码地址:

结果:

图片 25

圆:canvas2D

ctx1.arc(100, 100, 50, 0, Math.PI * 2, false);
ctx1.fill();

webgl

var angle;
var x, y;
var aPo = [0, 0, 0];
var aIndex = [];
var s = 1;
for(var i = 1; i <= 36; i++) {
    angle = Math.PI * 2 * (i / 36);
    x = Math.cos(angle) * 0.5;
    y = Math.sin(angle) * 0.5;

    aPo.push(x, y, 0);

    aIndex.push(0, s, s+1);

    s++;
}

aIndex[aIndex.length - 1] = 1; // hack一下

webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo), webgl.STATIC_DRAW);
webgl.vertexAttribPointer(aPosition, 3, webgl.FLOAT, false, 0, 0);

webgl.vertexAttrib3f(aColor, 0, 0, 0);

webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint16Array(aIndex), webgl.STATIC_DRAW);

webgl.drawElements(webgl.TRIANGLES, aIndex.length, webgl.UNSIGNED_SHORT, 0);

完全代码地址:

结果:

图片 26

小结:大家抛开shader中的代码和webgl发轫化情形的代码,开掘webgl比canvas2D便是劳动众多哟。光是三种为主图形就多了如此多行代码,抓其一贯多的缘由正是因为作者们必要顶点新闻。轻巧如矩形大家能够间接写出它的巅峰,可是复杂一点的圆,大家还得用数学方法去变通,显明阻碍了人类文明的升华。
绝比较数学方法调换,若是大家能直接获取顶点消息那应该是最佳的,有未有高速的方法获得极限新闻呢?
有,使用建立模型软件生成obj文件。

Obj文件简单来讲就是包蕴八个3D模型消息的文书,那里新闻包括:顶点、纹理、法线以及该3D模型中纹理所使用的贴图。

上边那几个是三个obj文件的地址:

四、装饰星星

光秃秃的多少个文字肯定不够,所以大家还索要一些点缀,就用多少个点作为星星,分外轻松
注意私下认可渲染webgl.POINTS是方形的,所以我们得在fragment
shader中加工管理一下

JavaScript

precision highp float;   void main() {     float dist =
distance(gl_PointCoord, vec二(0.⑤, 0.伍)); // 总计距离     if(dist <
0.五) {         gl_FragColor = vec4(0.9, 0.9, 0.8, pow((1.0 – dist *
2.0), 3.0));     } else {         discard; // 丢弃     } }

1
2
3
4
5
6
7
8
9
10
precision highp float;
 
void main() {
    float dist = distance(gl_PointCoord, vec2(0.5, 0.5)); // 计算距离
    if(dist < 0.5) {
        gl_FragColor = vec4(0.9, 0.9, 0.8, pow((1.0 – dist * 2.0), 3.0));
    } else {
        discard; // 丢弃
    }
}

5-1贰数据类型判别 

引用待用,修改转发已得到腾讯云授权

结语

亟需关注的是那里自个儿用了别的1对shader,此时就关系到了关于是用四个program
shader依然在同2个shader中采纳if
statements,这两者质量如何,有怎么样分别
此间将身处下一篇webgl相关优化中去说

正文就到此处呀,有标题和建议的伴儿迎接留言一齐座谈~!

1 赞 收藏
评论

图片 27

            case ‘number’:

注:本文适合稍微有点webgl基础的人同学,至少知道shader,知道怎么着画二个物体在webgl画布中

肆、旋转运动等转移

实体全部导入进去,剩下来的职务就是进展转变了,首先我们解析一下有何动画效果
因为大家模拟的是七个大自然,3D文字就像星球同样,有公转和自转;还有正是大家导入的obj文件都是依据(0,0,0)点的,所以大家还亟需把它们举行活动操作
先上宗旨代码~

JavaScript

…… this.angle += this.rotate; // 自转的角度   var s =
Math.sin(this.angle); var c = Math.cos(this.angle);   // 公转相关数据
var gs = Math.sin(globalTime * this.revolution); //
globalTime是大局的时刻 var gc = Math.cos(global提姆e * this.revolution);
    webgl.uniformMatrix4fv(     this.program.uMMatrix, false,
mat4.multiply([             gc,0,-gs,0,             0,1,0,0,
            gs,0,gc,0,             0,0,0,1         ], mat4.multiply(
            [                 1,0,0,0,                 0,1,0,0,
                0,0,一,0,                 this.x,this.y,this.z,一 //
x,y,z是偏移的岗位             ],[                 c,0,-s,0,
                0,1,0,0,                 s,0,c,0,
                0,0,0,1             ]         )     ) );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
……
this.angle += this.rotate; // 自转的角度
 
var s = Math.sin(this.angle);
var c = Math.cos(this.angle);
 
// 公转相关数据
var gs = Math.sin(globalTime * this.revolution); // globalTime是全局的时间
var gc = Math.cos(globalTime * this.revolution);
 
 
webgl.uniformMatrix4fv(
    this.program.uMMatrix, false, mat4.multiply([
            gc,0,-gs,0,
            0,1,0,0,
            gs,0,gc,0,
            0,0,0,1
        ], mat4.multiply(
            [
                1,0,0,0,
                0,1,0,0,
                0,0,1,0,
                this.x,this.y,this.z,1 // x,y,z是偏移的位置
            ],[
                c,0,-s,0,
                0,1,0,0,
                s,0,c,0,
                0,0,0,1
            ]
        )
    )
);

一眼望去uMMatrix(模型矩阵)里面有三个矩阵,为啥有多少个吗,它们的依次有啥须求么?
因为矩阵不满足调换率,所以大家矩阵的位移和旋转的各种13分重要,先平移再旋转和先旋转再平移有如下的差异
(下边图片源于互连网)
先旋转后活动:图片 28
先平移后旋转:图片 29
从图中颇负盛名看出来先旋转后活动是自转,而先平移后旋转是公转
据此大家矩阵的依次一定是 公转 * 平移 * 自转 * 顶点新闻(右乘)
实际矩阵为何如此写可知上1篇矩阵入门小说
那样3个3D文字的八大行星就产生啦

三-11 排除对象数组某个项 

1、首先建模生成obj文件

那边我们应用blender生成文字

[][]())

何以说webgl生成物体麻烦

我们先稍微比较下宗旨图形的开创代码
矩形:
canvas2D

JavaScript

ctx1.rect(50, 50, 100, 100); ctx1.fill();

1
2
ctx1.rect(50, 50, 100, 100);
ctx1.fill();

webgl(shader和webgl遇到代码忽略)

JavaScript

var aPo = [     -0.5, -0.5, 0,     0.5, -0.5, 0,     0.5, 0.5, 0,
    -0.5, 0.5, 0 ];   var aIndex = [0, 1, 2, 0, 2, 3];  
webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo),
webgl.STATIC_DRAW); webgl.vertexAttribPointer(aPosition, 3,
webgl.FLOAT, false, 0, 0);   webgl.vertexAttrib3f(aColor, 0, 0, 0);  
webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint16Array(aIndex),
webgl.STATIC_DRAW);   webgl.drawElements(webgl.TRIANGLES, 6,
webgl.UNSIGNED_SHORT, 0);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var aPo = [
    -0.5, -0.5, 0,
    0.5, -0.5, 0,
    0.5, 0.5, 0,
    -0.5, 0.5, 0
];
 
var aIndex = [0, 1, 2, 0, 2, 3];
 
webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo), webgl.STATIC_DRAW);
webgl.vertexAttribPointer(aPosition, 3, webgl.FLOAT, false, 0, 0);
 
webgl.vertexAttrib3f(aColor, 0, 0, 0);
 
webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint16Array(aIndex), webgl.STATIC_DRAW);
 
webgl.drawElements(webgl.TRIANGLES, 6, webgl.UNSIGNED_SHORT, 0);

完全代码地址:
结果:
图片 30

圆:
canvas2D

JavaScript

ctx1.arc(100, 100, 50, 0, Math.PI * 2, false); ctx1.fill();

1
2
ctx1.arc(100, 100, 50, 0, Math.PI * 2, false);
ctx1.fill();

webgl

JavaScript

var angle; var x, y; var aPo = [0, 0, 0]; var aIndex = []; var s =
1; for(var i = 1; i <= 36; i++) {     angle = Math.PI * 2 * (i /
36);     x = Math.cos(angle) * 0.5;     y = Math.sin(angle) * 0.5;  
    aPo.push(x, y, 0);       aIndex.push(0, s, s+1);       s++; }  
aIndex[aIndex.length – 1] = 1; // hack一下  
webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo),
webgl.STATIC_DRAW); webgl.vertexAttribPointer(aPosition, 3,
webgl.FLOAT, false, 0, 0);   webgl.vertexAttrib3f(aColor, 0, 0, 0);  
webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint16Array(aIndex),
webgl.STATIC_DRAW);   webgl.drawElements(webgl.TRIANGLES,
aIndex.length, webgl.UNSIGNED_SHORT, 0);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var angle;
var x, y;
var aPo = [0, 0, 0];
var aIndex = [];
var s = 1;
for(var i = 1; i <= 36; i++) {
    angle = Math.PI * 2 * (i / 36);
    x = Math.cos(angle) * 0.5;
    y = Math.sin(angle) * 0.5;
 
    aPo.push(x, y, 0);
 
    aIndex.push(0, s, s+1);
 
    s++;
}
 
aIndex[aIndex.length – 1] = 1; // hack一下
 
webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo), webgl.STATIC_DRAW);
webgl.vertexAttribPointer(aPosition, 3, webgl.FLOAT, false, 0, 0);
 
webgl.vertexAttrib3f(aColor, 0, 0, 0);
 
webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer());
webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint16Array(aIndex), webgl.STATIC_DRAW);
 
webgl.drawElements(webgl.TRIANGLES, aIndex.length, webgl.UNSIGNED_SHORT, 0);

总体代码地址:
结果:
图片 31

总括:大家抛开shader中的代码和webgl初叶化情形的代码,开采webgl比canvas二D正是劳动众多啊。光是二种为主图形就多了这么多行代码,抓其一直多的由来就是因为我们供给顶点新闻。轻松如矩形我们得以一贯写出它的极限,不过复杂一点的圆,大家还得用数学方法去变通,明显阻碍了人类文明的发展。
相相比数学方法生成,假使大家能一贯获取顶点消息那应该是最棒的,有没有高效的点子获得极限音讯吗?
有,使用建立模型软件生成obj文件。

Obj文件轻松的话就是富含一个3D模子音信的公文,这里消息包蕴:顶点、纹理、法线以及该3D模型中纹理所使用的贴图
上面这些是三个obj文件的地点:

世家在付出的时候理应精通,有诸多大面积的实例操作。比如数组去重,关键词高亮,打乱数组等。那一个操作,代码一般不会过多,完结的逻辑也不会很难,下边包车型大巴代码,笔者表明就不解释太多了,打上注释,相信我们就会懂了。不过,用的地方会相比,假使项目有哪个地点供给用,如果重新写的话,就是代码沉余,开荒功用也不用,复用基本就是复制粘贴!那样是贰个很不佳的习贯,大家能够思索一下把部分广阔的操作封装成函数,调用的时候,直接调用就好!

4、旋转运动等转移

实体全体导入进去,剩下来的职务便是进展改动了,首先大家分析一下有何动画效果

因为我们模拟的是二个星体,3D文字就像星球同样,有公转和自转;还有就是大家导入的obj文件都以根据(0,0,0)点的,所以大家还索要把它们举办运动操作

先上大旨代码

......
this.angle += this.rotate; // 自转的角度

var s = Math.sin(this.angle);
var c = Math.cos(this.angle);

// 公转相关数据
var gs = Math.sin(globalTime * this.revolution); // globalTime是全局的时间
var gc = Math.cos(globalTime * this.revolution);

webgl.uniformMatrix4fv(
    this.program.uMMatrix, false, mat4.multiply([
            gc,0,-gs,0,
            0,1,0,0,
            gs,0,gc,0,
            0,0,0,1
        ], mat4.multiply(
            [
                1,0,0,0,
                0,1,0,0,
                0,0,1,0,
                this.x,this.y,this.z,1 // x,y,z是偏移的位置
            ],[
                c,0,-s,0,
                0,1,0,0,
                s,0,c,0,
                0,0,0,1
            ]
        )
    )
);

1眼望去uMMatrix(模型矩阵)里面有多个矩阵,为啥有五个吗,它们的相继有如何供给么?
因为矩阵不满意交流率,所以我们矩阵的运动和旋转的依次十一分重中之重,先平移再旋转和先旋转再平移有如下的差别

(上面图片来源于互联网)

先旋转后移动:图片 32

先平移后旋转:图片 33

从图中鲜明看出来先旋转后活动是自转,而先平移后旋转是公转

因而我们矩阵的顺序一定是 公转 × 平移 × 自转 × 顶点新闻(右乘)

具体矩阵为啥如此写可知上1篇矩阵入门小说

那样二个3D文字的八大行星就变成啦

3、将obj中数量真正的行使3D对象中去

JavaScript

Text3d.prototype.addFace = function(data) {
    this.addIndex(+data[1], +data[4], +data[7], +data[10]);
    this.addUv(+data[2], +data[5], +data[8], +data[11]);
    this.addNormal(+data[3], +data[6], +data[9], +data[12]); };
  Text3d.prototype.addIndex = function(a, b, c, d) {     if(!d) {
        this.index.push(a, b, c);     } else {
        this.index.push(a, b, c, a, c, d);     } };  
Text3d.prototype.addNormal = function(a, b, c, d) {     if(!d) {
        this.normal.push(             3 * this.normalArr[a], 3 *
this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,             3 *
this.normalArr[b], 3 * this.normalArr[b] + 1, 3 *
this.normalArr[b] + 2,             3 * this.normalArr[c], 3 *
this.normalArr[c] + 1, 3 * this.normalArr[c] + 2         );     }
else {         this.normal.push(             3 * this.normalArr[a], 3
* this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,             3
* this.normalArr[b], 3 * this.normalArr[b] + 1, 3 *
this.normalArr[b] + 2,             3 * this.normalArr[c], 3 *
this.normalArr[c] + 1, 3 * this.normalArr[c] + 2,             3 *
this.normalArr[a], 3 * this.normalArr[a] + 1, 3 *
this.normalArr[a] + 2,             3 * this.normalArr[c], 3 *
this.normalArr[c] + 1, 3 * this.normalArr[c] + 2,             3 *
this.normalArr[d], 3 * this.normalArr[d] + 1, 3 *
this.normalArr[d] + 2         );     } };   Text3d.prototype.addUv =
function(a, b, c, d) {     if(!d) {         this.uv.push(2 *
this.uvArr[a], 2 * this.uvArr[a] + 1);         this.uv.push(2 *
this.uvArr[b], 2 * this.uvArr[b] + 1);         this.uv.push(2 *
this.uvArr[c], 2 * this.uvArr[c] + 1);     } else {
        this.uv.push(2 * this.uvArr[a], 2 * this.uvArr[a] + 1);
        this.uv.push(2 * this.uvArr[b], 2 * this.uvArr[b] + 1);
        this.uv.push(2 * this.uvArr[c], 2 * this.uvArr[c] + 1);
        this.uv.push(2 * this.uvArr[d], 2 * this.uvArr[d] + 1);
    } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Text3d.prototype.addFace = function(data) {
    this.addIndex(+data[1], +data[4], +data[7], +data[10]);
    this.addUv(+data[2], +data[5], +data[8], +data[11]);
    this.addNormal(+data[3], +data[6], +data[9], +data[12]);
};
 
Text3d.prototype.addIndex = function(a, b, c, d) {
    if(!d) {
        this.index.push(a, b, c);
    } else {
        this.index.push(a, b, c, a, c, d);
    }
};
 
Text3d.prototype.addNormal = function(a, b, c, d) {
    if(!d) {
        this.normal.push(
            3 * this.normalArr[a], 3 * this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,
            3 * this.normalArr[b], 3 * this.normalArr[b] + 1, 3 * this.normalArr[b] + 2,
            3 * this.normalArr[c], 3 * this.normalArr[c] + 1, 3 * this.normalArr[c] + 2
        );
    } else {
        this.normal.push(
            3 * this.normalArr[a], 3 * this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,
            3 * this.normalArr[b], 3 * this.normalArr[b] + 1, 3 * this.normalArr[b] + 2,
            3 * this.normalArr[c], 3 * this.normalArr[c] + 1, 3 * this.normalArr[c] + 2,
            3 * this.normalArr[a], 3 * this.normalArr[a] + 1, 3 * this.normalArr[a] + 2,
            3 * this.normalArr[c], 3 * this.normalArr[c] + 1, 3 * this.normalArr[c] + 2,
            3 * this.normalArr[d], 3 * this.normalArr[d] + 1, 3 * this.normalArr[d] + 2
        );
    }
};
 
Text3d.prototype.addUv = function(a, b, c, d) {
    if(!d) {
        this.uv.push(2 * this.uvArr[a], 2 * this.uvArr[a] + 1);
        this.uv.push(2 * this.uvArr[b], 2 * this.uvArr[b] + 1);
        this.uv.push(2 * this.uvArr[c], 2 * this.uvArr[c] + 1);
    } else {
        this.uv.push(2 * this.uvArr[a], 2 * this.uvArr[a] + 1);
        this.uv.push(2 * this.uvArr[b], 2 * this.uvArr[b] + 1);
        this.uv.push(2 * this.uvArr[c], 2 * this.uvArr[c] + 1);
        this.uv.push(2 * this.uvArr[d], 2 * this.uvArr[d] + 1);
    }
};

此处我们着想到包容obj文件中f(ace)行中多少个值的情况,导出obj文件中得以强行采纳只有三角面,不过大家在代码中相称一下比较安妥

2-1二搜索最长单词

具体贯彻

教你用webgl火速创制2个小世界

2017/03/25 · HTML5 ·
AlloyTeam

初稿出处:
AlloyTeam   

Webgl的吸重力在于能够创立多少个自个儿的3D世界,但绝相比较canvas二D来讲,除了物体的移位旋转换换完全正视矩阵增添了复杂度,就连生成三个实体都变得很复杂。

怎么样?!为何不用Threejs?Threejs等库确实可以十分大程度的加强支付作用,而且各方面封装的极度棒,不过不引入初专家直接注重Threejs,最棒是把webgl各方面都学会,再去拥抱Three等相关库。

上篇矩阵入门中牵线了矩阵的基本知识,让我们探听到了基本的仿射调换矩阵,可以对实体举办活动旋转等变化,而那篇小说将教大家快捷生成二个实体,并且结合转变矩阵在物体在你的社会风气里动起来。

注:本文适合稍微有点webgl基础的人同学,至少知道shader,知道怎么样画贰个实体在webgl画布中

>need-to-insert-img


//删除值为’val’的数组成分//ecDo.removeArrayForValue([‘test’,’test1′,’test2′,’test’,’aaa’],’test’,’)

4、装饰星星

光秃秃的多少个文字料定不够,所以大家还索要或多或少点缀,就用多少个点作为星星,万分轻便

注意默许渲染webgl.POINTS是方形的,所以大家得在fragment
shader中加工处理一下

precision highp float;

void main() {
    float dist = distance(gl_PointCoord, vec2(0.5, 0.5)); // 计算距离
    if(dist < 0.5) {
        gl_FragColor = vec4(0.9, 0.9, 0.8, pow((1.0 - dist * 2.0), 3.0));
    } else {
        discard; // 丢弃
    }
}

4.基础DOM操作

怎样?!为啥不用Threejs?Threejs等库确实能够异常的大程度的加强开销效用,而且各方面封装的好厉害,可是不引入初我们间接依赖Threejs,最佳是把webgl各方面都学会,再去拥抱Three等相关库。

                _str = _str.replace(/[0-9]/g, _restr);

二、读取分析obj文件

var regex = { // 这里正则只去匹配了我们obj文件中用到数据
    vertex_pattern: /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, // 顶点
    normal_pattern: /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, // 法线
    uv_pattern: /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, // 纹理坐标
    face_vertex_uv_normal: /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, // 面信息
    material_library_pattern: /^mtllib\s+([\d|\w|\.]+)/, // 依赖哪一个mtl文件
    material_use_pattern: /^usemtl\s+([\S]+)/
};

function loadFile(src, cb) {
    var xhr = new XMLHttpRequest();

    xhr.open('get', src, false);

    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4) {

            cb(xhr.responseText);
        }
    };

    xhr.send();
}

function handleLine(str) {
    var result = [];
    result = str.split('\n');

    for(var i = 0; i < result.length; i++) {
        if(/^#/.test(result[i]) || !result[i]) { // 注释部分过滤掉
            result.splice(i, 1);

            i--;
        }
    }

    return result;
}

function handleWord(str, obj) {
    var firstChar = str.charAt(0);
    var secondChar;
    var result;

    if(firstChar === 'v') {

        secondChar = str.charAt(1);

        if(secondChar === ' ' && (result = regex.vertex_pattern.exec(str)) !== null) {
            obj.position.push(+result[1], +result[2], +result[3]); // 加入到3D对象顶点数组
        } else if(secondChar === 'n' && (result = regex.normal_pattern.exec(str)) !== null) {
            obj.normalArr.push(+result[1], +result[2], +result[3]); // 加入到3D对象法线数组
        } else if(secondChar === 't' && (result = regex.uv_pattern.exec(str)) !== null) {
            obj.uvArr.push(+result[1], +result[2]); // 加入到3D对象纹理坐标数组
        }

    } else if(firstChar === 'f') {
        if((result = regex.face_vertex_uv_normal.exec(str)) !== null) {
            obj.addFace(result); // 将顶点、发现、纹理坐标数组变成面
        }
    } else if((result = regex.material_library_pattern.exec(str)) !== null) {
        obj.loadMtl(result[1]); // 加载mtl文件
    } else if((result = regex.material_use_pattern.exec(str)) !== null) {
        obj.loadImg(result[1]); // 加载图片
    }
}

代码宗旨的地方都进展了讲授,注意那里的正则只去相配大家obj文件中蕴藏的字段,其余消息未有去相配,借使有对obj文件全体望带有的音讯成功相称的校友可以去看下Threejs中objLoad部分源码

2-1叁句中单词首字母大写 

作者:TAT.vorshen

//那壹块的包裹,重假设本着数字类型的数组//求和sumArr:function(arr)
{returnarr.reduce(function(pre, cur) {returnpre + cur   
})}//数组平均值,小数点大概会有数不胜数位,那里不做管理,管理了选择就不灵便!covArr:function(arr)
{returnthis.sumArr(arr) / arr.length;},

上篇矩阵入门中介绍了矩阵的基本知识,让大家驾驭到了基本的仿射转变矩阵,能够对实体举办活动旋转等生成,而这篇小说将教大家快快速生成成3个实体,并且结合转换矩阵在实体在你的世界里动起来。

                _str = _str.replace(pattern, _restr);

Webgl的吸重力在于可以创立一个融洽的3D世界,但相相比较canvas二D来说,除了物体的活动旋转换换完全依附矩阵增添了复杂度,就连生成二个实体都变得很复杂。

//ecDo.getArrayNum([0,1,2,3,4,5,6,7,8,9],5,9)//result:[5, 6, 7, 8,
9]//getArrayNum([0,1,2,3,4,5,6,7,8,9],二)
//不传第二个参数,暗许重临从n壹到数组停止的成分//result:[2, 3, 4, 5, 6,
7, 8, 9]getArrayNum:function(arr, n1, n2) {returnarr.slice(n1, n2);}

//result:[“aaa”]  带有’test’的都剔除

5-8适配rem

                break;

二-三字符串循环复制

removeClass:function(obj, classStr) {if((this.istype(obj,’array’) ||
this.istype(obj,’elements’)) && obj.length > 1) {for(var i = 0, len =
obj.length; i < len; i++) {if(this.hasClass(obj[i], classStr)) {   
            var reg = new RegExp(‘(\\s|^)’+ classStr +'(\\s|$)’);   
            obj[i].className = obj[i].className.replace(reg,”);   
        }        }    }else{if(this.hasClass(obj, classStr)) {         
  var reg = new RegExp(‘(\\s|^)’+ classStr +'(\\s|$)’);           
obj.className = obj.className.replace(reg,”);        }    }}

本身要好包装那个,并不是笔者有造轮子的习贯,而是:

                _str = _str.replace(/[\u4E00-\u9FA5]/g, _restr);

或者有同伴会有失水准,那样封装,调用有点劳碌,为啥不直接在原型下边封装,调用方便。比方上面包车型地铁板栗!

伍-二消除对象中值为空的属性

//ecDo.siblings(obj,’#id’)siblings:function(obj, opt) {    var a =
[]; //定义3个数组,用来存o的弟兄成分    var p =
obj.previousSibling;while(p) { //先取o的父兄们
判别有没有上2个小弟成分,若是有则往下实行p表示previousSiblingif(p.nodeType === 一) {            a.push(p);       
}        p = p.previousSibling //最后把上叁个节点赋给p    }   
a.reverse() //把顺序反转一下 这样成分的次第便是按程序的了    var n =
obj.nextSibling; //再取o的兄弟while(n) { //判别有未有下一个兄弟结点
n是nextSibling的情致if(n.nodeType === 一) {            a.push(n);       
}        n = n.nextSibling;    }if(opt) {        var _opt =
opt.substr(1);        var b =
[];//定义四个数组,用于积存过滤a的数组if(opt[0] ===’.’) {           
b = a.filter(function(item) {returnitem.className === _opt           
});        }elseif(opt[0] ===’#’) {            b =
a.filter(function(item) {returnitem.id === _opt            });       
}else{            b = a.filter(function(item)
{returnitem.tagName.toLowerCase() === opt            });       
}returnb;    }returna;}

                        _regText += _spstr[j];

2-一去除字符串空格

                _str = _str.replace(/<\/?[^>]*>/g,
_restr);

2-5替换* 

//cookie//设置cookiesetCookie:function(name, value, iDay) {    var oDate
= new Date();    oDate.setDate(oDate.getDate() + iDay);   
document.cookie = name +’=’+ value +’;expires=’+
oDate;},//获取cookiegetCookie:function(name) {    var arr =
document.cookie.split(‘; ‘);for(var i = 0; i < arr.length; i++) {   
    var arr2 = arr[i].split(‘=’);if(arr2[0] == name)
{returnarr2[1];        }   
}return”;},//删除cookieremoveCookie:function(name) {   
this.setCookie(name, 1, -1);},

                _str =
_str.replace(/[^\u4e00-\u9fa5|\u0000-\u00ff|\u3002|\uFF1F|\uFF01|\uff0c|\u3001|\uff1b|\uff1a|\u3008-\u300f|\u2018|\u2019|\u201c|\u201d|\uff08|\uff09|\u2014|\u2026|\u2013|\uff0e]/g,
_restr);

//ecDo.randomOne([1,2,3,6,8,5,4,2,6])//2//ecDo.randomOne([1,2,3,6,8,5,4,2,6])//8//ecDo.randomOne([1,2,3,6,8,5,4,2,6])//8//ecDo.randomOne([1,2,3,6,8,5,4,2,6])//1randomOne:function(arr)
{returnarr[Math.floor(Math.random() * arr.length)];}

三-1三 数组扁平化

1.前言

                break;

//

addClass:function(obj, classStr) {if((this.istype(obj,’array’) ||
this.istype(obj,’elements’)) && obj.length >= 1) {for(var i = 0, len
= obj.length; i < len; i++) {if(!this.hasClass(obj[i], classStr))
{                obj[i].className +=” “+ classStr;            }       
}    }else{if(!this.hasClass(obj, classStr)) {            obj.className
+=” “+ classStr;        }    }}

/*type一:首字母大写 二:首页母小写 三:大小写转变 肆:全体大写 伍:全体小写
*
*///ecDo.changeCase(‘asdasd’,1)//result:AsdasdchangeCase:function(str,type)
{functionToggleCase(str) {        var itemText
=””str.split(“”).forEach(function(item) {if(/^([a-z]+)/.test(item)) { 
                  itemText += item.toUpperCase();               
}elseif(/^([A-Z]+)/.test(item)) {                    itemText +=
item.toLowerCase();                }else{                    itemText +=
item;                }            });returnitemText;    }    switch
(type) {case1:returnstr.replace(/\b\w+\b/g,function(word)
{returnword.substring(0, 1).toUpperCase() +
word.substring(1).toLowerCase();           
});case2:returnstr.replace(/\b\w+\b/g,function(word)
{returnword.substring(0, 1).toLowerCase() +
word.substring(1).toUpperCase();           
});case3:returnToggleCase(str);case4:returnstr.toUpperCase();case5:returnstr.toLowerCase(); 
      default:returnstr;    }}

图片 34

                for (var j = 0, len1 = _spstr.length; j < len1; j++)
{

肆-3去除类名

//var
arr=[{a:1,b:2,c:9},{a:2,b:3,c:5},{a:5,b:9},{a:4,b:2,c:5},{a:4,b:5,c:7}]//ecDo.filterOptionArray(arr,’a’)//result:[{b:2,c:9},{b:3,c:5},{b:9},{b:2,c:5},{b:5,c:7}]//ecDo.filterOptionArray(arr,’a,c’)//result:[{b:2},{b:3},{b:9},{b:2},{b:5}]filterOptionArray:function(arr,
keys) {    var newArr = []    var _keys = keys.split(‘,’), newArrOne
= {};for(var i = 0, len = arr.length; i < len; i++) {       
newArrOne = {};for(var keyinarr[i]) {           
//如若key不存在排除keys里面,增多数码if(_keys.indexOf(key) === -1) {   
            newArrOne[key] = arr[i][key];            }        }   
    newArr.push(newArrOne);    }returnnewArr}

因此建议的包裹姿势是

                }

源码都坐落github上了,我们想未来现在有啥修改只怕扩张的,迎接大家来star一下ec-do。

相关文章

发表评论

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

网站地图xml地图