简单的三维立方体滚动动画不适用于threejs



我是THREEJS的新手,目前我正在尝试使用箭头键移动立方体。请看这把小提琴:https://jsfiddle.net/mauricederegt/y6cw7foj/26/

所有的工作,我可以使用箭头键移动立方体,我甚至设法在移动立方体时绕正确的轴旋转立方体。问题出在动画上。我似乎无法让他们工作。在按下向左箭头键的那一刻,立方体向左移动,并围绕轴滚动。好吧…就在此时,它突然就位,而不是平稳过渡。

我想要的是,当它旋转时,它平稳地向左移动,但如何做到这一点?在代码的末尾,我确实调用了

requestAnimationFrame

但这并没有多大作用。我在这里尝试用CSS来做这件事。这里的动画工作(但从未得到正确的旋转方向(:https://jsfiddle.net/mauricederegt/5ozqg9uL/3/这确实显示了我想要的动画。

那么,我在THREEjs中缺少了什么?非常感谢

您想要的是一种叫做";粗花呢;绘制中间步骤,而不是立即跳到最终结果。有几个JavaScript库可以为您做到这一点,但我将介绍自己实现它的一些基础知识。

以你为例。单击向左箭头时,将沿着-x轴移动网格1单元,并围绕+y轴旋转-PI/2。与其捕捉到这些位置/旋转,不如考虑动画需要多长时间,然后开始划分步骤。

假设你想拿500ms(半秒(。您的浏览器试图以大约60fps的速率运行,因此您有30帧(大约500ms(可以以该速率运行。因此,对于每一帧,可以将长方体移动1/30个单位,并将其旋转-PI/6030帧之后,框应该在正确的位置,进行或进行一些舍入。

我使用";关于";当谈到浏览器的帧速率时,因为你并不总是能保证得到60FPS。如果你的帧速率下降,并且你被锁定到帧速率来绘制动画,那么它也会减慢速度,花费的时间也会比你想要的更长。那么该怎么办呢?

您可以设置实际计时器来逐步执行动画,而不是依赖requestAnimationFrame作为计时器。抛出计算完成动画所需的帧,而不是计算所需的步骤。

我们已经知道,60fps大约是每个16.6ms1帧,所以这是浏览器可以绘制的绝对最大目标。但是,当我们逐步更新时,没有什么能阻止我们走得更快。为了更容易计算,假设我们希望执行50更新步骤,而不是以前的30。这意味着对于500ms播放时间,我们将需要每10ms执行一次更新(略快于帧速率(。此外,由于我们正在执行50个步骤,我们将以1/50为单位更新位置,并以-PI/100为单位旋转。

let animationId = setInterval( ()=>{
// update position by 1/50 units
// update rotation by -PI/100
}, 10 ); // every 10 ms

随着时间间隔的运行,它将更新对象。同时,动画循环会在可能的时候大量生成新的帧。

下面是一个完整的运行示例,只支持左箭头:

let W = window.innerWidth;
let H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(0, 0, 50);
camera.lookAt(scene.position);
scene.add(camera);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1, 1, 1),
new THREE.MeshPhongMaterial({
color: "red"
})
);
cube.position.set(10, 0, 0);
scene.add(cube);
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
function animate() {
requestAnimationFrame(animate);
render();
}
requestAnimationFrame(animate);
const yAxis = new THREE.Vector3(0, 1, 0);
function updateCube() {
//cube.position.x -= 1;
//cube.rotateOnWorldAxis(yAxis, THREE.Math.degToRad(-90));
cube.position.x -= 1 / 50;
cube.rotateOnWorldAxis(yAxis, -(Math.PI / 100));
}
let step = 0;
let animationId = null;
function startStepping() {
animationId = setInterval(() => {
updateCube();
if (++step === 50) {
clearInterval(animationId);
animationId = null;
}
}, 10)
}
function handleKeyboard(e) {
//if (e.keyCode == 65 || e.keyCode == 37) {
//  updateCube();
//}
if (animationId === null && (e.keyCode == 65 || e.keyCode == 37)) {
step = 0;
startStepping();
}
}
document.addEventListener("keydown", handleKeyboard, false);
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>

现在这种方法有一个缺点。有时,您可能会看到跳过的更新(如果浏览器的帧速率下降(,或者两次绘制相同的位置(如果更新率明显低于浏览器的帧率(。你可以尝试两全其美,但从渲染循环中实时计算帧速率,并相应地调整帧步数,但此时你必须问,计算这些统计数据所花费的额外时间是否真的会影响实现稳定的帧速率锁定绘制速率。

基于按键输入的切换

因为您的键输入现在与绘图脱节,所以您现在需要某种标志来确定要执行的操作。您的按键处理程序将设置该标志,然后updateCube将根据该标志进行操作。类似于:

let action = null
function startStepping(){
// set up the interval...
// but then also ensure the action stops after the animation plays:
setTimeout( () => action = null, 500 );
}
function handleKeyboard(e){
if (animationId === null) {
step = 0;
switch(e.keyCode){
case 37:
case 65:
action = "left";
break;
// other keys...
}
startStepping();
}
}
function updateCube(){
switch(action){
case "left":
// move as if it's rolling left
break;
case "right":
// move as if it's rolling right
break;
// etc. for the other directions
}
}

最新更新