воскресенье, 19 июля 2009 г.

Real-time reflection/refraction

Нашёл способ редактировать блог при блокировке https, по этому поводу надо что-то написать :).

Решил провести некоторый research c отражениями и преломлениями. Как известно, для растеризатора это большая проблема, и если отражения ещё можно как-то делать (на машинах там, или на всяких рюмках), то с преломлениями всё очень непросто. Дело в том, что для сложных прозрачных тел луч может преломляться по крайней мере несколько раз, и в случае растеризации найти, с какой геометрией соприкоснулся преломлённый луч, с кондачка не получится. Эта задача элементарно решается в случае рэйтрейсинга, но на сегодняшний день осуществить его на commodity hardware с нормальной скоростью не представляется возможным. Ждать появления рэйтрейсинга долго и не интересно, а красивых преломлений в real-time хочется уже сейчас, благо железо позволяет извращаться :)

Простейший способ учесть back-face refraction - это отрисовать back-face нормали с тестом глубины GREATER, и смешать front-face и back-face нормали с каким-нить весом, скажем, 0.77. Понятно, что это дедовский способ, но выглядит лучше, чем one-sided вариант. Есть подходы с различными аппроксимациями (Chris Wyman). Тоже в топку. Наиболее правильное решение - это делать рэйтрейсинг преломлённого луча прямо в image-space z-буфера.

Основная идея такая: имеем точку, где выпущенный из камеры луч соприкосается с front-face геометрией преломляющего тела. Находим преломлённый луч, и вытягиваем его до far plane. После этого идём по лучу, отсчитывая на нём позиции с заданным количеством шагов, проецируем их в image space и сравниваем с позицией в z-буфере. Позицию с ближайшим z-значением используем для выборки back-face нормали, которую используем для повторного преломления луча. Затем делаем выборку из кубической карты.
Interactive Image-Space
Refraction of Nearby Geometry

Это работает, но ограниченное кол-во выборок на луче приводит к тому, что мы находим только приблизительно место пересечения луча и геометрии, а это приводит к артефактам изображения. Можно увеличить кол-во выборок (> 8), но производительность начинает падать, а артефакты лечатся плохо. Более правильный подход - binary search на луче.
Interactively Rendering Dynamic Caustics on GPU
(Работа по каустикам, но это не суть важно).
Если тело более-менее выпуклое (а не вогнутое или другой сложной формы), можно за ограниченное кол-во выборок точно найти, где луч соприкасается со второй поверхностью, и использовать эти данные для повторного преломления луча.


Как правильно заметили Oliveira и Brauwers, прямой binary search может давать сбои, в случае если луч сильно отклоняется за пределы преломляющей среды и мы можем сделать выборку в невалидной области image space. Чтобы забороть это, необходимо использовать conservative binary search, т. е. прежде чем разделить луч надвое, нужно проверить, валидна ли область выборки. Если нет, то вторая половина луча отбрасывается, а первая снова делится надвое с проверкой на валидность деления.
Real-Time Refraction Through Deformable Objects
В общем, данная методика позволяет правильно учитывать преломление луча двумя поверхностями, чего обычно бывает достаточно. В принципе можно попробовать и для сложных моделей, с многократным преломлением луча, используя depth peeling или k-buffer для сохранения и сортировки преломляющих поверхностей, но это уже в некоторой степени изврат :(

Зачем нужны все эти сложности с точным поиском пересечения? Дело в том, что у меня есть идея получения преломлённого изображения тела, помещённого внутрь другого (жидкого) тела. Более конкретно - нужно засунуть некоего монстра в контейнер с биожидкостью, где он спокойно плавает и красиво так искажается по Френелю :-))). Стоит задача поиска пересечения луча не только со второй поверхностью, но и с поверхностью другого тела внутри преломляющего тела. Если делать это лишь приблизительно, то конечный результат будет неудовлетворительным.

Другая проблема, связанная с артефактами, заключается в том, что преломлённое изображение внутреннего тела получается больше, чем то, что отрисовывалось в буфер для последующей выборки, вследствие того, что преломляющее тело выступает в роли линзы. При этом часть пикселей растягивается, что неэстетично. Можно как-то фильтровать, но отдача от этого небольшая, изображение больше замыливается, а края всё равно рваные. Тут просто недостаток precomputed данных, поэтому нужно попробовать рисовать в двое большее, чем номинальное, разрешение, там же искать преломление, а уже потом делать downsampling полученного изображения и вклеивать его в кадр. +При поиске преломления делать несколько выборок вокруг найденных uv координат и усреднять цвет.

И последнее, кубические карты. Для повышения реализма хочется, чтобы не всё тело было жидким, а только 2/3 от него, но оставшаяся треть продолжала производить незначительные преломления из-за свойств стекла. Вот ссылка на видео, как это приблизительно должно выглядеть:
Glass in 3d max
(если кто знает, как напрямую вставлять видео с ютюба в блоггер, буду рад совету).

Если делать не по-пацански, то нужно два cubemap-а. Первый - рендерится из камеры, используется для преломлений только от стекла (луч, выпущенный из камеры, испытывает незначительное преломление, поэтому погрешности неважны). Второй cubemap рендерится из центра преломляющего объекта. Зачем? Потому что луч уже после первого (заметного) преломления жидкостью "видит" сцену уже другой, нежели она видна из камеры, и если расстояние между камерой и преломляющим телом более-менее значительно, преломлённое изображение из первого cubemap будет неправильным. Это легко представить, если вообразить, что рядом с преломляющим телом есть какой-то предмет. Преломлённый луч видит его "вблизи", а не так как он виден из камеры. Если не использовать второй корректирующий cubemap, то мы будем видеть просто искажённое изображение из первого. И наоборот, со вторым cubemap-ом должен возникать true эффект линзы. Кроме того, второй cubemap нужен для правильных отражений.

Пока это всё только спекуляции, большая часть этого ещё не реализована. Главное сейчас - понять, как сделать :)

Комментариев нет:

Отправить комментарий