Suslik
Добавил TransferFunction. Стало действительно годно
IBets
сделай ещё preintegrated transfer function или как он там называется, где в 2д текстуру предрассчитываются все возможные варианты реймарчинга через transfer function. это может улучшить производительность на порядок.
Suslik
> preintegrated transfer function
Вряд ли у меня там стохастический Ray-Marching.
IBets
никакой разницы нет. это просто ускорение для любого transfer function raymarching'а.
Suslik
А кто-нибудь из Volume texture делал поле расстояний по пороговому isosurface или оно того не стоит?
IBets
да, так делают. это на самом деле можно делать более-менее эффективно с помощью flood fill'а. но для меня основной интерес в volume rendering'е представлял всегда рендеринг именно volume'а, то есть не изоповерхности, а именно интегрирование по объёму, а там этот хак не прокатит.
Suslik
Ну смотри что бы по пустому пространству много не идти. Доходим до порогового и там интегрируем
IBets
без ограничения общности можно полагать, что пустого пространства вообще нет. по крайней мере я в таком допущении интегрирорвал. я использовал другую схему, где шаг подбирается таким образом, чтобы за один шаг интегрирования цвет результирующего пикселя поменялся меньше, чем на пороговую величину.
IBets
> что бы по пустому пространству много не идти
Для этого перед основным рендером я трассировал в картинку размером, кажется, в 64 раза меньше, чем экран, и по уменьшенной volume texture, в которой записана только плотность с точностью 1 байт. Трассировал с двух сторон, грубо говоря спереди (из камеры) и наоборот сзади в сторону камеры. Луч останавливался, когда плотность превышала порог, который рассчитывался из transfer function, меняющейся в реал-тайме. Таким образом получалась 2d текстура со значениями near и far. Основные лучи уже трассировались от near до far, таким образом пропуская пустоту в начале и в конце, но не в середине.
Прибавка по скорости была просто огромной, помнится что-то типа 1.5-2 раза, относительно обычного ray marching'а без этой оптимизации. Сам этот первый шаг рендерился предельно быстро - уменьшенная 1-байтовая volume texture лучше кешируется, и количество лучей очень маленькое. В итоге получается, что на один луч, рассчитывающий near-far, приходится 64 полноценных луча.
Distance field я тоже пробовал рассчитывать. Профит кажется был не существенный относительно оптимизации описанной выше, но была ещё проблема в том, что его нужно перерассчитывать каждый раз, когда меняется transfer function, а в моём случае нужна была возможность крутить transfer function в реал-тайме.
То же самое с pre-integrate - изменения в transfer function приводили к перерассчёту и были неприятные заедания. Хотя я его конечно не сильно оптимизировал, может и можно было улучшить. Кроме того, профита я особо не получил, поскольку на моих данных нельзя было использовать большой шаг ray marching'а в любом случае - можно было проскочить тонкие или плотные поверхности, и тогда появлялись артефакты. А на большом шаге да, профит был хороший - скорость была совсем чуть-чуть медленнее, но качество было ощутимо лучше - артефактов меньше, но они всё равно оставались. Пробовал делать хаки с шагом назад при резком повышении плотности, но всё это в итоге работало медленнее, чем честный ray marching с шагом, настолько мелким, чтобы артефакты не были заметны глазу. Понятно, что тут всё зависит от твоих данных.
Добавил блендинг.
Alexander K
Как ты поборолся с полосами?
Чувствую косяк с градиентом
IBets
такое чувство, что ты градиент считаешь после применения transfer function. надо до.
можешь дать ссылку на датасеты, которые ты гоняешь? сколько у тебя считается один кадр?
Suslik
Но градиент никак не влияет на выборку из transfer function, только Position. Ну если только не рассчитывать как Position = Positon + EPSILON * Normal. Я тестил разные комбинации особой разницы нет. Вот отсюда я взял manix.dat https://github.com/mlavik1/UnityVolumeRendering/tree/master/DataFiles
Формат R16. Первые 3 значения dim x, y, z uint16_t. Сказать сколько кадр рендерится сложно и я еще important sampling не пилил.
Нормали в world space
IBets
> Но градиент никак не влияет на выборку из transfer function, только Position
можно считать градиент функции F(p) = transferFunction(data(p)), а можно — F(p) = data(p). у второго больше точность (и считается быстрее). ещё качественный результат даёт предрасчёт нормалей через центральную разницу в текстуру, а потом интерполяция между нормалями. дело в том, что нормаль, вычисленная по интерполированным текселям — это вовсе не то же самое, что интерполированная нормаль, второе кушает больше памяти (в 4 раза, лол), но даёт красивые нормали без ступенек.
> https://github.com/mlavik1/UnityVolumeRendering/tree/master/DataFiles
спасибо
я тож за вечер решил написать volume renderer на vulkan'е на path tracing'е, пока что нога спанчбоба (не реализовал BRDF):
Suslik
У меня по интерполированным пикселям. Вот дает лучше чем центральная разность но все равно ступеньки
float3 GetGradientFiltered(VolumeDesc volume, float3 position) { const float3 G0 = GetGradientCD( volume, position); const float3 G1 = GetGradientCD( volume, position + float3( -volume.GradientDelta.x, -volume.GradientDelta.y, -volume.GradientDelta.z)); const float3 G2 = GetGradientCD( volume, position + float3( volume.GradientDelta.x, volume.GradientDelta.y, volume.GradientDelta.z)); const float3 G3 = GetGradientCD( volume, position + float3( -volume.GradientDelta.x, volume.GradientDelta.y, -volume.GradientDelta.z)); const float3 G4 = GetGradientCD( volume, position + float3( volume.GradientDelta.x, -volume.GradientDelta.y, volume.GradientDelta.z)); const float3 G5 = GetGradientCD( volume, position + float3( -volume.GradientDelta.x, -volume.GradientDelta.y, volume.GradientDelta.z)); const float3 G6 = GetGradientCD( volume, position + float3( volume.GradientDelta.x, volume.GradientDelta.y, -volume.GradientDelta.z)); const float3 G7 = GetGradientCD( volume, position + float3( -volume.GradientDelta.x, volume.GradientDelta.y, volume.GradientDelta.z)); const float3 G8 = GetGradientCD( volume, position + float3( volume.GradientDelta.x, -volume.GradientDelta.y, -volume.GradientDelta.z)); const float3 L0 = lerp( lerp( G1, G2, 0.5), lerp( G3, G4, 0.5), 0.5); const float3 L1 = lerp( lerp( G5, G6, 0.5), lerp( G7, G8, 0.5), 0.5); return lerp( G0, lerp( L0, L1, 0.5), 0.75); }
Тема в архиве.