精华内容
参与话题
问答
  • threejs入门

    万次阅读 2019-03-07 20:26:54
    今年刚刚接触threejs。这玩意封装得挺不错。为了使大家感观上能够了解threejs.这里直接分享一段代码。看完就知道threejs的套路了。所有的学习资料,源代码,从github上已经足够。5000多的源码分享,天啊。。。 ...

    今年刚刚接触threejs。这玩意封装得挺不错。为了使大家感观上能够了解threejs.这里直接分享一段代码。看完就知道threejs的套路了。所有的学习资料,源代码,从github上已经足够。5000多的源码分享,天啊。。。

    https://github.com/search?q=threejs

     

    有几个例子也比较好的:https://github.com/luosijie/threejs-examples 

    以下一个案例,配置运行起来可以直接了解到threejs的结构。threejs的JS引用文件自己去官方找。https://threejs.org/

    效果示意图:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <title>three.js webgl - Simple text from json</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body {
                font-family: Monospace;
                background-color: #f0f0f0;
                margin: 0px;
                overflow: hidden;
            }
    
            #info {
                position: absolute;
                top: 10px;
                width: 100%;
                text-align: center;
            }
        </style>
    </head>
    
    <body>
        <div id="info">
            <script src="../build/three.js"></script>
    
            <script src="js/controls/OrbitControls.js"></script>
    
            <script src="js/loaders/VRMLLoader.js"></script>
    
            <script src="js/WebGL.js"></script>
            <script src="js/libs/stats.min.js"></script>
            <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
            <script src="https://cdn.bootcss.com/jszip/2.1.0/jszip.min.js"></script>
            <script src="https://cdn.bootcss.com/jszip-utils/0.0.2/jszip-utils.min.js"></script>
    
            <script>
    
                var camera, scene, renderer;
                var components = [];
                var selComps = [];
                var controls;
                var mouseInfo;
                var tube;
                var ptone;
                initComp();
                animate();
    
    
                
                function initComp() {
                    document.addEventListener('mousedown', onDocumentMouseDown, false);
                    document.addEventListener('mousemove', onDocumentMouseMove, false);
                    document.addEventListener('mouseup', onDocumentMouseUp, false);
                    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 1e10);
                    camera.up.set(0, 1, 0);
                    controls = new THREE.OrbitControls(camera);
    
                    scene = new THREE.Scene();
                    
                    camera.position.set(100,100,100);
                    
                    scene.position = new THREE.Vector3(0,0,0);
                    scene.up = new THREE.Vector3(0,0,1);
    
    
                    dxplane();
                    cube();
                    sphere();
                    cylinder();
    
                    var axes = new THREE.AxisHelper(100);
                    scene.add(axes);
    
    
                    controls.update();
    
    
                    var ambientLight = new THREE.AmbientLight(0x606060);
                    scene.add(ambientLight);
                    //平行光源
                    var directionalLight = new THREE.DirectionalLight(0xffffff);
                    directionalLight.position.set(1, 0.75, 0.5).normalize();
                    scene.add(directionalLight);
    
    
                    scene.background = new THREE.Color(0xffffff);
                    renderer = new THREE.WebGLRenderer({ antialias: true });
                    renderer.setPixelRatio(window.devicePixelRatio);
                    renderer.setSize(window.innerWidth, window.innerHeight);
                    document.body.appendChild(renderer.domElement);
    
                    window.addEventListener('resize', onWindowResize, false);
    
                } // end init
                
                function initLight() {
                    scene.add(new THREE.AmbientLight(0x444444));
    
                    light = new THREE.DirectionalLight(0xffffff);
                    light.position.set(0, 0, 0);
    
                    light.castShadow = true;
                    light.shadow.camera.top = 10;
                    light.shadow.camera.bottom = -10;
                    light.shadow.camera.left = -10;
                    light.shadow.camera.right = 10;
    
                    //告诉平行光需要开启阴影投射
                    light.castShadow = true;
    
                    scene.add(light);
                }
    
                function onWindowResize() {
    
                    camera.aspect = window.innerWidth / window.innerHeight;
                    camera.updateProjectionMatrix();
    
                    renderer.setSize(window.innerWidth, window.innerHeight);
    
                }
    
                function animate() {
    
                    requestAnimationFrame(animate);
    
                    render();
    
                }
    
                function render() {
                    if (renderer)
                        renderer.render(scene, camera);
    
                }
    
    
                function onDocumentMouseDown(event) {
                    console.log(event);
                    if (event.button != 0) return;
                    mouseInfo = { start: [event.x, event.y] };
    
    
                }
                function onDocumentMouseUp(event) {
                    if (event.button != 0) return;
                    if (event.x - mouseInfo.start[0] != 0 || event.y - mouseInfo.start[1] != 0) return;
                    var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
                    vector = vector.unproject(camera);
    
                    var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
    
                    var intersects = raycaster.intersectObjects(components);
                    console.log(intersects)
                    // console.log(intersects.length)
                    if (selComps.length > 0) {
                        selComps[0].object.material.transparent = false;
                        selComps[0].object.material.opacity = 1;
                        selComps = [];
                    }
                    if (intersects.length > 0) {
                        // console.log(intersects[0]);
                        intersects[0].object.material.transparent = true;
                        intersects[0].object.material.opacity = 0.5;
                        selComps.push(intersects[0]);
                    }
                }
    
                function onDocumentMouseMove(event) {
                    if (controls.showRay) {
                        var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
                        vector = vector.unproject(camera);
    
                        var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
                        var intersects = raycaster.intersectObjects(components);
    
                        if (intersects.length > 0) {
    
                            var points = [];
                            points.push(new THREE.Vector3(-30, 39.8, 30));
                            points.push(intersects[0].point);
    
                            var mat = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.6 });
                            var tubeGeometry = new THREE.TubeGeometry(new THREE.SplineCurve3(points), 60, 0.001);
    
                            if (tube) scene.remove(tube);
    
                            if (controls.showRay) {
                                tube = new THREE.Mesh(tubeGeometry, mat);
                                scene.add(tube);
                            }
                        }
                    }
                }
                //创建一个平面
                function dxplane() {
                    var planeGeo = new THREE.PlaneGeometry(100, 100, 10, 10);//创建平面
                    var planeMat = new THREE.MeshLambertMaterial({  //创建材料
                        color: 0x666666,
                        wireframe: false
                    });
                    var planeMesh = new THREE.Mesh(planeGeo, planeMat);//创建网格模型
                    planeMesh.position.set(0, 0, -20);//设置平面的坐标
                    planeMesh.rotation.x = -0.5 * Math.PI;//将平面绕X轴逆时针旋转90度
                    scene.add(planeMesh);//将平面添加到场景中
                }
                //创建一个立方体
                function cube() {
                    var cubeGeo = new THREE.CubeGeometry(20, 20, 20, 5, 5, 5);//创建立方体
                    var cubeMat = new THREE.MeshLambertMaterial({//创建材料
                        color: 0x003300,
                        wireframe: false
                    });
                    var cubeMesh = new THREE.Mesh(cubeGeo, cubeMat);//创建立方体网格模型
                    cubeMesh.position.set(20, 10, 0);//设置立方体的坐标
                    scene.add(cubeMesh);//将立方体添加到场景中
                }
                //创建一个球
                function sphere() {
                    var sphereGeo = new THREE.SphereGeometry(16, 40, 40);//创建球体
                    var sphereMat = new THREE.MeshLambertMaterial({//创建材料
                        color: 0x0000FF,
                        wireframe: false
                    });
                    var sphereMesh = new THREE.Mesh(sphereGeo, sphereMat);//创建球体网格模型
                    sphereMesh.position.set(-25, 10, 0);//设置球的坐标
                    scene.add(sphereMesh);//将球体添加到场景
                }
                //创建圆柱体
                function cylinder() {
                    //创建圆柱体
                    var cylinderGeo = new THREE.CylinderGeometry(15, 15, 40, 40, 40);
                    var cylinderMat = new THREE.MeshLambertMaterial({//创建材料
                        color: 0xFF6600,
                        wireframe: false
                    });
                    //创建圆柱体网格模型
                    var cylinderMesh = new THREE.Mesh(cylinderGeo, cylinderMat);
                    cylinderMesh.position.set(0, 20, -40);//设置圆柱坐标
                    cylinderMesh.up.set(0, 0, 1);
                    scene.add(cylinderMesh);//向场景添加圆柱体
                }
    
    
            </script>
    
    </body>

    </html>

    展开全文
  • threejs:从入门,进阶到实战

    千次阅读 2019-05-18 02:33:15
    本教程的目的是实现网页端实现三维模型显示,绘制等… 首先,这是threejs的官方例子(直接显示出来的例子.)https://threejs.org/examples/

    本教程的目的是实现网页端实现三维模型显示,绘制等…建立系统的知识体系。(之前有断更,现在恢复更新)
    文章内容包括了,常规在网页上的使用,同时还包括了在小程序端的使用。

    1.three.js 入门文章

    该章节旨在大致介绍一下three.js的基础使用
    threejs:二维绘图(基础点,线,不规则图形等)
    threejs:scene相关

    2. three.js 进阶篇

    2.1 三维基础部分

    threejs:二维绘图(基础点,线,不规则图形等)

    2.2 三维交互(interactive)

    三维交互:1.picker 拾取
    三维交互:2.dragcontrol 拖拽
    三维交互:3.boxSelection 框选

    2.3 具体案例

    剖切
    爆炸图
    测量:点至点,面至面,面积、体积等

    3.GLTF相关


    GLTF格式学习:1. glTF介绍( 基于WebGL
    GLTF格式学习:2. glTF的基础结构
    GLTF格式学习:3. 最小glTF文件
    GLTF格式学习:4 scenes 和Nodes(场景和节点)
    GLTF格式学习:5.buffer缓存、bufferView视窗、accesors访问器

    three.js小程序相关

    three.js小程序:概述

    补充WebGL相关知识

    常见问题

    1.当物体转到一定角度,有些面消失了解决方案

    展开全文
  • ThreeJS 3D基础教程

    千人学习 2019-07-11 22:51:15
    由于WebGL的出现,在网页端渲染3D物体已经变成了现实,由于Web端不需要安装,直接可以运行,所以它获得了很多编程着的喜爱,但是由于很多人并不懂图形学编程,很难掌握Web端的3D渲染,本教程借助ThreeJS 3D给读者...
  • ThreeJS 3D高级教程

    千人学习 2019-07-11 23:09:16
    该课程是在ThreeJS 3D基础版的提升,学习基础版以后,再学习该课程,目的是帮助在ThreeJS 3D应用方面做一个技术提升,该课程实现了一些比较复杂的效果,当然这些效果实现起来也不难,只要掌握了ThreeJS 3D编程的精髓...
  • ThreeJs做智慧城市项目后记

    万次阅读 多人点赞 2019-11-01 18:01:13
    只是会用一点ThreeJs,对于WebGl的原理并没了解过,这并不影响我们利用ThreeJs去做出一个非常炫酷的项目。 开始 新世界的大门打开啦! 写在前面 不要因为不了解就被这种3D展示的项目给吓到 其实实现起来...

    demo展示效果

    随着时间的推移技术的进步,前端越来越杂了,但是也越来越精彩了。只是会用一点ThreeJs,对于WebGl的原理并没了解过,这并不影响我们利用ThreeJs去做出一个非常炫酷的项目。

    开始

    新世界的大门打开啦!

    写在前面

    1. 不要因为不了解就被这种3D展示的项目给吓到 其实实现起来很简单 很简单 很简单
    2. 城市模型一份 最好是gltf模型,obj模型也没问题,我会介绍如何转化与压缩 PS:为什么只有这俩,因为我写这个项目只用到了这俩,处理的经验也是针对这俩的,我项目中所用的模型是公司所有暂不能提供。
    3. 有一定ThreeJs的基础 俗话说得好 万丈高楼平地起嘛 如果没有这方面基础的同学也不要急 推荐一本书《THREE.JS开发指南》,有基础也有提高 很棒
    4. 本文所示代码大部分只是思路 我也是第一次上手用ThreeJs处理模型并应用到项目中,可能有少许不足之处,还望各路大神指正教导
    5. 项目进行一半的时候,因为没经验,我发现让建模看着地图建模的思路是不对的,应该让他们利用geoJson作为地理数据,去建模,建造出来的更精确,而且可以利用地理坐标和世界坐标去关联(猜想),利于项目开发,毕竟第一次,这个锅我背了
    6. Threejs的文档是不全的,很多控制器loader后期处理都没有文档,要自己多看看Threejsexamples,很多效果都可以基于Demo去实现
    7. 单页面应用一定要清除ThreeJs 的创建的对象,避免内存泄露,能disposedispose,多个children的要遍历remove掉 而且里面的 materialgeometry也要删掉,最近刚知道一个取消占用的妙招,WEBGL_lose_context
    8. 后期处理对显卡有一定要求
    9. 最好一次渲染,不要多次渲染
    10. 最好不要把和数据无关的挂在vue的data上,会造成不必要的性能浪费,因为vue会深度遍历给每个对象加getter和setter,不过在这个项目里,我没发现啥差别。

    HTML部分

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Threejs-city-model-show</title>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
        />
        <style>
          body {
            color: #fff;
            margin: 0px;
            overflow: hidden;
          }
        </style>
      </head>
    
      <body>
        <script src="../build/three.min.js"></script>
      </body>
    </html> 
    

    创建场景

    首先,我们要祭出ThreeJs的最重要的几大组件——scene(场景)camera(相机)renderer(渲染器)light(灯光),以及渲染的目标——container(就是DOM结构),老生常谈,不多说

    打个比方,scene就是舞台,camera就是拍摄舞台的摄像机,它能决定观众看到什么,而一个舞台没有灯光的话它就是黑乎乎的,所以light就是舞台上的各种灯光,所以舞台上表演什么,就是舞台中有什么,所以要加入到scene中 scene.add(“演员们(模型)”)

    var camera, scene, renderer;
    var container;
    var ambientLight, pointLight;
    
    // 初始化
    init()
    // 循环渲染每一帧  一帧一帧的 就是你打游戏时的FPS
    animate()
    
    function init(){
    	// 初始化相机 
    	// 这里使用的是透视相机来模拟人眼看到的效果 近大远小
    	camera = new THREE.PerspectiveCamera(
          45,
          window.innerWidth / window.innerHeight,
          1,
          2000
        );
        camera.position.z = 70;
        camera.position.x = 50;
        camera.position.y = 10;
    	
    	// 初始化场景
    	scene = new THREE.Scene();
    
    	// 初始化灯光
    	// 环境光 能保持整体都是亮点
    	ambientLight = new THREE.AmbientLight(0x404040)
    	// 点光源 就像灯泡一样的效果  白色灯光 亮度0.6
    	pointLight = new THREE.PointLight(0xffffff, 0.6);
    
    	// 将灯光加入到场景中
    	scene.add(ambientLight)
    	// 将灯光加到摄像机中 点光源跟随摄像机移动
    	// 为什么这样做  因为这样可以让后期处理时的辉光效果更漂亮 
    	camera.add(pointLight);
    
    	// 我们将摄像机加入到场景中
        scene.add(camera);
    
    	// 初始化渲染器
    	renderer = new THREE.WebGLRenderer({
    	  // 开启抗锯齿
          antialias: true,
          // 开启背景透明
          alpha: true
        });
        // 把自动清除颜色缓存关闭 这个如果不关闭 后期处理这块会不能有效显示
        // 书上的描述是 如果不这样做,每次调用效果组合器的render()函数时,之前渲染的场景会被清理掉。通过这种方法,我们只会在render循环开始时,把所有东西清理一遍。
        renderer.autoClear = false;
        // 背景透明 配合 alpha
        renderer.setClearColor(0xffffff, 0);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 伽马值启动 更像人眼观察的场景
        renderer.gammaInput = true;
        renderer.gammaOutput = true;
    	
    	// 渲染到DOM中去
    	container = document.createElement("div");
        container.appendChild(renderer.domElement);
        document.body.appendChild(container);
    }
    // 这样一来,基础场景创建就完成了,接下来我们来让它循环渲染起来
    
    function animate() {
       // 这个方法低版本浏览器兼容不好 可以从github上找些兼容库 如果要兼容低版本浏览器
       requestAnimationFrame(animate);
       // 渲染我们的场景  摄像机啪啪啪的拍和录
       // 由于把renderer autoClear  关闭了 所以我们要在渲染函数中手动清除
       renderer.clear();
       renderer.render(scene, camera);
     }
    // ok 基础部分完成 接下来我们来加载模型
    

    加载城市模型

    限于经验和技术等各种外力因素影响,项目最开始时编写demo使用的是Obj模型Mtl贴图文件(不太确定贴图文件的叫法是否准确),使用起来也很简单(ThreeJs仓库里的webgl_loader_obj_mtl.html拿来改下就行了)

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Threejs-city-model-show</title>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
        />
        <style>
          body {
            color: #fff;
            margin: 0px;
            overflow: hidden;
          }
        </style>
      </head>
    
      <body>
        <script src="../build/three.min.js"></script>
        <!-- 引入我们可爱的加载器 -->
        <script src="js/loaders/MTLLoader.js"></script>
        <script src="js/loaders/OBJLoader.js"></script>
        <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() {
     		// 载入出错时候
    	  };
    
    	  // 加载Mtl贴图文件
          new THREE.MTLLoader()
            // 贴图文件的路径 
            .setPath("models/obj/male02/")
            .load("male02_dds.mtl", function(materials) {
              // 看代码意思是预加载
              materials.preload();
    
    		  // 加载OBJ模型
              new THREE.OBJLoader()
                // 设置OBJ模型的材质贴图
                .setMaterials(materials)
                .setPath("models/obj/male02/")
                .load(
                  "male02.obj",
                  function(object) {
                    object.position.y = -95;
                    scene.add(object);
                  },
                  onProgress,
                  onError
                );
            });
    	</script>
      </body>
    </html> 
    

    这一步一般会出现的问题有如下

    1. 模型加载后,不显示也不报错?
      检查场景是否正常渲染了,如果正常渲染模型的位置在哪里,摄像机在哪里,摄像机是否对着模型,灯光是否配置,模型是否太大或者太小了,超出了摄像机的摄影范围……
    2. 模型可以正常加载,但是贴图不显示?
      首先检查network是否报404错误,如果报错,一般都是mtl贴图文件(看起来像是雪碧图那种)没给你,或者路径配置的不是相对路径,如果贴图没错误,模型是黑色的,在mtl文件中可以更改kakd的三个值(对应rgb),或者打印出模型属性,在material.color中更改点色值或别的属性。黑色的时候,看不到贴图。一般这样一通操作之后,就能看到了模型了
    3. 模型文件太大了,浏览器在渲染的时候进程被完全卡死!要等待几十秒之久!天呐!
      这个问题看起来比较棘手,其实很好解决。ThreeJs官方推荐gltf格式的模型在浏览器中渲染,因为它是为浏览器而生的,性能好,体积小。我们项目中使用的模型文件,一开始是ObjMtl的,达到25MB大小,在vue项目中渲染会阻塞浏览器46s,原生html+js的项目中好些,几秒时间就行了,我怀疑是我写法的问题,但是我测试仅仅是加载模型渲染到场景,并没有多余操作和数据绑定,还是一样,阻塞进程,一度导致我怀疑人生???黑人问号脸。那么如何将Obj模型转换为gltf模型,还能再优化吗?进入下一章节!对了对了,Obj模型也是可以压缩的,而且ObjLoader2加载会快一点

    Obj模型转Gltf模型并压缩Gltf模型,性能爆炸提升!

    真的很牛逼 模型加贴图从 25mb 减小到了1.8mb 上效果图

    1.这是不加贴图和mtlobj文件 已经达到了22.5MB在这里插入图片描述

    1. 这是objgltf之后的文件,贴图转成了base64包含在了gltf文件中,可通过配置项提取出文件,稍后介绍
      在这里插入图片描述

    2. 这是经过gltf压缩处理之后的贴图+模型的文件大小在这里插入图片描述

    obj2gltf —— Obj模型转Gltf

    obj2gltf-github

    1. 用法
    // 全局安装后
                 obj文件所在目录                             输出目录 
    obj2gltf  -i ./examples/models/obj/hanchuan/city.obj -o ./gltf/city.gltf --unlit --separate
    
    1. 介绍下为什么要加这两个参数
      --unlit的作用是可以保留环境贴图的效果,环境贴图后面再介绍
      --separate是将贴图文件提取出来,提出来浏览器可以缓存,如果你需要继续压缩gltf文件,这里不加这个参数也行,因为压缩的时候也能提出来

    gltf-pipeline

    gltf-pipeline-github

    1. 用法
    gltf-pipeline -i  ../../../gltf/city.gltf  -o  ../../../examples/models/obj/hanchuan/city_small1.gltf -d --separate
    
    1. 介绍下参数
      -d--draco.compressMeshes的缩写,使用draco算法压缩模型
      --separate就是将贴图文件提取出来,不提可以不加

    这样,我们就完成了gltf模型的转化和压缩,性能暴增!秒开!
    在我们最终的模型中,obj模型297Mb,转gltf之后还有150Mb左右,最终经过压缩,还有7.3Mb!

    Gltf模型的加载

    抛弃了ObjMtl之后,我们的加载器也要做一下改变

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Threejs-city-model-show</title>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
        />
        <style>
          body {
            color: #fff;
            margin: 0px;
            overflow: hidden;
          }
        </style>
      </head>
    
      <body>
        <script src="../build/three.min.js"></script>
        <!-- 引入我们可爱的加载器 -->
        <script src="js/loaders/GLTFLoader.js"></script>
        <script src="js/loaders/DRACOLoader.js"></script>
        <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() {
     		// 载入出错时候
    	  };
    
          var loader = new THREE.GLTFLoader();
          // 这个是Threejs解析draco压缩之后的解析器 
          // 它从这里读取解析器JS
          THREE.DRACOLoader.setDecoderPath("js/libs/draco/gltf/");
          // 将Draco解析器和GltfLoader绑定在一起
          loader.setDRACOLoader(new THREE.DRACOLoader());
          loader.load(
            "models/obj/hanchuan/city_small1.gltf",
            function(gltf) {
             // gltf.scene 拿到这个可以处理模型
             scene.add(gltf.scene)
            },
            onProgress,
            onError
          );
    	</script>
      </body>
    </html> 
    

    这时候的场景,应该是这样的,很丑吧哈哈哈,没关系没关系,我们可以为它美容,不过在此之前,我们先来试着转动这个模型,看看性能怎么样。
    在这里插入图片描述

    OrbitControls——轨道控制器

    var controls
    
    function init(){
    	// 省略创建场景部分
    	controls = new THREE.OrbitControls(camera, renderer.domElement);
    }
    

    它的常用参数在源码中可以找到,也可以百度/goggle一下中文翻译的,不做太多介绍,这是其中一段源码。

    // Set to false to disable this control
    	this.enabled = true;
    
    	// "target" sets the location of focus, where the object orbits around
    	this.target = new THREE.Vector3();
    
    	// How far you can dolly in and out ( PerspectiveCamera only )
    	this.minDistance = 0;
    	this.maxDistance = Infinity;
    
    	// How far you can zoom in and out ( OrthographicCamera only )
    	this.minZoom = 0;
    	this.maxZoom = Infinity;
    
    	// How far you can orbit vertically, upper and lower limits.
    	// Range is 0 to Math.PI radians.
    	this.minPolarAngle = 0; // radians
    	this.maxPolarAngle = Math.PI; // radians
    
    	// How far you can orbit horizontally, upper and lower limits.
    	// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
    	this.minAzimuthAngle = - Infinity; // radians
    	this.maxAzimuthAngle = Infinity; // radians
    
    	// Set to true to enable damping (inertia)
    	// If damping is enabled, you must call controls.update() in your animation loop
    	this.enableDamping = false;
    	this.dampingFactor = 0.25;
    
    	// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
    	// Set to false to disable zooming
    	this.enableZoom = true;
    	this.zoomSpeed = 1.0;
    
    	// Set to false to disable rotating
    	this.enableRotate = true;
    	this.rotateSpeed = 1.0;
    
    	// Set to false to disable panning
    	this.enablePan = true;
    	this.panSpeed = 1.0;
    	this.screenSpacePanning = false; // if true, pan in screen-space
    	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
    
    	// Set to true to automatically rotate around the target
    	// If auto-rotate is enabled, you must call controls.update() in your animation loop
    	this.autoRotate = false;
    	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
    
    	// Set to false to disable use of the keys
    	this.enableKeys = true;
    
    	// The four arrow keys
    	this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
    
    	// Mouse buttons
    	this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT };
    
    	// for reset
    	this.target0 = this.target.clone();
    	this.position0 = this.object.position.clone();
    	this.zoom0 = this.object.zoom;
    
    	//
    	// public methods
    	//
    
    	this.getPolarAngle = function () {
    
    • 初始化这个控制器之后,就可以操作模型旋转放大缩小了。它的原理就是控制摄像机和模型的距离,同理也可以控制模型与摄像机的距离去实现移动放大缩小等功能,可以自己尝试一下。一个比较有趣的操作是在function animate(){}中,设置camera.lookAt=scene.position效果也很不错。
    • ThreeJs中内置了很多有趣的控制器,用法和效果都可以从ThreeJsexamples中找到,记得看看。

    Stats

    玩过LOL,大型单机游戏的同学都知道,如果帧率不好,画面看起来就会卡顿,影响体验,这也为什么用requestAnimationFrame去作为渲染调用的原因之一,它的性能比函数递归setInterval实现渲染调用好很多。那么我们如何去检测我们的场景渲染的性能怎么样呢?就可以使用Stats

    // <script src="js/libs/stats.min.js"></script> 不要忘了引入进来
    var stats;
    
    function init(){
    	// 省略创建场景部分
    	stats = new Stats();
    	container.appendChild(stats.dom);
    }
    
    function animatie(){
    	stats.update();
    	// 省略renderer
    }
    
    • 初始化之后在页面左上角会看到,这个原理还没研究过,有机会翻翻源码看看。
    • 在这里插入图片描述
    • 如果实在vue/react等单页面环境中,可以通过process.env.NODE_ENV控制开发环境再显示这个。
    • 这样一来,我们在开发调试的时候,就能很直观的看出效果了。

    给scene添加自定义背景

    若不为空,在渲染场景的时候将设置背景,且背景总是首先被渲染的。 可以设置一个用于的“clear”的Color(颜色)、一个覆盖canvas的Texture(纹理),或是一个CubeTexture。默认值为null。

    • 实验结果是,TextureLoaderCubeTextureSphereGeometry都可以作为背景图,简单介绍下这三者。
    1. TextureLoader 一张图,背景看起来是静止不动的
    2. CubeTexture 立方纹理 图片是分割成6块 相当于摄像机和模型在一个正方体盒子中 背景随着摄像机转动而转动
    3. SphereGeometry 一张图 全景图原理 相当于摄像机和模型在一个圆球盒子中 背景随着摄像机转动而转动
    4. 不太理解可以百度下threejs全景图原理,不做过多叙述
    function init(){
    	// 省略其余代码
    	// ....
    	// 添加一张静止的背景图
    	scene.background = new THREE.TextureLoader().load("你的背景图")
    	// ....
    }
    
    1. 之后效果大概是这样的,我们的世界里有了天空,其实这里用CubeTexture或者SphereGeometry效果更好
    2. 在这里插入图片描述

    设置模型环境贴图和材质颜色

    细心的同学会发现,河流和楼上会有星星点点的光,这是怎么实现的呢?答案就是环境贴图

    环境贴图
    简单的讲,环境贴图就像把物体的表面化作一面镜子,可以反射出你为它赋予的图片。

    如何设置环境贴图呢?回到我们加载模型的部分。核心就是创建立方纹理然后设置某个模型的materialenvMap为这个立方纹理。 环境贴图的使用限制受纹理影响,有一部分纹理加不上环境贴图。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Threejs-city-model-show</title>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
        />
        <style>
          body {
            color: #fff;
            margin: 0px;
            overflow: hidden;
          }
        </style>
      </head>
    
      <body>
        <script src="../build/three.min.js"></script>
        <!-- 引入我们可爱的加载器 -->
        <script src="js/loaders/GLTFLoader.js"></script>
        <script src="js/loaders/DRACOLoader.js"></script>
        <script>
    	  /* 省略创建场景部分的代码 */
    
    	 // 创建一个立方纹理
    	 var envMap = new THREE.CubeTextureLoader()
                .setPath("textures/")
                .load(new Array(6).fill("start.jpg"));
    
          var loader = new THREE.GLTFLoader();
          // 这个是Threejs解析draco压缩之后的解析器 
          // 它从这里读取解析器JS
          THREE.DRACOLoader.setDecoderPath("js/libs/draco/gltf/");
          // 将Draco解析器和GltfLoader绑定在一起
          loader.setDRACOLoader(new THREE.DRACOLoader());
          loader.load(
            "models/obj/hanchuan/city_small1.gltf",
            function(gltf) {
             // gltf.scene 拿到这个可以处理模型
             gltf.scene.traverse(function(child) {
                if (child.isMesh) {
                  /* 这些都是DEMO  具体看你模型调整 下节介绍通过鼠标点击确定模型所属对象 然后去调试模型 */
    			  // 这些名称都可以通过打印看出 console.log(child)
    
    			  // 比如我想给这些加上环境贴图 就可以这样写
                  /hai|city|liubianxing/i.test(child.name) &&
                    (child.material.envMap = envMap);
                  
                  if (/city/i.test(child.name)) {
                    // 更改模型颜色
                    child.material.color = new THREE.Color(6, 6, 5);
                    // 更改模型环境贴图影响  0-1
                    child.material.reflectivity = 0.9;
                  }
                  
    			  // 更改模型位置
                  /lumian|hai/i.test(child.name) && (child.position.y = 0.5);
                  
                  // ...
                }
              });
              
              scene.add(gltf.scene)
            },
            onProgress,
            onError
          );
          
    	</script>
      </body>
    </html> 
    

    Raycaster 光线投射

    光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)。

    • 打印出所有的child不好定位是哪块模型,有没有更快的方法?
    • 您好,有的。
    • 通过 THREE.Raycaster 实现模型选中与信息显示,点击打印出当前点击的模型,在它的属性中修改颜色,位置等,可以直接更新效果,调试更方便
    • 到此,经过我们的美化之后,效果就是这样了。还缺了点什么,道路咋不发光啊,看着没光效,不炫酷!
    • 在这里插入图片描述

    利用EffectComposer(效果组合器)进行后期处理

    这一块的基础建议好好看看《THREE.JS开发指南》这本书。如果需要多个pass,要学会使用MaskPassclearPass。这一块因为不熟悉,我在添加效果的时候花费了很大量的时间,尤其是Threejs内置的pass效果没有文档,甚至你都不知道内置了多少种效果…《THREE.JS开发指南》这本书介绍的比较全面,用法也很详细。

    利用EffectComposer进行后期处理——辉光(bloompass)

    如何设置后期处理?

    1. 创建一个EffectComposer对象,然后在该对象上添加后期处理通道。
    2. 配置该对象,使它可以渲染我们的场景,并用额外的后期处理步骤
    3. render循环中,使用EffectComposer渲染场景、应用通道,并输出结果

    几个引用介绍

    • EffectComposer效果组合器,每个通道会按照其加入EffectComposer的顺序执行。
    • RenderPass该通道在指定的场景和相机的基础上渲染出一个新的场景。一般在第一个加入到Composer中,它会渲染场景,但是不会将渲染结果输出到屏幕上。
    • ShaderPass使用该通道可以传入一个自定义的着色器,用来生成高级的、自定义的后期处理通道
    • BloomPass该通道会使明亮区域渗入较暗的区域,模拟相机照到过多亮光的情形
    • CopyShader它不会添加任何特殊效果,只是将最后一个通道的结果复制到屏幕上,BloomPass无法直接添加到屏幕上,需要借助这个Shader,其实使用bloompass.renderToScreen = true是可以添加的,但是后续再加处理效果会无效,所以一定要借用这个Shader
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Threejs-city-model-show</title>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
        />
        <style>
          body {
            color: #fff;
            margin: 0px;
            overflow: hidden;
          }
        </style>
      </head>
    
      <body>
        <!-- 省略其他引入的 -->
        <!-- 引入Effect -->
        <script src="js/postprocessing/EffectComposer.js"></script>
        <!-- 引入Effect配套的render -->
        <script src="js/postprocessing/RenderPass.js"></script>
        <script src="js/postprocessing/ShaderPass.js"></script>
        <!-- 引入各种需要的shader -->
        <script src="js/shaders/CopyShader.js"></script>
        <script src="js/shaders/LuminosityHighPassShader.js"></script>
        <script src="js/postprocessing/UnrealBloomPass.js"></script>
        <script>
          var clock;
    	  /* 省略创建场景部分的代码 */
    	  // 初始化renderPass
    	  var renderScene = new THREE.RenderPass(scene, camera);
    	
    	  // 初始化bloomPass 
    	  var bloomPass = new THREE.UnrealBloomPass(
    	    // 没研究过这些参数的意义 会提上日程
            new THREE.Vector2(window.innerWidth, window.innerHeight),
            1.5,
            0.4,
            0.85
          );
          // 一些参数 可以调整看效果
          bloomPass.threshold = 0.36;
          bloomPass.strength = 0.6;
          bloomPass.radius = 0;
    
    	  // effectCopy
          var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
          // 让effectCopy渲染到屏幕上 没这句不会再屏幕上渲染
          effectCopy.renderToScreen = true;
    	  
    	  // 初始化 composer
    	  var composer = new THREE.EffectComposer(renderer);
    	  // 模版缓冲(stencil buffer) https://blog.csdn.net/silangquan/article/details/46608915
          composer.renderTarget1.stencilBuffer = true;
          composer.renderTarget2.stencilBuffer = true;
          composer.setSize(window.innerWidth, window.innerHeight);
          composer.addPass(renderScene);
    	  composer.addPass(bloomPass);
          composer.addPass(effectCopy);
    
    	  // 修改animate
    	  function animate() {
            requestAnimationFrame(animate);
            var delt = clock.getDelta();
            stats.update();
            renderer.clear();
            // 删除renderer使用composerrender去渲染
            // renderer.render(scene, camera);
            
    		// 没理解透这个delt的作用 ???
            composer.render(delt);
          }
    	</script>
      </body>
    </html> 
    

    在这里插入图片描述这样 辉光效果就出来了。还不够还不够,让我们加上FocusShaper,让它看起来像聚焦在中心一样(突出中心)。

    1. 颜色越亮,发光效果越强
    2. 辉光受环境贴图影响
    3. 模型可以通过map贴图来更改亮度,比如暗色的贴图,它反光就会很软

    为场景添加聚焦效果——FocusShader

    我们要引入FocusShader

    • FocusShader是一个简单的着色器,其结果是中央区域渲染的比较锐利,单周围比较模糊。
    • 在这里插入图片描述
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Threejs-city-model-show</title>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
        />
        <style>
          body {
            color: #fff;
            margin: 0px;
            overflow: hidden;
          }
        </style>
      </head>
    
      <body>
        <!-- 省略其他引入的 -->
        <!-- 引入Effect -->
        <script src="js/postprocessing/EffectComposer.js"></script>
        <!-- 引入Effect配套的render -->
        <script src="js/postprocessing/RenderPass.js"></script>
        <script src="js/postprocessing/ShaderPass.js"></script>
        <!-- 引入各种需要的shader -->
        <script src="js/shaders/CopyShader.js"></script>
        <script src="js/shaders/LuminosityHighPassShader.js"></script>
        <script src="js/postprocessing/UnrealBloomPass.js"></script>
        <!-- focusShader 相对于bloompass新加的 -->
        <script src="js/shaders/FocusShader.js"></script>
        <script>
          var clock;
    	  /* 省略创建场景部分的代码 */
    	
    	 // 创建focusShader 相对于bloompass新加的
    	 var focusShader = new THREE.ShaderPass(THREE.FocusShader);
         focusShader.uniforms["screenWidth"].value = window.innerWidth;
         focusShader.uniforms["screenHeight"].value = window.innerHeight;
         focusShader.uniforms["sampleDistance"].value = 1.07;
    
    	  // 初始化renderPass
    	  var renderScene = new THREE.RenderPass(scene, camera);
    	
    	  // 初始化bloomPass 
    	  var bloomPass = new THREE.UnrealBloomPass(
    	    // 没研究过这些参数的意义 会提上日程
            new THREE.Vector2(window.innerWidth, window.innerHeight),
            1.5,
            0.4,
            0.85
          );
          // 一些参数 可以调整看效果
          bloomPass.threshold = 0.36;
          bloomPass.strength = 0.6;
          bloomPass.radius = 0;
    
    	  // effectCopy
          var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
          // 让effectCopy渲染到屏幕上 没这句不会再屏幕上渲染
          effectCopy.renderToScreen = true;
    	  
    	  // 初始化 composer
    	  var composer = new THREE.EffectComposer(renderer);
    	  // 模版缓冲(stencil buffer) https://blog.csdn.net/silangquan/article/details/46608915
          composer.renderTarget1.stencilBuffer = true;
          composer.renderTarget2.stencilBuffer = true;
          composer.setSize(window.innerWidth, window.innerHeight);
          composer.addPass(renderScene);
    	  composer.addPass(bloomPass);
    	  // 相对于bloompass新加的
    	  composer.addPass(focusShader);
          composer.addPass(effectCopy);
    
    	  // 修改animate
    	  function animate() {
            requestAnimationFrame(animate);
            var delt = clock.getDelta();
            stats.update();
            renderer.clear();
            // 删除renderer使用composerrender去渲染
            // renderer.render(scene, camera);
            
    		// 没理解透这个delt的作用 ???
            composer.render(delt);
          }
    	</script>
      </body>
    </html> 
    

    模型的渲染和后期处理就到此就全部结束了。

    Sprite精灵的应用

    精灵是一个总是面朝着摄像机的平面,通常含有使用一个半透明的纹理。

    在这里插入图片描述

     var textured = new THREE.TextureLoader().load("textures/warning.png");
     var spriteMaterial = new THREE.SpriteMaterial({
       // color: 0xffffff,
       map: textured
     });
     var sprite = new THREE.Sprite(spriteMaterial);
     sprite.position.set(
       25.729931791092394,
       10.179400757773436,
       36.07142388020101
     );
     // console.log(sprite);
     sprite.scale.x = 10;
     sprite.scale.y = 5;
    
     scene.add(sprite);
    

    这张图火灾预警的图其实就是一张透明的png图片,精灵可以用canvas贴图,你可以自己编写canvas渲染在指定点上,也可以使用CSS3DRenderer去实现。

    Group

    通常的情况下Threejs里的模型是要分组的。在处理交互起来,有分组会更加清晰明了,就像模块拆分一样。

    var group = new THREE.Group();
    

    区域、路线、移动等功能实现逻辑

    1. 不规则区域可以用ShapeGeometry创建,使用可以设置透明的material比较好。material设置transparent:true可以支持透明
    2. 移动就是更改模型位置,很简单model.position.set(x,y,z)
    3. 画线,linelineLoopCubicBezierCurve3Threejs提供的画线方法
    4. 路线循环流动效果可以创建一个管道,然后增加一个路径一样的贴图,设置wrap为重复,在animate中不断更改texture.offset即可

    VUE/React等单页面注意点

    由于单页面中,Threejs创建的任何材质,模型,贴图……只要含有dispose方法的,你在页面组件即将销毁的周期中,都要调用下dispose方法清除,不然可能内存泄漏。刚学会一个妙招,利用WEBGL_lose_context这个API 可以让当前的webgl环境失效,达到取消占用的目的。

    beforeDestory(){
    	this.bloomPass.dispose();
        this.envMap.dispose();
        this.skymap.dispose();
        this.dracoLoader.dispose();
        this.spriteMaterial.dispose();
        this.sphereGeometry.dispose();
        this.meshBasicMaterial.dispose();
        this.scene.dispose();
        this.controls.dispose();
    	
    	/*
    	const data = this.$data;
        for (let i in data) {
          if (data.hasOwnProperty(i)) {
            if (data[i] && typeof data[i].dispose == "function") {
              data[i].dispose();
            }
          }
        }
    	*/
    	// this.renderer.domElement 就是你的threejs的canvas Dom
    	let gl = this.renderer.domElement.getContext("webgl");
    
        gl && gl.getExtension("WEBGL_lose_context").loseContext();
    }
    

    模型发光还带线的效果怎么做?

    在这里插入图片描述

    
    var lineMaterial = new THREE.LineBasicMaterial({
      // 线的颜色
      color: "blue",
      transparent: true,
      opacity: 0.8,
      depthFunc: THREE.AlwaysDepth
    });
    模型.add(
      new THREE.LineSegments(模型geometry, lineMaterial)
    );
    // 之后把模型设置下透明度就成了
    
    

    坐标转换 经纬度转墨卡托

    • 先把经纬度转墨卡托坐标 然后由于墨卡托坐标比较大,找到地图模型的中心点,墨卡托转Threejs的坐标时,减去这个中心点,之后就能画出一样的点或区域,之后再将z轴(y)取反
    • x+对应东,z+对应南
    • z算出来还得取个反
    • 根据坐标系适当调整
    function lonlatToMercator(lon, lat, height) {
            var z = height ? height : 0;
            var x = (lon / 180.0) * 20037508.3427892;
            var y = (Math.PI / 180.0) * lat;
            var tmp = Math.PI / 4.0 + y / 2.0;
            y = (20037508.3427892 * Math.log(Math.tan(tmp))) / Math.PI;
            return { x: x, y: y, z: z };
          }
    
    // 找到地图的中心对应的经纬度坐标
    var center = lonlatToMercator(113.82909, 30.6549, 1);
    
    function lonlatToThree(lon, lat, height) {
      var z = height ? height : 0;
      var x = (lon / 180.0) * 20037508.3427892;
      var y = (Math.PI / 180.0) * lat;
      var tmp = Math.PI / 4.0 + y / 2.0;
      y = (20037508.3427892 * Math.log(Math.tan(tmp))) / Math.PI;
      var result = {
        x: x - center.x,
        y: y - center.y,
        z: z - center.z
      };
      // x 越大越远
      // 因为比地图大了 可以让地图整体放大或缩小 然后偏移到大概位置
      return [result.x / 100 + 17, -result.y / 100 + 33];
      // [-result.x / 100 - 14, -result.y / 100 - 35];
    }
    console.log(lonlatToThree(113.84411, 30.65231));
    

    antialias开启后,渲染还有锯齿怎么办?

    使用SSAAFXAASMAA等抗锯齿后处理。任选其一即可。

    initFxaaPass() {
    	let fxaaPass = new ShaderPass(FXAAShader);
    	const pixelRatio = this.renderer.getPixelRatio();
    	fxaaPass.material.uniforms["resolution"].value.x =
    	  1 / (this.width * pixelRatio);
    	fxaaPass.material.uniforms["resolution"].value.y =
    	  1 / (this.height * pixelRatio);
    	fxaaPass.renderToScreen = true;
    	this.fxaaPass= fxaaPass;
    },
    
    initSmaaShader() {
    	const pixelRatio = this.renderer.getPixelRatio();
    	this.smaaPass = new SMAAPass(
    	  this.width * pixelRatio,
    	  this.height * pixelRatio
    	);
    	this.smaaShader.renderToScreen = true;
    },
    
    initSsaaShader() {
    	this.ssaaRenderPass = new SSAARenderPass(this.scene, this.camera);
    	this.ssaaRenderPass.unbiased = false;
    	this.ssaaRenderPass.sampleLevel = 2;
    },
    

    利用EffectComposer应用某个效果

    initEffectComposer() {
    	const composer = new EffectComposer(this.renderer);
    	composer.setSize(this.width, this.height);
    	composer.addPass(this.renderScene);
    	composer.addPass(this.ssaaRenderPass);
    	composer.addPass(this.bloomPass);
    	composer.addPass(this.focusShader);
    	composer.addPass(this.effectCopy);
    	
    	this.composer = composer;
    },
    

    光柱效果如何实现

    在这里插入图片描述

    1. 准备一张渐变灰色png图片, 类似如下图
      在这里插入图片描述我在这 ↑
    2. 代码部分
    import * as THREE from "three";
    
    const scaleSpeed = 0.01;
    
    export default {
      data(){
        return {
          // ...  
        }
      },
      created(){
        this.loadRangeMap()
      },
      beforeDestory(){
          // ...
      },
      methods: {
        initRingAnimate() {
          Array.isArray(this.gatewayGroup.children) &&
            this.gatewayGroup.children.forEach(v => {
              Array.isArray(v.children) &&
                v.children.forEach(item => {
                  if (item.userData.type === "ring") {
                    item.rotation.z = item.rotation.z + scaleSpeed;
                  }
                });
            });
        },
        loadRangeMap() {
          this.rangeMap = this.textureLoader.load(require("../images/range.png"));
        },
        initOctahedronBufferGeometry() {
          this.octahedronBufferGeometry = new THREE.OctahedronBufferGeometry();
        },
        initCylinderBufferGeometry() {
          this.cylinderBufferGeometry = new THREE.CylinderBufferGeometry(
            2,
            2,
            14,
            12,
            1,
            true
          );
        },
        initOctahedron(color) {
          let geometry = this.octahedronBufferGeometry;
          let material = new THREE.MeshBasicMaterial({
            color,
            transparent: true,
            opacity: 0.3
          });
          let lineMaterial = new THREE.LineBasicMaterial({
            color,
            depthFunc: THREE.AlwaysDepth
          });
          let octahedron = new THREE.Mesh(geometry, material);
          let line = new THREE.LineSegments(geometry, lineMaterial);
          octahedron.add(line);
          octahedron.position.z = -8;
          return octahedron;
        },
        initRing(color) {
          let geometry = this.cylinderBufferGeometry;
          let material = new THREE.MeshBasicMaterial({
            color,
            map: this.rangeMap,
            side: THREE.DoubleSide,
            transparent: true,
            depthWrite: false
          });
          let cylinder = new THREE.Mesh(geometry, material);
          cylinder.rotation.x = (Math.PI / 180) * -90;
          cylinder.position.z = -2;
          return cylinder;
        },
        initGateway(data = { color: "#54C41D",x: 0, z: 0 }) {
          let group = new THREE.Group();
          let octahedron = this.initOctahedron(data.color);
          let ring = this.initRing(data.color);
          group.add(ring);
          group.add(octahedron);
          group.rotation.x = (Math.PI / 180) * 90;
          group.position.y = 0.2;
          group.position.x = data.x;
          group.position.z = data.z;
          this.gatewayGroup.add(group);
        }
      }
    };
    
    

    删除子对象时,用forEach等高阶循环删不干净?

    • 因为group.children是个数组,每次删除的时候,数组都会变动,比如长度是5,你删了第一个,下次循环你要删除第二个,但是数组长度变了,第二次删除的时候其实删的是第三个了。
    • 解决方案1 children.map(v=>{group.remove(children[0])}) 一直删除第一个
    • 解决方案2 for(let i = 0, l = children.length; i < l; i++){ group.remove(children[i]) } 将数组长度存储下来,就不会变啦!

    我们项目的最终效果

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • threejs官网:https://threejs.org/ github各个版本:https://github.com/mrdoob/three.js/tags 版本更迭很快,我用的时候还是r90秒秒钟r91出来了 刚入门的时候可以看看这个教程:...

    前言

    threejs官网:https://threejs.org/
    github各个版本:https://github.com/mrdoob/three.js/tags
    版本更迭很快,我用的时候还是r90秒秒钟r91出来了

    刚入门的时候可以看看这个教程:http://www.hewebgl.com/article/articledir/1
    初级教程是免费的,中高级是收费的,写的不是很枯燥,也不用跟着敲代码,有个相机,渲染器,场景,光源的概念就可以了。它适配的版本是比较旧的,我当时用新版本跟着写有些demo无法完成,新手更谈不上根据版本之间的差别找到问题,慢慢就失去兴趣了。

    我的这个教程用的是r90版本,它有个比较好的地方是,当你使用高版本的threejs却使用低版本的语法时,浏览器console会有警告告诉你新的写法是什么样的。

    我的DEMO下载

    演示地址: 太穷了,带宽很小,加载卡顿请谅解(哭泣)
    相机转动做的比较仓促没有考虑细节,导致鼠标拖拽或是放大缩小卡顿,emmm小问题暂时不想改了
    https://www.hugeoyzy.top/threejs/src/demo1.html
    https://www.hugeoyzy.top/threejs/src/demo2.html
    下载:https://download.csdn.net/download/u010588262/10288776

    开发工具

    因为我主要是做java开发,所以就用idea了,因为idea写js也是有提示的,所以还挺方便,就建个Static Web项目就可以了:
    这里写图片描述

    我的项目结构是这样的:
    这里写图片描述

    调试需要做两点配置:
    1. 浏览器路径:
    这里写图片描述

    1. 浏览器启动参数,因为threejs要加载本地的图片文件,obj文件之类的,看网上教程都说是不允许的,要部署在apache和tomcat之类的,我没试,看到说可以加启动参数就加了,调试没问题
      –allow-file-access-from-files
      这里写图片描述

    调试的时候就在html文件上鼠标移到右上角,点击相应的浏览器图标就可以了
    这里写图片描述
    如果修改了html的话直接保存文件在浏览器刷新就能看到效果了,但是修改了js的话就要关闭页面重新点击浏览器图标。

    官方DEMO(肥肠重要)

    我入门最快的时候就是找到了官方DEMO之后

    一个是:http://stemkoski.github.io/Three.js/#hello-world
    这里的demo很入门,很好理解,页面最上方有github地址,可以去下载。唯一不好的是使用的版本很老。r60版本的,不过对于初学者一开始最重要的是理解大概的使用,不用纠结于最新版本。

    还有一个是:https://threejs.org/examples/#webgl_animation_cloth
    这里的demo相比第一个是比较高大上的,源代码就在你下载的threejs包里的examples目录下

    代码讲解

    这一块针对初学者中的初学者了,只要看过前言中的初级教程之后再看我的demo应该没问题了,demo的代码也比较整洁。

    demo中的css,fonts,images和objs基本都是从官方demo中搞过来的,页面上引用的js分别是什么作用也做了注释了:
    这里写图片描述

    util.js是我把公用的东西放在一起了,要看懂util.js需要对threejs基本的组件有一些了解。

    threejs的三围坐标是这样的,比如你现在对着电脑屏幕吧,把你屏幕的左下角想象成二维坐标原点,那往右就是x正方向,网上就是y正方向,从屏幕往外直盖脸的就是z正方向了。

    最上面的src()是用来初始化场景,相机和渲染器的,最开始的textureLoader = new THREE.TextureLoader();是用来加载图片材质的,在大多数教程里用的都是ImageUtils来加载的,这个是新版本的改动,我说一下我对这仨玩意儿的理解吧

    场景:就是一个黑盒子呗,把你要展示的东西全往里面装,地板啊,天花板啊,盒子啊,球啊,货架啊,三D字体啊全塞进去。为什么是黑盒子?因为你看不见它,也不看不见塞进去的东西。场景的初始化很简答就一句话scene = new THREE.Scene();

    相机:相机可以在任意的位置,任意的角度”拍摄”黑盒子里的场景,相机的初始化稍微复杂一些。

        var SCREEN_WIDTH = window.innerWidth;
        var SCREEN_HEIGHT = window.innerHeight;
        var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
        camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
        scene.add(camera);
        camera.position.set(0, 600, 1000);
        cameraRadius = Math.sqrt(camera.position.x * camera.position.x + camera.position.z * camera.position.z);
        camera.up.set(0, 1, 0);
        camera.lookAt(new THREE.Vector3(0, 0, 0));

    关于相机比较难理解的是position,up和lookAt的关系。你可看看这个视频http://www.hewebgl.com/video/2,虽然我看完了也没怎么看懂。
    尝试解释一下吧。
    现在,把你的手机打开摄像头,正对着屏幕,想象它就像单反一样上面右边有个快门,这就是threejs中相机的初始方向,就是从z轴正方向看向z轴负方向。position是什么意思呢,现在把手机往右平移,意思是position.x在增加。lookAt也很容易,就是相机往哪儿拍呗。比较难理解的是up,好现在把手机位置固定,只能旋转哦,也就是说position固定了,好了现在把手机旋转镜头对着屏幕左下角,也就是咱们的三维坐标原点,也就是说lookAt也固定了。up的作用就是你现在可以旋转手机了,保持镜头对着屏幕左下角的方向,手机的中心点位置不要变,旋转手机,拍出来的画面就会是正的,或者斜着的,或者上下倒过来的。随着你旋转手机快门的朝向也在变是吧,默认快门是对着Y轴正方向的,也就是(0,1,0)如果把手机颠倒,up就变成了(0,-1,0),拍出来的画面也就是上下颠倒的,因为你虽然可以倒着手机拍,但是看照片的时候得把手机正过来看,画面也就是反的咯。

    渲染器
    可以看到相机的初始化过程中没有与场景绑定,他们俩就像在两个平行世界一样,看似在一起,其实没在一起,需要用渲染器把他们俩关联起来,这样,相机就拍到了场景里的东西,再通过渲染器展示在浏览器上被我们看到了。Decetor是通过Detector.js引入进来的东西,用于判断浏览器兼容性的。

        if (Detector.webgl)
            renderer = new THREE.WebGLRenderer({antialias: true});
        else
            renderer = new THREE.CanvasRenderer();
        renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
        container = document.getElementById('ThreeJS');
        container.appendChild(renderer.domElement);

    以地板为例怎么搞一个东西放在场景里,CSDN的代码注释没有这么详细,写博客的时候加的,对不住只下资源不看文章的朋友。PlaneGeometry就是一个平面,SphereGeometry就是一个球,CubeGeometry就是一个立方体…….感兴趣的可以百度多了解一下其他组件。材质就是各种Material,也有很多种,有受光照影响的,有不受光照影响的。把组件和材质组合起来就是一个完整的物体啦,就是THREE.Mesh这个方法把他们组合起来,第一个参数是组件,第二个参数是材质,材质还可以传入一个数组,你可能觉得设置多个有啥意义啊一个盖住一个的,材质是可以设置透明度的,明白了吧。

    function addfloor() {
        // 加载图片作为地板材质
        var floorTexture = textureLoader.load('images/checkerboard.jpg');
        // 沿x方向和Y方向都重复填充
        floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
        // x方向和y方向各重复几次,不理解的话改改这个值看看效果就知道了
        floorTexture.repeat.set(4, 4);
        // 将材质包装成表面材料,设置正反两面都要铺上
        var floorMaterial = new THREE.MeshBasicMaterial({map: floorTexture, side: THREE.DoubleSide});
        // 平面对象
        var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 1, 1);
        // 把对象和材料包装成Mesh
        floor = new THREE.Mesh(floorGeometry, floorMaterial);
        // 设置位置放入场景啦
        floor.position.y = -0.5;
        floor.rotation.x = Math.PI / 2;
        scene.add(floor);
    }

    others()方法里是一些可有可无的辅助,看注释就明白了。
    animate()就是开始持续渲染,渲染就是通过renderer.render(scene, camera);把渲染器搞到的东西放在浏览器上给你看,持续渲染就是通过重复调用方法本身可以实现动画的效果requestAnimationFrame(animate);,除了这两个东西还调用了update();方法,update方法里就是慢慢的移动组件或者刷新其他需要更新得组件。requestAnimationFrame方法是比较平滑的动画,我之前想用Tween.js实现相机转动的效果,很卡顿,不流畅。

    woodycube()就是封装了获得木箱,因为两个demo里都用到了木头箱子。

    后面有空再说一下demo中的代码

    2018年3月16日
    更新了一下util.js中的代码,把鼠标点击选中事件和getMeshed方法从demo2中移到util里面了
    点击事件

    document.addEventListener("mousedown", onDocumentMouseDown, false);
    function onDocumentMouseDown(e) {
        e.preventDefault();
        // 点击页面时停止相机旋转,因为相机旋转的时候无法选中物体,这个问题没有解决,有点麻烦
        rotate = false;
        //将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义
        var mouse = {};
        mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
        //新建一个三维单位向量 假设z方向就是0.5
        //根据照相机,把这个向量转换到视点坐标系
        var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(camera);
        var sub = vector.sub(camera.position);=
        var param2 = sub.normalize();
        var raycaster = new THREE.Raycaster(camera.position, param2);
    
        //射线和模型求交,选中一系列直线,getMeshs方法用于获得场景中可以被选中的所有Mesh
        var intersects = raycaster.intersectObjects(getMeshs());
    
        if (intersects.length > 0) {
            // outlineMesh是物体边框组件,每次只会有一个物体被选中,就把这个东西定义为全局的
            // 每次选中另一个物体之前先删除,相当于取消前一个物体的选中
            scene.remove(outlineMesh);
            //选中第一个射线相交的物体
            var SELECTED = intersects[0].object;
            var outlineMaterial2 = new THREE.MeshBasicMaterial({color: 0x00ff00, side: THREE.BackSide});
            outlineMesh = new THREE.Mesh(SELECTED.geometry.clone(), outlineMaterial2);
            outlineMesh.position.set(SELECTED.position.x, SELECTED.position.y, SELECTED.position.z);
            outlineMesh.scale.multiplyScalar(1.05);
            scene.add(outlineMesh);
        }
    }
    展开全文
  • ThreeJS 开发实例

    万次阅读 多人点赞 2016-08-09 09:21:32
    形状和材质 ... var geometry = new THREE.Geometry(); /** * vertexColors: false 关闭使用点颜色来生成线的颜色,这个值默认是false,如果设置为true,那么后面设置的color将不起作用 * ...
  • threejs demo

    2018-03-15 16:49:17
    threejs入门博客http://blog.csdn.net/u010588262/article/details/79570436的配套demo,演示地址: https://www.hugeoyzy.top/threejs/src/demo1.html https://www.hugeoyzy.top/threejs/src/demo2.html
  • ThreeJs 基础入门

    千次阅读 2018-09-27 10:50:08
    本文来自网易云社区作者:唐钊Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它在 web 中创建各种三维场景,包括了摄影机、光影、材质等各种对象。使用它可以让我们更加直观的了解 webgl 的世界。3D 场景前置...
  • ThreeJSAPI文档(中文)

    2017-08-25 09:09:32
    THREEJS 的中文API文档
  • threejs概览

    千次阅读 2017-09-09 08:46:05
    threejs术语和概念 threejs的API很长,有很多概念和术语,理解了这些概念和术语,才能更好的使用threejs。这些概念和术语都藏在API右侧的大纲中,下图简单整理了一下这些概念: 这些概念又分为四个大类(见上图...
  • ThreeJS —— 机房Demo(三)目录结构模拟一条管线为管线添加动画自定义管线运动部分的长度 Three世界中的物体都是大部分都是由一个个几何体Geometry构成的,上一节我们在场景中加入了一些几何体模拟机器,并渲染...
  • Three.js 中文文档和在线演示实例

    万次阅读 多人点赞 2016-06-16 19:40:24
    Three.js是当下最流行的网页3D渲染JS引擎,其主要是对WebGL编程以面向对象方式进行的封装。 踏得网专注于HTML5技术生态链的资源开发,鉴于网络上Three.js方面的资料比较散乱,且良莠不齐, 因此我们把Three.js的官方...
  • Three.js 基础入门

    万次阅读 多人点赞 2018-07-01 03:45:55
    课程介绍 近些年,浏览器的功能越来越强大,渐渐得成为了...面对这种情况,Three.js 应运而生,它不但对 WebGL 进行了封装,将复杂的接口简单化,而且基于面向对象思维,将数据结构对象化,非常方便我们开发。Thre...
  • Three.js和其它webgl框架

    万次阅读 多人点赞 2018-08-24 11:23:08
    WebGL (Web图形库) 是一种JavaScript API,用于在任何兼容的Web浏览器中呈现交互式3D和2D图形,而无需使用插件。WebGL通过引入一个与OpenGL ES 2.0紧密相符合的API,可以在HTML5 元素中使用。(MDN简介) 在我的...
  • 利用three.js 实际开发的一个实例

    万次阅读 多人点赞 2018-07-30 11:12:05
    首个threejs项目-前端填坑指南 第一次使用threejs到实际项目中,开始的时候心情有点小激动,毕竟是第一次嘛,然而做着做着就感受到这玩意水好深,满满的都是坑,填都填不过来。经过老板20天惨无人道的摧残,终于小...
  • 初识three.js,搭建three.js+vue.js项目

    千次阅读 2018-09-22 06:37:04
    简介:WebGL(全写Web Graphics Library)是一种3D绘图协议,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0(OpenGL for Embedded Systems,OpenGL嵌入式版本,针对手机、游戏机等设备相对较轻量级的版本)结合...
  • 58 Three.js 通过THREE.Raycaster给模型绑定点击事件

    万次阅读 热门讨论 2018-01-14 02:59:03
    由于浏览器是一个2d视口,而在里面显示three.js的内容是3d场景,所以,现在有一个问题就是如何将2d视口的x和y坐标转换成three.js场景中的3d坐标。好在three.js已经有了解决相关问题的方案,那就是THREE.Raycaster...
  • Three.js编辑器editor使用详解

    万次阅读 2020-08-03 15:34:14
    Three.js编辑器editor使用详解官网下载Three.js压缩包其他的文件内容如下:了解过文件内容之后下一步: 官网下载Three.js压缩包 github官网源码包 解压后文件目录如下图: 其他的文件内容如下: Build目录:包含两...
  • Three.js】五、three.js中的材质——Material一、 three.js中的材质就是几何体表面的材料。 一、
  • threejs-inspector, 用于调试 three.js的Chrome devtool扩展 Three.js-检查器three.js 检查器是 Chrome devtool的扩展名。 它允许你在网页中查看 Three.js 场景。 你可以从 Chrome 网上商店安装 Three.js Inspector ...
  • Three.js fbx文件导入

    万次阅读 热门讨论 2018-03-08 16:39:57
    源码 下载 https://download.csdn.net/download/qq_34206863/10361429 其实可以先看看官方案例 你想要的东西 官方里面都有,就看你找不找得到了。。。。 话不多说 我们先看看这个例子吧 ...am
  • Three.jsthree.js之几何体与网格
  • 使用three.js开发3d地图初探

    万次阅读 多人点赞 2018-05-15 16:28:10
    公司要做智慧消防楼层可视化,需要用到web3d,开源的引擎中先研究了cesium三维地球,但cesium做楼层感觉是大材小用,而且体验也不好,最终选用的是功能强大、更适合小型场景的threethree是图形引擎,而web二维三...
  • 38 Three.js高级材质THREE.ShaderMaterial

    千次阅读 2017-12-06 22:36:06
    注意 看这一篇文章最好有webgl基础的同学看,如果没有webgl原生基础,你会看...着色器可以将Three.js中的JavaScript网格转换为屏幕上的像素。通过这些自定义的着色器,可以明确地指定对象如何渲染,以及如何覆盖或...
  • 简介 这种材质可以用来创建暗淡的并不光亮的表面。 无光泽表面的材质,无镜面高光。 这可以很好地模拟一些表面(如未经处理的木材或石头),但不能用镜面高光(如上漆木材)模拟光泽表面。 该材质非常易用,而且会...
  • Three.js+tween.js 基础(一)

    千次阅读 2019-01-26 11:08:36
    Three.js 简介 Three.js是众多WebGL三维引擎框架其中之一,源自github的一个开源项目,项目地址:https://github.com/mrdoob/three.js 。可以利用three.js进行网页上的三维场景(机械、建筑、游戏等)创建,能写出...
  • Threejs 官网 - Three.js 的图形用户界面工具(GUI Tools with Three.js)
  • three.js 绘制地板Every developer has their favorite tools, languages, and technologies to work with. Sometimes it derives from another passion such as solving mathematical problems, white-hat hacking,...
  • 简介 上一节我们介绍了一下THREE.PointCloud。THREE.PointCloud构造函数接收两个属性:...如果我们基于THREE.BoxGeometry对象创建THREE.PointCloud对象,即使不添加顶点,我们也将会得到八个粒子,立方体一个角上...

空空如也

1 2 3 4 5 ... 20
收藏数 62,515
精华内容 25,006
关键字:

threejs