新浦京娱乐场官网-301net-新浦京娱乐www.301net
做最好的网站

但是不推荐初学者直接依赖Threejs

教你用webgl快捷创立三个小世界

2017/03/25 · HTML5 · AlloyTeam

原稿出处: AlloyTeam   

Webgl的魔力在于能够创建一个和好的3D世界,但相相比较canvas2D来讲,除了物体的运动旋调换换完全信任矩阵增添了复杂度,就连生成三个物体都变得很复杂。

什么样?!为啥不用Threejs?Threejs等库确实可以十分大程度的增进开垦功能,何况各地方封装的不得了棒,不过不推荐初读书人直接正视Threejs,最佳是把webgl各个地区面都学会,再去拥抱Three等相关库。

上篇矩阵入门中牵线了矩阵的基本知识,让大家探听到了主旨的仿射转换矩阵,能够对实体进行运动旋转等生成,而那篇文章将教大家快速生成贰个实体,并且结合转换矩阵在物体在您的社会风气里动起来。

注:本文符合稍稍有一点webgl幼功的人同学,最少知道shader,知道什么画三个物体在webgl画布中

为啥说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);

全体代码地址:
结果:
图片 1

圆:
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);

完整代码地址:
结果:
图片 2

小结:大家抛开shader中的代码和webgl初叶化遭遇的代码,发现webgl比canvas2D就是劳累众多啊。光是二种为主图形就多了这样多行代码,抓其根本多的来由正是因为咱俩要求顶点新闻。简单如矩形大家能够直接写出它的极端,不过复杂一点的圆,大家还得用数学方法去变通,分明阻碍了人类文明的腾飞。
相相比较数学方法生成,固然我们能一贯拿走极点消息那应该是最佳的,有未有神速的法子得到极限新闻吗?
有,使用建立模型软件生成obj文件。

Obj文件简单来说正是带有二个3D模型新闻的文本,这里新闻富含:顶点、纹理、法线以至该3D模型中纹理所使用的贴图
下边那一个是三个obj文件的地址:

轻便深入分析一下以此obj文件

图片 3
前两行见到#标志就精通这几个是注释了,该obj文件是用blender导出的。Blender是豆蔻梢头款很好用的建立模型软件,最重要的它是无偿的!

图片 4
Mtllib(material library)指的是该obj文件所利用的材料库文件(.mtl)
单独的obj生成的模子是白模的,它只包括纹理坐标的新闻,但未有贴图,有纹理坐标也没用

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

图片 6
Usemtl 使用材料库文件中实际哪二个材质

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

此间超过八分之四也都以我们这一个常用的性质了,还也是有局地别样的,这里就相当少说,能够google搜一下,超级多介绍很详细的随笔。
若果有了obj文件,那我们的干活也正是将obj文件导入,然后读取内容还要按行拆解剖判就足以了。
先放出最终的结果,七个效仿银系的3D文字效果。
在线地址查看:

在这里间顺便说一下,2D文字是能够通过深入分析获得3D文字模型数据的,将文字写到canvas上今后读取像素,获取路线。大家这里未有使用该办法,因为就算这么辩护上其它2D文字都能转3D,还是能做出相符input输入文字,3D呈现的意义。不过本文是教咱们火速搭建一个小世界,所以大家依旧使用blender去建立模型。

切切实实落到实处

1、首先建模生成obj文件

那边我们使用blender生成文字
图片 8

2、读取解析obj文件

JavaScript

var regex = { // 那太守则只去相配了我们obj文件中用到数码     vertex_pattern: /^vs ([d|.| |-|e|E] )s ([d|.| |-|e|E] )s ([d|.| |-|e|E] )/, // 顶点     normal_pattern: /^vns ([d|.| |-|e|E] )s ([d|.| |-|e|E] )s ([d|.| |-|e|E] )/, // 法线     uv_pattern: /^vts ([d|.| |-|e|E] )s ([d|.| |-|e|E] )/, // 纹理坐标     face_vertex_uv_normal: /^fs (-?d )/(-?d )/(-?d )s (-?d )/(-?d )/(-?d )s (-?d )/(-?d )/(-?d )(?:s (-?d )/(-?d )/(-?d ))?/, // 面信息     material_library_pattern: /^mtllibs ([d|w|.] )/, // 注重哪贰个mtl文件     material_use_pattern: /^usemtls ([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]); // 加载图片     } }

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: /^vs ([d|.| |-|e|E] )s ([d|.| |-|e|E] )s ([d|.| |-|e|E] )/, // 顶点
    normal_pattern: /^vns ([d|.| |-|e|E] )s ([d|.| |-|e|E] )s ([d|.| |-|e|E] )/, // 法线
    uv_pattern: /^vts ([d|.| |-|e|E] )s ([d|.| |-|e|E] )/, // 纹理坐标
    face_vertex_uv_normal: /^fs (-?d )/(-?d )/(-?d )s (-?d )/(-?d )/(-?d )s (-?d )/(-?d )/(-?d )(?:s (-?d )/(-?d )/(-?d ))?/, // 面信息
    material_library_pattern: /^mtllibs ([d|w|.] )/, // 依赖哪一个mtl文件
    material_use_pattern: /^usemtls ([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对象中去

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)行中4个值的状态,导出obj文件中得以强行选拔唯有三角面,但是大家在代码中非常一下比较妥帖

4、旋转运动等转移

实体全体导入进去,剩下来的任务便是拓宽转变了,首先我们深入深入分析一下有何动漫效果
因为我们模拟的是多个宇宙,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(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
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(模型矩阵)里面有八个矩阵,为何有八个吗,它们的依次有哪些要求么?
因为矩阵不满足交流率,所以大家矩阵的运动和旋转的次第十一分重视,先平移再旋转和先旋转再平移犹如下的差异
(下面图片来源于网络)
先旋转后移动:图片 9
先平移后旋转:图片 10
从图中明显看出来先旋转后活动是自转,而先平移后旋转是公转
进而大家矩阵的依次一定是 公转 * 平移 * 自转 * 极点音讯(右乘)
实际矩阵为啥如此写可以预知上生机勃勃篇矩阵入门小说
那样二个3D文字的8大行星就产生啦

4、装饰星星

光秃秃的多少个文字鲜明缺乏,所以我们还亟需一些点缀,就用多少个点作为星星,非常轻松
注意暗中同意渲染webgl.POINTS是方形的,所以大家得在fragment shader中加工管理一下

JavaScript

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; // 丢弃     } }

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; // 丢弃
    }
}

结语

亟待关怀的是这里本身用了别的大器晚成对shader,那个时候就提到到了有关是用多个program shader照旧在同多个shader中应用if statements,这两侧品质如何,有怎么着差距
那边将位于下大器晚成篇webgl相关优化中去说

本文就到此处呀,有标题和提出的同伙接待留言一同座谈~!

1 赞 收藏 评论

图片 11

本文由新浦京娱乐场官网-301net-新浦京娱乐www.301net发布于301net网站建设,转载请注明出处:但是不推荐初学者直接依赖Threejs

您可能还会对下面的文章感兴趣: