请添加图片描述

Threejs围墙动画

难点1:

根据顶点构建出来的围墙,默认是z轴朝上。假如模型是y轴朝上,则不好控制围墙的边界顶点。
这里的思路是 把模型绕x轴翻转90度,然后根据rayser射线选取顶点。根据顶点构建完围墙后,再把围墙mesh翻转90度回来

代码:

function addShape() {
  let c = [53.95640312236819, 193.4562016693937, 123.4327246849135, 187.81369736193992,
        125.41600819650955, 218.75893553744257, 51.14432668036085, 223.0195030041947, 53.95640312236819, 193.4562016693937];
  let posArr = [];
  let uvrr = [];
  let h = 10; //围墙拉伸高度
  for (let i = 0; i < c.length - 2; i += 2) {
    // 围墙多边形上两个点构成一个直线扫描出来一个高度为h的矩形
    // 矩形的三角形1
    posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], 0, c[i + 2], c[i + 3], h);
    // 矩形的三角形2
    posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], h, c[i], c[i + 1], h);

    // 注意顺序问题,和顶点位置坐标对应
    uvrr.push(0, 0, 1, 0, 1, 1);
    uvrr.push(0, 0, 1, 1, 0, 1);
  }
  let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
  // 设置几何体attributes属性的位置position属性
  geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
  // 设置几何体attributes属性的位置uv属性
  geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvrr), 2);
  geometry.computeVertexNormals()

  let mesh = new THREE.Mesh(geometry, custMaterial); //网格模型对象Mesh
  scene.add(mesh);
  mesh.rotateX(-Math.PI / 2);

难点2:

围墙的shader编写,这个shader是拷贝的maptalks里面的围墙shader,查看源代码直接拿来用即可。
代码:

const vertexs = {
  normal_vertex: "\n  precision lowp float;\n  precision lowp int;\n  "
      .concat(
          THREE.ShaderChunk.fog_pars_vertex,
          "\n  varying vec2 vUv;\n  void main() {\n    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n    "
      )
      .concat(THREE.ShaderChunk.fog_vertex, "\n  }\n"),
};

const fragments = {
  rippleWall_fragment:
      "\n  precision lowp float;\n  precision lowp int;\n  uniform float time;\n  uniform float opacity;\n  uniform vec3 color;\n  uniform float num;\n  uniform float hiz;\n\n  varying vec2 vUv;\n\n  void main() {\n    vec4 fragColor = vec4(0.);\n    float sin = sin((vUv.y - time * hiz) * 10. * num);\n    float high = 0.92;\n    float medium = 0.4;\n    if (sin > high) {\n      fragColor = vec4(mix(vec3(.8, 1., 1.), color, (1. - sin) / (1. - high)), 1.);\n    } else if(sin > medium) {\n      fragColor = vec4(color, mix(1., 0., 1.-(sin - medium) / (high - medium)));\n    } else {\n      fragColor = vec4(color, 0.);\n    }\n\n    vec3 fade = mix(color, vec3(0., 0., 0.), vUv.y);\n    fragColor = mix(fragColor, vec4(fade, 1.), 0.85);\n    gl_FragColor = vec4(fragColor.rgb, fragColor.a * opacity * (1. - vUv.y));\n  }\n",
};

const custMaterial = new THREE.ShaderMaterial({
  uniforms: {
    time: {
      type: "pv2",
      value: 0,
    },
    color: {
      type: "uvs",
      value: new THREE.Color("#FF4127"),
    },
    opacity: {
      type: "pv2",
      value: 1.0,
    },
    num: {
      type: "pv2",
      value: 10,
    },
    hiz: {
      type: "pv2",
      value: 0.15,
    },
  },
  vertexShader: vertexs.normal_vertex,
  fragmentShader: fragments.rippleWall_fragment,
  blending: THREE.AdditiveBlending,
  transparent: !0,
  depthWrite: !1,
  depthTest: !0,
  side: THREE.DoubleSide,
});

然后循环函数里面控制时间参数。

function animal() {
  renderer.render(scene, camera)
  if (custMaterial) {
    custMaterial.uniforms.time.value += 0.015;
  }
  requestAnimationFrame(animal)
}

demo代码
shader代码来源于maptalks

<template>
   <div ref="test" class="test">

   </div>
</template>

<script setup>

import * as THREE from "three";
import {onMounted, ref} from "vue";
import {MapControls} from 'three/examples/jsm/controls/OrbitControls'

const vertexs = {
  normal_vertex: "\n  precision lowp float;\n  precision lowp int;\n  "
      .concat(
          THREE.ShaderChunk.fog_pars_vertex,
          "\n  varying vec2 vUv;\n  void main() {\n    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n    "
      )
      .concat(THREE.ShaderChunk.fog_vertex, "\n  }\n"),
};

const fragments = {
  rippleWall_fragment:
      "\n  precision lowp float;\n  precision lowp int;\n  uniform float time;\n  uniform float opacity;\n  uniform vec3 color;\n  uniform float num;\n  uniform float hiz;\n\n  varying vec2 vUv;\n\n  void main() {\n    vec4 fragColor = vec4(0.);\n    float sin = sin((vUv.y - time * hiz) * 10. * num);\n    float high = 0.92;\n    float medium = 0.4;\n    if (sin > high) {\n      fragColor = vec4(mix(vec3(.8, 1., 1.), color, (1. - sin) / (1. - high)), 1.);\n    } else if(sin > medium) {\n      fragColor = vec4(color, mix(1., 0., 1.-(sin - medium) / (high - medium)));\n    } else {\n      fragColor = vec4(color, 0.);\n    }\n\n    vec3 fade = mix(color, vec3(0., 0., 0.), vUv.y);\n    fragColor = mix(fragColor, vec4(fade, 1.), 0.85);\n    gl_FragColor = vec4(fragColor.rgb, fragColor.a * opacity * (1. - vUv.y));\n  }\n",
};

const custMaterial1 = new THREE.ShaderMaterial({
  uniforms: {
    time: {
      type: "pv2",
      value: 0,
    },
    color: {
      type: "uvs",
      value: new THREE.Color("#FF4127"),
    },
    opacity: {
      type: "pv2",
      value: 1.0,
    },
    num: {
      type: "pv2",
      value: 10,
    },
    hiz: {
      type: "pv2",
      value: 0.15,
    },
  },
  vertexShader: vertexs.normal_vertex,
  fragmentShader: fragments.rippleWall_fragment,
  blending: THREE.AdditiveBlending,
  transparent: !0,
  depthWrite: !1,
  depthTest: !0,
  side: THREE.DoubleSide,
});

let test =  ref(null), renderer, scene, camera, controls, pmremGenerator

onMounted(() => {
  initModel();
  test.value.appendChild(renderer.domElement)
  animal();
})

function animal() {
  renderer.render(scene, camera)
  if (custMaterial1) {
    custMaterial1.uniforms.time.value += 0.015;
  }
  requestAnimationFrame(animal)
}

function initModel() {
  renderer = new THREE.WebGLRenderer({
    antialias: true, //开启锯齿
  })
  renderer.physicallyCorrectLights = true;
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 0.85
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  let width = test.value.clientWidth
  let height = test.value.clientHeight
  renderer.setPixelRatio(window.devicePixelRatio) //设置设备像素比率,防止Canvas画布输出模糊。
  renderer.setSize(width, height) //设置渲染区域尺寸

  scene = new THREE.Scene()
  camera = new THREE.PerspectiveCamera(30, width / height, 1, 40000)
  camera.up.set(0, 0 ,1)
  controls = new MapControls(camera, renderer.domElement)
  camera.position.set(50, 50, 50)
  addShape()
  scene.add(new THREE.AxesHelper(50))
  let ambient = new THREE.AmbientLight(0xffffff, 0.8)
  scene.add(ambient)
  const hemiLight = new THREE.HemisphereLight(0x00AAFF, 0xFFAA00, 0.8);
  scene.add(hemiLight);

  console.log(scene)
}

function addShape() {
  let c = [0,0, 10, 0, 10, 10, 0, 10, 0, 0]
  let posArr = [];
  let uvrr = [];
  let h = 10; //围墙拉伸高度
  for (let i = 0; i < c.length - 2; i += 2) {
    // 围墙多边形上两个点构成一个直线扫描出来一个高度为h的矩形
    // 矩形的三角形1
    posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], 0, c[i + 2], c[i + 3], h);
    // 矩形的三角形2
    posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], h, c[i], c[i + 1], h);

    // 注意顺序问题,和顶点位置坐标对应
    uvrr.push(0, 0, 1, 0, 1, 1);
    uvrr.push(0, 0, 1, 1, 0, 1);
  }
  let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
  // 设置几何体attributes属性的位置position属性
  geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
  // 设置几何体attributes属性的位置uv属性
  geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvrr), 2);
  geometry.computeVertexNormals()


  let custMaterial = new  THREE.MeshLambertMaterial({
    color: 0X049ef4,
    side : THREE.DoubleSide
  })

  let mesh = new THREE.Mesh(geometry, custMaterial1); //网格模型对象Mesh
  scene.add(mesh);
  // mesh.rotateX(-Math.PI / 2);

}

</script>

<style scoped lang="less">
.test {
  height: 100%;
}
</style>

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐