Технология RayCasting ( Основы )

   Несколько лет назад, поиграв в Wolfenstein-3D, я удивился почему можно поворачиваться только по одной оси. Так же этот вопрос не покидал меня, когда я играл в Doom, Blood, Duke Nukem 3D и подобные игры. Тогда я не знал что движки этих игр основаны на технологии, именуемой RayCasting. Когда я узнал о том, что же это за технология, все встало на свои места.
   Суть данной технологии в том, что для каждого пиксела изображения выпускается луч, который проверяется на пересечение с различными объектами сцены. Эта технология нераспространенна по причине своей ресурсоемкости. Современные компьютеры не способны справиться с такими просчетами в реальном времени. Но почему тогда вышеперечисленные игры так быстро работают? Неужели это чудо? На самом деле все значительно проще. Секрет такой скорости как раз в том, что игры эти скорее двухмерные чем трехмерные. Все просчеты происходят в двухмерном пространстве, а затем на основе полученных данных строится трехмерная картинка.
   Как уже было сказано выше, из глаз наблюдателя выпускается определенное количество лучей. В Wolfenstein-3D лучи выпускались только в одной плоскости, а трехмерное изображение строилось с использованием расстояния от точки наблюдателя, до ближайшей точки пересечения с обьектом. В моем прилагающейся программе, демонстрирующей элементарный RayCasting в деле, этих лучей 320. Разберем данный рисунок:

RayCasting in 2D

   Отрезок LM это гипотенуза равнобедренного треугольника видимости. Отрезок a это половина гипотенузы. Тоесть половина видимости, до вектора направления взгляда и высоты треугольника b. Соответственно c это положение наблюдателя.
   Лучи, выпускаемые из глаз наблюдателя, можно представить как массив векторов единичной длины. Функция, которая создает эти вектора в прилагающемся примере называется rcInitDir. Ей не передаются какие-либо параметры.


	int	x;
	...
	//Данный цикл имеет количество итераций, равное количеству векторов направления
	for( x = 0; x < rlist.iSSizeX; x ++ )
	{
		//Лучи по X-оси равномерно распределяются. Менять не нужно. На рисунке LM
		dirData[ x ].v[ 0 ] = x - rlist.iSSizeXDiv2;
		//А это как раз высота треугольника видимости b. Ось Y
		dirData[ x ].v[ 1 ] = 250;	// Меняя это значение можно задать необходимый угол обзора

		// Приведение вектора к единичной длине
		rcVecNormalize( dirData[ x ] );
	}

   Ширина экрана в пикселях хранится в переменной rlist.iSSizeX, а в rlist.iSSizeXDiv2 половина ширины. В качестве вектора направления луча выступает dirData, в который входит массив из двух элементов, отвечающих за X и Y координаты.
   Безусловно, это не единственный способ вычисления направлений векторов. С помощью этого способа можно задать максимум 180-градусный угол обзора. Так же можно эти вектора создать с помощью поворота единичного вектора на определенный угол, который зависит от угла обзора и количества векторов. Но в любом случае длина вектора направления должна быть равна единице.
   Теперь остается добавить различные объекты в сцену и проверить каждый луч на пересечение с ними. В прилагающемся примере есть два типа объектов:
- отрезок;
- окружность.
   Сначала нужно выделить память для массива, где хранятся информация о каждом объекте. Для этого была написана функция rcSetCount, которая принимает один параметр типа int.


	void clRayCast::rcSetCount( RCINT iCountShapes )
	{
		// Выделяет память под количество объектов, равное iCountShapes
		shpData = new strcRCShape[ iCountShapes ];
		// В класс записывается пороговое количество объектов
		this->iCountShapes = iCountShapes;
	}

   Теперь можно создавать сами элементы сцены. В примере это можно сделать вызывая функции rcLine( для отрезка ) и rcCircle( для окружности ).
   Цвет для этих объектов можно задать, предварительно вызвав функцию crColor4b.
   Функция rcTrace является главной. В ней осуществляются проверки на пересечения лучей с объектами сцены, и построение трехмерного изображения.
   В каждой итерации цикла( в примере итераций 320 ) проверяется пересечения луча dirData[ номер_луча ] со всеми активными объектами сцены. Если пересечение произошло, то вычисляется расстояние от наблюдателя до точки пересечения. Так же существует некая переменная, в которой хранится глубина пиксела, в примере она fSaveDepth. В начале каждой итерации ее значение становится равным пределу видимости. Так вот, получившиеся расстояние до точки пересечения сравнивается со значением в переменной fSaveDepth, если оно меньше, то в fSaveDepth записывается текущее расстояние. Так же в этом случае значение переменной iIntersect, становится равным номеру объекта, с которым произошло пересечение.
   Таким образом находится номер объекта и расстояние до точки пересечения для каждого луча.
   Зная расстояние и номер объекта, можно вычислить высоту вертикальной линии, которая будет выведена на экран. Это наипростейший случай, не предусматривающий даже затекстуривания. В примере высота линии вычисляется по формуле высота_экрана / расстояние_до_точки.
   Вот, в принципе, и весь минимум, который нужно знать о технологии RayCasting. Ниже я приведу краткий обзор функций класса clRayCast.

   rcInit( HDC hDc, RCINT iSizeX, RCINT iSizeY );
     hDc - контекст окна, в которое будет выводится изображение.
     iSizeX, iSizeY - размеры изображения.
     Вызывается один раз.

   rcInitDir( void );
     Вычисляются вектора направления лучей. Вызывается один раз.

   rcClear( RCINT rtiMode );
     rtiMode - режим очистки. В примере есть лишь RC_COLOR_BUFFER_BIT.
     Очистка буфера, в котором хранится изображение.

   rcClearColor4b( RCUCHAR r, RCUCHAR g, RCUCHAR b, RCUCHAR a );
     r, g, b, a - компоненты цвета. Значения от 0 до 255.
     Задает цвет, которым заполняется буфер изображения, при вызове rcClear.

   rcColor4b( RCUCHAR r, RCUCHAR g, RCUCHAR b, RCUCHAR a );
     r, g, b, a - компоненты цвета. Значения от 0 до 255.
     Задает цвет, который является текущим, для объектов сцены, вызванных после него.

   rcSetCount( RCINT iCountShapes );
     iCountShapes - максимальное количество объектов на сцену.
     Выделяет память под указанное количество объектов. Вызывается один раз.

   rcLine( RCFLOAT x0, RCFLOAT y0, RCFLOAT x1, RCFLOAT y1 );
     x0, y0 - начало отрезка..
     x1, y1 - конец отрезка.
     Добавляет линию к сцене.

   rcCircle( RCFLOAT x, RCFLOAT y, RCFLOAT r );
     x, y - центр окружности.
     r - радиус окружности.
     Добавляет окружность к сцене.

   rcTrace( void );
     Главная функция класса. В ней реализован RayCasting.

   rcSwapBuffers( void );
     Вывод буфера изображения на окно.

   rcCamRot( RCFLOAT ang );
     ang - угол поворота.
     Поворачивает все лучи на заданый угол.

   Многие считают RayCasting и RayTracing одной и той же технологией, но на самом деле RayCasting это технология с обратным ходом луча, а RayTracing с прямым. RayTracing позволяет добиться очень реалистичных эффектов, но он намного более требователен к железу, и поэтому создать движок, который бы быстро работал на RayTracing сейчас не представляется возможным.
   А теперь простенькая программа, с исходниками, демонстрирующая простейший RayCasting ( exe + src )

Статью написал faceH0r 29.04.2006
Используются технологии uCoz