Помогите пожалуйста разобраться с блендингом в Direct3D 9.
Игра 2D, все спрайты выводятся как квады, D3DX не используется.
Имеется фон, на который нужно накладывать полупрозрачный тайл, который при этом ещё и обрезается по альфа-каналу маски.
Полупрозрачный тайл, это блики на стекле. Это часть большой тайлящейся картинки и его содержимое постоянно меняется (смещается при движении камеры).
Вот задача в картинках:
Я реализовал это с помощью пиксельного шейдера, в который передаю большую текстуру с бликом, сдвиг на текстуре блика и текстуру маски. Сопоставляю тексели маски и бликов и перемножаю альфу. Оно работает, но не батчится, т.к. для каждого тайла стеклянного пола (на картинке один тайл 256х256) приходится заряжать шейдер новыми параметрами. Да и вообще, до этого обходился без шейдеров, не хочется вводить новые требования к железу ради одного эффекта на стёклах.
Можно ли последовательно отрисовывая спрайт маски и спрайт с бликами добиться результата, который на картинке?
Нашел решение для наложения непрозрачного спрайта по альфа-маске: http://stackoverflow.com/questions/5097145/opengl-mask-with-multiple-textures
У меня это получается так.
1. Вывожу фон так:
_d3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); _d3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
2. Накладываю маску так (она забрасывает свой альфа-канал и всё, сама не выводится):
_d3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE); _d3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); _d3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); _d3dDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ZERO); _d3dDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_SRCALPHA);
3. Вот, теперь альфа канал у Dest заряжен какой мне нужен и нужно только перемножить альфу у Dest и Source, но я не знаю как. Поэтому тупо использую заряженную маской альфу из фона:
_d3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, FALSE); _d3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTALPHA); _d3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVDESTALPHA);
В результате вывожу по маске, но теряю прозрачность спрайта бликов:
Задача вывести одну текстуру с альфой из другой текстуры. Остальное что ты понаписал к этому имеет какое-то отношение?
В нулевой стейдж грузишь маску (альфу).
В первый стейдж - стекло, с его альфой. Режим смешивания альфы MODULATE.
Всё, на выходе полупрозрачное стекло обрезанное по маске.
Оу, спасибо! А я могу это без мультитекстурирования провернуть?
Дело в том, что маска и тайл бликов разных размеров, мне понадобятся разные текстурные координаты для них. Если бы можно было обойтись последовательным наложением, я бы с удовольствием этим способом воспользовался.
Вроде как msdn уверяет, что так можно: http://msdn.microsoft.com/en-us/library/windows/desktop/bb147218%… vs.85%29.aspx Попробую просто подряд накладывать стейдж 0 с D3DTSS_ALPHAOP - D3DTOP_MODULATE.
Не, просто так не получается. Утонул в этих SRCDESCINVALPHAOP'ах...
>мне понадобятся разные текстурные координаты для них
Ну, если существует матрица переводящая первые во вторые (а я не вижу причин ей не существовать), то можно установить её как трансформацию на первом стейдже для нулевого набора координат.
В два прохода вариант наверное через стенсил буфер.
Но это какой-то совсем крайний вариант. Мультитекстурингом тупо быстрее.
Можно конечно извратиться, сделать pre-multiplied цвет текстуре стекла и затем рендерить его вторым проходом установив SRC=DESTALPHA, DEST=INVSRCALPHA
Но чет я не уверен что тебе это настолько нужно.
Имхо самый оптимальный вариант: запечь блики в атлас с накладкой на фон, а накладку выводить не квадом, а подготовленным мешем, который будет из себя представлять квад + круг из треугольников. Понятное дело что у треугольников будут свои текстурные координаты.
Можно без запекания в атлас, в два прохода. Рисуем квад с накладкой, потом рисуем круг из треугольников со стеклом.
Боюсь я недостаточно подробно описал свою задачу. У меня тайловый ландшафт, дырки в полу, это такая мозаика, собираемая из набора в 15 тайлов.
Форма этой дырки может быть вот такой (желтые квадраты - тайлы):
То, что я описывал выше - круглая дырка, состоящая на самом деле из четырёх тайлов, с окошками в уголках. Мне нужно рендерить окно состоящее из различных фрагментов, каждый из которых имеет свою форму и свою маску точно размером с тайл.
Но это не всё - блики, это тоже тайл, но какого-то своего размера (на блике не нужно супер качество, я его буду нещадно растягивать), который на каждый конкретный тайл стекла проецируется определённым своим фрагментом - блики всех тайлов стекла должны не иметь швов между собой и при этом вся эта конструкция должна перемещаться относительно стекла в зависимости от положения камера. Игрок сдвинул чуть-чуть экран - стёкла остались на месте, а блики на них переместились.
Итого, для отрисовки массива стёкол и бликов я бы хотел уместиться в два-три батча. Все тайлы стёкол лежат в одной текстуре, маски в другой такой же, блики - третья текстура. Если бы я мог рисовать это всё многопроходно, то я бы сделал так:
1. Первым батчем отрисовал все тайлы стёкол.
2. Вторым батчем модифицировал альфа канал невидимой отрисовывкой туда всех тайлов маски.
3. Третьим батчем наложил на все тайлы блики.
С третьим пунктом проблема, т.к. я не понимаю как накладывать спрайты так, чтобы их альфа канал перемножился с альфаканалом того, на что я их накладываю.
В пиксельном шейдере я смогу узнать альфа пикселя на который рисуется пиксель моей текстуры? Можно попробовать шейдер без параметров только на третьем этапе использовать.
x
> > не понадобятся разные текстурные координаты для них
> Ну, если существует матрица переводящая первые во вторые (а я не вижу причин ей не существовать)
Эта матрица будет разной для разных тайлов, для каждого придётся делать отдельный батч.
> В два прохода вариант наверное через стенсил буфер.
> Но это какой-то совсем крайний вариант. Мультитекстурингом тупо быстрее.
Стенсил буфер даст мне полупрозрачность маски? Я с ним никогда не работал, но, насколько я понимаю, он на весь экран один, а если мне поверх стекла с бликами понадобится выводить другой объект со своими бликами, то это будет невозможно. А в игре такая ситуация очень вероятна - игровые объекты, которые находятся на стеклянной поверхности могут иметь свои собственные бликующие части.
x
> Можно конечно извратиться, сделать pre-multiplied цвет текстуре стекла и затем
> рендерить его вторым проходом установив SRC=DESTALPHA, DEST=INVSRCALPHA
> Но чет я не уверен что тебе это настолько нужно.
MrShoor
> Имхо самый оптимальный вариант: запечь блики в атлас с накладкой на фон.
Боюсь такие варианты с не подойдут, т.к. стекло (на моей картинке я это накладкой на фон назвал) должно быть самостоятельным полупрозрачным тайлом. Просто без блика. Дело в том, что если машина игрока слабая и нужно бороться за FPS или его видеократа не поддерживает шейдеры или мультитекстурирование, то я просто не вывожу блики, а просто рисую стеклянные тайлы ландшафта.
Вот видеопруф игрухи, дырки в полу, это оно самое - там будет бликующее стекло. Весь ландшафт собирается из тайликов.
>С третьим пунктом проблема, т.к. я не понимаю как накладывать спрайты так, чтобы их альфа канал перемножился с альфаканалом того, на что я их накладываю.
Именно это я тебе уже и написал. Сделать pre-multiplied цвет текстуре бликов и затем рендерить его установив SRC=DESTALPHA, DEST=INVSRCALPHA
Т.е. формула блендинга получится Target.RGB * (1.0 - Texture.A) + Texture.RGB * Target.A,
где в Target.A уже отрендерили маску с кругом,
а Texture.RGB предварительно умножили на Texture.A
Блики к "накладке на фон" здесь не имеют никакого отношения.
Прости, я не понимаю. Можешь чуть подробнее про приём с pre-multiplied цветом?
Судя по этой ссылке http://www.spherevfx.com/understanding-premultiplied-images/ pre-multiplied, это изображение, в котором полупрозрачные области специально обрабатывают (накладывают на чёрный цвет).
Но в спрайте бликов я не могу заранее знать где ляжет маска, т.к. он будет отрисовываться разными своими частями разными масками. Я не догоняю сути?
Внимательно посмотри на формулу. Блики ты предварительно умножаешь на СОБСТВЕННУЮ альфу.
Круг же берется из альфы таргета уже в рантайме.
>pre-multiplied, это изображение, в котором полупрозрачные области специально обрабатывают (накладывают на чёрный цвет).
Формально это можно назвать и "накладыванием на черный цвет", но это бредовая формулировка.
Pre-multiplied это, внезапно, когда каналы цвета заранее умножаются на альфу: RGBA -> (R*A, G*A, B*A, A)
x
> Pre-multiplied это, внезапно, когда каналы цвета заранее умножаются на альфу:
> RGBA -> (R*A, G*A, B*A, A)
Пока не понял, как это работает, но, вроде, догоняю что нужно сделать.
Вечером попробую реализовать эту схему. Спасибо за совет!
Kozinaka
> Боюсь такие варианты с не подойдут, т.к. стекло (на моей картинке я это
> накладкой на фон назвал) должно быть самостоятельным полупрозрачным тайлом.
> Просто без блика. Дело в том, что если машина игрока слабая и нужно бороться за
> FPS или его видеократа не поддерживает шейдеры или мультитекстурирование, то я
> просто не вывожу блики, а просто рисую стеклянные тайлы ландшафта.
> Вот видеопруф игрухи, дырки в полу, это оно самое - там будет бликующее стекло.
> Весь ландшафт собирается из тайликов.
>
Ну глядя на видео - похоже тебе нужно стекло строго внуртри дырок? Без нахлеста же?
Классный пруф. Мне игруха нравится. Когда релиз?
>как это работает
Классический альфаблендинг это
DEST.RGB * (1 - SRC.A) + SRC.RGB * SRC.A
Правое слагаемое можно посчитать заранее раз и навсегда, тот самый pre-multiply (если используешь DXT то для этого даже есть редко используемые DXT2 и 4)
Получается:
DEST.RGB * (1 - SRC.A) + SRC.R'G'B'
Мы освободили правую часть от операции, но можем её снова использовать, на этот раз уже для дополнительного маскирования по альфе из таргета:
DEST.RGB * (1 - SRC.A) + SRC.R'G'B' * DEST.A
Собственно все, выбираем соответсвующие формуле стейты блендинга:
(1 - SRC.A) = _INVSRCALPHA
DEST.A = _DESTALPHA
Это и есть твой третий пункт.
Тема в архиве.