threejs实现任意模型的爆炸效果

实现原理

  • 初始化数据:遍历计算每个mesh的中心点,保存在userData上
  • 爆炸计算逻辑:以当前中心的到原点的矢量A作为爆炸方向,|A| * d(爆炸系数 ) 作为爆炸距离;
  • 渲染:在循环渲染中每次更改 d ,这样就可以实现爆炸动画效果了
// 初始化爆炸数据保存到每个mesh的userdata上
function initExplodeModel(modelObject: THREE.Object3D) {
  if (!modelObject) return;

  // 计算模型中心
  const explodeBox = new THREE.Box3();
  explodeBox.setFromObject(modelObject);
  const explodeCenter = getWorldCenterPosition(explodeBox);

  const meshBox = new THREE.Box3();

  // 遍历整个模型,保存数据到userData上,以便爆炸函数使用
  modelObject.traverse(function (value: any) {
    if (value.isLine || value.isSprite) return;
    if (value.isMesh) {
      meshBox.setFromObject(value);

      const meshCenter = getWorldCenterPosition(meshBox);
      // 爆炸方向
      value.userData.worldDir = new THREE.Vector3()
        .subVectors(meshCenter, explodeCenter)
        .normalize();
      // 爆炸距离 mesh中心点到爆炸中心点的距离
      value.userData.worldDistance = new THREE.Vector3().subVectors(meshCenter, explodeCenter);
      // 原始坐标
      value.userData.originPosition = value.getWorldPosition(new THREE.Vector3());
      // mesh中心点
      value.userData.meshCenter = meshCenter.clone();
      value.userData.explodeCenter = explodeCenter.clone();
    }
  });
}
// 模型爆炸函数 
const explodeModel = (model: THREE.Object3D, scalar: number) => {
  model.traverse(function (value) {
    // @ts-ignore
    if (!value.isMesh || !value.userData.originPosition) return;
    const distance = value.userData.worldDir
      .clone()
      .multiplyScalar(value.userData.worldDistance.length() * scalar);
    const offset = new THREE.Vector3().subVectors(
      value.userData.meshCenter,
      value.userData.originPosition
    );
    const center = value.userData.explodeCenter;
    const newPos = new THREE.Vector3().copy(center).add(distance).sub(offset);
    const localPosition = value.parent?.worldToLocal(newPos.clone());
    localPosition && value.position.copy(localPosition);
  });
};

辅助代码

function getWorldCenterPosition(box: THREE.Box3, scalar = 0.5): THREE.Vector3 {
  return new THREE.Vector3().addVectors(box.max, box.min).multiplyScalar(scalar);
}

伪代码

	<Slider onChange={onChnage}/>

	import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
	const loader = new GLTFLoader();
    let obj= null
    loader.load('your-model-url', (model) => {
        obj = model.scene
        initExplodeModel(model.scene)
    })

    const onChnage = (val) => {
        explodeModel(obj, value) 
    }

完整代码

  • 视图层
<Slider onChange={onChnage}/>
  • 逻辑层
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 初始化爆炸数据保存到每个mesh的userdata上
function initExplodeModel(modelObject: THREE.Object3D) {
  if (!modelObject) return;

  // 计算模型中心
  const explodeBox = new THREE.Box3();
  explodeBox.setFromObject(modelObject);
  const explodeCenter = getWorldCenterPosition(explodeBox);

  const meshBox = new THREE.Box3();

  // 遍历整个模型,保存数据到userData上,以便爆炸函数使用
  modelObject.traverse(function (value: any) {
    if (value.isMark || value.isMarkChild || value.isLine || value.isSprite) return;
    if (value.isMesh) {
      meshBox.setFromObject(value);

      const meshCenter = getWorldCenterPosition(meshBox);
      // 爆炸方向
      value.userData.worldDir = new THREE.Vector3()
        .subVectors(meshCenter, explodeCenter)
        .normalize();
      // 爆炸距离 mesh中心点到爆炸中心点的距离
      value.userData.worldDistance = new THREE.Vector3().subVectors(meshCenter, explodeCenter);
      // 原始坐标
      value.userData.originPosition = value.getWorldPosition(new THREE.Vector3());
      // mesh中心点
      value.userData.meshCenter = meshCenter.clone();
      value.userData.explodeCenter = explodeCenter.clone();
    }
  });
}

function getWorldCenterPosition(box: THREE.Box3, scalar = 0.5): THREE.Vector3 {
  return new THREE.Vector3().addVectors(box.max, box.min).multiplyScalar(scalar);
}

function explodeModel(model: THREE.Object3D, scalar: number) {
  model.traverse(function (value) {
    // @ts-ignore
    if (!value.isMesh || !value.userData.originPosition) return;
    const distance = value.userData.worldDir
      .clone()
      .multiplyScalar(value.userData.worldDistance.length() * scalar);
    const offset = new THREE.Vector3().subVectors(
      value.userData.meshCenter,
      value.userData.originPosition
    );
    const center = value.userData.explodeCenter;
    const newPos = new THREE.Vector3().copy(center).add(distance).sub(offset);
    const localPosition = value.parent?.worldToLocal(newPos.clone());
    localPosition && value.position.copy(localPosition);
  });
};

let obj: any = null

const loader = new GLTFLoader()
loader.load('your-model-url', (model) => {
    obj = model.scene
    initExplodeModel(model.scene)
})

const onChnage = (val) => {
    explodeModel(obj, val) 
}

效果图

  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
实现模型波纹效果,可以使用three.js中的ShaderMaterial和RenderTarget实现。具体步骤如下: 1. 创建两个RenderTarget,一个用于存储模型的深度信息,另一个用于存储波纹效果的纹理信息。 ```javascript var depthRenderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter } ); var rippleRenderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter } ); ``` 2. 创建一个ShaderMaterial,该材质需要使用深度RenderTarget和波纹RenderTarget中的信息来计算出波纹效果的纹理。 ```javascript var rippleMaterial = new THREE.ShaderMaterial( { uniforms: { depthTexture: { value: depthRenderTarget.texture }, rippleTexture: { value: rippleRenderTarget.texture }, time: { value: 0 }, resolution: { value: new THREE.Vector2( window.innerWidth, window.innerHeight ) } }, vertexShader: document.getElementById( 'rippleVertexShader' ).textContent, fragmentShader: document.getElementById( 'rippleFragmentShader' ).textContent } ); ``` 3. 将该ShaderMaterial应用到模型上,并将模型渲染到深度RenderTarget中。 ```javascript var model = ...; // 创建模型 var scene = new THREE.Scene(); scene.add( model ); var camera = ...; // 创建相机 var renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); renderer.setRenderTarget( depthRenderTarget ); renderer.render( scene, camera ); ``` 4. 创建一个平面,使用上面创建的ShaderMaterial来渲染该平面,并将该平面渲染到波纹RenderTarget中。 ```javascript var ripplePlaneMaterial = new THREE.MeshBasicMaterial( { map: rippleRenderTarget.texture } ); var ripplePlaneGeometry = new THREE.PlaneGeometry( window.innerWidth, window.innerHeight ); var ripplePlane = new THREE.Mesh( ripplePlaneGeometry, ripplePlaneMaterial ); scene.add( ripplePlane ); var rippleCamera = ...; // 创建相机 var rippleRenderer = new THREE.WebGLRenderer(); rippleRenderer.setSize( window.innerWidth, window.innerHeight ); rippleRenderer.setRenderTarget( rippleRenderTarget ); rippleRenderer.render( rippleScene, rippleCamera ); ``` 5. 在循环渲染中更新ShaderMaterial的uniform变量,并重新渲染波纹RenderTarget。 ```javascript function animate() { requestAnimationFrame( animate ); rippleMaterial.uniforms.time.value += 0.1; rippleRenderer.setRenderTarget( rippleRenderTarget ); rippleRenderer.render( rippleScene, rippleCamera ); rippleRenderer.setRenderTarget( null ); renderer.render( scene, camera ); } ``` 6. 在ShaderMaterial的vertexShader和fragmentShader中实现波纹效果的计算。 vertexShader: ```glsl uniform float time; varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); // 计算波纹效果 float frequency = 10.0; float amplitude = 0.2; float speed = 1.0; vec3 pos = position; pos.z += sin( pos.x * frequency + time * speed ) * amplitude; pos.z += sin( pos.y * frequency + time * speed ) * amplitude; gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 ); } ``` fragmentShader: ```glsl uniform sampler2D depthTexture; uniform sampler2D rippleTexture; uniform vec2 resolution; varying vec2 vUv; void main() { // 获取深度信息 float depth = texture2D( depthTexture, vUv ).r; // 获取波纹信息 float ripple = texture2D( rippleTexture, vUv ).g; // 根据深度和波纹信息计算出颜色 vec3 color = vec3( depth * ripple ); gl_FragColor = vec4( color, 1.0 ); } ``` 这样就可以在模型实现波纹效果了。具体实现可以参考以下示例代码: https://codepen.io/wanbo/pen/XWpXzjV

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值