20 марта 2011 г.

Пишем первые шейдеры, используя Molehill

Для того, чтобы понять как писать программы с использованием API Molehill давайте разработаем каркас приложения и разберемся как это всё работает. Сделаем это вместе, вооружившись ссылкой на статью Thibault Imbert "Digging more into the Molehill APIs", нашими предыдущими познаниями в этой области:

а так же здоровым любопытством и терпением.

Надо сказать, что статья Тибальда не блещет особым углублением в детали, не взирая на заманчивый заголовок. Приводятся лишь отрывки кода (а где исходник-то?!) и описание, куда как скуповатое. Но нас это не пугает. Попробуем восстановить картину полностью, хотя залезть в дебри AGAL всё же не получится, т.к. документации по нему я так и не нашел.

Напрасно я про отсутствие документации на AGAL. В конце страницы класса flash.display3D.Program3D есть описание регистров. Это конечно не полная спецификация, но уже не нужно строить предположений по многим вещам.

Если кто знает где лежит заветное - поделитесь, вам будут многие признательны. А пока будем использовать то что есть и если надо будет додумаем пробелы из GLSL или HLSL. Надо сказать, что будучи знакомым с шейдерами OpenGL и DirectX я нахожу в AGAL немало общего с ними, но это очень даже понятно почему.

Итак, для начала настроим среду разработки, как описано в одной из моих предыдущих статей (см. ссылку выше) и создадим новый проект ActionScript 3. По-умолчанию FlashDevelop предлагает неплохой готовый каркас, который мы дополним лишь слушателями событий мыши (они нам пригодятся при взаимодействии с пользователем) и инициализацией объекта 3d-контекста:

stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener);
stage.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelListener);

stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
stage.stage3Ds[0].requestContext3D();
stage.stage3Ds[0].viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

Если браузеру посчастливится захватить 3d-контекст, то мы можем смело продолжать наши изыскания. Дальше конфигурируем бэк-буфер

context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 0, true);

Бэк-буфер - это такая штука, в которой происходит окончательная отрисовка (или растеризация) нашей 3-мерной графики. Потом, впоследствии, когда буфер готов, он очень быстро сменяет текущее изображение.

context3D.present();

Теперь подготовим наши шейдеры. О том, что такое шейдеры можно прочитать много где, но после того как вы досконально изучите данный код вы точно будете знать что это такое. Сначала мы напишем вершинный шейдер. Наш первый шейдер будет прост до невозможности. Он просто перемножает каждую вершину определенного нами треугольника на матрицу преобразований, которая вычисляется путем построения преобразований модели, вида, проекции. Данные о положении вершин заносятся в регистр vc0 следующим кодом:

context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);

А матрица преобразования в va0 следующим:

context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,  0, modelViewProjection, true);

Результат перенаправляется в выход вершинного шейдера op (output position). В вершинном шейдере мы сделаем дополнительные действия — направим цвет каждой вершины, который заносится в шейдер так:

context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3);

во фрагментный шейдер:

mov v0, va1

Как написано в одной из книг по OpenGL вершинные шейдеры — это такие трудяги, которые делают всю рутинную черновую работу, а шоу устраивают фрагментные шейдеры. Утверждение может оказаться спорным, но тем не менее у нас будет три фрагментных шейдера, один из которых вы можете включать перед очередной компиляцией и смотреть на результат. Первый из фрагментных шейдеров будет просто выводить цвет вершины, а вот второй и третий уже будут "как взрослые" — что-то с этим цветом делать.

Именно для этих шейдеров нам понадобятся константные векторы или входные параметры шейдера. Второй шейдер просто инвертирует цвета. Для этого необходимо каждый компонент цвета (кроме альфы) вычесть из 1.

Третий шейдер делает художественную обработку изображения, которая называется сепия — цвета старой фотографии.

Для этого нам придется вначале сформировать изображение в полутоновом режиме. Вектор Vector.<Number>( [ 0.3, 0.59, 0.11, 1 ] ) позволяет извлечь необходимые весовые коэффициенты из цвета каждой вершины, чтобы добиться желаемого, а вектор Vector.<Number>( [ 1.2, 1.0, 0.8, 1 ] ) создает эффект сепии. Заносим эти вектора в фрагментный шейдер, каждый в свой регистр. Затем компилируем шейдеры в виде объекта Program3D:

shaderProgram = context3D.createProgram();
shaderProgram.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);

Теперь для полноты картины нам осталось сформировать отображаемый объект. Пусть в нашем "хеллоуворлд"-шейдере это будет самый простой треугольник:

vertexData = Vector.([
  1.224, -1, 0, 1, 0, 0,
  0,      1, 0, 0, 1, 0,
 -1.224, -1, 0, 0, 0, 1
]);
vertexBuffer = context3D.createVertexBuffer(vertexData.length / 6, 6);
vertexBuffer.uploadFromVector(vertexData, 0, 3);

indexBuffer = context3D.createIndexBuffer(3);
indexBuffer.uploadFromVector(Vector.([0, 1, 2]), 0, 3);

Осталось нужным образом сформировать матрицы преобразований и настроить прослушиватели событий, но эти задачи достаточно тривиальны. Вот и всё. Теперь вы можете компилировать пример комментируя 2 ненужных фрагментных шейдера. А после компиляции немного поуправлять треугольником (в виде бонуса): стрелки двигают вправо-влево-вверх-вниз, колесо мыши удаляет-приближает.

Загрузить исходный файл (который я щедро сдобрил комментариями) и результат можно отсюда. Загрузить классы утилит от компании Adobe для функционирования примера можно отсюда.

6 комментариев:

Анонимный комментирует...

http://xproger.mentalx.org/archives/475 8)

Иван комментирует...

2 xproger: да, там круто :).

Max комментирует...

Остается теперь понять, когда будет официальная версия флэш-плеера 11

Иван комментирует...

Понять - никак, только ждать. Уже немного осталось. В любой момент может выстрелить. Хотя мне кажется там всё очень непросто, кто знает...

Osmomыsl комментирует...

stage.stage3Ds[0].viewPort в 11 flashPlayer'e нет такой алхимии

Иван комментирует...

Нет. Данные примеры написаны для Flash Player 11 Incubator. Указано же.

Отправить комментарий