WebGL学习(3) – 3D模型语言

那篇小说首假使上学一下JavaScript中的难题——原型和原型链

  原来的小说地址:WebGL学习(3) –
3D模型

  相信广大人是以成立逼真酷炫的三维效果为对象而读书webGL的吗,首先笔者正是。作者主宰了足足的webGL技巧后,正准备大展身手时,遭受了一种处境狼狈的图景:依然做不出想要的东西。为何呢,因为从没3D模型可供操作啊,纯粹用代码创设复杂的3D模型完全不行想像。那必须采取3d马克斯,maya,以及开源的blender等建立模型软件拓展营造。既然已经入了webGL的坑了,那也只可以硬着头皮继续读书3D建立模型,断断续续学了3个多月的blender教程,总算入门了。
  那节重要学习怎么样导入模型文件,然后用代码应用效率,操作模型。首先体现下本人的大小说,喷火战斗机的3D模型:webGL
喷火战斗机

语言 1

自定义2个指标

我们上学一门编制程序语言,必然要使用它形成都部队分特定的效应,而面向对象的言语因为符合人类的咀嚼规律,在那方面做得很好,前天自小编以JS为例,探索一下JS分裂于其余面向对象的语言的地点——-原型和原型链

  • 率先,假使你在做2个品类,要造3个新的对象,标准Curry面没有。那您只能用构造函数去组织3个

function Person(){
  //构造函数
}

以上Person便是八个构造函数,能够用来扭转小明 小红 等等人类的实例。

var person = new Person() //构造出一个对象
person.name = 'xiaoming'
console.log(person.name) // 'xiaoming'

那原型在哪吧,认识原型先认识一下prototype属性

剧情大纲

  1. 模型文件
  2. 着色器
  3. 光照
  4. 模型变换
  5. 事件处理

prototype

先看一段代码

Person.prototype.name = 'god'
var person2 = new Person()
console.log(person2.name) //person2的名字是啥呢???

从上边三行代码,猜一猜person2的名字是啥,没错,正是god

person2没有和谐显著名字,不过Person构造函数的prototype上绑定了name,所以由Person构造函数构造的实例对象都私下认可有2个god的名字。

  • 再就是以此prototype属性只设有构造函数上,也正是说prototype是构造函数的属性!!!
  • 那这一个prototype指向了何地,那几个地方便是——–调用构造函数而变更的目的实例的原型,存的是以此原型的地点。

构造函数和原型的关系

以上正是构造函数和原型之间的关系,构造函数内部的prototype属性指向了实例对象原型的地方。

  • 原型里面存的是颇具实例化对象的共有属性,比如那一个例子的name

Person实例原型的剧情

地方红框是Person实例的原型,若是不直观的话,下边直接看Array实例的原型

Array实例的原型

红框的都以豪门耳熟能详的数组的法子吗,他们都坐落数组的共有属性之中。


地点的两幅原型图里面,大家居然发现有共同点,都有贰个耳熟能详 constructor
属性
,待会研究一下这一个天性。


后天,我们早就知道了构造函数和原型的涉嫌了,那person
person2这一个实例对象和原型有吗关系吧

模型文件

  blender导出的模子文件plane.obj,
同时还包涵材料文件plane.mtl。模型包涵2800五个极点,2200四个面,共200多k的体量,内容相比较大,所以只可以将文件加载入html文件比较方便。
  那怎么加载呢?一般会选择ajax获取,但本身那边有更便利的措施。那就是将模型文件内容预编写翻译直出到html中,那样不光升高了加载质量,开发也更便宜。具体可参照作者事先的篇章:前端急迅支付模版
  那里运用我后面包车型客车开支模版,
将模型(obj、mtl)文件以字符串的样式写入text/template模版中,同时将GLSL语言写的着色器也预编写翻译到html中。到时用gulp的下令构建页面,全数情节就会自动生成到页面中,html部分的代码如下所示:

    {% extends '../layout/layout.html' %}
    {% block title %}spitfire fighter{% endblock %}
    {% block js %}
    <script src="./lib/webgl.js"></script>
    <script src="./lib/objParse.js"></script>
    <script src="./lib/matrix.js"></script>
    <script src="./js/index.js"></script>
    {% endblock %}
    {% block content %}
    <div class="content">
    <p>上下左右方向键 调整视角,W/S/A/D键 旋转模型, +/-键 放大缩小</p>
    <canvas id="canvas" width="800" height="600"></canvas>
    </div>
    <!-- obj文件 -->
    <script type="text/template" id="tplObj">
    {% include '../model/plane.obj' %}
    </script>
    <!-- mtl文件 -->
    <script type="text/template" id="tplMtl">
    {% include '../model/plane.mtl' %}
    </script>
    <!-- 顶点着色器 -->
    <script type="x-shader/x-vertex" id="vs">
    {% include '../glsl/vs.glsl' %}
    </script>
    <!-- 片元着色器 -->
    <script type="x-shader/x-fragment" id="fs">
    {% include '../glsl/fs.glsl' %} 
    </script>
    {% endblock %}

__proto__属性

每二个布局的实例对象,内部有3个__proto__属性,它指向了实例原型,存的是原型的地点。

person.__proto__ === Person.prototype 
true
  • __proto__是指标的性情,而且是浏览器强逼着ECMAScript添加的这一个正式。

person

以上是构造函数、实例、实例原型之间的涉嫌,不过方向是单向的,哪能还是不能够让它循环起来吧,原型可以还是不可以本着构造函数恐怕实例呢?

obj文件

  obj文件包括的是模型的顶点法线索引等新闻。那里以最简便易行的立方体为例。

  • v 几何体顶点
  • vt 贴图坐标点
  • vn 顶点法线
  • f 面:顶点索引 / 纹理坐标索引 / 法线索引
  • usemtl 使用的品质名称

    # Blender v2.79 (sub 0) OBJ File: ''
    # www.blender.org
    mtllib cube.mtl
    o Cube
    v -0.442946 -1.000000 -1.000000
    v -0.442946 -1.000000 1.000000
    v -2.442946 -1.000000 1.000000
    v -2.442945 -1.000000 -1.000000
    v -0.442945 1.000000 -0.999999
    v -0.442946 1.000000 1.000001
    v -2.442946 1.000000 1.000000
    v -2.442945 1.000000 -1.000000
    vn 0.0000 -1.0000 0.0000
    vn 0.0000 1.0000 0.0000
    vn 1.0000 0.0000 0.0000
    vn -0.0000 -0.0000 1.0000
    vn -1.0000 -0.0000 -0.0000
    vn 0.0000 0.0000 -1.0000
    usemtl Material
    s off
    f 1//1 2//1 3//1 4//1
    f 5//2 8//2 7//2 6//2
    f 1//3 5//3 6//3 2//3
    f 2//4 6//4 7//4 3//4
    f 3//5 7//5 8//5 4//5
    f 5//6 1//6 4//6 8//6

constructor

  • 还记得下边大家发现的老大 不一致的原型 都有的2个联手的属性
    constructor嘛

构造函数、实例、实例原型之间的涉嫌的来头能够循环的重要就在此间了。大家直接叫构造函数,构造函数的,为何那样叫呢,对,就是那么些原型里面包车型地铁constructor属性。

  • 可是原型是无能为力指向实例的,只好经过constructor质量指向 构造函数

Person === Person.prototype.constructor
true

construcor属性

上边正是经典的铁三角了。

mtl文件

  mtl文件包括的是模型的质感新闻

  • Ka 环境色 rgb
  • Kd 漫反射色,材料颜色 rgb
  • Ks 高光色,材质高光颜色 rgb
  • Ns 反射高光度 钦命材料的反射指数
  • Ni 折射值 钦定材质表面包车型大巴光密度
  • d 透明度

    # Blender MTL File: 'None'
    # Material Count: 1

    newmtl Material
    Ns 96.078431
    Ka 1.000000 1.000000 1.000000
    Kd 0.640000 0.640000 0.640000
    Ks 0.500000 0.500000 0.500000
    Ke 0.000000 0.000000 0.000000
    Ni 1.000000
    d 1.000000
    illum 2

  知道了obj和mtl文件的格式,大家须要做的正是读取它们,逐行分析,那里运用的objParse读取解析,想驾驭在那之中原理,能够查看源代码,那里不详述。
  提取出需求的音讯后,就可将模型音信写入缓冲区,然后渲染出来。

    var canvas = document.getElementById('canvas'),
        gl = get3DContext(canvas, true),
        objElem = document.getElementById('tplObj'),
        mtlElem = document.getElementById('tplMtl');
    function main() {
        //...

        //获取变量地址
        var program = gl.program;
        program.a_Position = gl.getAttribLocation(gl.program, 'a_Position');
        //...

        // 创建空数据缓冲
        var vertexBuffer = createEmptyArrayBuffer(gl, program.a_Position, 3, gl.FLOAT);
        //...

        // 分析模型字符串
        var objDoc = new OBJDoc('plane',objElem.text,mtlElem.text);
        if(!objDoc.parse(1, false)){return;}
        var drawingInfo = objDoc.getDrawingInfo();

        // 将数据写入缓冲区
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, drawingInfo.vertices, gl.STATIC_DRAW);
        //...
    }

由上述文化得出二个小公式

对象.__proto__ === 构造函数.prototype

而Person这些构造函数也是目的,那么

Person.__proto__ === ???

地点的问号填什么吧,大家按公式填空,应该是构造函数.prototype,Person构造函数的构造函数是哪个人呢?没错,就是Function

Person.__proto__ === Function.prototype
true

在控制台验证确实如此。

之所以有个别从前的可疑也解开了

Array.__proto__ === Function.prototype
true
String.__proto__ === Function.prototype
true
  • 这难点又来了,构造函数.prototype也是目的啊,它指向哪个人
  • 既然如此是目的,那么内部就有__proto__属性

Person.prototype.__proto__ === ???

问号填什么吧,原型是由哪个人协会的啊,我们想到了全数目的的根———-Object

着色器

原型的原型

在控制台验证如下

Person.prototype.__proto__ === Object.prototype
true
Array.prototype.__proto__ === Object.prototype
true
String.prototype.__proto__ === Object.prototype
true

既然引出了Object,大家来看一下有所指标的祖辈的原型吧

Object.prototype.__proto__ === null
true

原型的原型

极限着色器

  顶点着色器相比不难,和在此之前的分化相比大的是,把总括颜色光照部分移到了片元着色器,那样能够兑现逐片元光照,效果会更为绘声绘色和自然。

    attribute vec4 a_Position;//顶点位置
    attribute vec4 a_Color;//顶点颜色
    attribute vec4 a_Scolor;//顶点高光颜色
    attribute vec4 a_Normal;//法向量
    uniform mat4 u_MvpMatrix;//mvp矩阵
    uniform mat4 u_ModelMatrix;//模型矩阵
    uniform mat4 u_NormalMatrix;
    varying vec4 v_Color;
    varying vec4 v_Scolor;
    varying vec3 v_Normal;
    varying vec3 v_Position;

    void main() {
        gl_Position = u_MvpMatrix * a_Position;
        // 计算顶点在世界坐标系的位置
        v_Position = vec3(u_ModelMatrix * a_Position);
        // 计算变换后的法向量并归一化
        v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));
        v_Color = a_Color;
        v_Scolor = a_Scolor;
    }

特殊的Function

近日大家看来了Function构造方法构造除了具备的函数,包含普通的构造函数。

那么他本身也是贰个函数,所以也是由Function构造函数构造的。所以由计算的公式能够清楚

Function.__proto__ === Function.prototype

而且,上面那么些很要紧,易错

Function.prototype === Object.__proto__ //哈哈,这个老别扭了吧,还给你倒过来写,很容易错的

表达:Object也是构造函数啊,属于对象。Object构造函数也是由Function把它构造出来的,所以是结果是true

说到底的关联图

光照

  光照相关的盘算首要在片元着色器中,首先科学普及一下光照的相干音讯。

    物体呈现出颜色亮度就是表面的反射光导致,计算反射光公式如下:
    <表面的反射光颜色> = <漫反射光颜色> + <环境反射光颜色> + <镜面反射光颜色>

    1. 其中漫反射公式如下:
    <漫反射光颜色> = <入射光颜色> * <表面基底色> * <光线入射角度>

    光线入射角度可以由光线方向和表面的法线进行点积求得:
    <光线入射角度> = <光线方向> * <法线方向>

    最后的漫反射公式如下:
    <漫反射光颜色> = <入射光颜色> * <表面基底色> * (<光线方向> * <法线方向>)

    2. 环境反射光颜色根据如下公式得到:
    <环境反射光颜色> = <入射光颜色> * <表面基底色>

    3. 镜面(高光)反射光颜色公式,这里使用的是冯氏反射原理
    <镜面反射光颜色> = <高光颜色> * <镜面反射亮度权重> 

    其中镜面反射亮度权重又如下
    <镜面反射亮度权重> = (<观察方向的单位向量> * <入射光反射方向>) ^ 光泽度

总体的下结论

  • 当您new三个构造函数的时候,创建2个函数实例,那么 『
    函数实例.__proto__ === 该构造函数.prototype
  • 持有的函数都以由Function布局出来的,那么
    被构造出来的其他函数.__proto__ === Function.prototype
  • 语言,抱有的构造函数的原型对象都以由Object构造出来的,那么
    所有的构造函数.prototype.__proto__ === Object.prototype

片元着色器

  着色器代码就是对上边公式内容的演绎

    #ifdef GL_ES
    precision mediump float;
    #endif
    uniform vec3 u_LightPosition;//光源位置
    uniform vec3 u_diffuseColor;//漫反射光颜色
    uniform vec3 u_AmbientColor;//环境光颜色
    uniform vec3 u_specularColor;//镜面反射光颜色
    uniform float u_MaterialShininess;// 镜面反射光泽度
    varying vec3 v_Normal;//法向量
    varying vec3 v_Position;//顶点位置
    varying vec4 v_Color;//顶点颜色
    varying vec4 v_Scolor;//顶点高光颜色

    void main() {
        // 对法线归一化
        vec3 normal = normalize(v_Normal);
        // 计算光线方向(光源位置-顶点位置)并归一化
        vec3 lightDirection = normalize(u_LightPosition - v_Position);
        // 计算光线方向和法向量点积
        float nDotL = max(dot(lightDirection, normal), 0.0);
        // 漫反射光亮度
        vec3 diffuse = u_diffuseColor  * nDotL * v_Color.rgb;
        // 环境光亮度
        vec3 ambient = u_AmbientColor * v_Color.rgb;
        // 观察方向的单位向量V
        vec3 eyeDirection = normalize(-v_Position);
        // 反射方向
        vec3 reflectionDirection = reflect(-lightDirection, normal);
        // 镜面反射亮度权重
        float specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), u_MaterialShininess);
        // 镜面高光亮度
        vec3 specular =  v_Scolor.rgb * specularLightWeighting ;
        gl_FragColor = vec4(ambient + diffuse + specular, v_Color.a);
    }

instanceof运算符的五台山真面目

首先那有多少个题

Object instanceof Function
Function instanceof Object
Function instanceof Function
Object instanceof Object
  • 能澄思渺虑的说出去啊,大声告诉小编,答案是哪些。
  • 没错,全是true

虽然 instanceof运算符算是大家的故交了,然而背后是咋判断的啊

标准是这么写的

object instanceof constructor

参数

  • object

    要检查和测试的对象.

  • constructor

    有些构造函数

    instanceof运算符用来检测 constructor.prototype是或不是留存于参数
    object的原型链上

  • 对于 Object instanceof Function
    Object.__proto__ === Function.prototypetrue,解决

  • 对于Function instanceof Object
    Function.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototypetrue,解决。
  • 对于 Function instanceof Function
    Function.__proto__ === Function.prototypetrue,解决
  • 对于Object instanceof Object ,
    Object.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype
    true,解决

借使上面包车型地铁推理,任一环节你写错照旧压根写不出去(在昨天在此之前自身也是瞎搞,运气好了蒙对了),表明您就不是真懂原型链,面试问到稍微变形的题依然易错。


  • 在上边的种种原型的转移中,其实难题就在于Function Object
    构造函数也是对象 原型对象等所有对象都由Object构造

那三个点。

  • 并且看待难题的角度差异,对真情的回味影响极大。比如 Object
    Function
    你把它们作为构造函数大概指标,结果不一样的。不一样的场子,换分裂的角度去认识它们,事物有着两面性。差不多正是背了连年的同一性,巴拉巴拉一堆的历史学原理吧。
  • 各个原型的分析进程,让自家想起起了,被数学支配的害怕。逻辑必须合理、一步是一步、抽丝剥茧看本质,大致是那般长年累月下场教育留在脑子里面包车型客车深深烙印吧。
  • JS越来越有意思了,感觉应该快入门了。

模型变换

  那里先安装光照相关的上马标准,然后是mvp矩阵变换和法向量矩阵相关的盘算,具体知识点可参看在此之前的篇章*[WebGL学习(2)

  要留意的是逆袭置矩阵,首要用以总括模型变换之后的法向量,有了转移后的法向量才能科学总结光照。

     求逆转置矩阵步骤
        1.求原模型矩阵的逆矩阵
        2.将逆矩阵转置

    <变换后法向量> = <逆转置矩阵> * <变换前法向量>

  给着色器变量赋值然后绘制出模型,最后调用requestAnimationFrame不断执行动画。矩阵的团团转部分可组成下边包车型客车keydown事件进行查看。

    function main() {
        //...

        // 光线方向
        gl.uniform3f(u_LightPosition, 0.0, 2.0, 12.0);
        // 漫反射光照颜色
        gl.uniform3f(u_diffuseColor, 1.0, 1.0, 1.0);
        // 设置环境光颜色
        gl.uniform3f(u_AmbientColor, 0.5, 0.5, 0.5);
        // 镜面反射光泽度
        gl.uniform1f(u_MaterialShininess, 30.0);

        var modelMatrix = new Matrix4();
        var mvpMatrix = new Matrix4();
        var normalMatrix = new Matrix4();
        var n = drawingInfo.indices.length;

        (function animate() {
            // 模型矩阵
            if(notMan){ angleY+=0.5; }
            modelMatrix.setRotate(angleY % 360, 0, 1, 0); // 绕y轴旋转
            modelMatrix.rotate(angleX % 360, 1, 0, 0); // 绕x轴旋转

            var eyeY=viewLEN*Math.sin(viewAngleY*Math.PI/180),
                len=viewLEN*Math.cos(viewAngleY*Math.PI/180),
                eyeX=len*Math.sin(viewAngleX*Math.PI/180),
                eyeZ=len*Math.cos(viewAngleX*Math.PI/180);

            // 视点投影
            mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 300);
            mvpMatrix.lookAt(eyeX, eyeY, eyeZ, 0, 0, 0, 0, (viewAngleY>90||viewAngleY<-90)?-1:1, 0);
            mvpMatrix.multiply(modelMatrix);
            // 根据模型矩阵计算用来变换法向量的矩阵
            normalMatrix.setInverseOf(modelMatrix);
            normalMatrix.transpose();

            // 模型矩阵
            gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
            // mvp矩阵
            gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
            // 法向量矩阵
            gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);

            // 清屏|清深度缓冲
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            // 根据顶点索引绘制图形(图形类型,绘制顶点个数,顶点索引数据类型,顶点索引中开始绘制的位置)
            gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);
            requestAnimationFrame(animate);
        }());
    }

原型链

  • 咦,在哪呢。
  • 原型链就在地点啊。每一种对象,沿着__prto__属性有一条链子,找呀找呀,一向找到Object.prototype为止

感谢冴羽大神和若愚大神的文章

事件处理

  +/-
键落成放大/裁减场景的意义;WSAD键达成模型的转动,也正是贯彻绕x轴和y轴旋转;上下左右方向键完结的是视点的团团转。矩阵变换的连锁兑现参考下边代码的动画片部分。

  模型旋转和视点旋转望着很相像,其实又有例外的。视点的团团转是一体场景比如光照模型等都是跟着变动的,如若以场景做参照物,它就相当于人转移观看地点看到物体。而模型旋转呢,它只旋转模型本身,外部的光照和场景都以不变的,以场景做参照物,也就是人在同一任务来看模型在活动。从demo的光照可以见到两种情势的区分。

    document.addEventListener('keydown',function(e){
        if([37,38,39,65,58,83,87,40].indexOf(e.keyCode)>-1){
            notMan=false;
        }
        switch(e.keyCode){
            case 38:        //up
                viewAngleY-=2;
                if(viewAngleY<-270){
                    viewAngleY+=360
                }
                break;
            case 40:        //down
                viewAngleY+=2;
                if(viewAngleY>270){
                    viewAngleY-=360
                }
                break;
            case 37:        //left
                viewAngleX+=2;
                break;
            case 39:        //right
                viewAngleX-=2;
                break;
            case 87:        //w
                angleX-=2;
                break;
            case 83:        //s
                angleX+=2;
                break;
            case 65:        //a
                angleY+=2;
                break;
            case 68:        //d
                angleY-=2;
                break;
            case 187:       //zoom in
                if(viewLEN>6) viewLEN--;
                break;
            case 189:       //zoom out
                if(viewLEN<30) viewLEN++;
                break;
            default:break;
        }
    },false);

总结

  最终,个人感觉建立3D模型照旧挺费时间,要求花心机稳步调整,才能做出相比健全的模子。

发表评论

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

网站地图xml地图