В приложениях компьютерной графики (как для традиционных, так и для интернет-приложений) регулярно встречаются задачи создания различных текстур – растровых изображений, которые накладываются на поверхности трехмерных объектов. Разные типы текстур могут накладываться на объекты по-разному в зависимости от текстурных координат. Встречаются различные сложные специфические текстуры: составленные из нескольких других текстур, размноженные по поверхности, с применением масок по определенному цветовому ключу, а также комбинированные текстуры, обладающие свойствами сразу нескольких типов текстур (см. рис. 1).
Основная проблема, возникающая при работе с текстурами, – объемы памяти, необходимые для их хранения. При этом имеют значение объемы файла текстуры, текстуры в оперативной памяти (для ее обработки) и объем видеопамяти (для отображения текстуры). Объем памяти, занимаемой текстурой, зависит от различных параметров: размера (ширины и длины изображения), требуемого качества, количества слоев, повторяемости. Для простых многослойных и повторяющихся текстур существуют стандартные способы их создания: многослойные текстуры без повторно используемых элементов готовятся в редакторах; текстуры с повторяющимся узором по всей поверхности создаются и накладываются в процессе визуализации с помощью тайлингового (бесшовного) повторения заданного шаблона. Однако для более сложных текстур не всегда удается найти оптимальный способ создания: трудно отыскать хорошее решение для многослойных текстур, в которых есть независимые, повторно используемые слои (можно найти хорошее решение для отдельных слоев, но не для всей текстуры); метод тайлингового наложения нельзя применить к большим текстурам с неповторяющимся узором.
Одним из возможных путей решения задач оптимизации хранения и использования текстур является мультитекстурирование на основе шейдеров. Мультитекстурирование – это метод рендеринга с использованием нескольких текстур за минимальное число проходов [1], как правило, при помощи шейдеров. Шейдеры – короткие программы, которые выполняются непосредственно видеокартой и потому способны динамически и гибко осуществлять визуализацию [2–3].
В данной статье описываются способы применения шейдеров на языке AGAL во Flash для создания различных текстур, позволяющих, в частности, выполнять мультитекстурирование многослойных и повторяющихся текстур, а также многослойных разнородных текстур, в которых слои могут иметь разные параметры повторяемости.
Шейдеры во Flash
Данные шейдеры пишутся на специализированном языке AGAL [4–5] (в технологиях Microsoft DirectX используется подобный язык HLSL, в OpenGL – GLSL). По своей структуре и синтаксису AGAL очень напоминает ассемблер.
В AGAL есть вершинные и пиксельные шейдеры. В процессе рендеринга на каждом проходе графического конвейера вершинные шейдеры обрабатывают каждую вершину геометрии, пиксельные шейдеры – каждый пиксель. Рассмотрим более подробно пиксельные шейдеры.
Задача пиксельного шейдера – определить итоговое значение цвета пикселя на экране и передать эту информацию видеокарте. Пиксельный шейдер может получить цвет пикселя из текстуры и передать его дальше видеокарте, более того, перед отправкой он может обработать и изменить его. Таким образом, в процессе рендеринга можно динамически генерировать цвет любого пикселя необходимой сложной текстуры, то есть строить (или изменять) текстуру прямо в процессе визуализации.
Пиксельные шейдеры и многослойные текстуры
Пиксельные шейдеры могут получать на вход несколько (до 8) текстур. Это означает, что для каждого пикселя имеется несколько источников данных о цвете. Для получения итогового цвета пикселя в шейдере эти данные из различных текстур можно смешивать и изменять произвольным образом. Для соединения нескольких полученных текстур в одну многослойную текстуру достаточно сложить значения цветов пикселей всех текстур с каким-либо параметром смешивания. Это и есть шейдерный способ создания многослойных текстур. Его отличие от других способов в том, что не нужно хранить итоговую текстуру в памяти, так как она строится шейдером из полученных составляющих текстур динамически, непосредственно в процессе визуализации.
На рисунке 2 показаны различные способы получения сложной текстуры: в редакторе, в прикладной программе, в шейдере.
Алгоритм построения двухслойной текстуры с помощью шейдера позволяет последовательно получить
– текстурные координаты пикселя;
– цвет пикселя из текстуры 1;
– цвет пикселя из текстуры 2;
– коэффициент смешивания текстур для данного пикселя (коэффициент можно задать заранее или вычислить динамически);
– итоговый цвет пикселя по формуле с=с1*(1–k)+c2*k, где с – итоговый цвет; с1 – цвет пикселя из 1-й текстуры; с2 – цвет пикселя из 2-й текстуры; k – коэффициент смешивания.
Соответствующий код программы на AGAL. Пусть в вершинном шейдере текстурные координаты помещаются во входном регистре va1. Тогда передаем в переходный регистр v0 значение текстурных координат из регистра va1:
mov v0, va1
В пиксельном шейдере получаем текстурные координаты, умножаем на коэффициент и помещаем результат в регистр ft2:
mov ft2, v0
В регистр ft1 записывается цвет пикселя c1 из первой текстуры:
tex ft1, ft2, fs1 <2d, linear, repeat
В регистр ft2 записывается цвет пикселя c2 из второй текстуры:
tex ft2, ft2, fs2 <2d, linear, repeat>
В регистр ft1 записывается результат умножения цвета c1 на (1–k):
mul ft1, ft1, fc0.r
В регистр ft2 записывается результат умножения цвета c2 на k:
mul ft2, ft2, fc0.g
Итоговый цвет пикселя получается сложением полученных цветов:
add oc, ft1, ft2
Перед использованием данного шейдера, помимо другой информации, в него необходимо обязательно передать данные о текстурных координатах и коэффициенте смешивания в правильные регистры. Например, в движке Alternativa3D это можно сделать так:
drawUnit.setVertexBufferAt(1, uvBuffer, geometry._attributesOffsets [VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS [VertexAttributes.TEXCOORDS[0]]);
drawUnit.setFragmentConstantsFromNumbers (0, 0.4, 0.6);
Маски. По определению, маска – это текстура, в которой цвет каждого пикселя несет информацию о коэффициенте смешивания для других слоев. Таким образом, для создания многослойных текстур с масками можно с успехом применять вышеописанный алгоритм с той разницей, что коэффициент смешивания необходимо будет дополнительно получать еще из одной текстуры.
Пиксельные шейдеры и повторяющиеся текстуры
Повторяющиеся (тайлинговые) текстуры также можно создать с помощью шейдеров. Для получения информации о цвете пикселя шейдер использует текстурные координаты – двухмерные координаты пикселя на матрице текстуры. Текстурные координаты лежат в диапазоне от 0 до 1. В случае выхода за эти пределы значения модулируются. Благодаря возможности указывать (и менять) текстурные координаты в шейдере, можно получить цвет любой точки текстуры. Таким образом, чтобы получить повторяющуюся текстуру, достаточно умножить текстурные координаты каждого пикселя на нужные коэффициенты.
Алгоритм построения повторяющейся текстуры:
– из вершинного шейдера передать текстурные координаты пикселя;
– в пиксельном шейдере умножить полученные текстурные координаты на необходимый коэффициент;
– получить цвет пикселя из текстуры на основе умноженных текстурных координат: u2 = =u1*k1, v2=v1*k2, где (u1, v1) – исходные текстурные координаты, (u2, v2) – измененные текстурные координаты, k1, k2 – коэффициенты повторения для каждой координатной оси.
Соответствующий код на AGAL:
– в вершинном шейдере текстурные координаты помещаются во входном регистре va1; передаем в переходный регистр v0 значение текстурных координат из регистра va1:
mov v0, va1
– в пиксельном шейдере умножаем полученные текстурные координаты на коэффициент, который хранится в регистре fc0, и помещаем результат в регистр ft0:
mul ft0, v0, fc0
– получаем результирующий пиксель из измененных текстурных координат:
tex ft0, ft0, fs1 <2d, linear, repeat>
Пиксельные шейдеры и многослойные разнородные текстуры
Проблема создания многослойных разнородных текстур в том, что невозможно заранее подготовить текстуру, одна часть которой будет многократно повторена, а другая растянута. Однако шейдеры генерируют текстуру прямо в процессе визуализации, управляя каждым ее пикселем. Это позволяет решить проблему разнородности составных частей текстуры, так как их можно подготовить отдельно и сложить вместе в шейдере.
Основная идея алгоритма построения многослойной разнородной текстуры заключается в объединении двух вышеописанных алгоритмов. При этом повторение используется только для тех составных частей текстуры, для которых оно необходимо, а остальные остаются без изменения. Далее все слои каждого пикселя складываются и получается итоговый результат.
Алгоритм построения двухслойной разнородной текстуры:
– получить текстурные координаты пикселя;
– получить цвет пикселя из первой текстуры по неизмененным текстурным координатам;
– умножить полученные текстурные координаты на необходимый коэффициент повторения;
– получить цвет пикселя из второй текстуры на основе умноженных текстурных координат;
– получить коэффициент смешивания текстур для данного пикселя;
– получить итоговый цвет пикселя.
Соответствующий код шейдера на AGAL:
mov v0, va1
mov ft0, v0
tex ft1, ft0, fs1 <2d, linear, repeat>
mul ft0, v0, fc1
tex ft2, ft2, fs2 <2d, linear, repeat>
mul ft1, ft1, fc0.r
mul ft2, ft2, fc0.g
add oc, ft1, ft2
Пример применения шейдера для создания многослойной разнородной текстуры покрытия улицы
Допустим, необходимо покрыть модель виртуальной улицы текстурой. Пусть покрытие улицы включает два основных текстурных элемента: земля и асфальт. Земля – базовое покрытие, асфальт – дорожное покрытие. Учитывая относительные размеры объектов улицы (например домов), сама улица может иметь достаточно большие абсолютные размеры и при этом неоднородную многослойную (земля и асфальт) текстуру покрытия. Если текстуры домов будут иметь размеры 1 000 на 1 000 пикселей, текстура улицы должна будет иметь размеры не менее 10 000 на 10 000 пикселей. Если делать такую текстуру заранее, то из расчета, что один пиксель занимает 3 байта (1 байт на каждый цветовой канал), ее объем составит 10 000´10 000´3=300 000 000 байтов = примерно 300 Мб. Это очень большой объем памяти для одной текстуры. Вероятно, для нескольких улиц памяти уже не хватит. С другой стороны, основные текстурные элементы (земля и асфальт) повторяются на площади всей улицы и могут иметь размеры, например, 100 на 100 или 100*100*3=30 000 байтов @ 30 Кб. Размер маски для дорожного покрытия может занимать, например, 3*1 000*1 000 = 3 000 000 байт @ 3 Мб. Общий размер составляющих элементов в данном случае будет равен 3 Мб + 30 Кб @ 3 Мб. Используя шейдер при динамическом построении итоговой текстуры, нет необходимости тратить 300 Мб памяти для всей текстуры – достаточно только 3 Мб для ее частей. Применение шейдера в данном случае дает стократный выигрыш расхода памяти.
Таким образом, в статье были рассмотрены способы создания и визуализации разных видов текстур при помощи шейдеров AGAL. Предложен способ динамического генерирования сложной многослойной текстуры с повторением из составляющих элементов при помощи мультитекстурирования на основе шейдеров AGAL. Данный способ может применяться не только в шейдерах на языке AGAL для технологии Flash, но и в других технологиях, использующих шейдеры.
Литература
1. Боресков А. Мультитекстурирование в OpenGL. URL: http://steps3d.narod.ru/tutorials/tutorial-3.html (дата обращения: 07.03.2012).
2. Боресков А. Разработка и отладка шейдеров. СПб: БХВ-Петербург, 2006.
3. Андреев И. XNA для начинающих. Пиксельный шейдер. Мультитекстурирование. URL: http://iandreev.wordpress. com/2010/08/30/xnamultitex/ (дата обращения: 07.03.2012).
4. Гончар С. Molehill шейдеры. URL: http://flastar.ru/blog/ flastarposts/1156 (дата обращения: 07.03.2012).
5. Тимур Гагиев. Пишем шейдер на AGAL. URL: http:// xproger.mentalx.org/archives/568 (дата обращения: 07.03.2012).