threejs--模型动画线性控制
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了threejs--模型动画线性控制相关的知识,希望对你有一定的参考价值。
参考技术A 最近遇见一个比较有意思需求,需要将模型动画像视频一样可以线性拖拽播放动画进程、调节动画播放速度及随时暂停和播放动画。效果如下图现在开始干货分享:
1. 初始化场景、相机、灯光、及模型加载的基本工作就不再赘述了,不太清楚的朋友看下threejs--初创项目。
2. 需要一个k好动画的模型,这个大家可以自己完成
3. 我们需要一个可以自由滑动的滑块,用来记录动画播放的进程。还需要一个暂停和播放的按钮来控制动画的开启和暂停
<p class="h_manualDrop"><span>装配进度</span><input class="h_manualInstallVal" type="range" min="0" step="0.01"></p>
4. 模型加载阶段 我们需要对模型动画进行处理
load.loader('./model/test.glb,function(gl)
scene.add(gl.scene)//将模型加入到场景中
mixer =new THREE.AnimationMixer(gl.scene);
action = mixer.clipAction(gl.animations[0]);
action.play();///
$('.h_manualInstallVal').attr('max',glb.animations[0].duration.toFixed(1));// 给滑块初始值
renderer.render( scene, camera );//渲染
);
5. 在刷新场景时同步滑块上动画进度
function onUpdate()
let renderTime = clock.getDelta();
if (action) // 实时更新滑块进度
$('.h_manualInstallVal').val(action.time);
if (mixer)
mixer.update(renderTime);
requestAnimationFrame( onUpdate );
renderer.render( scene, camera );
controls.update();// 想让相机控制器有效 这个就需要实时更新控制器。必须在摄像机的变换发生任何手动改变后调用
6. 滑块添加事件,反向控制模型动画播放进度
$('.h_manualInstallVal').on('input',function ()
action.time=$(this).val()*1;
action.paused=true;
$('.h_manualDrop').attr('data-bool','act');
)
7. 播放暂停按钮控制动画的播放与暂停
$('.h_manualDrop').on(‘click',function ()// 动画 播放与暂停
if($(this).attr('data-bool')=='act')
$(this).attr('data-bool','');
action.paused=false;
else
$(this).attr('data-bool','act');
action.paused=true;
);
结语:模型的自主操控之前已经讲过了,不再赘述,欢迎大家一起学习交流 需要测试模型的话 可以给我留言
threejs+blender页面滚动伴随着三维动画
这几天在跟着国外的教程学习在网页中利用建好的动画模型如何呈现出来,经过一周的学习,终于可以自己在网页中操作3D模型了。
现在可以点击模型中某个对象改变这个对象的材质
可以利用GASP控制模型的运动
可以控制模型中某些对象的动画暂停和播放
随着页面滚动,模型切换不同的视角
我一周学完了https://threejsfundamentals.org/这个网址里的所有内容,并在https://sbcode.net/threejs/这个网址上补充学习了模型压缩后的处理方式,基本了解了网页中三维动画的原理,虽然对threejs的模型概念还不是很清楚,相信过一段时间会有更深入的了解。中文文档方面也参考了http://www.yanhuangxueyuan.com/Three.js/ 这个网站。
学完之后,就想着找几个案例来模仿一下。于是,找了下面几个参考案例。
1. https://superfanstudio.greenparksports.com/
这个案例看了一下源码,模型都是骨骼动画,还是有些复杂的,过一段时间再补充说明哈。
2. https://ar.egorovagency.com/
这个案例通过滚动插件来切换不同的模型,同时伴随着各种动画。它的模型都是经过压缩处理的,用到了DRACOLoader这个插件。也是比较容易模仿的。
3. https://circus-inc.com/en/
这个案例里提到的模型gltb格式,没有压缩,直接拿下来按着这个动画做了一遍,还是比较容易的,难点在于控制动画什么时候暂停和播放,模型中某个元件的拖拽。这个后续会继续讲。
下面拿第二个案例描述代码:
代码都在node+webpack环境中运行,引用的js和样式通过import导入:
import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import {GUI} from 'dat.gui'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import gsap from 'gsap'
关联html的canvas
const canvas = document.querySelector('.webgl');
const renderer = new THREE.WebGLRenderer({canvas,alpha: true,});
renderer.shadowMap.enabled = true;
相机初始化
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.01;
const far = 1200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
控制器
const controls = new OrbitControls(camera, canvas);
controls.update();
新建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color('#DEFEFF');
环境光
const skyColor = 0xffffff; // light blue
const groundColor = 0xB97A20; // brownish orange
const intensity = 1;
const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
scene.add(light);
模型压缩解压处理
var dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('');
dracoLoader.setDecoderConfig({ type: 'js' });
导入gltf格式的模型文件
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('bear.gltf', (gltf) => {
//模型初始大小位置
gltf.scene.scale.set(.2,.2,.2)
gltf.scene.position.set(3.3,5.4,3.2)
//模型初始化位移缩放动画
t1.to(gltf.scene.scale,{x:.08,y:.08,z:.08,duration:1})
t1.to(gltf.scene.position,{y:5.2,duration:1})
//调用模型动画
var mixer = new THREE.AnimationMixer( gltf.scene );
AnimationAction = mixer.clipAction( gltf.animations[ 0 ] )
// AnimationAction.loop = THREE.LoopOnce;
// AnimationAction.clampWhenFinished = true;
AnimationAction.time = 0;
//gltf.animations[ 0 ].duration = 18;
AnimationAction.play();
// setTimeout(function(){
// AnimationAction.paused = true
// },9000)
// 加入混合器中
mixers.push( mixer );
scene.add(gltf.scene);
gltf.scene.traverse((obj) => {
if (obj.castShadow !== undefined) {
obj.castShadow = true;
obj.receiveShadow = true;
}
});
//更新
gltf.scene.updateMatrixWorld();
// compute the box that contains all the stuff
// from root and below
const box = new THREE.Box3().setFromObject(gltf.scene);
const boxSize = box.getSize(new THREE.Vector3()).length();
const boxCenter = box.getCenter(new THREE.Vector3());
// set the camera to frame the box
frameArea(boxSize * 0.5, boxSize, boxCenter, camera);
// update the Trackball controls to handle the new size
controls.maxDistance = boxSize;
controls.target.copy(boxCenter);
controls.update();
},
(xhr) => {
console.log((xhr.loaded/xhr.total*100)+'% loaded')
},
(error) => {
console.log(error)
}
);
}
//render
let clock = new THREE.Clock();
function render(time) {
time *= 0.001; // convert to seconds
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
var delta = clock.getDelta();
//console.log(mixers.length)
for ( var i = 0; i < mixers.length; i ++ ) { // 重复播放动画
mixers[ 0 ].update( delta );
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
防锯齿
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
调整中心位置
function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
const halfFovY = THREE.MathUtils.degToRad(camera.fov * .5);
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
// compute a unit vector that points in the direction the camera is now
// in the xz plane from the center of the box
const direction = (new THREE.Vector3())
.subVectors(camera.position, boxCenter)
.multiply(new THREE.Vector3(1, 0, 1))
.normalize();
// move the camera to a position distance units way from the center
// in whatever direction the camera was from the center already
camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
// pick some near and far values for the frustum that
// will contain the box.
camera.near = boxSize / 100;
camera.far = boxSize * 100;
camera.updateProjectionMatrix();
// point the camera to look at the center of the box
camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
}
点击某个元件
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick( event ) {
//通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
raycaster.setFromCamera( mouse, camera );
// 获取raycaster直线和所有模型相交的数组集合
var intersects = raycaster.intersectObjects( scene.children,true );
//console.log(intersects);
//将所有的相交的模型的颜色设置为红色,如果只需要将第一个触发事件,那就数组的第一个模型改变颜色即可
for ( var i = 0; i < intersects.length; i++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
console.log(intersects[ i ].object.name)
if(intersects[ i ].object.name==='ball_pachi'){
AnimationAction.paused = false
}
//intersects[ i ].object.material.color.set( 0xff0000 );
// if(intersects[ i ].object.name=="brain"){
// intersects[ i ].object.material.color.set( 0xff0000 );
// }
}
}
window.addEventListener( 'click', onMouseClick, false );
以上是关于threejs--模型动画线性控制的主要内容,如果未能解决你的问题,请参考以下文章