Рейтрейсер на D, часть 3: Камера, свет!
Здравствуйте дорогие посетители!
Сегодня мы с вами продолжим писать рейтрейсер, попутно изучая язык D.
Для тех кто пропустил предыдущую часть - рекомендуем начать с нее. Продолжаем писать рейтрейсер на D.
И так, в прошлый раз мы с вами реализовали загрузку сцены из внешнего JSON-файла и простейший поиск пересечения с объектами сцены. Уже неплохо, мы, по крайней мере, уже можем воочию наблюдать результаты нашей с вами работы. Однако картинка пока что не сильно симпатичная, да и вообще - довольно плоская. Сказывается отсутствие освещения. В этой части мы реализуем простейшее бликовое освещение методом Фонга. Самое крутое в рейтрейсинге - это то что при реализации освещения честные тени являются "бесплатным бонусом". Так же реализуем "камеру", загружать настройки которой мы так же будем из файла описания сцены. Приступим.
Для начала добавим нашим объектам сцены параметр specular - показатель "бликовости" в диапазоне [0, 1]. Затем добавим описание камеры. Состоять оно будет из позиции (pos), направления взгляда (dir), определением где верх (up) и углом обзора (fov). В коде нашей камере будет соответствовать класс Camera.
Раньше мы в коде нашего трассировщика хардкодом задавали параметры экранной плоскости и направления лучей, что, естественно, не есть хорошо. Теперь мы имеем возможность задавать любые настройки камеры или даже импортировать их из пакетов 3D-моделирования (3DSMax например). Для того чтобы упростить построения луча из заданной точки экрана, в классе Camera реализованы методы Setup и ComputeRay. Задача первого посчитать опорные направляющие вектора экранной плоскости, а второго - построить луч выходящий из заданной точки и отвечающий требованиям камеры.
Как видно из приведенного рисунка - задача этих направляющих векторов однозначно определять экранную плоскость. Теперь, зная координаты точки на экране и параметры камеры, мы легко можем построить луч.
С камерой разобрались, перейдем к освещению. Для того чтобы получить освещенность объекта в точке пересечения, нужно узнать - видна ли эта точка хоть одному источнику освещения, и под каким углом. Для этого из точки пересечения нужно в сторону каждого источника света в сцене выпустить по лучу, и проверить - достигает ли он источника света, или нет. Мы подошли к выводу что нам придется: а) - выделить поиск пересечения в отдельный метод, б) - создать структуру, которая будет хранить результаты поиска пересечения.
На данный момент структура хранения результата пересечения выглядит так:
struct RayHitInfo { float hitDistance; vec3 hitPosition; vec3 hitNormal; vec3 hitColor; float hitSpecular; }; |
Где:
hitDistance - расстояние от начальной точки луча на котором произошло пересечение
hitPosition - собственно точка пересечения
hitNormal - нормаль в точке пересечения (определяется объектом)
hitColor - цвет объекта в точке пересечения
hitSpecular - показатель "бликовости" объекта
Теперь мы можем вводить новые методы в наш трассировщик. Это поиск пересечения:
bool FindIntersection(Scene scene, Ray ray, ref RayHitInfo hitInfo, ref float distance) |
И, собственно, метод расчета финального цвета пиксела для которого произошла трассировка (шейдер):
vec3 Shader(Ray ray, RayHitInfo hitInfo, Scene scene) |
Теперь немного об используемых методах расчета освещения. Как уже писалось выше, найдя пересечение с объектом сцены, мы в сторону каждого источника света строим луч, берущий начало в точке пересечения (hitPosition). Далее, используя закон Ламберта (Lambert's cosine law (wikipedia)) мы высчитываем освещенность в точке пересечения и аккумулируем результат. В случае освещенности, мы также расчитываем блик используя метод Фонга (Phong reflection model (wikipedia)) как самый простой.
В результате наших трудов мы получаем вот такую замечательную картинку! Обратите внимание на тени на "полу".
Данная часть получилась довольно короткой, однако результаты ее не менее ценны и интересны. Теперь мы имеем возможность наполнять нашу сцену объектами, выставлять камеру и получать довольно симпатичные картинки.
В следующей статье мы постараемся реализовать зеркальные и, возможно, прозрачные объекты
Как обычно - прикладываю исходный код к статье.
RayD_p3.zip (1300)