将setInterval应用于哪个函数

时间:2014-04-26 16:46:23

标签: javascript jquery animation webgl

以下代码是在浏览器中使用编程语言J(未显示)生成海龟图形的应用程序的一部分。代码有效,但没有动画。相反,海龟的复杂路径只是在最终状态下显示出来。所以我想使用setInterval或setTimeout来产生动画效果,但我看不出如何。

我尝试了setInterval(drawPrimitive, 1000/2);之类的命令,但结果没有变化。

更新0

当我在setInterval()调用中提供一些虚构的参数时,我确实得到了一些反应。例如,当我输入setInterval(function(){drawPrimitive( gl.LINES, linecolors[0], moves[0]);}, 3000);然后在我发出乌龟命令之后,该命令立即执行,然后在多达3秒后执行 - 但通常更少 - 画布变白并保持白色直到我发出另一个命令。但是绘图/绘画中还有仍然没有动画。这有什么意义吗?

更新0

感谢您的想法。

顺便说一句,如果你想看看我在说什么,你可以看一下about 1:34 in this video或者只看视频的原始闪屏。

drawTurtles(linecolors,moves,leftColors,rightColors,backColors,bottoms,lefts,rights,backs,bottomNs,leftNs,rightNs,backNs);

    function drawTurtles(linecolors,moves,leftColor,rightColor,backColor,bottom,left,right,back,bottomNs,leftNs,rightNs,backNs){
    gl.uniform1i( uLit, 0 );
    drawLines(linecolors,moves)
    bottomColor = [ 1,1,1,0]; 
    gl.uniform1i( uLit, 1 );
    for(var i=0;i<leftColor.length;i++)
    {
    gl.uniform3f( uNormal, leftNs[i][0],leftNs[i][1],leftNs[i][2]);
    drawPrimitive( gl.TRIANGLES, leftColor[i], left[i]);
    gl.uniform3f( uNormal, rightNs[i][0],rightNs[i][1],rightNs[i][2]);
    drawPrimitive( gl.TRIANGLES, rightColor[i], right[i]);
    gl.uniform3f( uNormal, backNs[i][0],backNs[i][1],backNs[i][2]);
    drawPrimitive( gl.TRIANGLES, backColor[i], back[i]);
    gl.uniform3f( uNormal, -bottomNs[i][0],-bottomNs[i][1],-bottomNs[i][2]);
    drawPrimitive( gl.TRIANGLES, bottomColor, bottom[i]);
    }
}

function drawLines(linecolors,moves) { 
setInterval(drawPrimitive, 1000/2);
    gl.lineWidth(2);
    gl.uniform1i( uLit, 0 );
    for(var i=0;i<linecolors.length;i++)
    {
    drawPrimitive( gl.LINES, linecolors[i], moves[i]);
    }
    gl.lineWidth(1);
}


function drawPrimitive( primitiveType, color, vertices ) {
    gl.enableVertexAttribArray(aCoords);
    gl.bindBuffer(gl.ARRAY_BUFFER,aCoordsBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STREAM_DRAW);
    gl.uniform4fv(uColor, color);
    gl.vertexAttribPointer(aCoords, 3, gl.FLOAT, false, 0, 0);
    gl.drawArrays(primitiveType, 0, vertices.length/3);
}

function init() {
    var canvas = document.getElementById("glcanvas");
    var vertexShaderSource = getTextContent("vshader"); 
    var fragmentShaderSource = getTextContent("fshader");
    var prog = createProgram(gl,vertexShaderSource,fragmentShaderSource);
    linecolors = [];
    moves = [];
    gl.useProgram(prog);
    aCoords =  gl.getAttribLocation(prog, "coords");
    uModelview = gl.getUniformLocation(prog, "modelview");
    uProjection = gl.getUniformLocation(prog, "projection");
    uColor =  gl.getUniformLocation(prog, "color");
    uLit =  gl.getUniformLocation(prog, "lit");
    uNormal =  gl.getUniformLocation(prog, "normal");
    uNormalMatrix =  gl.getUniformLocation(prog, "normalMatrix");
    aCoordsBuffer = gl.createBuffer();
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.CULL_FACE);
}

2 个答案:

答案 0 :(得分:1)

老实说,您似乎必须真正重新构建代码才能使其按步骤运行

但是,如果你想要这个,你必须改变至少一件事,你需要使用preserveDrawingBuffer: true创建画布,如

gl = canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true });

这是因为默认情况下,WebGL会在每个事件后清除画布。或者更确切地说,它标志着它在下一次抽签之前被清除。所以,在哪里

drawThing1();
drawThing2();

会显示thing1和thing2,

drawThing1();
setTimeout(drawThing2, 100);

只显示thing2(好吧,你可能会看到thing1片刻)但是当执行超时时,画布将被清除,然后thing2将被绘制到该清除的画布上。为防止清除,您需要设置preserveDrawingBuffer: true

这个&#34;功能&#34;专门针对移动设备。为了使WebGL能够在屏幕上绘制和显示具有正确浏览器行为的内容,必须对其进行双重缓冲。当您绘制时,您正在绘制到屏幕外缓冲区。当您的活动退出时,屏幕外缓冲区已交换已复制 [1]。交换速度更快但是你要交换的缓冲区中的内容是未定义的,因此WebGL会清除它。如果您想要较慢的复制行为,请设置preserveDrawingBuffer: false。在这种情况下,您总是渲染到相同的屏幕外缓冲区,因此没有理由需要清除它。但是,现在必须在当前事件退出的任何时候复制它。

[1]从技术上讲,当事件退出时它不会被复制或交换,而是被标记 被复制或交换。除非在下次浏览器将页面合成到屏幕时发生,否则没有明确定义复制或交换发生的时间。

虽然preserveDrawingBuffer: true非常需要复制,但将其设置为false并不能保证交换。它只是意味着如果浏览器认为这是最好的事情,它就可以交换。无论是交换还是复制,如果preserveDrawingbufferfalse,它将清除缓冲区,使行为保持一致。

至于重组,只是改变drawPrimitive就不够了。

首先关闭setInterval(drawPrimitive, 1000/2)只会在没有参数的情况下调用drawPrimitive。但是你可以看到drawPrimitive需要3个参数。要提供参数,您可以像这样调用它

setInterval(function(i) {
  return function() {
    drawPrimitive(gl.LINES, linecolors[i], moves[i]);
  }(i),
  1000/2);}

为了拆分它,我们有一个函数创建一个函数来调用drawPrimitives。更通用的版本可能看起来像这样

function makeFunctionToCallDrawPrimitive(arg1, arg2, arg2) {
  return function() {
    drawPrimitive(arg1, arg2, arg3);
  }
}

现在你可以这样称呼它

function drawLines(linecolors,moves) { 
  gl.lineWidth(2);
  gl.uniform1i( uLit, 0 );
  for(var i=0;i<linecolors.length;i++)
  {
    var func = makeFunctionToCallDrawPrimitive(gl.LINES, linecolors[i], moves[i]);
    setTimeout(func, i * 500);
  }
  gl.lineWidth(1);
}

我们甚至可以设置一个函数来设置超时

var count = 0;
function deferredDrawPrimitive(arg1, arg2, arg3) {
    var func = makeFunctionToCallDrawPrimitive(arg1, arg2, arg3);
    setTimeout(func, ++count * 500);
}

然后将对drawPrimtive的每次通话都改为拨打deferredDrawPrimitive,但对于一般情况来说仍然不够。

问题是虽然这会使drawPrimitive实际上每隔1/2秒使用正确的参数调用,但drawPrimitives依赖于其他状态。例如,在您的代码中,有几行像这样

...
gl.uniform3f( uNormal, leftNs[i][0],leftNs[i][1],leftNs[i][2]);
drawPrimitive( gl.TRIANGLES, leftColor[i], left[i]);
gl.uniform3f( uNormal, rightNs[i][0],rightNs[i][1],rightNs[i][2]);
drawPrimitive( gl.TRIANGLES, rightColor[i], right[i]);
...

您可以将其更改为此

...
gl.uniform3f( uNormal, leftNs[i][0],leftNs[i][1],leftNs[i][2]);
deferredDrawPrimitive( gl.TRIANGLES, leftColor[i], left[i]);
gl.uniform3f( uNormal, rightNs[i][0],rightNs[i][1],rightNs[i][2]);
deferredDrawPrimitive( gl.TRIANGLES, rightColor[i], right[i]);
...

gl.uniform3f行会影响drawPrimtive的运作方式。您还需要了解如何保存这些以及所有其他gl调用的状态,以使其执行您想要的操作。换句话说,只需在setTimeout或setInterval 中调用drawPrimitive就行了

这就是我说它需要进行一些重大改组的原因。或者你需要捕获所有对gl的调用然后再播放它们。 This code attempts to do that。一旦你捕获了所有的gl调用,你就可以以更慢的速度播放。您必须编写该代码。


从评论我的观点这个工作因为drawPrimitives依赖于状态被遗漏了。也许这个例子有助于说清楚。假设你有一个使用canvas 2d的程序,它会绘制两个不同颜色的矩形,如下所示。

ctx.fillStyle = "red";
ctx.fillRect(10, 10, 20, 20);
ctx.fillStyle = "blue";
ctx.fillRect(15, 15, 20, 20);

现在让我们假设你想让它每隔1/2秒显示一次,这样你就可以写出一个与deferredFillRect类似的deferredDrawPrimitive。然后,您可以像这样更改代码。

ctx.fillStyle = "red";
deferredFillRect(10, 10, 20, 20);
ctx.fillStyle = "blue";
deferredFillRect(15, 15, 20, 20);

那么当我们运行它时会发生什么?

  1. fillStyle设置为&#34;红色&#34;
  2. setTimeout设置为在{1/2}后用1/2 fillRect
  3. 调用10,10,20,20
  4. fillStyle设置为&#34;蓝色&#34;
  5. setTimeout设置为在{1}}
  6. 之后1秒钟调用fillRect
  7. 您的活动退出(可能是窗口的加载事件)
  8. 1/2秒通过
  9. 使用15,15,20,20调用fillRect。它吸引了 BLUE ,但你想要红色。
  10. 1/2秒通过
  11. 使用10,10,20,20调用fillRect。它用红色绘制
  12. 你在第7步看到了问题吗?要绘制的颜色在开头运行的代码中设置,但状态丢失。当fillRect最终被调用1/2秒后,它将使用错误的颜色进行绘制。

    在你的例子中也发生了同样的事情。有{10}或100个15,15,20,20次调用为gl设置状态。哪些缓冲区取决于哪些属性,哪个着色器程序是最新的,哪些纹理绑定,哪些值是哪些制服,混合模式是什么等等等等。所有这些都是错误时{{最后从setTimeout / setInterval

    调用1}}

    如果您希望它能够工作,您必须将所有状态移动到设定的间隔。由于WebGL示例太复杂,我将展示画布2d示例。为了使它工作,你必须做类似

    的事情
    drawPrimitive

    画布2d还有很多其他状态。当前drawPrimitivefunction step1() { ctx.fillStyle = "blue"; ctx.fillRect(10,10,20,20); } function step2() { ctx.fillStyle = "red"; ctx.fillRect(15,15,20,20); } setTimeout(step1, 500); setTimeout(step2, 1000); transformglobalCompositingOperation等等所有这些都需要移动到每个步骤,否则当它运行时它将使用无论状态从其他步骤中遗留下来。

    在您的示例中,您还必须摆脱所有循环,或者让它们生成功能,逐步完成所有操作并为每个循环设置所需的所有状态。它不会是一个小小的变化,让它在步骤中运行。换句话说,您还需要在setInterval / setTimeout函数中调用strokeStyle之间包含每个font调用。

    有很多方法可以使它工作,但它需要大量的重写代码,否则它需要记录gl调用的东西并稍后播放它们。我链接到上面的一些代码记录了所有drawPrimitive。调用。您可以将其用作以较慢速率播放gl电话的基础。尽管如此,它仍然不会简单。有些gl次调用需要立即发生(例如glgl和类似的功能,而其他需要稍后发生。)因此,无论需要做多少工作,都需要做。

答案 1 :(得分:-1)

尝试setInterval(function(){drawPrimitive();},500),应该可以。