vue2+three.js做出一个精美的3D地图——3.给地图描上边线,并添加上辉光效果

文章目录

  • 前言
  • 一、如何生成边线?
  • 二、给线条加上辉光效果
    • 下期预告: 下一期 我们将在地图上添加精灵和别的几何体,然后让它们可以被点击,从而来产生一些交互


前言

1.3D地图的第三步,增加描边的线条和辉光

一、如何生成边线?

1.还记得当初我们引入的map.json文件去生成的地图吗?如果忘了,可以去复习第二章,现在我们要做的是创建一个arr,把map.json的数据以省份的格式放进来,如果直接使用地图的数据,线条的起始点和另一个起始点会交叉在一起,非常混乱

//在 generateGeometry方法下新建 linArr数组
 let linArr=[]

2.每次循环清零linArr,并push进当前省份的数据,把当前linArr的省份数据 发给getLin(),去创建线条,
要调整成一下z轴的高度,是地图z轴高度的下面一点点
linArr.push(5.7)
注意代码里我关于linArr数组的操作

multiPolygon.forEach((polygon) => {
                // linArr.length=0
                //构建几何图形
                //清零
                linArr.length=0
                const shape = new THREE.Shape()
                for (let i = 0; i < polygon.length; i++) {
                    const [x, y] = projection(polygon[i])
                    //如果有NvN的数据就让他跳过,要不然three.js会报错
                    if(!x){
                        continue;
                    }
                    //指定我们的起点
                    if (i === 0) {
                        shape.moveTo(x, -y)
                    }
                    // this. pointArr.push(x)
                    // this. pointArr.push(-y)
                    // this. pointArr.push(2)
                    linArr.push(x)
                    linArr.push(-y)
                    linArr.push(5.7)

                     //后续就开始从起点画线
                     //如果你使用过canvans画线,那你肯定秒懂,他们是一个道理
                    shape.lineTo(x, -y)

                    // lineGeometry.vertices.push(new THREE.Vector3(x, -y, 4))
                }
                  
                //注意这里
                this.getLin(linArr) 


               

                //构建地图的 正面贴图 ,这里使用基础材质,关于材质,大家可以去详细看看api
                material = new THREE.MeshBasicMaterial  ({
                    // color:'#144685',
                    transparent: true,
                    opacity:1,
                    map:texture,
                })
                   //构建地图的 侧面贴图 ,这里使用基础材质
                const material_1 = new THREE.MeshBasicMaterial   ({
                    color: '#558BAB',
                    transparent: true,
                    opacity: 0.45,

                })

                const extrudeSettings = {
                    depth: 2,
                    bevelEnabled: false,
                }
                //这里我们把先前构造的几何图形 通过ExtrudeGeometry 拉伸成几何体
                const geometry = new THREE.ExtrudeGeometry(
                    shape,
                    extrudeSettings
                )

               
                const  extrudeSettings_2={
                    depth: 1,
                    bevelEnabled: false,
                }
                const geometry_2 = new THREE.ExtrudeGeometry(
                    shape,
                    extrudeSettings_2
                )
                const material_2 = new THREE.MeshBasicMaterial   ({
                    color: '#ffffff',
                    transparent: true,
                    opacity: 0,

                })

                //把几何体和  我们两个贴图 合成一个 网格对象 mesh
                const mesh = new THREE.Mesh(geometry, [material, material_1])

                const mesh_2 = new THREE.Mesh(geometry_2, [material_1,material_2])
               
                const mesh_3= mesh_2.clone()
                mesh.castShadow=true
              //把网格对象加入省份的3d对象
                mesh.position.z=4
                mesh_2.position.z=1
                mesh_3.position.z=-2
                province.add(mesh)
                province.add(mesh_2)
                province.add(mesh_3)
               
              

            })

3.在生成线条之前,我们需要引入three.meshline来控制线条的宽度

   npm i three.meshline
   //引入
   import { MeshLine, MeshLineMaterial, MeshLineRaycast } from 'three.meshline';

webgl不支持线条的线条的宽度属性,用这个就可以很好实现,还不受版本的限制。 接下来我们看看如何生成线条,我会用大量注释帮助你理解。

getLin(arr){
               //Group对象,你可以把他看做一个数组,用这个主要是为了更方便的去控制每一个省份的线条
                let  groups=new THREE.Group()
                if(arr.length!=0){
                  // 初始化MeshLine
                      const line = new MeshLine()
                  // 传入顶点坐标数据
                     line.setPoints(vertices)
                  // 生成线材质
                let material = new MeshLineMaterial({
                    useMap: 0,
                    color: '#0fb9ee',
                    opacity: 1,
                    resolution: new THREE.Vector2(window.innerWidth, window.innerHeight),
                    sizeAttenuation: 2,
                    lineWidth: 0.6,
                    transparent: true,
                    wireframe: false,
                    })   
                    //构建线条的网格对象
                    const mesh = new THREE.Mesh(line.geometry,material)
                   //把他放进去分层1  类似于z-index,方便后面实现辉光
                    mesh.layers.enable(1)
                    //加入数组
                    groups.add(mesh)
                    //把group加入地图对象
                    map.add(groups)
                }
            },

效果展示:vue2+three.js做出一个精美的3D地图——3.给地图描上边线,并添加上辉光效果_第1张图片

二、给线条加上辉光效果

1.对于新手来讲,接下来的内容会比较晦涩难懂,我建议你可以结合起来看看three.js的官网案例代码去帮助你理解

three.js案例

2.准备工作,引入包,并开启双层渲染
这些包都是three.js自带的,不要额外引入,去包里找就好了。

import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader';
     //混合器
    let composer
    const ENTIRE_SCENE = 0,// 全部的,整个的场景
        BLOOM_SCENE = 1; // 光晕场景
    const bloomLayer = new THREE.Layers();// 光晕层次-创建一个图层对象
    bloomLayer.set(BLOOM_SCENE);// 先把光晕层次设置光晕场景的层次1
    const darkMaterial = new THREE.MeshBasicMaterial({ color: "black" });// 跟辉光光晕有关的变量

    const materials = {};// 跟辉光光晕有关的变量
    const params = {
        exposure: 0,// 暴露
        bloomStrength: 0.78,// 光晕强度
        bloomThreshold: 0,// 光晕阈值
        bloomRadius: 0.1,// 光晕半径
    };
    let bloomPass
    let finalComposer
    let bloomComposer

主要思路: 请一定要先想明白这里,再继续下去,辉光渲染的主要思路在于 分层渲染,其中的分层 它相当于重新创建了一个渲染的通道,你也可以简单的理解为z-index分层,他们不在一个层次,我们渲染先渲染需要发光的部分 也就是 layers.enable(1)的部分,在渲染另一个 layers.enable(0)的部分,最后再让他们合并。
3.开始构建
顶点和片段着色器
需要用GLSL语言来编写,目前我还不会,这是从官网demo找的代码片段,主要作用就是用于制造光晕

//顶点着色器
<script type="x-shader/x-vertex" id="vertexshader">
      varying vec2 vUv;
      void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
      }
</script>
//片段着色器
<script type="x-shader/x-fragment" id="fragmentshader">
      uniform sampler2D baseTexture;
      uniform sampler2D bloomTexture;
      varying vec2 vUv;
      void main() {
          gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
      }
</script>
feng(){

// 通过ShaderPass构造函数把FXAAShader着色器和uniforms构成的对象作为参数,创建一个锯齿通道FXAAShaderPass,然后把锯齿通道插入到composer中。
const effectFXAA = new ShaderPass(FXAAShader);
effectFXAA.uniforms["resolution"].value.set(
    0.6 / window.innerWidth,
    0.6 / window.innerHeight
); // 渲染区域Canvas画布宽高度 
effectFXAA.renderToScreen = true;
// 去掉锯齿---1
const renderScene = new RenderPass(scene, camera);// RenderPass这个通道会在当前场景(scene)和摄像机(camera)的基础上渲染出一个新场景,新建:
// 添加光晕效果---2
bloomPass = new UnrealBloomPass( // UnrealBloomPass通道可实现一个泛光效果。
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    1.5,
    0.4,
    0.85
);
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
// 添加光晕效果---2
// 着色器通道容器--放进容器里
bloomComposer = new  EffectComposer(renderer); // EffectComposer可以理解为着色器通道容器,着色器通道按照先后顺序添加进来并执行
bloomComposer.renderToScreen = false;
bloomComposer.addPass(renderScene);
bloomComposer.addPass(bloomPass); // 添加光晕效果
bloomComposer.addPass(effectFXAA);// 去掉锯齿
// 着色器通道容器--放进容器里
const finalPass = new ShaderPass(
    new THREE.ShaderMaterial({
        uniforms: {
            baseTexture: { value: null },
            bloomTexture: { value: bloomComposer.renderTarget2.texture },
        },
        vertexShader: document.getElementById("vertexshader").textContent,
        fragmentShader: document.getElementById("fragmentshader")
            .textContent,
        defines: {},
    }),
    "baseTexture"
);
finalPass.needsSwap = true;
finalComposer = new EffectComposer(renderer);
finalComposer.addPass(renderScene);
finalComposer.addPass(finalPass);
finalComposer.addPass(effectFXAA);
},

关键的四个类

  1. ShaderPass:用于添加自定义的GLSL着色器(FXAAShader主要用于制造模糊效果 )

  2. RenderPass:一个新的通道,他会渲染出一个新的场景

  3. UnrealBloomPass:后期处理通道,用于模拟真实世界中的光晕效果,即模拟光线在明亮区域产生的模糊和光晕效果,他有四个参数,主要用来调节光晕
    radius:表示光晕散发的半径。
    strength:表示光晕的强度,值越大,光晕效果越明显。
    threshold:表示产生光晕的光照强度阈值,只有光照强度大于该值的部分才会产生光晕效果。
    blurKernel:表示用于模糊的卷积核,可以根据需要自定义。

  4. EffectComposer:用于实现后期处理效果的一个对象,他可以把先前生成的渲染通道合并起来渲染,每个通道都是一种后期处理的效果,包括颗粒 模糊等

    然后去吧 渲染器的 alpha打开,渲染器输出的图像将具有透明度,黑色将完全透明。

   renderer = new THREE.WebGLRenderer({
          alpha: true,
      })

重写 render方法,渲染手法重点来了

  1. 先把场景里的所有几何体的纹理拿出来,然后给几何体的加上黑色纹理,前面把渲染器的 alpha打开了 渲染的都是透明的
  2. bloomComposer.render(); 渲染通道的集合
  3. 再把纹理还给场景里的几何体,然后删除刚给上的黑色纹理
  4. finalComposer.render(); 在渲染一遍
render() {

scene.traverse((obj) => {

      if (bloomLayer.test(obj.layers) === false) {
          materials[obj.uuid] = obj.material;
          obj.material = darkMaterial;
      }
  });

  bloomComposer.render();
 scene.traverse((obj) => {
      if (materials[obj.uuid]) {
          obj.material = materials[obj.uuid];
          delete materials[obj.uuid];
      }
  });
  finalComposer.render();
}

现在已经完工了 你想把哪个几何体设置为辉光效果,就加上 layers.enable(1),让他进入另一个通道渲染
我们回到 getlin方法

const mesh = new THREE.Mesh(line.geometry,material)

                    mesh.layers.enable(1)

效果:
vue2+three.js做出一个精美的3D地图——3.给地图描上边线,并添加上辉光效果_第2张图片

下期预告: 下一期 我们将在地图上添加精灵和别的几何体,然后让它们可以被点击,从而来产生一些交互

你可能感兴趣的:(vue2+three.js,构建一个精美的中国地图,javascript,3d,开发语言,vue.js,前端)