
Stage3d и Крутящийся Кубик
Немного поразбирался с новым 3d API из Flash Player 11 (то, что раньше называлось Molehill).
Все буду писать во FlashDevelop. Специально обновил версию до последней на текущий момент 4.0.0 RTM. Чего и всем желаю. В предыдущей версии, кроме выбора Flash Player 11 в опциях, надо было еще добавлять дополнительный параметр компилятора -swf-version=13. А в еще более предыдущих поддержки 11-ого флешплеера вообще не было.
Для компиляции примеров недостаточно SDK, нужна еще библиотека отсюда. Она маленькая, а нужен из нее всего один класс (AGAL mini assembler).
Документация на сайте Adobe по умолчанию не показывает классы Stage3D, Context3D и прочие, а заодно и их методы, пока не поменяешь фильтр с Flash Player 10.1 на Flash Player 11.1 (сверху окошка).
Сразу дам ссылку на отличную статью про написание шейдеров на AGAL: Пишем шейдер на AGAL.
Начать предлагаю с простого и короткого примера, который выведет на экран треугольник.
Разберемся что же он делает. Всяческую инициализацию пропускаю, думаю, тут всё понятно из кода.
Еще из этого примера можно заметить, что координаты X и Y, независимо от размера окна, меняются от -1 до 1, также можно чуть-чуть поэкспериментировать и убедиться, что точки с координатой Z больше 1 или меньше 0 на экран не выводятся. Также можно понять, что оси направлены так: X — вправо, а Y — вверх. Куда направлена Z пока непонятно, но будем считать, что от нас.
Внесем небольшие изменения, чтобы двигаться дальше. Сделаем так, чтобы размер окна вывода соответствовал размеру окна флешки. Поменяем вызов configureBackBuffer на
Теперь добавим матрицу проекции. (Все знают что такое матрица? А что такое проекция?) Чтобы не забивать себе голову, воспользуемся любезно предоставленным Adobe (в той же библиотеке, что и AGAL mini assembler) классом PerspectiveMatrix3D
Поменяем наш треугольник на что-то более объемное, и заодно добавим ему цвета. Пусть это будет кубик с разноцветными гранями. Раз грани разноцветные, нам придется задать цвет для каждой вершины (или делать несколько вызовов drawTriangles меняя между ними константу, определяющую цвет, но так делать не следует.) Я был несколько ленив, чтобы вручную заполнять эти координаты поэтому написал немного кода, который заполнил вершинный и индексные шейдеры за меня, а я только задал координаты для одной стороны кубика и углы поворота. В вершинный буфер мы теперь записываем по 6 чисел на каждую вершину: первые 3 числа — координата, следующие 3 числа — цвет. Я сделал так, чтобы у вершин одной грани были немножко разные цвета.
Но мы не видим никаких других частей нашего кубика. Надо заставить его вращаться. Для этого введем еще одну матрицу: матрицу задающую положение кубика.
Куда двигаться дальше? Зависит от целей. Я бы, может быть, попробовал использовать готовые движки, хоть это и не спортивно :-)
Вот окончательный код:
А вот ссылка на проект для FlashDevelop: скачать.
Все буду писать во FlashDevelop. Специально обновил версию до последней на текущий момент 4.0.0 RTM. Чего и всем желаю. В предыдущей версии, кроме выбора Flash Player 11 в опциях, надо было еще добавлять дополнительный параметр компилятора -swf-version=13. А в еще более предыдущих поддержки 11-ого флешплеера вообще не было.
Для компиляции примеров недостаточно SDK, нужна еще библиотека отсюда. Она маленькая, а нужен из нее всего один класс (AGAL mini assembler).
Документация на сайте Adobe по умолчанию не показывает классы Stage3D, Context3D и прочие, а заодно и их методы, пока не поменяешь фильтр с Flash Player 10.1 на Flash Player 11.1 (сверху окошка).
Сразу дам ссылку на отличную статью про написание шейдеров на AGAL: Пишем шейдер на AGAL.
Начать предлагаю с простого и короткого примера, который выведет на экран треугольник.
package
{
import flash.display.*;
import flash.display3D.*;
import flash.events.*;
import flash.geom.*;
import flash.utils.*;
import com.adobe.utils.*;
[Frame(factoryClass="Preloader")]
[SWF(width=640,height=480,backgroundColor="#FFFFFF")]
public class Main extends Sprite
{
private var stage3D:Stage3D;
private var ctx3d:Context3D;
private var indices:IndexBuffer3D;
private var asm:AGALMiniAssembler = new AGALMiniAssembler();
public function Main()
{
if (stage)
addedToStage(null)
else
addEventListener(Event.ADDED_TO_STAGE, addedToStage)
}
private function addedToStage(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, addedToStage);
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage3D = this.stage.stage3Ds[0];
stage3D.addEventListener(Event.CONTEXT3D_CREATE, stage3D_context3dCreate);
stage3D.addEventListener(ErrorEvent.ERROR, function(e:Event):void { trace(e); });
stage3D.requestContext3D(Context3DRenderMode.AUTO);
}
private function stage3D_context3dCreate(e:Event):void
{
ctx3d = stage3D.context3D;
trace("Driver: " + ctx3d.driverInfo);
ctx3d.enableErrorChecking = true;
ctx3d.configureBackBuffer(600, 400, 0, true);
var vertices:VertexBuffer3D = ctx3d.createVertexBuffer(3, 3);
vertices.uploadFromVector(new <Number>[0, 0, 0, 1, 0, 0, 0, 1, 0], 0, 3);
ctx3d.setVertexBufferAt(0, vertices, 0, Context3DVertexBufferFormat.FLOAT_3);
indices = ctx3d.createIndexBuffer(3);
indices.uploadFromVector(new <uint>[0, 1, 2], 0, 3);
asm.assemble(Context3DProgramType.VERTEX, "mov op, va0");
var vertexCode:ByteArray = asm.agalcode;
asm.assemble(Context3DProgramType.FRAGMENT, "mov oc, fc0");
var fragmentCode:ByteArray = asm.agalcode;
var program:Program3D = ctx3d.createProgram();
program.upload(vertexCode, fragmentCode);
ctx3d.setProgram(program);
ctx3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, new <Number>[1, 1, 1, 1]);
addEventListener(Event.ENTER_FRAME, enterFrame);
}
private function enterFrame(e:Event):void
{
ctx3d.clear(0, 0, 0);
ctx3d.drawTriangles(indices);
ctx3d.present();
}
}
}
Разберемся что же он делает. Всяческую инициализацию пропускаю, думаю, тут всё понятно из кода.
ctx3d.configureBackBuffer(600, 400, 0, true);
600 и 400 — это размер выводимого изображения. Я специально сделал его размером меньше самой флешки, чтобы продемонстрировать это. Также можно менять stage3D.x и stage3D.y, чтобы двигать изображение по экрану. 0 — это уровень антиалисинга, попробуйте поставить его в 4 и присмотреться к выводимому изображению. А true — это включение буфера глубины и stencil-буфера, не буду пока в это вдаваться.var vertices:VertexBuffer3D = ctx3d.createVertexBuffer(3, 3);
vertices.uploadFromVector(new <Number>[0, 0, 0, 1, 0, 0, 0, 1, 0], 0, 3);
ctx3d.setVertexBufferAt(0, vertices, 0, Context3DVertexBufferFormat.FLOAT_3);
Здесь создается вершинный буфер из трех вершин, в котором для каждой вершину хранится три числа (в нашем случае x, y и z). Т.е. у нас получились вершины {0,0,0}, {1,0,0} и {0,1,0}. Кроме того устанавливается, что эти три числа будут соответствовать переменной va0 в вершинном шейдере.private var indices:IndexBuffer3D;
...
indices = ctx3d.createIndexBuffer(3);
indices.uploadFromVector(new <uint>[0, 1, 2], 0, 3);
А это создается индексный буфер, в нем хранятся номера (индексы) вершин в вершинном буфере. Каждые три индекса в этом буфере задают треугольник.ctx3d.clear(0, 0, 0);
ctx3d.drawTriangles(indices);
ctx3d.present();
Тут, думаю, всё понятно: очистили экран, нарисовали заранее подготовленные треугольники, и вывели нарисованное на экран.Еще из этого примера можно заметить, что координаты X и Y, независимо от размера окна, меняются от -1 до 1, также можно чуть-чуть поэкспериментировать и убедиться, что точки с координатой Z больше 1 или меньше 0 на экран не выводятся. Также можно понять, что оси направлены так: X — вправо, а Y — вверх. Куда направлена Z пока непонятно, но будем считать, что от нас.
Внесем небольшие изменения, чтобы двигаться дальше. Сделаем так, чтобы размер окна вывода соответствовал размеру окна флешки. Поменяем вызов configureBackBuffer на
stage.addEventListener(Event.RESIZE, stage_resize);
stage_resize(null);
...
private function stage_resize(e:Event):void
{
var w:int = Math.max(100, Math.min(2000, stage.stageWidth));
var h:int = Math.max(100, Math.min(2000, stage.stageHeight));
ctx3d.configureBackBuffer(w, h, 0, true);
}
Теперь добавим матрицу проекции. (Все знают что такое матрица? А что такое проекция?) Чтобы не забивать себе голову, воспользуемся любезно предоставленным Adobe (в той же библиотеке, что и AGAL mini assembler) классом PerspectiveMatrix3D
private var projectionMatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D;
...
asm.assemble(Context3DProgramType.VERTEX, "m44 op, va0, vc0");//поменяем вершинный шейдер
...
projectionMatrix.perspectiveLH(320, 320 * h / w, 10, 1000);//это в stage_resize
...
ctx3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, projectionMatrix, true);//а это в enterFrame
Итак мы поменяли вершинный шейдер так, что он умножает переданный ему вектор на матрицу записанную в регистр vc0, и поместили в этот регистр матрицу. Матрица с заданными параметрами делает преобразование координат такое, что объект шириной 320 будет размером ровно с ширину нашего окна, если он будет на расстоянии 10 от камеры, а на расстоянии 1000 — он будет в сто (1000/10) раз меньше, при этом объекты ближе 10 и дальше 1000 выводиться на экран не будут.Поменяем наш треугольник на что-то более объемное, и заодно добавим ему цвета. Пусть это будет кубик с разноцветными гранями. Раз грани разноцветные, нам придется задать цвет для каждой вершины (или делать несколько вызовов drawTriangles меняя между ними константу, определяющую цвет, но так делать не следует.) Я был несколько ленив, чтобы вручную заполнять эти координаты поэтому написал немного кода, который заполнил вершинный и индексные шейдеры за меня, а я только задал координаты для одной стороны кубика и углы поворота. В вершинный буфер мы теперь записываем по 6 чисел на каждую вершину: первые 3 числа — координата, следующие 3 числа — цвет. Я сделал так, чтобы у вершин одной грани были немножко разные цвета.
var verticesVector:Vector.<Number> = new Vector.<Number>;
var indicesVector:Vector.<uint> = new Vector.<uint>;
const numbersPerVertex:int = 6;
var cubeSize:Number = 40;
var colors:Array = [0xFF0000, 0x00FF00, 0x0000FF, 0xFF8000, 0xFFFF00, 0x00FFFF];
var rotations:Array = [0, 90, 180, 270, 90, 270];
var rotAxis:Array = [Vector3D.Y_AXIS, Vector3D.Y_AXIS, Vector3D.Y_AXIS, Vector3D.Y_AXIS, Vector3D.X_AXIS, Vector3D.X_AXIS];
var planeVertices:Array = [new Vector3D(-cubeSize, -cubeSize, cubeSize),
new Vector3D(cubeSize, -cubeSize, cubeSize),
new Vector3D(cubeSize, cubeSize, cubeSize),
new Vector3D( -cubeSize, cubeSize, cubeSize)];
var planeIndices:Array = [0, 1, 2, 0, 2, 3];
var mat:Matrix3D = new Matrix3D();
for (var i:int = 0; i < 6; ++i)
{
var startIndex:int = verticesVector.length / numbersPerVertex;
var red:Number = ((colors[i] >> 16) & 0xFF) / 255.0;
var green :Number = ((colors[i] >> 8) & 0xFF) / 255.0;
var blue:Number = (colors[i] & 0xFF) / 255.0;
mat.identity();
mat.appendRotation(rotations[i], rotAxis[i]);
for each(var vec:Vector3D in planeVertices)
{
var transformedVec:Vector3D = mat.transformVector(vec);
verticesVector.push(transformedVec.x, transformedVec.y, transformedVec.z, red, green, blue);
red += 0.3;
green += 0.3;
blue += 0.3;
}
for each(var index:uint in planeIndices)
indicesVector.push(index + startIndex);
}
var vertices:VertexBuffer3D = ctx3d.createVertexBuffer(verticesVector.length / numbersPerVertex, numbersPerVertex);
vertices.uploadFromVector(verticesVector, 0, verticesVector.length / numbersPerVertex);
ctx3d.setVertexBufferAt(0, vertices, 0, Context3DVertexBufferFormat.FLOAT_3); //va0 = position
ctx3d.setVertexBufferAt(1, vertices, 3, Context3DVertexBufferFormat.FLOAT_3); //va1 = color
indices = ctx3d.createIndexBuffer(indicesVector.length);
indices.uploadFromVector(indicesVector, 0, indicesVector.length);
Кроме того надо поменять вершинный и фрагментный шейдеры, чтобы они брали цвет из вершины.
asm.assemble(Context3DProgramType.VERTEX,
"m44 op, va0, vc0\n" +
"mov v0, va1"
);
var vertexCode:ByteArray = asm.agalcode;
asm.assemble(Context3DProgramType.FRAGMENT, "mov oc, v0");
var fragmentCode:ByteArray = asm.agalcode;
Если теперь запустить программу, она выведет на экран переднюю грань нашего кубика. Можно увидеть, что значения цвета заданные в вершинах между вершинами плавно перетекают друг в друга.Но мы не видим никаких других частей нашего кубика. Надо заставить его вращаться. Для этого введем еще одну матрицу: матрицу задающую положение кубика.
private var modelMatrix:Matrix3D = new Matrix3D;
...
modelMatrix.appendRotation(1, Vector3D.Y_AXIS);
modelMatrix.appendRotation(10, Vector3D.Y_AXIS);
var mat:Matrix3D = modelMatrix.clone();
mat.append(projectionMatrix);
ctx3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mat, true);
Если запустить программу сейчас, увидим какую-то ерунду — это из-за того, что мы периодически оказываемся внутри кубика. Чтобы увидеть кубик со стороны, зададим положение камеры. Для этого добавим еще и матрицу вида.
private var viewMatrix:Matrix3D = new Matrix3D;
...
viewMatrix.appendTranslation(0, 0, cubeSize * 3);
...
var mat:Matrix3D = modelMatrix.clone();
mat.append(viewMatrix);
mat.append(projectionMatrix);
Теперь кубик вращается как надо. Впрочем видно, что желательно подправить параметры проекции и положение кубика. Но это уже не важно…Куда двигаться дальше? Зависит от целей. Я бы, может быть, попробовал использовать готовые движки, хоть это и не спортивно :-)
Вот окончательный код:
package
{
import flash.display.*;
import flash.display3D.*;
import flash.events.*;
import flash.geom.*;
import flash.utils.*;
import com.adobe.utils.*;
[Frame(factoryClass="Preloader")]
[SWF(width=640,height=480,backgroundColor="#FFFFFF")]
public class Main extends Sprite
{
// stage3D - это поверхность, на которой рисуются всякие трехмерные штуки
// Можно ее двигать, меняя stage3D.x и stage3D.y. Размер поверхности задается отдельно (см. stage_resize)
private var stage3D:Stage3D;
// context3D - (для краткости ctx3d) это просто ссылка на переменную stage3D.context3D.
// т.к. непосредственно рисование осуществляется именно через нее, удобно иметь короткий способ к ней обратится
private var ctx3d:Context3D;
// индексный буфер. См. комментарии в коде. Вынесен в переменную класса, т.к. нужен на каждом шаге рисования
private var indices:IndexBuffer3D;
// Матрицы. Они вначале перемножаются друг на друга, а потом передаются в шейдер, который перемножает все вершины на получившуюся матрицу.
// Матрица проекции задает проекцию :-)
private var projectionMatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D;
// Матрица модели задает положение модели
private var modelMatrix:Matrix3D = new Matrix3D;
// Матрица вида задает положение камеры.
// В том смысле, что она должна всё смещать так, чтобы камера оказалась в точке 0,0,0 и смотрела вдоль оси Z в сторону увеличения.
private var viewMatrix:Matrix3D = new Matrix3D;
// Конструктор класса. Всё стандартно.
public function Main()
{
// Если stage уже есть, значит идем дальше, иначе ждем пока нас добавят на stage.
if (stage)
addedToStage(null)
else
addEventListener(Event.ADDED_TO_STAGE, addedToStage)
}
// Нас добавили на stage, можно двигаться дальше
private function addedToStage(e:Event):void
{
// Очистим мусор
removeEventListener(Event.ADDED_TO_STAGE, addedToStage);
// Сделаем так, чтобы изменение размеров окна не вызывало масштабирования
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
// получим stage3D. Вообще их много, т.е. можно иметь много трехмерных картинок одновременно, но мы просто возьмем первый
stage3D = this.stage.stage3Ds[0];
// попробуем получить context3D, этот процесс может вызвать ошибки. Если они возникли просто сделаем trace, рисовать мы уже не сможем.
// да и в дальнейшем при возниковении ошибок могут кидаться исключения...
stage3D.addEventListener(Event.CONTEXT3D_CREATE, stage3D_context3dCreate);
stage3D.addEventListener(ErrorEvent.ERROR, function(e:Event):void { trace(e); });
stage3D.requestContext3D(Context3DRenderMode.AUTO);
}
// Получили context3D. Ура! Запускаемся дальше.
private function stage3D_context3dCreate(e:Event):void
{
// сохраним для краткости context3D
ctx3d = stage3D.context3D;
// Трейсанем интересную инфу :-)
trace("Driver: " + ctx3d.driverInfo);
// Включим проверку ошибок.
// TODO не забыть выключить в релизе :-)
ctx3d.enableErrorChecking = true;
// Добавим обработчик изменения размера окна флешплеера. И сразу вызовем его, чтобы он все проинициализировал.
stage.addEventListener(Event.RESIZE, stage_resize);
stage_resize(null);
// Этот кусок кода заполняет verticesVector и indicesVector вершинами и индексами для рисования кубика
// Я решил что проще задать одну грань и потом програмно ее поворочивать, получив таким образом шесть граней
var verticesVector:Vector.<Number> = new Vector.<Number>;
var indicesVector:Vector.<uint> = new Vector.<uint>;
// количество чисел на одну вершину: три координаты XYZ и три компонента цвета RGB
const numbersPerVertex:int = 6;
// характерный размер кубика: половина длины ребра :-)
var cubeSize:Number = 40;
// цвета граней
var colors:Array = [0xFF0000, 0x00FF00, 0x0000FF, 0xFF8000, 0xFFFF00, 0x00FFFF];
// углы поворота граней
var rotations:Array = [0, 90, 180, 270, 90, 270];
// оси, вокруг которых крутить грань
var rotAxis:Array = [Vector3D.Y_AXIS, Vector3D.Y_AXIS, Vector3D.Y_AXIS, Vector3D.Y_AXIS, Vector3D.X_AXIS, Vector3D.X_AXIS];
// четыре вершины задающие грань
var planeVertices:Array = [new Vector3D(-cubeSize, -cubeSize, cubeSize),
new Vector3D(cubeSize, -cubeSize, cubeSize),
new Vector3D(cubeSize, cubeSize, cubeSize),
new Vector3D( -cubeSize, cubeSize, cubeSize)];
// список треугольников. Здесь два треугольника: из вершин с индексами 1, 2 и 3 в предыдущем массиве и из вершин с индексами 0, 2 и 3
var planeIndices:Array = [0, 1, 2, 0, 2, 3];
// матрица, которую мы будем использовать для поворота нашей грани
var mat:Matrix3D = new Matrix3D();
for (var i:int = 0; i < 6; ++i)
{
// количество уже добавленных вершин в verticesVector
var startIndex:int = verticesVector.length / numbersPerVertex;
// компоненты цвета. Глупо, надо было их сразу числами задать.
var red:Number = ((colors[i] >> 16) & 0xFF) / 255.0;
var green :Number = ((colors[i] >> 8) & 0xFF) / 255.0;
var blue:Number = (colors[i] & 0xFF) / 255.0;
// сформируем матрицу поворота
mat.identity();
mat.appendRotation(rotations[i], rotAxis[i]);
for each(var vec:Vector3D in planeVertices)
{
// умножаем вектор на матрицу
var transformedVec:Vector3D = mat.transformVector(vec);
// и записываем вершину и ее цвет в verticesVector
verticesVector.push(transformedVec.x, transformedVec.y, transformedVec.z, red, green, blue);
// немного изменям цвет, чтобы он чуть отличался у разных вершин одной грани для демонстрации интерполяции цветов
red += 0.3;
green += 0.3;
blue += 0.3;
}
// записываем индексы в indicesVector.
// корректируем их, т.к. мы их должны указать на только что добавленные вершины, а они могут быть и не первыми в списке
for each(var index:uint in planeIndices)
indicesVector.push(index + startIndex);
}
// закончили заносить данные для кубика в verticesVector и indicesVector
// создадим вершинный буфер и загрузим в него данные из verticesVector
var vertices:VertexBuffer3D = ctx3d.createVertexBuffer(verticesVector.length / numbersPerVertex // количество вершин
, numbersPerVertex); // количество чисел на вершину
vertices.uploadFromVector(verticesVector, 0, verticesVector.length / numbersPerVertex);
// установим какие вершинные шейеры будут соответствовать каким регистрам
// va0 = position, а va1 = color
ctx3d.setVertexBufferAt(0 // номер регистра, в данном случае 0, т.е. va0
, vertices // вершинный буфер типа VertexBuffer3D
, 0 // начиная с какого числа брать числа из данных вершины. В нашем случае данные вершины это [X, Y, Z, R, G, B]. Указываем 0 и берем [X, Y, Z]
, Context3DVertexBufferFormat.FLOAT_3); // формат, определяет сколько и чего мы записали в вершинный буфер
ctx3d.setVertexBufferAt(1, vertices, 3, Context3DVertexBufferFormat.FLOAT_3); // здесь все аналогично, но регистр va1, а данные [R, G, B]
// создадим индексный буфер и загрузим в него данные из indicesVector
indices = ctx3d.createIndexBuffer(indicesVector.length);
indices.uploadFromVector(indicesVector, 0, indicesVector.length);
// откомпилируем шейдеры (вершинный и фрагментный), создадим программу (считай комбинацию двух шейдеров) и установим ее для использования при рендеринге
// AGALMiniAssembler надо брать тут: https://github.com/flashplatformsdk/Flash-Platform-SDK
// Про то, как писать шейдеры читать например тут: http://habrahabr.ru/blogs/Flash_Platform/130454/
var asm:AGALMiniAssembler = new AGALMiniAssembler();
// Программа вершинного шейдера вызывается для каждой вершины каждого треугольника.
asm.assemble(Context3DProgramType.VERTEX,
"m44 op, va0, vc0\n" + // умножим вектор из va0 (данные вершины) на матрицу из vc0 (константа) и передадим результат дальше
"mov v0, va1" // также передадим дальше цвет
);
var vertexCode:ByteArray = asm.agalcode;
// Программа фрагментного шейдера вызывается для каждого пикселя каждого треугольника
// в нее передаются значения из вершинного шейдера в регистрах с названиями v0, v1 и т.д.
// но поскольку вершин у трегольника всего три, а пикселей много, то эти значения равномерно размазываются по треугольнику (интерполируются)
asm.assemble(Context3DProgramType.FRAGMENT, "mov oc, v0"); // просто передадим на выход полученный цвет
var fragmentCode:ByteArray = asm.agalcode;
// создадим программу
var program:Program3D = ctx3d.createProgram();
// и загрузим в нее откомпилированные коды шейдеров
program.upload(vertexCode, fragmentCode);
// установим программу в качестве текущей
// в принципе можно иметь несколько программ и менять их, но у нас она одна
ctx3d.setProgram(program);
// Зададим матрицу вида, т.е. положение камеры.
// Такая матрица переместит все предметы на 3 cubeSize "вперед", т.е. как бы камера переместится на 3 cubeSize "назад"
viewMatrix.appendTranslation(0, 0, cubeSize * 3);
// ЧТО ЭТО???
addEventListener(Event.ENTER_FRAME, enterFrame);
}
// Обработчик изменения размера окна флешплеера, также вызывается при запуске программы
private function stage_resize(e:Event):void
{
// определим размеры нашего будущего окна
// размер видимой области Stage3D не может быть меньше 50 пикселей по ширине или высоте (я взял 100, все равно 100х100 - очень мало)
// и на всякий случай, я ограничил эти значения еще и сверху
var w:int = Math.max(100, Math.min(2000, stage.stageWidth));
var h:int = Math.max(100, Math.min(2000, stage.stageHeight));
// эта функция задает размер окна в пикселях, в которое выводится изображение
ctx3d.configureBackBuffer(w, h // размеh окна в пикселях
, 0 // уровень антиалисинга. чем больше, тем тормознее, но красивее
, true); // включать ли буфер глубины и stencil-буфер. Конечно, ДА!
// меняем матрицу проекции, т.к. она должна учитывать соотношения сторон окна
// такая матрица проекции означает, что объект размером 320 на расстоянии 10 будет размером с экран, и чем дальше, тем он будет меньше
// причем объекты ближе 10 или дальше 1000 не будут выводится.
// это не очень удачная матрица проекции :-)
projectionMatrix.perspectiveLH(320, 320 * h / w, 10, 1000);
}
// вызывается каждый кадр
private function enterFrame(e:Event):void
{
// крутим кубик
modelMatrix.appendRotation(3, Vector3D.X_AXIS);
modelMatrix.appendRotation(5, Vector3D.Y_AXIS);
// формируем матрицу трансформации для шейдера, перемножая матрицы модели, вида и проекции
// если моделей много, то надо это делать для каждой модели
var mat:Matrix3D = modelMatrix.clone();
mat.append(viewMatrix);
mat.append(projectionMatrix);
// записываем получившуюся матрицу в регистр vc0
// кстати, по идее при смене шейдерной программы, надо бы почистить все ее регистры на всякий случай
ctx3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mat, true);
// очистка экрана
ctx3d.clear(0, 0, 0);
// отрисовка с помощью текущей шейдерной программы и заданных вершинных буферов и констант треугольников, заданных переданным индексным буфером
ctx3d.drawTriangles(indices);
// вывод на экран картинки
ctx3d.present();
}
}
}
А вот ссылка на проект для FlashDevelop: скачать.
- +6
- romamik
Комментарии (21)
Если меня от 2ух строчек в дрожь бросает.
На AGAL оба эффекта не реализуемы, хотя за POM не скажу точно, не слишком себе представляю, как он работает.
SSAO бегает уже давно :)
Насколько я понимаю, для того чтобы сделать SSAO надо уметь читать z-buffer, а flash этого вроде как не позволяет.
Глубину надо писать в текстуру, а потом читать во втором проходе.
Так же и тени делаются, и все остальное.
А раз уж ты (сам реши, а я не нашел как тебя лучше умаслить), есть какие-то работающие техники для вывода прозрачных полигонов без сортировки. Я слышал про вариант с двойным буфером глубины, когда на каждом проходе рисуется то, что лежит дальше уже нарисованного, т.е. рендеринг многопроходный и от предыдущего прохода нужно отдельно сохранить zbuffer. Если использовать твою технику, то количество проходов увеличивается вдвое (отдельно сохрани z-buffer, отдельно картинку) Пихать z-buffer в альфу не очень хорошо, т.к. она нужна, поскольку полигоны-то прозрачные.
Можно сделать что-то вроде дизера, то есть для каждого пиксела писать 4 глубины в текстуру в два раза большую по размеру.
Но все равно, слоев слишком мало.
Сортировать проще.
Сортировать очень не хочется, нудно это :-)
Другой вариант менять блендинг на такой, который не зависит от порядка. Тоже не все так просто.
Спасибо за статью! Плюсую.