Имеется topdown-сцена по которой летают мухи.
Летают они, значит, и тени на землю отбрасывают.
Задача такая: размывать тени тем сильнее, чем выше муха поднялась от поверхности.
В игре много каких объектов по фиктивной оси z поднимаются, просто на мухах нагляднее.
То, что на скрине, это эрзац - тень хранится отдельным спрайтом и заранее размыта.
Хочется тень делать прямо из цветного спрайта мухи и размывать программно в шейдере (HLSL).
Вот из этой замечательной статьи я взял размытие попроще, с треугольным законом распределения, и попробовал реализовать шейдер с параметрическим размытием:
#define PIXEL_SIZE 0.00390625 //Уже отрисованная в текстуру тень, которую надо размыть sampler ShadowTex : register(s0); //Параметр передаваемый снаружи //param.x=1 param.y=0 - размытие по горизонтали //param.x=0 param.y=1 - размытие по вертикали float4 param: register( c0); float4 main( float2 texCoord : TEXCOORD0) : COLOR0 { float r = 10; //ПРОБЛЕМА 1: Не получается задавать радиус параметром r = param.z float totalScale = 1.0 + r; float4 value = tex2D( ShadowTex, texCoord) * totalScale; for( int x=1; x <= r; ++x) { float shift = PIXEL_SIZE * x; float scale = 1.0 + r - x; float2 sampleCoord = texCoord; //ПРОБЛЕМА 2: Не получается в рамках шейдеров 2.0 сначала //вычитать сдвиг, а потом прибавлять, для выборки до и после текущего пикселя sampleCoord.x = texCoord.x - shift * param.x; sampleCoord.y = texCoord.y - shift * param.y; value += tex2D( ShadowTex, sampleCoord.xy) * scale; sampleCoord.x = texCoord.x + shift * param.x; sampleCoord.y = texCoord.y + shift * param.y; value += tex2D( ShadowTex, sampleCoord.xy) * scale; } return value / ( totalScale * totalScale); }
Столкнулся с двумя проблемами, которые пока не в состоянии решить из-за потсутствия опыта работы с шейдерами:
ПРОБЛЕМА 1: Как мне сделать параметрический цикл? Шейдер отказывается компилироваться, если он не может развернуть цикл (что, конечно, с параметрическим циклом не получится).
ПРОБЛЕМА 2: Хотелось бы уместить решение задачи в модель шейдеров 2.0, если это возможно. Как делать выборки и до и после текущего пикселя так, чтобы шейдер компилировался в 2.0?
Окружение:
1. У меня DirectX 2D-движок на квадах в пространстве экрана, поэтому вершинные шейдеры не работают, только пиксельные.
2. Закон размытия в целом мне не сильно важен - если можно усреднением пикселей сделать параметрически - этого будет вполне достаточно.
3. Значение параметра размытия единое для всего отрисовываемого квада. Одна муха - одна тень с фиксированной степенью размытия, другая муха - другая степень размытия тени.
4. Тени черные, короче цвета не важны, важен альфа-канал.
Kozinaka
Самый очевидный и простой вариант - генерировать тени на ходу в программе (и кешировать), там же размывать, а в шейдере интерпоирвать между двумя уровнями размытия.
Попробуй сделать размытие с фиксированным радиусом в пикселях, но в зависимости от высоты мухи читать из разных mipmap уровней текстуры. Функция вроде tex2DLod называется, правда не уверен, есть ли она в SM2. Но если нет, то можно смещение mip уровней средствами самого DX'а сделать.
Kozinaka
>Шейдер отказывается компилироваться, если он не может развернуть цикл (что, конечно, с параметрическим циклом не получится).
>Хотелось бы уместить решение задачи в модель шейдеров 2.0, если это возможно. Как делать выборки и до и после текущего пикселя так, чтобы шейдер компилировался в 2.0?
>У меня DirectX 2D-движок на квадах в пространстве экрана, поэтому вершинные шейдеры не работают, только пиксельные.
Советую все-таки введение в шейдеры прочитать, чтобы не писать такое ;)
>Шейдер отказывается компилироваться, если он не может развернуть цикл (что, конечно, с параметрическим циклом не получится).
В твоей же ссылке опровержение.
>Хотелось бы уместить решение задачи в модель шейдеров 2.0, если это возможно. Как делать выборки и до и после текущего пикселя так, чтобы шейдер компилировался в 2.0?
Тебе дан sampler. В uniform можно передать размеры текстуры, откуда вычисляются параметры шага пикселя (допустим - текстура 256x256, шаг равен 1/256). Но шейдер на 2.0 выходит тяжелый, и на мобильниках такое не потянет.
>У меня DirectX 2D-движок на квадах в пространстве экрана, поэтому вершинные шейдеры не работают, только пиксельные.
shader состоит всегда как минимум из двух компонентов - vertex/pixel, а еще есть геометрический.
Kozinaka
Signed distance field тебе поможет. Очень дешево, и очень эффективно (именно то что тебе надо) и никаких циклов.
Первоисточник: http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_Alpha… ification.pdf
Кое-что на русском: http://habrahabr.ru/post/215905/
Сама идея такого Signed distance field - получить из размытого изображения четкие границы. Четкость границ определяется собственно шейдером.
В твоем случае нужно создать Signed distance field под максимальное размытие, и плавно увеличивать четкость по мере приближения мухи к земле.
x
> O'rly?
Вот гифка из хабрастатьи для убедительности:
x
> К примеру, как там насчет спрайтов с изначально не бинарной альфой?
Ты сейчас о чем?
MrShoor
Как генерится Sdf для чёрного контура?
-Eugene-
> Как генерится Sdf для чёрного контура?
Нужно обязательно сюда копипастить с тех ссылок?
MrShoor
> Нужно обязательно сюда копипастить с тех ссылок?
Тьфу, не то написал. Как генерится SDF для серого-черного конутра?
-Eugene-
> Тьфу, не то написал. Как генерится SDF для серого-черного конутра?
Очевидно, что SDF генерится для монохромного контура.
MrShoor
Вот в этом может быть проблема
-Eugene-
> Вот в этом может быть проблема
Проблема может быть во всем.
Проблема в дополнительных спрайтах для хранения тени, хотелось бы обойтись без них. :)
CasDev
> shader состоит всегда как минимум из двух компонентов - vertex/pixel, а еще есть геометрический.
Да кто спорит, просто я использую флаг D3DDECLUSAGE_POSITIONT (вся фишка в букве T в конце), сообщая, что вершины у меня уже обработаны и содержат экранные координаты. При таком раскладе пользовательские вершинные шейдеры просто не запускаются. Вот тут немного по теме: http://www.gamedev.net/topic/417067-question-about-vertex-shaders/#entry3777937 Я просто уточнил, что мне нужно решение без задействования вершинных шейдеров.
-Eugene-
> генерировать тени на ходу в программе (и кешировать), там же размывать, а в шейдере интерполирвать между двумя уровнями
x
> 3д текстура
gammaker
> Попробуй сделать размытие с фиксированным радиусом в пикселях, но в зависимости
> от высоты мухи читать из разных mipmap уровней текстуры.
MrShoor
> создать Signed distance field под максимальное размытие, и плавно увеличивать
> четкость по мере приближения мухи к земле
Спасибо за такое количество альтернатив! Особенно за Signed Distance Field, интереснейшая штука! Боюсь, все предложенные решения требуют хранения тени отдельным спрайтом, а это в два раза больше памяти, чем уже занимают спрайты. Сейчас я генерирую тени просто блендингом с чёрным цветом и полупрозрачностью, двумя проходами шейдера размытия я получаю почти всё, что требуется, осталось только заставить его размывать по параметру.
90% существ и объектов в игре не поднимаются от поверхности, их тени я буду размывать шейдером с фиксированной степенью размытия (самой маленькой), а вот для тех, кто поднялся в воздух я готов рассчитывать даже с избытком проходов, лишь бы иметь возможность плавно задавать степень размытия. Вот, собственно, игруха в своём текущем состоянии (от земли отрываются части червей, когда они прыгают, летающие мухи и прыгающие блохи):
x, MrShoor зацените блики! :) Я их сделал исключительно благодаря вашим советам в этой теме: http://www.gamedev.ru/code/forum/?id=184279
Позвольте чуть сузить тему: а что если сделать массив чисел в котором хранить функцию распределения весов соседних пикселей при формировании значения текущего пикселя? И передавать его в шейдер для каждого квада. Передача 20 float в шейдер, это тяжелая операция?
В таком случае количество выборок у меня всегда будет максимальным, как для r = 20. Что если использовать четыре разных шейдера, проходящих по 5, 15 и 20 соседним пикселям, взвешивая соответствующим фрагментом функции распределения? Переключение шейдера, это тяжелее, чем проход шейдера по спрайту максимум 256х256 с избыточным количеством проходов?
Если я в едином шейдере поставлю несколько грубых if'ов, разделив его на четыре, например, статических цикла по 5, 10, 15 и 20 выборок с фрагментом моей переданной функции распределения, то будут ли эти if'ы для каждого пикселя выполняться? Или соптимизируется и переключение веток будет один раз на квад производиться?
Kozinaka
Выглядит интересно, а уже есть где-нибудь демка?
Тема в архиве.