Следующий код является частью приложения для создания графики черепахи в браузере [с использованием языка программирования J (не показан)]. Код работает, но анимации нет. Вместо этого сложные пути черепах просто отображаются в их конечном состоянии, все сразу. Поэтому я хотел бы использовать setInterval или setTimeout для создания эффекта анимации, но я не вижу, как это сделать.
Я пробовал команды вроде setInterval(drawPrimitive, 1000/2);
но без изменений результатов.
Обновление 0
Я получаю немного реакции, когда я поставлю некоторые фиктивные аргументы в вызове setInterval(). Например, когда я вхожу в setInterval(function(){drawPrimitive( gl.LINES, linecolors[0], moves[0]);}, 3000);
то после того, как я выдаю команду черепахи, команда выполняется немедленно, а затем после 3 секунд - но часто меньше - холст становится белым и остается белым до тех пор, пока я не выдаст еще одну команду. Но в рисовании/рисовании анимации пока нет. Означает ли это что-нибудь?
Обновление 0
Спасибо за идеи.
Кстати, если вы хотите посмотреть, о чем я говорю, вы можете посмотреть примерно 1:34 в этом видео или просто на оригинальном заставке видео.
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);
}
Честно говоря, похоже, вам нужно будет реально перестроить код, чтобы он выполнялся пошагово
Но, по крайней мере, одно дело, которое вы должны изменить, если хотите, чтобы это сработало, вам нужно создать свой холст с помощью preserveDrawingBuffer: true
как в
gl = canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true });
Это связано с тем, что по умолчанию WebGL очищает холст после каждого события. Или, скорее, он отмечает, что он должен быть очищен до следующего розыгрыша. Итак, где as
drawThing1();
drawThing2();
Покажу как вещь1, так и вещь2,
drawThing1();
setTimeout(drawThing2, 100);
Покажет только вещь2 (ну, вы можете увидеть вещь1 на мгновение), но когда тайм-аут будет выполнен, холст будет очищен, а затем предмет2 будет нарисован на этот очищенный холст. Чтобы предотвратить очистку, вам необходимо установить preserveDrawingBuffer: true
.
Эта "особенность" специально предназначена для мобильных устройств. Для того, чтобы WebGL рисовал и показывал материал на экране с правильным поведением браузера, он должен быть дважды буферизирован. Когда вы рисуете, вы рисуете в буфер вне экрана. Когда ваше событие выйдет из того, что внеэкранный буфер обменивается или копируется [1]. Обмен происходит быстрее, но то, что в буфере, с которым вы будете обмениваться, не определено, поэтому WebGL очищает его. Если вы хотите более медленное копирование, вы устанавливаете preserveDrawingBuffer: false
. В этом случае вы всегда показываете один и тот же буфер вне экрана, поэтому нет необходимости его очищать. Но теперь его нужно копировать в любое время, когда текущее событие завершается.
[1] Технически он не копируется или не заменяется при выходе из события, а скорее становится помеченным для копирования или замены. Когда именно происходит копирование или своп, четко не определено иначе, чем это произойдет в следующий раз, когда браузер будет компоновать страницу на экране.
Также while preserveDrawingBuffer: true
значительной степени требует копирования, установка его в false
не гарантирует обмен. Это просто означает, что браузер может меняться, если он считает, что лучше всего делать. Независимо от того, будет ли он меняться или копироваться, если preserveDrawingbuffer
false
он очистит буфер, чтобы поведение было согласованным.
Что касается реструктуризации, то просто изменить 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
фактически вызван с правильными аргументами каждые полторы секунды, есть другое состояние, на которое полагается 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
чтобы заставить его делать то, что вы хотите. Другими словами, просто вызов drawPrimitive
в setTimeout или setInterval НЕ РАБОТАЕТ.
Вот почему я сказал, что это потребует серьезной реструктуризации. Либо это, либо вам нужно записать все вызовы на gl, а затем воспроизвести их. Этот код пытается это сделать. После того, как вы заработаете все вызовы 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);
Итак, что происходит, когда мы запускаем это?
fillStyle
установлен на "красный"fillRect
1/2 секунды с 10,10,20,20
fillStyle
установлен на "синий"fillRect
1 секунду с 15,15,20,20
10,10,20,20
. Он рисует синим цветом, но вам нужен красный.15,15,20,20
. Он рисует краснымВы видите проблему на шаге 7? Цвет рисунка устанавливается в коде, который запускается в начале, но это состояние теряется. Когда fillRect, наконец, будет вызван через 1/2 секунды позже, он будет рисовать с неправильным цветом.
То же самое происходит и в вашем примере. Там 10 или 100 drawPrimitive
gl
вызывает это состояние для drawPrimitive
. Какие буферы идут, какие атрибуты, какая программа шейдера текущая, какие текстуры связаны, какие значения находятся в форме, какой режим drawPrimitive
т.д. И т.д. И т.д. Все это НЕПРАВИЛЬНО, когда drawPrimitive
наконец вызывается из setTimeout/setInterval
Если вы хотите, чтобы он работал, вам пришлось бы переместить все это состояние на заданный интервал. Поскольку пример WebGL слишком сложный, я покажу пример canvas 2d. Чтобы заставить его работать, вам нужно будет сделать что-то вроде
function 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);
Там много других состояний в холсте 2d. В настоящее время transform
, то globalCompositingOperation
, то strokeStyle
, то font
и т.д. и т.д. и т.д. Все, что нужно будет перенести на каждый шаг в противном случае, когда это работает он будет использовать все состояние, оставшийся от других шагов.
В вашем примере вам также придется избавиться от всех циклов, иначе они будут генерировать функции, которые делают все по шагам и устанавливают все необходимое для каждого из них. Это не будет небольшим изменением, чтобы заставить его работать пошагово. Другими словами, вам нужно включить каждый вызов gl
между вызовами drawPrimitive
в ваших drawPrimitive
setInterval/setTimeout.
Там много способов заставить его работать, но для этого потребуется МАССИВНОЕ ЗАПИСЬ кода, иначе ему потребуется что-то, что записывает вызовы gl
и воспроизводит их позже. Я связан с некоторым кодом выше, который записывает все gl
. звонки. Вы можете использовать это в качестве основы для повторного воспроизведения вызовов gl
более медленными темпами. Тем не менее, это было бы непросто. Некоторые вызовы gl
должны выполняться немедленно (например, getUniformLocation
, createTexture
и аналогичные функции, тогда как другие должны произойти позже). Поэтому, несмотря на то, что нужно много работы.
var count = 0
но не увидел ни строк, ни черепах. Это насколько я получил. Спасибо за все ваши идеи. Я действительно не знаю, следую ли я последним, и, возможно, испортил те, о которых я сообщил.
Попробуйте setInterval(function(){drawPrimitive();},500)
, должен работать.
setInterval
иsetTimeout
из-за оптимизации. См: developer.mozilla.org/en/docs/Web/API/... и paulirish.com/2011/requestanimationframe-for-smart-animating для того, как использоватьrequestAnimationFrame
.