说说Babylon.js中scene.deltaTime的大坑
诡异的问题
下面是给一个材质设置发光颜色周期变化和纹理偏移的代码,你能感觉到这里面可能出现的问题吗?
var passTime = 0;
var uOffset = 0;
var deltaTime = 0;
function SetEmissiveColor() {
passTime += scene.deltaTime * 0.05;
if(passTime > 6.2832) passTime -= 6.2832;
var multi = (Math.sin(passTime) + 1) * 0.5;
material.emissiveColor = new BABYLON.Color3(9 * multi, 2.75 * multi, 0);
var offset = scene.deltaTime * 0.001;
material.diffuseTexture.uOffset -= offset;
material.opacityTexture.uOffset -= offset;
}
scene.onBeforeRenderObservable.add(SetEmissiveColor);
实际在浏览器中运行时,你会发现有可能完全没有实现预期的效果。如果你打印一下passTime的值,可能是一直都是NaN,所以最终会导致material.emissiveColor 的值完全不可用。
这是咋回事呢?原来scene.deltaTime 在场景的第一帧渲染之前是 undefined,这导致 passTime变量在下面这行代码
passTime += scene.deltaTime * 0.05;
执行的时候就变成了NaN,然后NaN的自增就一直是NaN了,所以计算得到的颜色值也就一直错了,哈哈。
下面说说解决方法。
解决方法一
添加if语句检查scene.deltaTime的值是否可用,参考代码如下:
var passTime = 0;
var uOffset = 0;
var deltaTime = 0;
function SetEmissiveColor() {
if (scene.deltaTime !== void 0 && !isNaN(scene.deltaTime)){
passTime += scene.deltaTime * 0.05;
if(passTime > 6.2832) passTime -= 6.2832;
var multi = (Math.sin(passTime) + 1) * 0.5;
material.emissiveColor = new BABYLON.Color3(9 * multi, 2.75 * multi, 0);
var offset = scene.deltaTime * 0.001;
material.diffuseTexture.uOffset -= offset;
material.opacityTexture.uOffset -= offset;
}
}
scene.onBeforeRenderObservable.add(SetEmissiveColor);
上述代码在执行passTime的自增的之前通过 if 语句对scene.deltaTime进行了检查,这样就不会在scene.deltaTime不可用的时候进行计算了。
知识点:scene.deltaTime !== void 0 这个判断里面也可以写成 scene.deltaTime !== undefined,但是这里为啥用的是 void 0 而没有用undefined 呢,下面这个链接讲了这个问题:
关于void 0 与 undefined。
解决方法二
使用定时器,由于scene.deltaTime只是在第一帧渲染完成之前有问题,就没有必要每帧判断,下面的代码在定时器中判断scene.deltaTime的值,待其合理之后再把SetEmissiveColor方法添加到scene.onBeforeRenderObservable事件中,与此同时移除了这个定时器,这样就避免了每帧都要检查scene.deltaTime的合理性。参考代码如下:
var passTime = 0;
var uOffset = 0;
var deltaTime = 0;
function SetEmissiveColor() {
passTime += scene.deltaTime * 0.05;
if(passTime > 6.2832) passTime -= 6.2832;
var multi = (Math.sin(passTime) + 1) * 0.5;
material.emissiveColor = new BABYLON.Color3(9 * multi, 2.75 * multi, 0);
var offset = scene.deltaTime * 0.001;
material.diffuseTexture.uOffset -= offset;
material.opacityTexture.uOffset -= offset;
}
var checkDeltaTime = setInterval(function () {
if (scene.deltaTime !== void 0 && !isNaN(scene.deltaTime)) {
scene.onBeforeRenderObservable.add(SetEmissiveColor);//scene.deltaTime可用之后再添加到事件中
clearInterval(checkDeltaTime); // 清除定时器
}
}, 20); // 每20毫秒检查一次
解决方法三
不使用scene.deltaTime,改用engine.getDeltaTime()方法,参考代码如下:
var passTime = 0;
var uOffset = 0;
var deltaTime = 0;
function SetEmissiveColor() {
passTime += engine.getDeltaTime() * 0.05;
console.log(engine.getDeltaTime());
if(passTime > 6.2832) passTime -= 6.2832;
var multi = (Math.sin(passTime) + 1) * 0.5;
material.emissiveColor = new BABYLON.Color3(9 * multi, 2.75 * multi, 0);
var offset = engine.getDeltaTime() * 0.001;
material.diffuseTexture.uOffset -= offset;
material.opacityTexture.uOffset -= offset;
}
scene.onBeforeRenderObservable.add(SetEmissiveColor);
这里的engine.getDeltaTime()在第一帧渲染完成之前会被赋值为0,不会出现值为NaN的情况(scene.deltaTime为啥就不能这么干呢???)。
好了,就到这里,通过这个问题又学会了一些东西,大家共勉。