Продолжаем писать рейтрейсер на D

 

Здравствуйте дорогие посетители!

Сегодня мы с вами продолжим писать рейтрейсер, попутно изучая язык D.

Для тех кто пропустил предыдущую часть - рекомендуем начать с нее. Учим язык D на примере рейтрейсинга.

И так, в прошлый раз мы с вами создали каркас нашего будущего рейтрейсера. Во всяком случае приложение запускается и заполняет окно оранжевым цветом ;) Совсем неплохо.
Давайте выпишем основные блоки из которых должен состоять каждый уважающий себя рейтрейсер:

  • Tracer - класс, который собственно и занимается трассировкой лучей и синтезом конечного изображения
  • Scene - класс, представляющий трехмерную сцену, которую будет трассировать Tracer
  • Primitive (Plane, Sphere) - классы, представляющие объекты сцены (пока простейшие геометрические фигуры)

Естественно что хотелось бы загружать сцену из файла. При этом, чтобы файл сцены можно было подправить вручную. Я остановил свой выбор на JSON. Простой текстовый формат, легок для чтения и редактирования. К тому же парсер JSON входит в стандартную библиотеку D.

Давайте создадим новый модуль. Файл модуля назовем scene.d, и положим его в папочку rayd. Создадим класс Primitive. Самый важный функционал каждого нашего примитива какой? Правильно - уметь определить пересечение луча с самим собой. Для этого добавим в наш новый класс метод Intersecion

abstract int Intersection(in Ray ray, ref float distance);

Давайте немного рассмотрим это объявление метода. Метод принимает 2 параметра - луч и максимальное расстояние на котором можно искать пересечение. Возвращает результат пересечения как int (пока 0 или 1, задел на будущее). Однако нас интересуют фишки языка D. Ключевое слово abstract говорит компилятору что этот метод полностью абстрактный (pure virtual), и должен быть обязательно имплементирован во всех его наследующих классах. Как по мне намного симпатичнее чем

virtual void Foo() = 0;

Для того чтобы перегрузить виртуальный метод (так как все методы в D по умолчанию виртуальны) - нужно указать ключевое слово override перед определением метода:

override int Intersection(in Ray ray, ref float distance)

Параметр ray помечен как in - это означает что он не может быть изменен (т.е. константен) а так же назначен внутренней или глобальной переменной. Таким образом модификатор in равен по значению const scope, однако намного лаконичнее ;)  Второй параметр - distance - помечен модификатором ref, что означает что он будет передан по изменяемой ссылке. Таким образом мы можем передавать значение distance, равно как метод может его изменять. Полная аналогия с ссылками в С++.

Теперь рассмотрим еще одну "фишку" языка D - свойства (properties) объектов. Если не считать отсебятины от Microsoft (http://msdn.microsoft.com/en-us/library/yhfk0thd.aspx), в С++ свойств нет. Однако это очень удобная штука, и грех было бы ей не воспользоваться. Реализуются свойства в D посредством ключевого слова @property. Рассмотрим на примере:

@property const vec3 Position()
{
    return m_Position;
}
@property void Position(vec3 value)
{
    m_Position = value;
}

Я думаю что уже из самого кода понятно что здесь происходит. Мы объявили свойство Position и реализовали так называемые setter и getter. Причем не обязательно реализовывать оба - тогда свойство будет только read или только write. Нереально удобная вещь!

Как я уже упоминал - описание сцены у нас будет храниться в формате JSON. К счастью парсер включен в стандартную библиотеку D, так что для загрузки нам не понадобятся сторонние библиотеки. Вот ссылка на официальную документацию модуля json - http://dlang.org/phobos/std_json.html.

Во время написания загрузки сцены я наткнулся на недоработку парсера - при чтении чисел, парсер автоматически определяет тип числа, и для получения числа другого типа нужно вручную делать приведение типов. Так что написав значение "5" - парсер скорее всего решит что это unsigned int, и если вам потребуется float - будьте добры приводите тип вручную. Для этого мне пришлось написать вспомогательную функцию, а в данный момент я думаю над доработкой модуля и отправки патча разработчикам (все исходники модулей поставляются вместе с дистрибутивом языка, что очень удобно).

// iOrange - little function that helps us to get float value
real GetJSONFloat(JSONValue v)
{
    real ret = 0.0f;
    if (v.type == JSON_TYPE.FLOAT)
        ret = v.floating;
    else if (v.type == JSON_TYPE.INTEGER)
        ret = cast(real)v.integer;
    else if (v.type == JSON_TYPE.UINTEGER)
        ret = cast(real)v.uinteger;
 
    return ret;
}

Кстати - обратите внимание как сделаны приведения типов в языке D - очень наглядно и удобно. Приведение типов в стиле С здесь просто не работает, а в отличии от С++ с его const_cast, static_cast, reinterpret_cast, dynamic_cast - здесь мы имеем автоматическое определение компилятором. При приведении простых типов будет сгенерировано простое приведение, а при приведении более сложных типов (объектов классов) будет сгенерировано динамическое приведение, которое будет проверять приведение по иерархии вниз и в случае чего возвращать null. Гениально!

Последний класс который мы рассмотрим - Tracer. Тут все просто - самый главный метод класса

void Render(Scene scene, uint* image, int width, int height)

То есть рендерим сцену в указанный нам буфер размером width на height пикселов. Зная размер выходного изображения, мы для каждого пиксела построим луч и пустим его гулять по сцене, пока он не пересечется с геометрией. Если пересечение найдено - запишем соответствующему пикселу цвет в точке пересечения. Вот так вот вкратце работает "обратная трассировка лучей". Пока  нас нет камеры (черт, проговорился, да - камеру мы добавим в следующей части), мы параметры вьюпорта захардкодим немного. Вот так, схематически, выглядит процесс трассировки лучей:

Схема обратной трассировки лучей

Схема обратной трассировки лучей

Остальной код довольно прост и в особых комментариях не нуждается. В следующей статье мы добавим камеру и немного света, а пока скриншот того что получилось на текущий момент.

Скриншот программы

Скриншот программы

Как обычно - прикладываю исходный код к статье.

RayD_p2.zip (654)

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

(required)

(required)

Вы можете использовать HTML теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2011 3D-Orange.com.ua
e-mail me

3D-Orange.com.ua is proudly powered by WordPress.
Suffusion theme by Sayontan Sinha