Последний актуальный скриншот с хитрой реализацией теней с эффектом размытия на расстоянии:
В действии:
Тестовая сцена:
По поводу исходников - пока попридержу, но позже обязательно выложу, плюс напишу инструкцию и все дела, только немного подождите.
——-
А вот, с чего всё начиналось:
Я в курсе, что DirectX 11 (и соответственно всяческие крутые двиги) поддерживают мягкость таких теней, но мы говорим о Unity3D, где с этим плохо. Хотя, если будет интересно, можете переписать код под другие движки. Это обычный CG, все команды стандартные.
Почему я не стал создавать ассет и продавать его за бешеные деньги:
1. Чтобы тени поддерживали все шейдеры по умолчанию, нужно подменить стандартный шейдер Unity. Это гораздо удобнее, чем возня с конвертерами в ShadowSoftener.
2. Я не такой прожжённый торгаш, чтобы геморроиться с написанием конвертеров и портить этим жизнь себе и окружающим (см. пункт 1).
3. Мне самому будет легче, если поддержку этих теней введут в следующей версии Unity, и мне не придётся снова редактировать этот файл. Да-да, я обращаюсь к создателям: сделайте вы уже мягкие тени для лампочек! :) Отдаю за так, берите, пока тёпленькие!
Итак, чтобы тени заработали нужно:
1. Ищем в папке "...\Unity5\Editor\Data\CGIncludes" файл "UnityShadowLibrary.cginc" и редактируем его.
2. Чтобы увидеть результат, найдите папку своего проекта, в ней – папку "Library". Удаляем из этой папочки папочку "ShaderCache" и перезапускаем Unity. Да, я тоже перезапускал Unity после каждой компиляции, вошёл во вкус.
Теперь заглянем в "UnityShadowLibrary.cginc". Нам нужен будет вот этот кусочек:
#if defined (SHADOWS_SOFT) float z = 1.0/128.0; float4 shadowVals; shadowVals.x = SampleCubeDistance (vec+float3( z, z, z)); shadowVals.y = SampleCubeDistance (vec+float3(-z,-z, z)); shadowVals.z = SampleCubeDistance (vec+float3(-z, z,-z)); shadowVals.w = SampleCubeDistance (vec+float3( z,-z,-z)); half4 shadows = (shadowVals < mydist.xxxx) ? _LightShadowData.rrrr : 1.0f; return dot(shadows,0.25); #else
Что мы видим? Хитрым образом из одной циферки z создаётся небольшое смещение для вектора. Работает быстро, выглядит ужасно:
Во-первых, конечно же, смещение нужно выбирать не во всех трёх измерениях, а на плоскости, перпендикулярной вектору, падающему из лампочки. Таким образом, мы будем работать с кубмапом, будто с плоской текстурой. Так для лучшего результата нужно будет меньше выборок.
Для начала получаем виртуальные векторы осей X и Y, потом складываем их в разных последовательностях, таким образом выбирая точки на виртуальной же плоскости. Никаких матриц, всё быстро, удобно, понятно.
Во-вторых, следует ввести фактор случайности, как в PCF. Чтобы получить аналог Disc Bloor, я буду вертеть точки выборки вокруг вектора из лампочки.
Я сделал свою реализацию случайного вектора, оптимизировав уже имеющийся способ получения случайного числа, так всё работает гораздо быстрее.
#if defined (SHADOWS_SOFT) // Чем меньше, тем тень размытестее float downscale = 32.0f; // Случайный вектор const float3 rndseed = float3(12.9898,78.233,45.5432); float3 randomvec = float3( dot(vec,rndseed) , dot(vec.yzx,rndseed) , dot(vec.zxy,rndseed) ); randomvec = frac(sin(randomvec) * 43758.5453); // Вот эти вектора для виртуальной плоскости float3 xvec = normalize(cross(vec,randomvec)); float3 yvec = normalize(cross(vec,xvec)); // Пара смещений (вторая пара - противоположные этим, см. ниже) float3 vec1 = xvec / downscale; float3 vec2 = yvec / downscale; float4 shadowVals; // Выборки из кубмапы shadowVals.x = SampleCubeDistance (vec+vec1); shadowVals.y = SampleCubeDistance (vec+vec2); shadowVals.z = SampleCubeDistance (vec-vec1); shadowVals.w = SampleCubeDistance (vec-vec2); // Смешиваем half4 shadows = (shadowVals < mydist.xxxx) ? _LightShadowData.rrrr : 1.0f; return dot(shadows,0.25); #else
И видим:
Ну вот, те же четыре выборки, а какой результат. По крайней мере, пикселей уже не видно, а FPS почти такой же.
На хорошей текстуре шум не будет сильно заметен. Но в крайнем случае, можно увеличить число выборок, состряпав дополнительные векторы из векторов X и Y. Например, вот так:
vec1 = (xvec + yvec) / downscale * 0.86f; vec2 = (xvec - yvec) / downscale * 0.86f;
Это векторы, повёрнутые на 45 градусов относительно первых. Таким образом, мы получаем не 4 выборки, а 8, но и платим за это производительностью. Сильно не упадёт, но мало ли. На мой личный вкус 8 выборок против 4 большой визуальной разницы не делает.
А теперь внимание! Мы можем сделать аналог Area Light. Для этого, как водится, мы должны оценить среднее расстояние пикселя и изменить ширину зоны размытия.
Вот шейдер (я специально ввёл дополнительный дефайн, чтобы можно было отключить этот эффект):
#if defined (SHADOWS_SOFT) #define SHADOWS_SOFT_AREALIGHT float downscale = 32.0f; float areascalefactor = 10.0f; const float3 rndseed = float3(12.9898,78.233,45.5432); float3 randomvec = float3( dot(vec,rndseed) , dot(vec.yzx,rndseed) , dot(vec.zxy,rndseed) ); randomvec = frac(sin(randomvec) * 43758.5453); float3 xvec = normalize(cross(vec,randomvec)); float3 yvec = normalize(cross(vec,xvec)); float3 vec1 = xvec / downscale; float3 vec2 = yvec / downscale; float4 shadowVals; #if defined (SHADOWS_SOFT_AREALIGHT) // Lookup расстояний shadowVals.x = SampleCubeDistance (vec+vec1); shadowVals.y = SampleCubeDistance (vec+vec2); shadowVals.z = SampleCubeDistance (vec-vec1); shadowVals.w = SampleCubeDistance (vec-vec2); // Выбираем среднее значение float shadowdist = mydist - dot(shadowVals, 0.25f); // Меняем размер выборки downscale *= 2.0f / (1.0 + (shadowdist > 0.0f) ? (shadowdist * areascalefactor ) : 0.0f); vec1 = xvec / downscale; vec2 = yvec / downscale; #endif // Продолжаем делать по новым координатам shadowVals.x = SampleCubeDistance (vec+vec1); shadowVals.y = SampleCubeDistance (vec+vec2); shadowVals.z = SampleCubeDistance (vec-vec1); shadowVals.w = SampleCubeDistance (vec-vec2); half4 shadows = (shadowVals < mydist.xxxx) ? _LightShadowData.rrrr : 1.0f; return dot(shadows,0.25); #else
Можно было бы всё это чутка доработать, можно было бы даже ввести свой вариант билинейной фильтрации с помощью математических функций lerp и ceil. Но это уже совсем другая история, а пока вуаля:
А платные ассеты я делать планирую. На другую тему, но тоже из разряда нововведений :)
а откуда приходят vec и xvec? что в них?
и вот это: это сознательно выбранные значения или от балды?
float3(12.9898,78.233,45.5432);
43758.5453
vec - это вектор из лампочки к пикселю.
xvec и yvec я получаю сам, это вектора, перпендикулярные vec, они нужны для того, чтобы проецировать координаты выборок на плоскость. Я написал, для чего это нужно.
В начале функции есть ещё такие строки, где получается mydist (расстояние с биасом в пространстве текстуры):
float mydist = length(vec) * _LightPositionRange.w; mydist *= 0.97; // bias
Также у меня там указана нестандартная функция-обёртка вокруг samplerCUBE_float , которая в свою очередь может быть ещё какой-нибудь обёрткой вокруг выборки из кубмапы... Я по возможности оставил все функции и переменные, которые были в стандартном варианте Unity.
Значения для рандомной функции популярные, все ими пользуются :) Я просто оптимизировал под вектор, разложив на три строчки вместо одной.
>Я по возможности оставил все функции и переменные, которые были в стандартном варианте Unity.
да я честно говоря вообще, не в курсе какие там стандартные функции и переменные в Unity, поэтому и спросил )
просто подумал что это можно применить и для другого движка в котором меня тоже достали угловатые края теней, хотя от части это решается правильной настройкой каскадных теней.
Каскадные тени - это солнышко :) То есть направленный свет и 2d-текстура. Можно включить фильтрацию и, сделав несколько выборок, получить очень плавную тень. А тут кубмап, фильтрацию просто так не включишь, поэтому столько проблем.
Изначально в эту функцию входит только vec (тип float3, он же vector3) про который я написал. Выходит это всё в виде float-значения затенённости, от нуля до одного. Кусок такой функции указан самым первым, в нём всё предельно понятно. А всё остальное я уже накрутил сам с горя :) Много эксперементировал с производительностью, как лучше создавать координаты, чтобы fps был повыше :)
А где, собственно, мягкие тени?
Тему не читал, картинки смотрел
LifeKILLED
Еще слегка блур на тень и будет не плохо.
А где, собственно, мягкие тени?
Везде :) На первом скриншоте - стандартная реализация Unity3D, которая уже лет 7 не меняется (олдскул, ретро!). На втором и третьем - с моими доработками. Я человек скромный, поэтому мне больше нравится моя реализация :)
Еще слегка блур на тень и будет не плохо.
Если бы ещё это было возможно... Делал из того, что есть. Там сразу идёт выборка из кубмапа. Только если эти выборки увеличить раз в десять.
У меня есть пара мыслишек по поводу того, как сделать свою билинейную фильтрацию из lerp-ов и ceil-ов, тогда шума не будет ВООБЩЕ. Но надо делать... А пока вот: тени в стиле первых версий GTA4 :)
А, и повторю на всякий случай. Кто не в курсе, это Point Light, а не спот или дирекшионал...
а то не видно нифигась
Ещё не надо забывать про второй вариант:
Он работает чуть побыстрее (четыре выборки, а не восемь), и всё же переход поплавнее, чем в третьем
выглядит не очень, как твердые тени с толстой неровной окантовкой, надо плавнее градиент
Плавнее - значит, больше выборок, а я о владельцах ноутбуков забочусь. Хотя есть мысль, как сделать реально плавно.
Уговорили. Сделал PCF4x4. Поскольку изначального сглаживания теней на лампочке с кубмапом в Unity3d нет (иными словами, там Nearest), за счёт дополнительного ряда пикселей, я Lerp-нул собственную билинейную фильтрацию и получил плавную-плавную тень PCF4x4 фактически из PCF5x5... Шейдер выкладывать не буду, он огромный, потому что все выборки я прописывал вручную, а не циклом. Для движка Unity3D это огромный прорыв, не побоюсь этого слова!
В данный момент никак не могу отладить Distance-Based, они же Persentage Closer, или как их там. Короче, размазанные вдали и чёткие вблизи. Но всё идёт к тому, что я и это сделаю.
LifeKILLED
гуд, это уже однозначно лучше встроенных.
> Distance-Based, они же Persentage Closer
PCSS?) не повредит
понакодят там крутых штук, а исходничками не делятся... ух, молодеееежь... =)
Тема в архиве.