教你用webgl快速创建一个小世界

时间:2019-11-26 15:58来源:澳洲幸运10平台
教你用webgl神速创立贰个小世界 2017/03/25 · HTML5 ·AlloyTeam 原稿出处:AlloyTeam    Webgl的吸重力在于能够创制多个友好的3D世界,但相相比canvas2D来讲,除了物体的位移旋调换换完全信任矩

教你用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(global提姆e * 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

编辑:澳洲幸运10平台 本文来源:教你用webgl快速创建一个小世界

关键词: