ПрограммированиеФорумГрафика

Мягкие тени для Point Light в Unity3D (моя реализация)

Страницы: 1 2 3 Следующая »
#0
14:25, 24 дек 2015

Последний актуальный скриншот с хитрой реализацией теней с эффектом размытия на расстоянии:

В действии:

Изображение

Тестовая сцена:

Изображение

По поводу исходников - пока попридержу, но позже обязательно выложу, плюс напишу инструкцию и все дела, только немного подождите.

——-

А вот, с чего всё начиналось:

Я в курсе, что 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. Но это уже совсем другая история, а пока вуаля:

Изображение

А платные ассеты я делать планирую. На другую тему, но тоже из разряда нововведений :)

#1
15:21, 24 дек 2015

а откуда приходят vec и xvec? что в них?

и вот это: это сознательно выбранные значения или от балды?
float3(12.9898,78.233,45.5432);
43758.5453

#2
15:39, 24 дек 2015

vec - это вектор из лампочки к пикселю.

xvec и yvec я получаю сам, это вектора, перпендикулярные vec, они нужны для того, чтобы проецировать координаты выборок на плоскость. Я написал, для чего это нужно.

В начале функции есть ещё такие строки, где получается mydist (расстояние с биасом в пространстве текстуры):

  float mydist = length(vec) * _LightPositionRange.w;
  mydist *= 0.97; // bias

Также у меня там указана нестандартная функция-обёртка вокруг samplerCUBE_float , которая в свою очередь может быть ещё какой-нибудь обёрткой вокруг выборки из кубмапы... Я по возможности оставил все функции и переменные, которые были в стандартном варианте Unity.

Значения для рандомной функции популярные, все ими пользуются :) Я просто оптимизировал под вектор, разложив на три строчки вместо одной.

#3
15:55, 24 дек 2015

>Я по возможности оставил все функции и переменные, которые были в стандартном варианте Unity.
да я честно говоря вообще, не в курсе какие там стандартные  функции и переменные в Unity, поэтому и спросил )
просто подумал что это можно применить и для другого движка в котором меня тоже достали угловатые края теней, хотя от части это решается правильной настройкой каскадных теней.

#4
16:03, 24 дек 2015

Каскадные тени - это солнышко :) То есть направленный свет и 2d-текстура. Можно включить фильтрацию и, сделав несколько выборок, получить очень плавную тень. А тут кубмап, фильтрацию просто так не включишь, поэтому столько проблем.

Изначально в эту функцию входит только vec (тип float3, он же vector3) про который я написал. Выходит это всё в виде float-значения затенённости, от нуля до одного. Кусок такой функции указан самым первым, в нём всё предельно понятно. А всё остальное я уже накрутил сам с горя :) Много эксперементировал с производительностью, как лучше создавать координаты, чтобы fps был повыше :)

#5
16:37, 24 дек 2015

А где, собственно, мягкие тени?

Тему не читал, картинки смотрел

#6
17:29, 24 дек 2015

LifeKILLED
Еще слегка блур на тень и будет не плохо.

#7
17:40, 24 дек 2015

А где, собственно, мягкие тени?

Везде :) На первом скриншоте - стандартная реализация Unity3D, которая уже лет 7 не меняется (олдскул, ретро!). На втором и третьем - с моими доработками. Я человек скромный, поэтому мне больше нравится моя реализация :)

Еще слегка блур на тень и будет не плохо.

Если бы ещё это было возможно... Делал из того, что есть. Там сразу идёт выборка из кубмапа. Только если эти выборки увеличить раз в десять.

У меня есть пара мыслишек по поводу того, как сделать свою билинейную фильтрацию из lerp-ов и ceil-ов, тогда шума не будет ВООБЩЕ. Но надо делать... А пока вот: тени в стиле первых версий GTA4 :)

А, и повторю на всякий случай. Кто не в курсе, это Point Light, а не спот или дирекшионал...

#8
17:44, 24 дек 2015
Изображение

Изображение

а то не видно нифигась

#9
17:47, 24 дек 2015

Ещё не надо забывать про второй вариант:

Изображение

Он работает чуть побыстрее (четыре выборки, а не восемь), и всё же переход поплавнее, чем в третьем

#10
18:22, 24 дек 2015

выглядит не очень, как твердые тени с толстой неровной окантовкой, надо плавнее градиент

#11
19:04, 24 дек 2015

Плавнее - значит, больше выборок, а я о владельцах ноутбуков забочусь. Хотя есть мысль, как сделать реально плавно.

#12
0:26, 23 янв 2016

Уговорили. Сделал PCF4x4. Поскольку изначального сглаживания теней на лампочке с кубмапом в Unity3d нет (иными словами, там Nearest), за счёт дополнительного ряда пикселей, я Lerp-нул собственную билинейную фильтрацию и получил плавную-плавную тень PCF4x4 фактически из PCF5x5... Шейдер выкладывать не буду, он огромный, потому что все выборки я прописывал вручную, а не циклом. Для движка Unity3D это огромный прорыв, не побоюсь этого слова!

Изображение
Изображение

В данный момент никак не могу отладить Distance-Based, они же Persentage Closer, или как их там. Короче, размазанные вдали и чёткие вблизи. Но всё идёт к тому, что я и это сделаю.

#13
0:54, 23 янв 2016

LifeKILLED
гуд, это уже однозначно лучше встроенных.

> Distance-Based, они же Persentage Closer
PCSS?) не повредит

#14
1:16, 23 янв 2016

понакодят там крутых штук, а исходничками не делятся... ух, молодеееежь... =)

Страницы: 1 2 3 Следующая »
ПрограммированиеФорумГрафика

Тема в архиве.