手记

Three.js的突破

Three.js只是炫,没有想象中的那么难

什么是webgl,什么是three.js

webgl是大部分浏览器直接支持的一种3D绘图标准。three.js在它的基础上进行了进一步的封装和简化开发开发过程。
three.js主要三个对象,一个行为
三个对象主要是场景(模型和灯光),相机(观察场景的视角),渲染器(场景渲染输出的目标),行为就是渲染。
three.js完整运行流程:


111077-20170424125001006-1547749106.png

本文中所需要的js全部都可以在官网例子中找到https://github.com/mrdoob/three.js/archive/master.zip

1.场景(Scene)

场景有两个参数  1参是模型,2参是灯光

模型

模型有两个参数  1参是几何模型,2参是材质

//网格模型  两个参数 1 几何模型 2材质var geometry = new THREE.BoxGeometry(100,100,100);  //几何模型var material = new THREE.MeshLambertMaterial({color:0xff0000});  //材质var mesh = new THREE.Mesh(geometry,material); 
scene.add(mesh);  //网格添加到场景当中

这里的模型可以添加多个!

灯光
var light = new THREE.DirectionalLight(0xffffff); 
//      light.position.set(30,500,800);    //设置光源位置
        light.castShadow = true;
            light.position.set( 300, 200, 800 ); 
            light.decay = 2;
            light.penumbra = 0.2;          
            light.shadow.mapSize.width = 1024;
            light.shadow.mapSize.height = 1024;  
        scene.add(light); //光源添加到场景之中
2.相机(Camera)
    //相机对象    透视相机
      var camera = new THREE.PerspectiveCamera(40,800/600,1,2000);  
      camera.position.set(200,200,200)   //设置相机的位置
      camera.lookAt(scene.position);  //相机朝向中心
3.渲染器(Renderer)
   //设置渲染器
   var renderer = new THREE.WebGLRenderer();   
   renderer.setSize(800,600);  //设置渲染器的大小
4.将渲染器添加到文档之中
  //渲染器添加到文档之中
  document.body.appendChild(renderer.domElement);
  renderer.render(scene,camera);  //渲染2个参数  1渲染的场景对象  2渲染的相机

这个我们就可以看到一个简单three的项目啦。
不过这个项目,它没有监听一些事件,比如说鼠标滚轮事件,拖动事件,three为我们提供了好多控制器(Controls),我们只用引用这个控制器,并且调用它, 就OK啦

        <script src="js/OrbitControls.js" type="text/javascript" charset="utf-8"></script>  
            function render(){
                renderer.render(scene,camera);
            }            //相机控制是场景交互和动画的基础
            var controls = new THREE.OrbitControls(camera);  //创建相机控制  2个参数 1是我们的相机  2可以忽略默认是文档
            controls.addEventListener('change',render);

加上这些就可以监听事件啦。

5.添加3D模型

这里的3d文件指的是obj文件,就是一个人或者一个车子,一个房子,我们首先要引入一个加载器

        <script src="js/OBJLoader.js" type="text/javascript" charset="utf-8"></script>
            //添加obj模型
            var loader = new THREE.OBJLoader();
            loader.load('img/man.obj',function(dogObject){
                dogObject.scale.set(40,40,40);  //缩放
                dogObject.position.y = 50;
                dogObject.position.y = -30;                
//              dogObject.rotateX(110);
                dogObject.rotateY(120);
                
                scene.add(dogObject);
                renderer.render(scene,camera);
            })

这样就把一个简单的3D模型放到浏览器中啦,当然你也可以添加多个3D模型。还有一点要注意的是加载文件是一个异步的行为,所以加载完之后一定要重新再渲染一次。或许你添加完之后,发现这个3D模型是没有材质的,材质可以看做皮肤,这个模型表面是什么都没有的。那继续往下看

6.添加材质

首先说下问题,添加一个obj的材质之前使用的是OBJMTLLoader.js,但是71以上的three版本,已经移除了这个组件,找啦好久找了一个新的解决方法,引入OBJLoader.js     MTLLoader.js      DDSLoader.js这3个js

        <script src="js/OBJLoader.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/MTLLoader.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/DDSLoader.js"></script> 
            var onProgress = function ( xhr ) {                if ( xhr.lengthComputable ) {                    var percentComplete = xhr.loaded / xhr.total * 100;                    console.log( Math.round(percentComplete, 2) + '% downloaded' );
                }
            };            var onError = function ( xhr ) { };
            THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() );            var mtlLoader = new THREE.MTLLoader();
            mtlLoader.setPath( './img/' );       //设置我们需要加载的mtl文件路径
            mtlLoader.load( 'man.mtl', function( material ) {      //这里加载我们需要的文件名  
                material.preload();                var objLoader = new THREE.OBJLoader();
                objLoader.setMaterials( material );      //材质,也可自定义
                objLoader.setPath( './img/' );               //设置要加载的obj文件的路径
                objLoader.load( 'man.obj', function ( object ) {           //加载obj文件
                    object.position.z = 1;         //这里设置我们的素材相对于原来的大小以及旋转缩放等
                    object.position.y = -0.5;
                    object.scale.x = 300;
                    object.scale.y = 300;
                    object.scale.z = 300;
                    object.rotation.x = 4.5;
                    object1 = object;               //这里是对素材设置阴影的操作
                    for(var k in object.children){  //由于我们的素材并不是看上去的一个整体,所以需要进行迭代
                                                    //对其中的所有孩子都设置接收阴影以及投射阴影
                                                    //才能看到阴影效果
                        object.children[k].castShadow = true;   //设置该对象可以产生阴影
                        object.children[k].receiveShadow = true;  //设置该对象可以接收阴影
                    }
                    scene.add( object1 );
                    renderer.render(scene,camera);
                     
                }, onProgress, onError );
            });

然后只要调整路径和文件名称就OK啦。

7.添加天空盒

什么是天空盒,给一张图吧,会给人身临其境的效果,感觉身处在这个3维空间里


20170802095554275.jpg

            //天空盒
            var path = 'img/';            var format = '.png';            var urls = [
                path + 'px' + format,path + 'nx' + format,
                path + 'py' + format,path + 'ny' + format,
                path + 'pz' + format,path + 'nz' + format,
            ]            var textureCube = THREE.ImageUtils.loadTextureCube(urls);            var meterial = new THREE.MeshBasicMaterial({                color:0xffffff,                envMap:textureCube
            })            
            
            var shader = THREE.ShaderLib['cube'];
            shader.uniforms['tCube'].value = textureCube;            
            var material = new THREE.ShaderMaterial({                fragmentShader:shader.fragmentShader,                vertexShader:shader.vertexShader,                uniforms:shader.uniforms,                depthWrite:false,                side:THREE.BackSide
            }),
            mesh = new THREE.Mesh(new THREE.BoxGeometry(1000,1000,1000),material);
            scene.add(mesh);

加入这些就OK啦。这些东西蛮好理解的, 就不一一作解释啦。最后给大家丢个全部代码吧!

8.事件

three的事件,因为它是个canvas,不像svg可以拿到元素,three的事件只能拿坐标去判断你点击的是什么模型,当然这个three是有组件去判断的

                /**********************************事件***************************************/
                var objects=[];                var raycaster = new THREE.Raycaster();                var mouse = new THREE.Vector2();                //监听全局点击事件,通过ray检测选中哪一个object
                document.addEventListener("mousedown", (event) => {
                  event.preventDefault();
                  mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
                  mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
                  raycaster.setFromCamera(mouse, camera);
                  scene.children.forEach(child => {
                    if (child instanceof THREE.Mesh) {//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
                      objects.push(child)
                    }
                })                var intersects = raycaster.intersectObjects(objects);                    if (intersects.length > 0) {                        console.log(intersects[0])                        try{                            if(ifCubeIdArr.indexOf(intersects[0].object.uuid) != -1){                                if(intersects[ 0 ].object.material.color.b == 1 && intersects[ 0 ].object.material.color.g == 1 && intersects[ 0 ].object.material.color.r == 1){
                                    intersects[ 0 ].object.material.color.set( 0xff0000);
                                }else{
                                    intersects[ 0 ].object.material.color.set( 0xffffff);
                                }
                                renderer.render(scene,camera);
                            }
                            
                            objects = [];
                        }catch(e){
                            objects = [];
                            
                        }
                    }
                }, false)
它有一个很严重的问题,困扰了我一段时间,就是我只想给一个模型添加事件,而不是给所有的模型都添加事件,官网看了一段时间,没有找到好的解决方法。后来自己写js解决这个问题,思路:在创建这个模型的时候会有一个uuid,保存这个uuid,放在一个数组。并且在点击事件的时候,去检索这个数组。
9.添加文字
                var fontLoader = new THREE.FontLoader();                //导入字体,设定字体,这里的话,你们找对自己的字体路径,可能和我的不一样的!!下载的three.js包里面examples/fonts里面有字体
                fontLoader.load('js/optimer_regular.typeface.json',function(font){                    for(let q = 0; q <arrobj.length;q++ ){                         // 文字
                        var g = new THREE.TextGeometry(q,{                            // 设定文字字体,
                            font:font,                            //尺寸
                            size:1,                            //厚度
                            height:0.1,
                        });                        //计算边界,暂时不用管
                        g.computeBoundingBox();                        //3D文字材质  
                        var m = new THREE.MeshBasicMaterial({color:0xffffff});                        //通过网格模型去添加   并且修改网格模型的位置
                        var mesh = new THREE.Mesh(g,m);
                        mesh.position.x = arrobj[q].x - 3;
                        mesh.position.y = arrobj[q].y + 2;
                        mesh.position.z = arrobj[q].z - 1.1;                   
                
        //                mesh.rotation.x = 0;
        //                mesh.rotation.y = Math.PI * 2;
                        // 加入到场景中
                        scene.add(mesh);  
                    }
                });
<!DOCTYPE html><html>
    <head>
        <meta charset="UTF-8">
        <script src="js/jquery-1.7.1.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/three.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/OrbitControls.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/OBJLoader.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/MTLLoader.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/DDSLoader.js"></script>      
        <title></title>
    </head>
    <style>
        body{            margin: 0;            overflow: hidden;
        }        #percent {            position: absolute;            width: 200px;            height: 20px;            color: red;            text-align: center;
        }    </style>
    <body>
        <div id="percent"></div>
        <div id="WebGL-output">
        </div>
        <script>
            var animatea; 
            // 修改过   OrbitControls.js 源码  676行
            $(function(){                var scene = new THREE.Scene();//场景构建
                var camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,2000);//相机构建
                var renderer = new THREE.WebGLRenderer();//渲染器构建
                renderer.setClearColor(0xEEEEEE);
                renderer.setSize(window.innerWidth,window.innerHeight);
                renderer.shadowMapEnabled = true;//激活阴影//              //构建一个坐标轴//              var axes = new THREE.AxisHelper(20);//              scene.add(axes);




                /****************************底座********************************/
                var planeGeometry = new THREE.PlaneGeometry(40,20);   //底座大小
                var planeMaterial = new THREE.MeshBasicMaterial({color:0xcccccc});                var planeMaterial = new THREE.MeshLambertMaterial({color:0xffffff});//转换对光源有渲染的材质
                var plane = new THREE.Mesh(planeGeometry,planeMaterial);
                plane.rotation.x = -0.5*Math.PI;
                plane.position.x = 15;
                plane.position.y = 0;
                plane.position.x = 0;
                scene.add(plane);
                plane.receiveShadow  = true;                //添加灯光
                var spotLight = new THREE.SpotLight(0xffffff);
                spotLight.position.set(-10,20,10);
                spotLight.castShadow = true;
                scene.add(spotLight);                //渲染视图视角
                camera.position.x = -30;
                camera.position.y = 20;
                camera.position.z = 30;
                camera.lookAt(scene.position)                
                var arrobj = [{                    x:0,y:-1,z:0
                },{                    x:0,y:-1,z:5
                },{                    x:5,y:-1,z:0
                },{                    x:10,y:-1,z:0
                },{                    x:12,y:-1,z:0
                },{                    x:14,y:-1,z:0
                },{                    x:16,y:-1,z:0
                },{                    x:18,y:-1,z:0
                },{                    x:20,y:-1,z:0
                },{                    x:22,y:-1,z:0
                },{                    x:23,y:-1,z:0
                },{                    x:24,y:-1,z:0
                },{                    x:26,y:-1,z:0
                },{                    x:28,y:-1,z:0
                },{                    x:30,y:-1,z:0
                },{                    x:32,y:-1,z:0
                },{                    x:34,y:-1,z:0
                }]                

                    var onProgress = function(xhr) {                        if (xhr.lengthComputable) {                            var percentComplete = xhr.loaded / xhr.total * 100;                            var percent = document.getElementById("percent");
                            percent.innerText = Math.round(percentComplete, 2) + '% 已经加载';
                        }
                    };            for(let i = 0; i <arrobj.length;i++ ){                    var onError = function(xhr) {};                    var mtlLoader = new THREE.MTLLoader();
                    mtlLoader.setPath('./img/');
                    mtlLoader.load('22.mtl', function(materials) {
                        materials.preload();                
                        var objLoader = new THREE.OBJLoader();
                        objLoader.setMaterials(materials);
                        objLoader.setPath('./img/');
                            objLoader.load('22.obj', function(object) {                                console.log(object);
                                object.position.y = arrobj[i].y;
                                object.position.x = arrobj[i].x;
                                object.position.z = arrobj[i].z;        //                      object.position.x = 5;
        //                      object.position.z = 5;//                              objectqi.rotation.x = 4.5;
                                object.scale.set(0.001, 0.001, 0.001);
                                scene.add(object);
                                renderer.render(scene,camera);
                            }, onProgress, onError);
                    });
                }                
//              // 事件//              //声明raycaster和mouse变量//              var raycaster = new THREE.Raycaster();//              var mouse = new THREE.Vector2();//              //              renderer.domElement. = function(e){//                      //通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.//                      mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;//                      mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;//              //                      // 通过鼠标点的位置和当前相机的矩阵计算出raycaster//                      raycaster.setFromCamera( mouse, camera );//              //                      // 获取raycaster直线和所有模型相交的数组集合//                      var intersects = raycaster.intersectObjects( scene.children );//              //                      console.log(intersects);//              //                      //将所有的相交的模型的颜色设置为红色,如果只需要将第一个触发事件,那就数组的第一个模型改变颜色即可//                      for ( var i = 0; i < intersects.length; i++ ) {//              //                          intersects[ i ].object.material.color.set( 0xff0000 );//              //                      }//              //              }
                /*************************************字体********************************************/
                var fontLoader = new THREE.FontLoader();                //导入字体,设定字体,这里的话,你们找对自己的字体路径,可能和我的不一样的!!下载的three.js包里面examples/fonts里面有字体
                fontLoader.load('js/optimer_regular.typeface.json',function(font){                    for(let q = 0; q <arrobj.length;q++ ){                         // 文字
                        var g = new THREE.TextGeometry(q,{                            // 设定文字字体,
                            font:font,                            //尺寸
                            size:1,                            //厚度
                            height:0.1,
                        });                        //计算边界,暂时不用管
                        g.computeBoundingBox();                        //3D文字材质  
                        var m = new THREE.MeshBasicMaterial({color:0xffffff});                        //通过网格模型去添加   并且修改网格模型的位置
                        var mesh = new THREE.Mesh(g,m);
                        mesh.position.x = arrobj[q].x - 3;
                        mesh.position.y = arrobj[q].y + 2;
                        mesh.position.z = arrobj[q].z - 1.1;                   
                
        //                mesh.rotation.x = 0;
        //                mesh.rotation.y = Math.PI * 2;
                        // 加入到场景中
                        scene.add(mesh);  
                    }
                });                /*************************************字体********************************************/
                
                
                
                /*********************************设备上面的立方体************************************/
                //因为不知道点击的是谁  所在在创建的时候保存uuid 点击的时候去检索
                var ifCubeIdArr = [];                for(let w = 0;w <arrobj.length;w++){                    //网格模型  两个参数 1 几何模型 2材质
                    var geometryCube = new THREE.BoxGeometry(100,100,100);  //几何模型
                    var materialCube = new THREE.MeshLambertMaterial({color:0xff0000});  //材质
                    var meshCube = new THREE.Mesh(geometryCube,materialCube); 
                    animatea = meshCube;
                    meshCube.position.x = arrobj[w].x - 3;
                    meshCube.position.y = arrobj[w].y + 3.5;
                    meshCube.position.z = arrobj[w].z - 1.6;
                    meshCube.scale.x = 0.01;
                    meshCube.scale.y = 0.01;
                    meshCube.scale.z = 0.01;
                    scene.add(meshCube);  //网格添加到场景当中
                    console.log(meshCube)
                    ifCubeIdArr.push(meshCube.uuid);
                }                var aa = true;                function animate() {                    if(aa == true){
                       animatea.position.y += 3.5;  //几何模型
            
                        aa = false;
                    }else{
                       animatea.position.y -= 3.5;  //几何模型
                
                        aa = true;
                    }
                    renderer.render(scene,camera);
                }
                animate();
                setInterval( animate,500 );                
                
            
                /**********************************事件***************************************/
                var objects=[];                var raycaster = new THREE.Raycaster();                var mouse = new THREE.Vector2();                //监听全局点击事件,通过ray检测选中哪一个object
                document.addEventListener("mousedown", (event) => {
                  event.preventDefault();
                  mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
                  mouse.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
                  raycaster.setFromCamera(mouse, camera);
                  scene.children.forEach(child => {
                    if (child instanceof THREE.Mesh) {//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
                      objects.push(child)
                    }
                })                var intersects = raycaster.intersectObjects(objects);                    if (intersects.length > 0) {                        console.log(intersects[0])                        try{                            if(ifCubeIdArr.indexOf(intersects[0].object.uuid) != -1){                                if(intersects[ 0 ].object.material.color.b == 1 && intersects[ 0 ].object.material.color.g == 1 && intersects[ 0 ].object.material.color.r == 1){
                                    intersects[ 0 ].object.material.color.set( 0xff0000);
                                }else{
                                    intersects[ 0 ].object.material.color.set( 0xffffff);
                                }
                                renderer.render(scene,camera);
                            }
                            
                            objects = [];
                        }catch(e){
                            objects = [];
                            
                        }
                    }
                }, false)                /**********************************事件***************************************/
                
                
                
                
                /************************************天空盒*************************************/
                var path = 'img/';                var format = '.png';                var urls = [
                    path + 'px' + format,path + 'nx' + format,
                    path + 'py' + format,path + 'ny' + format,
                    path + 'pz' + format,path + 'nz' + format,
                ]                var textureCube = THREE.ImageUtils.loadTextureCube(urls);                var meterial = new THREE.MeshBasicMaterial({                    color:0xffffff,                    envMap:textureCube
                })                
                
                var shader = THREE.ShaderLib['cube'];
                shader.uniforms['tCube'].value = textureCube;                
                var material = new THREE.ShaderMaterial({                    fragmentShader:shader.fragmentShader,                    vertexShader:shader.vertexShader,                    uniforms:shader.uniforms,                    depthWrite:false,                    side:THREE.BackSide
                }),
                meshTian = new THREE.Mesh(new THREE.BoxGeometry(1000,1000,1000),material);
                scene.add(meshTian);
                
                $("#WebGL-output").append(renderer.domElement)//              renderScene();//              function renderScene(){//                  requestAnimationFrame(renderScene);//                  renderer.render(scene,camera);//              }
                function render(){
                    renderer.render(scene,camera);
                }                
                
                //相机控制是场景交互和动画的基础
                var controls = new THREE.OrbitControls(camera);  //创建相机控制  2个参数 1是我们的相机  2可以忽略默认是文档
                controls.addEventListener('change',render);
            })            
        </script>
        
    </body></html>



作者:会飞的猪l
链接:https://www.jianshu.com/p/070baa80ca02


2人推荐
随时随地看视频
慕课网APP

热门评论

你好。我点击已经在模型上添加了一个点位。 现在需要这个点是扩散的,该怎么实现啊?

查看全部评论