Stage3D / AGAL с нуля. Часть 3 – Hello Triangle

Итак, вы уже начитались первой и второй частей и хотите наконец чего-то более насущного. Поехали.

Можете начать со скачивания исходников здесь.

В этой статье мы научимся рендерить треугольники при помощи чистого Stage3D. Будет немного информации об AGAL, без которой не получится отобразить что-либо на экране, но не волнуйтесь, это не так сложно, как кажется.
Мы много говорили о вершинах в последних статьях. Вершина — это точка в пространстве, которая вместе с двумя другими образует треугольник в пространстве.

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

Тем не менее, есть как минимум 2 стандартные схемы вершинного буфера, но сейчас я опишу только одну.

(длина буфера = 3 вершины * 6 ячеек на вершину = 18)
Как вы можете видеть, наш вершинный буфер будет структурирован как простой список чисел, где первые три — содержат координаты, а три остальных — цветовую информацию. Каждая вершина добавляемая в буфер должна повторять этот паттерн.

Пишем код.


Теперь, если вы все еще не сделали этого, стоит скачать исходник и открыть класс HelloTriangle в любимом редакторе, я подожду вас здесь.

Конструктор этого класса достаточно прост для понимания, так что давайте взглянем на функцию __init.

// ждем пока Stage3D предоставит нам Context3D
stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, __onCreate);
stage.stage3Ds[0].requestContext3D();


Работая со Stage3D, вы не используете все его классы. Вместо этого, инструментом является Context3D.

Первым делом, выберем один из Stage3D (stage.stage3Ds[0]) и запросим context3d. При получении события, мы сможем получить к нему доступ.

На этом шаге, Флеш попытается получить доступ к видеокарте. В случае, если это удалось, вы получите аппаратно ускоренный Context3D, работающий на DirectX в Windows, или OpenGL под Linux и MacOS. Если по каким-либо причинам это не удалось, вы все равно получите Context3D, но в этом случае он будет работать в режиме процессорной растеризации под названием SwiftShader.

Как только мы получили Context3D, присваиваем его соответствующему члену класса (переменной), ну а затем можно настроить буферы.

private function __onCreate(event:Event):void {
        // // // СОЗДАЕМ CONTEXT // //
        context = stage.stage3Ds[0].context3D;
 
        // При включении получения ошибок, вы можете получить важную информацию об ошибках в ваших шейдерах
        // Но это, само собой, сильно тормозит программу.
        // context.enableErrorChecking=true;
 
        // настроим размеры back-буфера. Так же можно включить антиалиасинг
        // back-буфер - место в памяти, куда отрисовывается финальная картинка.
        context.configureBackBuffer(W, H, 4, true);


Все рендерится в back-буфер, который потом можно отобразить на экране.

Подготавливаем буферы и программу


Теперь создадим буферы и программу. Не волнуйтесь по поводу AGAL, я объясню его чуть позже.

private function __createBuffers():void {
        // // // СОЗДАЕМ БУФЕРЫ // //
        vertexBuffer = context.createVertexBuffer(3, 6);
        indexBuffer = context.createIndexBuffer(3);
}


Создание буфера — достаточно простой процесс. Единственным, что вам нужно знать будет:

  • Сколько у нас вершин? (3)
  • Сколько информации хранит каждая вершина? (x,y,z,r,g,b = 6)

Буфер индексов должен знать сколько инструкций мы будем выполнять. Здесь их 3.

Часть, касающаяся программы сложновата на данный момент, надо видеть больше, чтоб понять код AGAL. Но давайте все же посмотрим на код:
private function __createAndCompileProgram() : void {
        // // // СОЗДАЕМ ШЕЙРЕРНУЮ ПРОГРАММУ // //
        // При вызове метода createProgram, мы выделяем некоторое количество видеопамяти
        // для шейдерной программы.
        program = context.createProgram();
 
        // Создаем AGALMiniAssembler.
        // MiniAssembler это инструмент Adobe, использующий простой
        // язык, похожий на ассемблер, для того, чтобы создавать и компилировать шейдеры в байткод.
        var assembler:AGALMiniAssembler = new AGALMiniAssembler();
 
        // ВЕРШИННЫЙ ШЕЙДЕР
        var code:String = "";
        code += "mov op, va0\n"; // переносим атрибут №0 вершины (Vertex Attribute 0) (va0), координату вершины, в результирующую позицию (Output Point)
        code += "mov v0, va1\n"; // переносим атрибут №1 вершины (Vertex Attribute 1) (va1), цвет вершины, в регистр v0 (variable register v0)
         // Variable register - место в памяти, доступное для вершинного и пиклельного шейдеров одновременно.
 
        // Компилируем код AGAL в байткод при помощи MiniAssembler
        vertexShader = assembler.assemble(Context3DProgramType.VERTEX, code);
        code = "mov oc, v0\n"; // переносим Variable register 0 (v0), куда мы скопировали цвет вершины, в результирующий цвет (output color)
 
        // Компилируем код AGAL в байткод при помощи MiniAssembler
        fragmentShader = assembler.assemble(Context3DProgramType.FRAGMENT, code);
}


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

Загрузка данных в видеокарту.


Теперь, загрузим все это безобразие в видеокарту.

private function __uploadBuffers():void {
        var vertexData:Vector.=Vector.([
        -0.3, -0.3, 0, 1, 0, 0,         // - 1я вершина x,y,z,r,g,b
        0, 0.3, 0, 0, 1, 0,             // - 2я вершина x,y,z,r,g,b
        0.3, -0.3, 0, 0, 0, 1           // - 3я вершина x,y,z,r,g,b
        ]);
 
        vertexBuffer.uploadFromVector(vertexData, 0, 3);
        indexBuffer.uploadFromVector(Vector.([0, 1, 2]), 0, 3);
}


Простейшим способом загрузки в буфер является загрузка вектора (Vector — тип данных). Как вы можете видеть, Я создаю Vector из 3 * 6 элементов, соответствующих данным трех вершин. Координаты начинаются будут от -1 до 1 для x и y, но z касаться мы пока не будем. Цветовые компоненты, R, G и B, будут от 0 до 1. Первая строка несет информацию о верхней вершине, которая будет красной, вторая олицетворяет левую нижнюю (зеленую), и т.д.

В буфере индексов (index buffer), мы всего лишь определяем порядок, в котором перебираются вершины, чтобы образовать треугольник — 0,1,2 (индексы вершины в вершинном буфере).

Давайте загрузим программу
private function __uploadProgram():void {
        // ЗАГРУЗКА В GPU
        program.upload(vertexShader, fragmentShader); // Передача скомбинированной программы в видеопамять
}

Это было легко, не так ли? теперь перейдем к AGAL.

Используем вершинный буфер в шейдерах


Когда мы создали вершинный буфер, мы сообщили видеокарте, что какждая вершина будет хранить по 6 свойств. Это важно потому, что когда будет выполняться вершинный шейдер, одни и те же операции будут выполнены для каждой вершины.

Видеокарта будет обрезать по 6 ячеек из буфера за раз, как мы и задали. Каждая вершина должна быть скопирована в регистр быстрого доступа (fast access register) перед тем, как ее можно будет использовать в программе. Можете представлять себе регистр быстрого доступа как маленький объем видеопамяти, работающий невероятно быстро и находящийся в непосредственной близости от графического процессора.

Последним шагом будет сообщить видеокарте как разделить каждый «кусок» информации и куда «положить».



Код:
private function __splitAndMakeChunkOfDataAvailableToProgram():void {
        // Здесь мы сообщаем видеокарте, что для каждой вершины, которая состоит из x,y,y,r,g,b
        // нужно скопировать в регистр "0", из буфера "vertexBuffer", начиная с позиции "0" следующие FLOAT_3 чисел
        context.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // теперь регистр "0" содержит x,y,z
 
        // Копируем в регистр "1", из буфера "vertexBuffer", начиная с позиции "3" следующие FLOAT_3 чисел
        context.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3); // теперь регистр 1 содержит r,g,b
}


Вперед, покорять AGAL


Только что мы дали понять видеократе как обращаться с вершинным буфером. Напоминание:

  • Взять первые 6 ячеек
  • С 0й по 3ю скопировать в регистр 0
  • С 3й по 6ю скопировать в регистр 1
  • Теперь, регистр 0 содержит координаты, а регистр 1 — цвет.

Код AGAL будет разделен на 2 шейдера: вершинный и пиксельный. Регистры могут быть доступны только из вершинного, но мы так же научимся обманывать видеокарту и получить доступ к пиксельному.

Давайте взглянем на синтаксис AGAL:
опкод цель, источник 1[, источник 2][, параметры]

opcode destination, source1[, source2][, options]


Например:
mov op, va0

означает:
op = va0


Еще пример:
mul vt0, va0, vc0

значит:
vt0 = va0 * vc0


Имена «переменных» на самом деле имена регистров. Запомнить их не сложно, но пока на понадобится только несколько:

  • va0: Vertex Attribute 0. В этот регистр мы копируем координаты каждой вершины (см. выше). Их всего 8, с va0 до va7.
  • op: Output Point. Это специальный регистр, куда должны быть скопированы финальные координаты.
  • v0: Variable Register 0. Переменные регистры доступны как для вершинных, так и для пиксельных шейдеров. Их можно ипользовать для передачи данных во фрагментный шейдер. Их 8, с v0 по v7.
  • oc: Output Color. Сюда копируется результирующий цвет.

Давайте взглянем на первый пример AGAL кода, который у нас есть — вершинный шейдер.
(Строки 159 и 160 класса Example)
mov op, va0
mov v0, va1


Первая строка перемещает регистр va0, куда мы скопировали координаты вершин, в результирующую точку (output point) без изменений. Далее, мы должны переместить данные в пиксельный шейдер при помощи переменного регистра. В данном случае мы переместим только информацию о цвете, хранящуюся в регистре va1 (см. перд. параграф).

Просто, не правда-ли? Смотрим на пиксельный шейдер.
mov oc, v0


Совсем просто: перемещаем цвет из переменного регистра в результирующий без изменений. И всего-то!

После того, как программа скомпилирована AGALMiniAssembler'ом и загружена в память, мы моем сделать ее текущей активной программой видеокарты.

private function __setActiveProgram():void {
        // Устанавливаем программу текущей активной
         context.setProgram(program);
}


Отрисовываем наш первый треугольник


В нашем случае отрисовка достаточно проста т.к. нет никакого интерактива, камеры, анимации и т.д. Метод, отвечающий за это должен вызываться на каждом кадре.

private function render(event:Event):void {
        context.clear(1, 1, 1, 1); // Очистим back-буфер, заполняя его одним цветом
 
        context.drawTriangles(indexBuffer); // Рисуем треугольник по вершинам из индексного буфера в back-буфер
        context.present(); // выводим back-буфер на экран
}


Каждый кадр мы начинаем с очистки сцены, заливая ее белым цветом. Далее, рисуем треугольник в соответствии с индексным буфером. Метод drawTriangles отрисовывает все в back-буфер. Чтобы показать содеримое этого буфера, вызываем метод present().

Компилируем:


Возможно некоторые из вас заметили, что данный пример проще тех, что вы читали где-нибудь (без Matrix3D, без опкода m44). Это было сделано умышленно.

Для начала стоит понять как все работает, а только потом идти дальше. Если все понятно, дальнейшие статьи будут проще для вас.

Практикуйтесь!


Чтобы лучше понять прочитанное, попробуйте сделать следующее:
  • Нарисуйте треугольник на черном фоне.
  • Избавьтесь от координаты z, раз мы ее не используем.
  • Не изменяя вектор vertexData или код AGAL, используйте координатные данные вместо цветовых и наоборот.
Это должно выглядеть следующим образом:



Надеюсь, вам понравилось!

Оригинал: Stage3D/AGAL from scratch. Hello Triangle
Автор оригинала: Norbz.
Перевод: MidnightOne.

PS: У меня сейчас мало времени, поэтому перевод так задержался и картинки не переведены. Картинки исправлю в течение недели.

Комментарии (18)

0
Хорошие уроки.
+4
А меня все интересует вопрос, уже можно переходить на стейдж3Д?
такая игрушка на фгл и других порталах запустится без косяков?
0
Я сейчас делаю игру на stage3D и скоро(я думаю) выложу её на ФГЛ.
Вот тогда и будет видно стоит делать или нет и как к этому относятся спонсоры.
Вообще считаю это довольно рискованной затеей и на 100% в результате не уверен, но всё же решил попробовать.
0
стейдж3Д вроде же требует wmode=direct. А это значит про виральность можно забыть.
+1
Как только уровень моей самооценки с великими усилиями добирается до положительных величин, я натыкаюсь на новую AGAL статью…
Спасибо, камрады, что не даете скучать ;D
0
учится, учится и еще раз учится! :)
0
Хороший цикл статей. Читаю с удовольствием.
0
ОГРОМНОЕ СПАСИБО ВАМ за то, что делитесь своими знаниями.
Они востребованы и полезны!!!


Позвольте уточнить.
Пиксельный шэйдер, он же фрагментарный?

и у меня во флэш девелоп ругается.


создал проект HelloTriangle
в HelloTriangle.as скопировал Ваш код
в /lib/ скопировал и подключил com.adobe.utils.AGALMiniAssembler;

при компиляции выдает ошибку
взгляните пожалуйста. что я делаю не так?

архив приложил
0
В Flashdevelop Project->Properties->Platform = flashplayer 11.*
0
Пиксельный шэйдер, он же фрагментарный?
Да, все правильно.
0
не помогает :(
скачал последний playerglobal11_3.swc
но во flex_sdk_4.6 воткнуть его не получается.
там максимум Flash Plaer 11.2

на каком flex_sdk и playerglobal Вы компилировали этот код?

никто не знает как playerglobal11_3.swc к flex_sdk_4.6 прикрутить
пробовал даже в папку 11.2 переместить playerglobal11_3.swc естественно переименовать в playerglobal.swc
но все равно те же ошибки выдает.

а так хочется код помучить :(
0
Flex SDK 4.6.0
Выложенный код (и ваш архив) работает на версиях FP начиная с 11.0

Playerglobal не нужен раз этот функционал входит в официальную сборку.
0
Там же видно, что AGALMiniAssembler не найден.
0
Он лежит в папке src\actionscript\libs\com\adobe\utils в Архиве из поста.
Не суть где лежит, надо правильно Classpaths настроить.
0
У_У_У_У_У_У__У_У_У_У_А_А_А!!!
работает)))))))))))))))))

MidnightOne, ryzed СПАСИБО :) Р А Б О Т А Е Т!!!!!!!

Да чего же он клёвый, этот треугольник :)
С нетерпением жду следующего поста. :)
0
ryzed, когда Ты раскроешь секрет? Над какой игрой Ты сейчас работаешь?
0
Пока ничего серьезного, мелочевку делаю.
0
:) все равно, интересно над чем работает рука Мастера.
Майкоп это Адыгея, если я не ошибаюсь, рядом с Краснодаром.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.