Threejs/Webgl智慧城市部分效果实现

很多同学看到许多智慧城市的效果,却始终苦苦无法学会,而且网上大多数特效都没有给到完整的开源代码。大家的学习之路,困难重重。
本人在学习智慧城市效果查阅资料的时候,也是花了大量的时间,网上太多的错误阉割代码。
所以在此把自己实现的效果和代码列出来,方便大家参考。
整体代码放在github上面,因为时间原因没整理了,代码比较乱… github

这里主要实现的功能有:飞线,粒子星空,防护墙,扩散圈,辉光图层特效,obj模型加载。

建筑物

建筑物采用纹理贴图的方式,通过json数据来渲染。 看到网上很多都是用模型加载的方式,但是这种方式,在真实的场景中,不存在。真实的场景应该是可以变动的,所以采用json加载的方式。
找一张建筑物图片 (可以看到这张图,有两种横向方向的贴图,和32种竖向的贴图)
Threejs/Webgl智慧城市部分效果实现_第1张图片

 // 纹理贴图是把立方体拆成三角形去贴。6面体就需要12个三角形面
 // position是该建筑在三维坐标系的坐标,x、y、z是长宽高
function createCube(position, x, y, z) { 
var geometry = new THREE.CubeGeometry(x, y, z);
var material = new THREE.MeshBasicMaterial({
     map: THREE.ImageUtils.loadTexture("image/ny1.jpg"),
});
        var temp1, temp2;
        // 从图片中切出两个面。
        var _bg1 = [
          new THREE.Vector2(0, 0.5),
          new THREE.Vector2(0.5, 0.5),
          new THREE.Vector2(0.5, 1),
          new THREE.Vector2(0, 1),
        ];
        var _bg2 = [
          new THREE.Vector2(0.875, 0.875),
          new THREE.Vector2(1, 0.875),
          new THREE.Vector2(1, 1),
          new THREE.Vector2(0.875, 1),
        ];

		geometry.faceVertexUvs[0] = [];  // 然后把这两个面拆分,贴到12个三角形上面。形成我们要的六面体建筑

        geometry.faceVertexUvs[0][0] = [temp1[3], temp1[0], temp1[2]];
        geometry.faceVertexUvs[0][1] = [temp1[0], temp1[1], temp1[2]];

        geometry.faceVertexUvs[0][2] = [temp1[3], temp1[0], temp1[2]];
        geometry.faceVertexUvs[0][3] = [temp1[0], temp1[1], temp1[2]];

        geometry.faceVertexUvs[0][4] = [temp2[3], temp2[0], temp2[2]];
        geometry.faceVertexUvs[0][5] = [temp2[0], temp2[1], temp2[2]];
        // geometry.faceVertexUvs[0][6] = [temp1[3], temp1[0], temp1[2]];
        // geometry.faceVertexUvs[0][7] = [temp1[0], temp1[1], temp1[2]];

        geometry.faceVertexUvs[0][8] = [temp1[3], temp1[0], temp1[2]];
        geometry.faceVertexUvs[0][9] = [temp1[0], temp1[1], temp1[2]];

        geometry.faceVertexUvs[0][10] = [temp1[3], temp1[0], temp1[2]];
        geometry.faceVertexUvs[0][11] = [temp1[0], temp1[1], temp1[2]];

		mesh = new THREE.Mesh(geometry, material);
        mesh.castShadow = true;
        mesh.position.z = position.z;
        mesh.position.y = position.y;
        mesh.position.x = position.x;
        scene.add(mesh);
  }

这里一个实现简单建筑的函数就完成了,真实的场景中,我们肯定会有不同类型的建筑存在,所以我们可以实现很多个不同的fn,来通过json调用渲染成我们要的建筑群。

飞线

Threejs/Webgl智慧城市部分效果实现_第2张图片
飞线我们采用顶点着色器和片元着色器来做。 原理是画一个完整的线,然后通过改变空白长度的透明度,来实现。
先声明两个着色器:

    
    

然后绘制飞线:

let curve = new THREE.EllipseCurve(
          0,
          0, // ax, aY
          10,
          10, // xRadius, yRadius
          0,
          2 * Math.PI, // aStartAngle, aEndAngle
          false, // aClockwise
          0 // aRotation
        );
        initFlyLine(
          curve,
          {
            speed: 1,
            color: new THREE.Vector3(1.0, 0.0, 1.0),
            number: 1.0,
            length: 0.7,
            size: 2.0,
          },
          500,
          0,
          {x:0,y:0,z:0}
        );
       // 根据curve和颜色 生成线条
        /**
         * @param curve {THREE.Curve} 路径,
         * @param matSetting {Object} 材质配置项
         * @param pointsNumber {Number} 点的个数 越多越细致
         * @param position {Object} 位置
         * */
        function initFlyLine(curve, matSetting, pointsNumber,position) {
          var points = curve.getPoints(pointsNumber);
          var geometry = new THREE.BufferGeometry().setFromPoints(points);

          let length = points.length;
          var percents = new Float32Array(length);
          for (let i = 0; i < points.length; i += 1) {
            percents[i] = i / length;
          }

          geometry.attributes.percent = new THREE.BufferAttribute(percents, 1);

          let lineMaterial = initLineMaterial(matSetting);

          var flyLine = new THREE.Points(geometry, lineMaterial);
          let euler = new THREE.Euler( // 有弧度的曲线
            Math.random() * Math.PI,
            Math.random() * Math.PI,
            0
          );
          flyLine.position.x = position.x
          flyLine.position.y = position.y
          flyLine.position.z = position.z
          flyLine.layers.enable(1)
          scene.add(flyLine);
        }

防护墙

Threejs/Webgl智慧城市部分效果实现_第3张图片
防护墙实现起来很简单,就实现一个圆柱体,里面是空心的(通过设置openEnded为true,或者设置材质的时候用数组,只贴图外围那个面)就好了,然后纹理贴图,贴一个渐变色的图。

	   /**
         * @param r {Number} 半径,
         * @param src {String} 纹理贴图的图片
         * @param pointsNumber {Number} 点的个数 越多越细致
         * @param position {Object} 位置
         * */
      function scatter3DCylinder(r, src) {
        const geometry = new THREE.CylinderGeometry(r, r, 6, 20);
        const circle = new THREE.Mesh(geometry, [
          new THREE.MeshBasicMaterial({
            color: 0xffffff,
            side: THREE.DoubleSide,
            transparent: true,
            map: THREE.ImageUtils.loadTexture(src),
          }),
        ]);
      }

扩散圈


扩散圈和防护墙的实现差不多,也是先实现一个半球,然后通过动画使这个圆的半径和高度扩大,同时改变opcity的值。

// 圆扩散
      function scatter3DCircle(r) {
        const geometry = new THREE.SphereGeometry(
          r,
          120,
          120,
          0,
          Math.PI * 2,
          0,
          Math.PI / 2
        );
        const circle = new THREE.Mesh(geometry, [
          new THREE.MeshBasicMaterial({
            side: THREE.DoubleSide,
            transparent: true,
            map: THREE.ImageUtils.loadTexture("image/gradual_red_01.png"),
          }),
        ]);
        // circle.rotation.x = -Math.PI / 2.0;
        let s = 0,
          p = 0;

        function render() {
          // animation
          if (s > 160) {
            (s = 0), (p = 160);
          }
          circle.scale.set(1 + s / 60, 1 + s / 80, 1 + s / 60);
          circle.material[0].opacity = p / 160;
          s++;
          p--;
          requestAnimationFrame(render);
        }
        render();
        return circle;
      }

辉光

辉光我觉得是一个比较重要的环姐,上面看的特效,基本被我加了辉光特效。所以看起来有光晕的效果。
首先我们得了解一个概念,图层:
threejs提供了层次的支持。和相机处于同一层次的对象可见,否则不可见。在threejs中,最多可以设置32层,默认的层次是1。层次在有些系统中很有用,可以将不同的模式的对象设成不同的层次,这样,切换模式就只需切换一下相机的层次就可以了。

辉光是直接作用于图层的,所以我们不能直接放在我们的渲染图层,我们可以给辉光放在单独的一个图层,然后把我们需要给辉光的物体模型,一个layers.enable,同时适用辉光图层就好了。

function addBloomPass() {
        // 辉光 后期处理   // 后期处理可以用图层来实现单独图层渲染(把某个物体放到辉光渲染的图层)  其中 OutlinePass外轮廓的后期处理 可以单独作用于物体,不需要分图层

        // layers.set() 设置某个对象为某个图层,会删除过去图层
        // layers.enable() 为某个对象增加某个图层
        // 注意物体的材质,有些材质不能用特效 最好用MeshBasicMaterial
        renderScene = new THREE.RenderPass(scene, camera);

        var bloomPass = new THREE.UnrealBloomPass(
          new THREE.Vector2(window.innerWidth, window.innerHeight),
          1.5,
          0,
          0
        );
        // bloomPass.renderToScreen = true;
        const effectCopy = new THREE.ShaderPass(THREE.CopyShader); //传入了CopyShader着色器,用于拷贝渲染结果
        effectCopy.renderToScreen = true;
        const FXAAShader = new THREE.ShaderPass(THREE.FXAAShader);
        bloomPass.threshold = 0;
        bloomPass.strength = 2.5;
        bloomPass.radius = 0;

        composer = new THREE.EffectComposer(renderer);
        composer.addPass(renderScene);
        composer.addPass(bloomPass);
        composer.addPass(effectCopy);
        composer.render();
      }

然后再animate方法里面添加:

function animate() {
        requestAnimationFrame(animate);
        // 渲染器清除颜色、深度或模板缓存. 此方法将颜色缓存初始化为当前颜色
        renderer.clear();
        controls.update();
        camera.layers.set(1);
        if (composer) { // 把辉光设置在图层1.
          composer.render();
        }
        // 清除深度缓存
        renderer.clearDepth();
        camera.layers.set(0); // 然后把正常部分渲染在图层0
        renderer.render(scene, camera);
      }

之后我们想把某个物体添加到辉光可以直接:
Mesh.layers.enable(1);

粒子星空


粒子星空,是由无数个粒子渲染出来的。
在3D建模过程中,当我们需要创建很多细小的物体时,并不会一个个地创建这些物体,而是通过创建粒子,粒子可以模拟很多效果,例如烟花、火焰、雨滴、雪花、云朵等等。Three.js提供了各种的粒子系统创建方式。从官网例子的demo来看,可以总结分为两类,分别是Points和Sprite
这里我们使用points来实现

现在我们来实现粒子星空:

// 创建粒子星空
      function createAi() {
        var vertexHeight = 15000;
        var planeDefinition = 100;
        var planeSize = 1445000;
        var totalObjects = 50000;
        var geometry = new THREE.Geometry();

        for (i = 0; i < totalObjects; i++) {
          var vertex = new THREE.Vector3();
          vertex.x = Math.random() * planeSize - planeSize * 0.5;
          vertex.y = Math.random() * 100000 + 10000;
          vertex.z = Math.random() * planeSize - planeSize * 0.5;
          geometry.vertices.push(vertex);
          geometry.colors.push(new THREE.Color(Math.random() * 0x00ffff));
        }
        var geometry2 = geometry.clone();

        var material = new THREE.PointCloudMaterial({
          size: 200,
          vertexColors: true,
        });
        var particles = new THREE.PointCloud(geometry, material);
        var material2 = new THREE.PointCloudMaterial({
          size: 200,
          vertexColors: true,
        });
        var particles2 = new THREE.PointCloud(geometry2, material2);
        particles2.rotation.z = -Math.PI;
        scene.add(particles);
        scene.add(particles2);
      }

埃菲尔铁塔obj模型加载

Threejs/Webgl智慧城市部分效果实现_第4张图片
模型加载比较简单,又尤为重要。
因为在我们做项目的过程中,很多复杂的模型,是不用自己去实现的,直接通过3d建模来实现模型,我们直接导入就可以了。同时我们还能对导入的模型进行材质或者点的修改。

使用三维软件导出.obj模型文件的时候,会同时导出一个材质文件.mtl, .obj和.stl文件包含的数据一样都是几何体对象的顶点位置、顶点法向量等顶点相关数据, 材质文件.mtl包含的是RGB颜色值等材质信息。

加载.obj三维模型的时候,可以只加载.obj文件,然后借助three.js引擎自定义材质对象Material,也可以同时加载obj和mtl文件

function loadAFEObj(){
        var OBJLoader = new THREE.OBJLoader();//obj加载器
            OBJLoader.load('./image/aifeier.obj', function(obj) {
                obj.children.forEach((item,index)=>{ // 这里修改下材质,因为我们用的免费的模型,他没有带上mtl文件,所以我们就自己改一下材质,给点颜色变得好看些。
                    item.material = new THREE.MeshBasicMaterial({
                            color: 0x050918,
                            depthWrite: false,
                            side: THREE.DoubleSide,
                            transparent: true
                        })  
                    
                })
                obj.scale.set(0.2, 0.25, 0.2); //放大obj组对象
                obj.position.x= 0
                obj.position.y= 0
                obj.position.z= 0
                scene.add(obj);//返回的组对象插入场景中
            })
        
      }

你可能感兴趣的:(webgl,智慧城市,webgl,three.js)