我编写了自己的HTML5画布 - 基于javaScript的物理引擎来模拟由弹簧连接的多个点。目前该计划的一般结构是
function init(){
// A bunch of event listeners
renderer();
physics();
}
var frameTime = 1;
function physics(){
// iterate the physics
parts.update();
setTimeout(physics, frameTime);
}
// render loop
function renderer(){
// draws a rectangle over the last frame
drawBackground();
// renders the objects themselves
parts.draw();
// update the timeout according to an onscreen slider
frameTime = Math.ceil(101 - speed_r.value) / 2;
setTimeout(renderer, 15);
}
两个不同循环背后的基本原理是人眼只需要看到60fps,但每秒做更多更新会产生更好的物理效果。
我自从做了更多研究后发现用javaScript渲染动画的标准方法是调用requestAnimationFrame()
,据我所知,它具有在取消选择制表符时不渲染的优点,改善电池寿命。但是,由于双循环结构,物理将继续计算,并可能超过渲染器开销。
问题是:性能最佳的是什么,理想情况下最有效的方法是什么?
答案 0 :(得分:5)
要将物理模拟与挂钟同步,并平滑渲染动画,您需要使用固定时间步和插值。阅读this excellent article两个主题。
使用requestAnimationFrame
是节省电池的好主意(如果大多数设备上的电池电量不足,它会降低帧速率)。您可以将它用于物理和渲染循环。
您需要做的是计算自上一帧以来经过的时间,然后使用零或多个固定步骤来保持物理循环与当前(挂钟)时间同步。这就是所有实时物理引擎的工作方式,包括Box2D和Bullet Physics。
根据上面提到的文章,我使用HTML5 Canvas和JavaScript创建了一个完整的JSFiddle,它实现了您的需求。请参阅下面的代码或open it on JSFiddle。
integrate
功能是您更新物理的地方。在代码中,它用于向前推进弹簧模拟。
var t = 0;
var dt = 0.01;
var currentTime;
var accumulator = 0;
var previousState = { x: 100, v: 0 };
var currentState = { x: 100, v: 0 };
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// start animation loop
requestAnimationFrame(animate);
function animate(newTime){
requestAnimationFrame(animate);
if (currentTime) {
var frameTime = newTime - currentTime;
if ( frameTime > 250 )
frameTime = 250;
accumulator += frameTime;
while ( accumulator >= dt )
{
previousState = currentState;
currentState = integrate( currentState, t, dt );
t += dt;
accumulator -= dt;
}
var alpha = accumulator / dt;
var interpolatedPosition = currentState.x * alpha + previousState.x * (1 - alpha);
render( interpolatedPosition );
}
currentTime = newTime;
}
// Move simulation forward
function integrate(state, time, fixedDeltaTime){
var fixedDeltaTimeSeconds = fixedDeltaTime / 1000;
var f = (200 - state.x) * 3;
var v = state.v + f * fixedDeltaTimeSeconds;
var x = state.x + v * fixedDeltaTimeSeconds;
return { x: x, v: v };
}
// Render the scene
function render(position){
// Clear
ctx.fillStyle = 'white';
ctx.fillRect(0,0,canvas.width,canvas.height);
// Draw circle
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(position,100,50,0,2*Math.PI);
ctx.closePath();
ctx.fill();
}
答案 1 :(得分:1)
我想我会考虑将物理部分放在web worker中,然后让它发布主UI线程的更新,这会在requestAnimationFrame
回调上呈现它们。这允许物理代码不断运行(你甚至不需要setTimeout
循环;虽然让它定期产生,所以它可以从前端访问消息 - 尤其是“停止”! - 会是一个好主意) ,同时只按实际需要更新显示。
2018更新:从ES2018开始,工作人员和主线程可以通过SharedArrayBuffer
和Atomics
对象的功能共享内存。它不是必须打破工作以便工作人员可以处理消息,而是可以只检查共享内存中的标志位置(例如,标记说它需要停止)。工作人员甚至可以在计算过程中暂停(即使在标准循环的中间,例如for
或while
),然后通过Atomics.wait
和{{ 1}}。