
Stage3D / AGAL с нуля. Часть 3 – Hello Triangle
Итак, вы уже начитались первой и второй частей и хотите наконец чего-то более насущного. Поехали.
Можете начать со скачивания исходников здесь.
В этой статье мы научимся рендерить треугольники при помощи чистого Stage3D. Будет немного информации об AGAL, без которой не получится отобразить что-либо на экране, но не волнуйтесь, это не так сложно, как кажется.
Мы много говорили о вершинах в последних статьях. Вершина — это точка в пространстве, которая вместе с двумя другими образует треугольник в пространстве.
Вершинный буфер является простым списком чисел, так что нужно определить структуру хранения этих значений. Информация о каждой из вершин в буфере должна будет повторять ту же структуру. Интересным моментом является то, что вы можете передать все что угодно в вершинный буфер и самостоятельно решать что для чего использовать.
Тем не менее, есть как минимум 2 стандартные схемы вершинного буфера, но сейчас я опишу только одну.

(длина буфера = 3 вершины * 6 ячеек на вершину = 18)
Как вы можете видеть, наш вершинный буфер будет структурирован как простой список чисел, где первые три — содержат координаты, а три остальных — цветовую информацию. Каждая вершина добавляемая в буфер должна повторять этот паттерн.
Теперь, если вы все еще не сделали этого, стоит скачать исходник и открыть класс HelloTriangle в любимом редакторе, я подожду вас здесь.
Конструктор этого класса достаточно прост для понимания, так что давайте взглянем на функцию __init.
Работая со Stage3D, вы не используете все его классы. Вместо этого, инструментом является Context3D.
Первым делом, выберем один из Stage3D (stage.stage3Ds[0]) и запросим context3d. При получении события, мы сможем получить к нему доступ.
На этом шаге, Флеш попытается получить доступ к видеокарте. В случае, если это удалось, вы получите аппаратно ускоренный Context3D, работающий на DirectX в Windows, или OpenGL под Linux и MacOS. Если по каким-либо причинам это не удалось, вы все равно получите Context3D, но в этом случае он будет работать в режиме процессорной растеризации под названием SwiftShader.
Как только мы получили Context3D, присваиваем его соответствующему члену класса (переменной), ну а затем можно настроить буферы.
Все рендерится в back-буфер, который потом можно отобразить на экране.
Теперь создадим буферы и программу. Не волнуйтесь по поводу AGAL, я объясню его чуть позже.
Создание буфера — достаточно простой процесс. Единственным, что вам нужно знать будет:
Буфер индексов должен знать сколько инструкций мы будем выполнять. Здесь их 3.
Часть, касающаяся программы сложновата на данный момент, надо видеть больше, чтоб понять код AGAL. Но давайте все же посмотрим на код:
Создание программы достаточно просто. Теперь у нас есть самый просто код AGAL. пропустим его пока. Для компиляции шейдеров и AGAL программы в байтокод, мы используем AGALMiniAssembler, предоставляющий два шейдера: один вершинный, и один пиксельный.
Теперь, загрузим все это безобразие в видеокарту.
Простейшим способом загрузки в буфер является загрузка вектора (Vector — тип данных). Как вы можете видеть, Я создаю Vector из 3 * 6 элементов, соответствующих данным трех вершин. Координаты начинаются будут от -1 до 1 для x и y, но z касаться мы пока не будем. Цветовые компоненты, R, G и B, будут от 0 до 1. Первая строка несет информацию о верхней вершине, которая будет красной, вторая олицетворяет левую нижнюю (зеленую), и т.д.
В буфере индексов (index buffer), мы всего лишь определяем порядок, в котором перебираются вершины, чтобы образовать треугольник — 0,1,2 (индексы вершины в вершинном буфере).
Давайте загрузим программу
Это было легко, не так ли? теперь перейдем к AGAL.
Когда мы создали вершинный буфер, мы сообщили видеокарте, что какждая вершина будет хранить по 6 свойств. Это важно потому, что когда будет выполняться вершинный шейдер, одни и те же операции будут выполнены для каждой вершины.
Видеокарта будет обрезать по 6 ячеек из буфера за раз, как мы и задали. Каждая вершина должна быть скопирована в регистр быстрого доступа (fast access register) перед тем, как ее можно будет использовать в программе. Можете представлять себе регистр быстрого доступа как маленький объем видеопамяти, работающий невероятно быстро и находящийся в непосредственной близости от графического процессора.
Последним шагом будет сообщить видеокарте как разделить каждый «кусок» информации и куда «положить».

Код:
Только что мы дали понять видеократе как обращаться с вершинным буфером. Напоминание:
Код AGAL будет разделен на 2 шейдера: вершинный и пиксельный. Регистры могут быть доступны только из вершинного, но мы так же научимся обманывать видеокарту и получить доступ к пиксельному.
Давайте взглянем на синтаксис AGAL:
Например:
означает:
Еще пример:
значит:
Имена «переменных» на самом деле имена регистров. Запомнить их не сложно, но пока на понадобится только несколько:
Давайте взглянем на первый пример AGAL кода, который у нас есть — вершинный шейдер.
(Строки 159 и 160 класса Example)
Первая строка перемещает регистр va0, куда мы скопировали координаты вершин, в результирующую точку (output point) без изменений. Далее, мы должны переместить данные в пиксельный шейдер при помощи переменного регистра. В данном случае мы переместим только информацию о цвете, хранящуюся в регистре va1 (см. перд. параграф).
Просто, не правда-ли? Смотрим на пиксельный шейдер.
Совсем просто: перемещаем цвет из переменного регистра в результирующий без изменений. И всего-то!
После того, как программа скомпилирована AGALMiniAssembler'ом и загружена в память, мы моем сделать ее текущей активной программой видеокарты.
В нашем случае отрисовка достаточно проста т.к. нет никакого интерактива, камеры, анимации и т.д. Метод, отвечающий за это должен вызываться на каждом кадре.
Каждый кадр мы начинаем с очистки сцены, заливая ее белым цветом. Далее, рисуем треугольник в соответствии с индексным буфером. Метод drawTriangles отрисовывает все в back-буфер. Чтобы показать содеримое этого буфера, вызываем метод present().
Компилируем:

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

Надеюсь, вам понравилось!
Оригинал: Stage3D/AGAL from scratch. Hello Triangle
Автор оригинала: Norbz.
Перевод: MidnightOne.
PS: У меня сейчас мало времени, поэтому перевод так задержался и картинки не переведены. Картинки исправлю в течение недели.
Можете начать со скачивания исходников здесь.
В этой статье мы научимся рендерить треугольники при помощи чистого 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: У меня сейчас мало времени, поэтому перевод так задержался и картинки не переведены. Картинки исправлю в течение недели.
- +14
- MidnightOne
Комментарии (18)
такая игрушка на фгл и других порталах запустится без косяков?
Вот тогда и будет видно стоит делать или нет и как к этому относятся спонсоры.
Вообще считаю это довольно рискованной затеей и на 100% в результате не уверен, но всё же решил попробовать.
Спасибо, камрады, что не даете скучать ;D
Они востребованы и полезны!!!
Позвольте уточнить.
Пиксельный шэйдер, он же фрагментарный?
и у меня во флэш девелоп ругается.
создал проект HelloTriangle
в HelloTriangle.as скопировал Ваш код
в /lib/ скопировал и подключил com.adobe.utils.AGALMiniAssembler;
при компиляции выдает ошибку
взгляните пожалуйста. что я делаю не так?
архив приложил
скачал последний 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
но все равно те же ошибки выдает.
а так хочется код помучить :(
Выложенный код (и ваш архив) работает на версиях FP начиная с 11.0
Playerglobal не нужен раз этот функционал входит в официальную сборку.
Не суть где лежит, надо правильно Classpaths настроить.
работает)))))))))))))))))
MidnightOne, ryzed СПАСИБО :) Р А Б О Т А Е Т!!!!!!!
Да чего же он клёвый, этот треугольник :)
С нетерпением жду следующего поста. :)
Майкоп это Адыгея, если я не ошибаюсь, рядом с Краснодаром.