首页 > 解决方案 > 三.js对象动态变化带LOD动画

问题描述

我用three.js 制作了一个用于3d 工作的网站。

它的概念是拥有如此多文本的宇宙。

我必须实施两件事。

第一的。当相机和物体之间的距离很远时,物体是星星。

第二。当相机和对象之间的距离很近时,对象是文本。

所以,我用LOD来实现它。

一切正常。

但是,star <-> text change trainsition 太粗糙了。

我想在其中插入平滑的变化过渡(动画)。

所以我读了一个文档,可悲的是,它有任何属性。

是否可以在 LOD 中插入动画?

或任何可能的解决方案?

[源代码]

// VARIABLES
let clock, camera, scene, renderer, mixer;

var myElement = document.getElementById("threejs");
const mouse = new THREE.Vector2();
const clicked = new THREE.Vector2();
const target = new THREE.Vector2();
const windowHalf = new THREE.Vector2( window.innerWidth / 2, window.innerHeight / 2 );
const moveState = {forward: 0, back: 0};
var isMobile = false;
var textCount = 200;

checkMobile()
init();

function init() { 
  // CAMERA
  camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2100 );
  camera.position.x = 0;
  camera.position.y = 0;
  camera.position.z = 1200;

  scene = new THREE.Scene();
  scene.fog = new THREE.Fog(0xf05fa6, 300, 400);

  clock = new THREE.Clock();

	// HELPER
  
  const gridHelper = new THREE.PolarGridHelper( 8, 16 );
  scene.add( gridHelper );

  // LIGHT
  const ambientLight = new THREE.AmbientLight( 0xffffff, 0.2 );
  scene.add( ambientLight );

  const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
  directionalLight.position.set( 1, 1, - 1 );
  scene.add( directionalLight );

  // CONTROLS
  if(isMobile) {
    var controls = new THREE.DeviceOrientationControls(camera);
    console.log('isMobile true');
  } else {
    console.log('isMobile false');
  }
  
  // ADD MESH
  var group = new THREE.Group();
  var size = ( isMobile ? 2 : 4 );
  var starsLights = new THREE.Group();

  for ( let i = 0; i < textCount; i ++ ) {
    var lod = new THREE.LOD();
    // Star
    var geometry = new THREE.SphereGeometry(0.3, 16, 16);
    var material = new THREE.MeshBasicMaterial({color: 0xffffff});
    var star = new THREE.Mesh(geometry, material);
    // Text
    let sprite = new THREE.TextSprite({
      textSize: size,
      redrawInterval: 250,
      texture: {
        text: 'TEST TEST TEST',
        fontFamily: 'Arial, Helvetica, sans-serif',
      },
      material: {
        color: 'white',
      },
    });
    // Star Light
    var velX = (Math.random() + 0.1) * 0.1 * (Math.random()<0.5?-1:1);
    var velY = (Math.random() + 0.1) * 0.1 * (Math.random()<0.5?-1:1);
    star.vel = new THREE.Vector2(velX, velY);
    var starLight = new THREE.PointLight(0x000000, 0.8, 3);
    starLight.position.copy(star.position);
    starLight.position.y += 0.5;
    starsLights.add(starLight);
    // Add
    lod.addLevel(sprite, 1);
    lod.addLevel(star, 200);
    lod.position.x = Math.random() * 180-100;
    lod.position.y = Math.random() * 180-100;
    lod.position.z = Math.random() * 1000-40;
    group.add(lod);
//    group.add(starsLights);
  }
  scene.add(group);
  scene.add(starLight);

  // Renderer
  renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.getElementById("universe").appendChild(renderer.domElement);

  // Event handler
  window.addEventListener( 'resize', onWindowResize, false );
  document.addEventListener('mousemove', onMouseMove, false);
  document.addEventListener('mousewheel', onMouseWheel, false);
  document.addEventListener('contextmenu', onContextMenu, false);


  function animate() {
    target.x = ( 1 - mouse.x ) * 0.002;
    target.y = ( 1 - mouse.y ) * 0.002;
    camera.rotation.x += 0.05 * ( target.y - camera.rotation.x );
    camera.rotation.y += 0.05 * ( target.x - camera.rotation.y );

    if(isMobile) {
      controls.update();
    }
    // Object change related to distance
    group.children.forEach(function(child) {
      child.update(camera);
      var distance = camera.position.distanceTo(child.position);
      var opacity = -1 / 400 * distance + 1;
      if (opacity < 0) {
        opacity = 0;
      }
      child.getObjectForDistance(distance).material.opacity = opacity;
    })
    // Render
    requestAnimationFrame( animate );
    render(scene, camera);
  }
  
  animate();
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}

function onMouseWheel(event) {
  if(camera.position.z > 100) {
    event.preventDefault();
    camera.position.z -= event.deltaY * 0.2;
  } else {

  }
}

function render() {
  const delta = clock.getDelta();
  if ( mixer !== undefined ) mixer.update( delta );
  renderer.render( scene, camera );
}

function onTransitionEnd( event ) {
  console.log("Loading Complete");
  event.target.remove();
}

// Exist functions
function checkMobile() {
  var UserAgent = navigator.userAgent;

  if (UserAgent.match(/iPhone|iPod|Android|Windows CE|BlackBerry|Symbian|Windows Phone|webOS|Opera Mini|Opera Mobi|POLARIS|IEMobile|lgtelecom|nokia|SonyEricsson/i) != null || UserAgent.match(/LG|SAMSUNG|Samsung/) != null) {
      isMobile = true;
  } else {
      isMobile = false;
  }
}

function onMouseMove(event) {
  mouse.x = ( (event.clientX/2) - (windowHalf.x/2) );
  mouse.y = ( (event.clientY/2) - (windowHalf.y/2) );
  clicked.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  clicked.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}

function onResize(event) {
  const width = window.innerWidth;
  const height = window.innerHeight;
  
  windowHalf.set( width / 2, height / 2 );
	
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize( width, height );
}

function onContextMenu(event) {
  event.preventDefault();
}

function topFunction() {
  document.body.scrollTop = 0;
  document.documentElement.scrollTop = 0;
}
canvas {
  width: 100%;
  height: 100%;
  
/*  background: #11e8bb;  Old browsers */
/*  background: -moz-linear-gradient(top,  #11e8bb 0%, #8200c9 100%);  FF3.6-15 */
/*  background: -webkit-linear-gradient(top,  #11e8bb 0%,#8200c9 100%);  Chrome10-25,Safari5.1-6 */
/*  background: linear-gradient(to bottom,  #11e8bb 0%,#8200c9 100%);  W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
  background: radial-gradient(circle, #ed1654, #f61e6c, #f76098);
  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#11e8bb', endColorstr='#8200c9',GradientType=0 ); /* IE6-9 */
}

body {
  margin: 0;
}
canvas {
  width: 100%;
  height: 100%;
}
#threejs {
  position: absolute;
  overflow: hidden;
  width: 100%;
  height: 100%;
}
header {
  position: fixed;
  z-index: 9999;
  background-color: white;
  width: 100%;
  top: 0;
  display: flex;
  align-items: center;
  height: 50px;
}
/* Header Left */
.header-left {
  display: flex;
  justify-content: center;
  flex: 1;
}
.header-left img {
  width: 80px;
  height: 20px;
}
/* Header Right */
.header-right {
  flex: 1;
  padding-left: 200px;
}
.header-right a {
  text-decoration: none;
  color: black;
  font-weight: 600;
}
.header-right a:nth-child(2) {
  padding-left: 50px;
  padding-right: 50px;
}
/* Main Company */
.down-btn {
  display: flex;
  position: absolute;
  justify-content: center;
  align-items: center;
  bottom: 0;
  color: white;
  left: 50%;
  font-size: 2rem;
  cursor: pointer;
}
.down-btn a {
  text-decoration: none;
  color: white;
  padding-bottom: 20px;
}
/* Section */
section {
  background-color: aliceblue;
  height: 100%;
}
<html>
<head>
  <link rel="stylesheet" type="text/css" href="style.css">
<!--  <script src="three.js"></script>-->
  <script src="https://cdn.rawgit.com/mrdoob/three.js/master/build/three.min.js"></script> 
  <script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
  <script src="https://unpkg.com/three.texttexture"></script>
  <script src="https://unpkg.com/three.textsprite"></script>
  <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
</head>
<body>
  <header>
    <div class="header-left">
      <a href="#">MAIN</a>
    </div>
    <div class="header-right">
      <a href="#">ABOUT</a>
      <a href="#">PRODUCT</a>
      <a href="#">CONTACT</a>
    </div>
  </header>
  <section id="universe"></section>
  <script src="src.js"></script>
  <section id="section">
    SECTION
  </section>
</body>
</html>

标签: three.js

解决方案


THREE.LOD不能做你正在寻找的东西。但是编写一个在两个级别之间执行平滑 alpha 混合的自定义 LOD 类并不难。这种方法的优点是它被认为比离散的 LOD 更连续。但是,由于在过渡期间会渲染两个对象,因此计算成本会更高一些。

下面的小提琴说明了这种方法(我确信它可以更优雅地实现,但它至少是一个起点^^):https ://jsfiddle.net/f2Lommf5/14585/

创建 LOD 对象时,您可以调整定义过渡范围的阈值。

 const lod = new LOD();
 lod.threshold = 250;

推荐阅读