Бесплатная реклама

Автор: Лев Симонов «LEVel»

Опубликовано: 15.01.2008

Изменено: 15.01.2008

Постоянная ссылка

Комментарии [0]

Саншафты в outdoor сценах


Автор статьи рассматривает возможный подход к рисованию саншафтов (sun shaft) в аутдор-сценах, прилагается пример реализации на DirectX 9.


Содержание

1. Введение
2. Что есть Саншафт?
3. Суть метода
4. Построение карт глубины
5. Построение карты шафтов
6. Рендеринг сцены и тумана с саншафтами
7. Результаты и перспективы
8. Исходный код

Введение

Объемный свет всегда производит сильное впечатление на зрителя. Он нагнетает атмосферу, придает сцене глубину и объем. Этот эффект частенько применяется в кинематографе и при создании трехмерных видеороликов, но вот в игры он начал перебираться совсем недавно, причем почти все реализации относятся к indoor-сценам. Причиной тому (как и в случае многих других кинематографических эффектов) была значительная сложность и ресурсоемкость процесса визуализации. И если в случае индор-сцен есть возможность локализовать тот небольшой кусочек пространства, в котором нужно нарисовать свет, то в случае аутдор-сцены это объем занимает все пространство сцены или значительную его часть.

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

Что есть Саншафт?

Прежде чем мы с головой погрузимся в разработку технологии, стоит уточнить, что за явление мы вообще моделируем. Саншафтом (Sun Shaft) называют частный случай лайтшафта (Light Shaft), в котором, как уже понятно из названия, источником света является солнце. Солнечный свет освещает присутствующие в атмосфере частицы, такие как капельки воды, частицы пыли или кристаллики льда, благодаря чему его можно «увидеть». При равномерном освещении ничего примечательного не происходит – мы видим обычный туман или дымку. Но если на пути солнечных лучей окажется какой-нибудь крупный объект, например гора или облако, то частицы, попавшие в тень, окажутся менее освещенными, и мы сможем увидеть «объемный свет».

Существующие подходы к рендерингу саншафтов условно делят на честные и нечестные (однако надо понимать, что честность тут относительная). В случае нечестного рендеринга эффект релизуется на уровне постпроцессинга – выделяются силуэты объектов сцены, после чего они масштабируются в направлении от солнца и смазываются. Похожий подход, кстати, используется в Crysis.

Второй вариант – честный рендеринг. Тут мы должны каким-то образом проверить затененность по всему пространству, и на основании полученной информации нарисовать наши саншафты. Но если пытаться проверять затененность в каждой точке пространства, то ничего хорошего из этого точно не выйдет – у нас на такое просто не хватит вычислительной мощности. Не говоря уж о реалтайме. А значит, нужно сделать ряд упрощений, которые позволят нам получить приемлемый результат за приемлемое время.

Обычно пространство, в котором нужно визуализировать свет заполняют плоскостями (или другими примитивами), на которые проецируется тень. В некоторых работах эти плоскости называют sample planes, думаю, можно позаимствовать это обозначение. Все это добро рисуется, смешивается и в результате получается целостная картина с саншафтами. На качество конечного результата значительно влияет расположение и количество отрисованных сэмплов. Раньше, в суровые времена FFP, процесс рендернинга был довольно сложным – нужно было нарисовать кучу настоящих примитивов и при этом позаботиться о том, чтобы они не выдали свою «плоскую» сущность, пересекаясь с объектами сцены. Да и overdraw при этом зашкаливает. Однако с появлением шейдеров, а особенно версии 3.0 – поддерживающей настоящие циклы, появилась возможность не только избежать рисования лишних пикселей, но и просто делать меньше вычислений. Именно такой метод мы и рассмотрим в этой статье.

Суть метода

Для начала хотелось бы вкратце описать процесс построения саншафта, так сказать, наметить каркас для дальнейшего повествования.

В первую очередь нам необходимо получить карту глубины сцены, «снятую» с позиции источника света – для построения теней, необходимых при рендеринге саншафтов. Как вы уже догадались, для построения теней используется технология shadow-map. В целом технология довольно распространена, так что те, кому еще не посчастливилось с ней познакомиться, могут без труда найти всю необходимую информацию в Интернете (в том числе на русском языке).

Вторым шагом будет опять же создание текстуры глубины. Только на этот раз отрисованной с позиции камеры – она понадобится нам для отсечения по глубине при рендеринге шафтов, да и вообще штука крайне полезная. Чтобы подкрепить сказанное делом, замечу, что эта же текстура используется при расчете тумана, который в свою очередь нужен нам для визуализации шафтов. Но об этом чуть попозже.

Теперь, когда у нас есть все необходимое, можно приступить непосредственно к рисованию саншафтов. Сразу скажу, что в моей модели в качестве вышеупомянутых примитивов используются плоскости, перпендикулярные камере и расположенные с постоянным шагом по локальной координате z. Для каждого пикселя экрана мы пробегаем по всем плоскостям и на каждой из них проверяем, попадает ли эта точка в тень. Результат складываем и записываем в специальную текстуру – назовем ее shaft-map (ну или картой шафтов). На ней темными областями будут отмечены будущие шафты. Чтобы сделать менее заметными неточности, связанные с ограниченным количеством примитивов замылим текстуру гауссовским блюром – опять же тривиальная задача с кучей примеров реализации. Все, теперь карта шафтов готова.

Саншафты в outdoor сценах
Рис 1. Расположение сэмплов

Саншафты в outdoor сценах
Pис 2. Shaft-map и соответствующая ей сцена

Настал момент нарисовать сцену. Ничего экстраординарного – просто рисуем сцену. Заодно можно нарисовать тени – раз уж у нас все равно есть текстура глубины.

И в заключение, наконец-таки рисуем сами саншафты. Вообще, имея на руках карту шафтов это можно сделать разными способами: можно заполнить весь экран полупрозрачной дымкой, сделав ее более прозрачной в областях, занятых саншафтами. Можно напротив немного затемнить шафты, а остальную сцену оставить без изменений. Я решил использовать туман с экспоненциальным спадом плотности по высоте, прозрачность которого регулируется картой шафтов. Конечно, любой из этих подходов имеет свои недостатки, однако всегда можно придумать более реалистичную модель, использующую все ту же карту шафтов. Результат всех наших терзаний, а именно туман, смешанный с шафтами, рисуется поверх сцены. Финальный кадр готов.

Ну вот, теперь можно со спокойной душой углубляться в реализацию.

Построение карт глубины

Как уже было замечено ранее, построение карты теней – довольно тривиальная задача. Однако, в силу принятых упрощений, она немного изменилась. Дело в том, что принято считать, будто бы солнечные лучи падают на поверхность Земли параллельно друг другу – хотя это и не так, но угол между ними настолько ничтожен, что идея учета этих углов при рендеринге кажется абсурдной. Итак, лучи параллельны. Это значит, что матрица проекции, используемая при построении теневой карты, превращается в обычную матрицу масштабирования. Правда, в этом случае плотность значений z-координаты окажется линейной, однако нам это только на руку – немного позднее мы узнаем почему.

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

И еще пару слов о самом процессе рисования. Для того, чтобы позволить полупрозрачным объектам (в нашем случае - облакам) отбрасывать тени, и, соответственно, оставлять за собой саншафты, я несколько изменил код шейдера, рисующего облака – он отбрасывает пиксели с прозрачностью ниже определенной константы.

//-----------------------------------------------------------------------------
// Vertex Shader: VertSkyDepth
// Desc: Process vertex for the Sky-shadow map
//-----------------------------------------------------------------------------
void VertSkyDepth( float4 Pos : POSITION,
                   float2 iTex : TEXCOORD0,
                   out float4 oPos : POSITION,
                   out float2 Tex : TEXCOORD0,
                   out float2 Depth : TEXCOORD1 )
{
	// Compute the projected coordinates
	oPos = mul( Pos, g_mWorldView );
	oPos = mul( oPos, g_mProj );

	// Store z and w in our spare texcoord
	Depth.xy = oPos.zw;
	Tex = iTex;
}

//-----------------------------------------------------------------------------
// Pixel Shader: PixSkyDepth
// Desc: Process pixel for the Sky- shadow map
//-----------------------------------------------------------------------------
float4 PixSkyDepth( float2 Tex : TEXCOORD0,
                    float2 Depth : TEXCOORD1 ) : COLOR
{
float4 tex = tex2D( g_samScene, Tex+time );
	clip(tex.x - SkyTransperency);
	return Depth.x / Depth.y;
} 

Вообще, можно использовать и другие, более прогрессивные методы построения карты теней. Например, можно учитывать прозрачность облаков, или вообще использовать для них отдельную теневую карту. Еще неплохой идеей было бы динамически изменять позицию, из которой снимается shadow-map – благодаря этому можно уменьшить ее размеры и избежать рисования того, тени от чего все равно не видно. Однако чтобы излишне не усложнять то, что не очень касается непосредственно метода, я возьму построение карты теней в самом простом исполнении.

Построение карты шафтов

Переходим к самой интересной части. Но для начала – очередное отступление.

Вообще говоря, при честном подходе нам нужно было бы для каждого пикселя каждого сэмпла (т.е. h*w*n раз, h и w – размеры экрана, а n – количество сэмплов) выполнить следующий ряд операций: определить его экранные координаты, сравнить с глубиной сцены, перевести эти координаты в координаты на карте теней и опять же сравнить глубину, после чего помножить на определенный коэффициент и записать в текстуру. Очевидно, что если мы будем делать это для каждого пикселя, то придется либо забыть про реалтайм, либо смириться с ужасным качеством шафтов. Ни того, ни другого мы делать не собираемся, поэтому придется хитрить.

А основная хитрость заключается в вычислении координат каждой конкретной точки. Рассмотрим «координатную обстановку» поподробнее. Всего у нас имеется четыре пространства: world-space, view-space, screen-space и light-space. Пожалуй, стоит сказать пару слов об этих пространствах. World-space – мировые координаты, независимая, неподвижная система отсчета. View-space – прямоугольная система координат, связанная с камерой. Screen-space – опять же связанная с камерой, но уже непрямоугольная система, значения координат x и y изменяются от -1 до 1, а z – от 0 до 1, причем нелинейно. Light-space - прямоугольная система координат, связанная с источником света, но с измененным масштабом координат. Вот так вот все запущено.

Теперь попробуем разобраться, зачем нам это надо. В идеальном случае – если бы мы рисовали каждый сэмпл отдельно – мы могли бы получить в пиксельном шейдере для каждого пикселя его координаты в view-space. Помножив эти координаты на предварительно заготовленную матрицу преобразования, мы бы получили координаты уже в light-space, которые можно использовать для выборки значения глубины из карты теней. Получив это значение и сравнив с z-координатой в light-space, узнаем, затеняется точка или нет, после чего записываем (или не записываем) в карту шафтов определенный цвет. И так для каждого пикселя каждого сэмпла.

Чтобы избавиться от многократного рисования сэмплов я решил вычислить матрицы преобразования только для двух из них – первого и последнего. В вершинном шейдере вычисляются видовые координаты для обоих из них, а в пиксельном мы можем получить видовые координаты, соответствующие точке любого, теперь уже виртуального, сэмпла просто линейно проинтерполировав координаты. Именно тут и вступают в игру преимущества шейдерной модели 3.0 – мы пишем цикл, в котором пробегаем предварительно заданное количество (в данном случае 80) сэмплов, и для каждого из них проверяем затененность.

...
float k = 0.0;
float stp = 1.5f/80.0f;  

for (int i = 0; i < 80; i++)
{
	// interpolation
	vPos = lerp(vPos1,vPos2,k += stp);

	// Transform the position to light projection space
	float4 vPosLight = mul( vPos, g_mViewToLightProj );
	float2 ShadowTexC = 0.5 * vPosLight.xy/vPosLight.w + float2( 0.5, 0.5 );
	ShadowTexC = 1.0f - ShadowTexC;

	// and depth-test
	float add = step(tex2D(g_samShadow, ShadowTexC).x, vPosLight.z);
	d += add*stp;
}

Однако появляется проблема – что делать с z-тестом? Очевидно, его придется взвалить на свои плечи, и в этом нам поможет уже готовая карта глубины, снятая из позиции камеры. Она хранит значения глубину сцены в screen-space, поэтому для того, чтобы определить, проходит пиксель тест глубины или нет, нужно привести координаты к одной системе. Чтобы не делать этого в цикле, мы просто переводим глубину сцены в view-space по следующей формуле:

Саншафты в outdoor сценах

где N и F – координаты near и far clip-planes, d-глубина, прочитанная из текстуры, z –координата в view-space

Теперь у нас в цикле есть одно преобразование координат, и два теста глубины:

...
float stp = 1.5f/80.0f;  //step of k - 80 samples
float k = 0.0f;

float depth = tex2D(g_samDepth, scrPos);
// g_vZTrans.zw = float2(1/N,(N-F)/(F*N));
float v_d = -1.0f/dot(float2(1.0,depth),g_vZTrans.zw);
float2 add; 

for (int i = 0; i < 80; i++)
{
	// interpolation
	vPos = lerp(vPos1,vPos2,k += stp);

	// Transform the position to light projection space
	float4 vPosLight = mul( vPos, g_mViewToLightProj );
	float2 ShadowTexC = 0.5 * vPosLight.xy/vPosLight.w + float2( 0.5, 0.5 );
	ShadowTexC = 1.0f - ShadowTexC;

	// and depth-test
	add.x = step(tex2D(g_samShadow, ShadowTexC).x, vPosLight.z);
	add.y = step(vPos.z, v_d);

	d += add.x*add.y*stp;
}

Причем, преобразование довольно тяжелое – нам нужно каждый раз перемножать вектор и матрицу. Но если опять обратиться к нашим пространствам, можно заметить, что прямая в view-space проецируется в light-space как прямая, причем если поделить ее на равные отрезки, то и проекции этих отрезков в light-space окажутся равными за счет того, что все координаты в light-space изменяются линейно. Таким образом, можно и тут применить трюк с интерполяцией, на сей раз интерполируя координаты в light-space. Причем даже не просто координаты, а текстурные координаты, которые можно непосредственно использовать для выборки из карты теней.

Саншафты в outdoor сценах
Pис 3. Проекция луча интеграции на карту теней

...
float stp = 1.5f/80.0f;  //step of k - 80 samples
float k = 0.0f;

float depth = tex2D(g_samDepth, scrPos);
// g_vZTrans.zw = float2(1/N,(N-F)/(F*N));
float v_d = -1.0f/dot(float2(1.0,depth),g_vZTrans.zw);
float2 add; 

for (int i = 0; i < 80; i++)
{
	// interpolation
	vPos = lerp(vPos1,vPos2,k);
	vPosLight = lerp(vPosLight1,vPosLight2,k);
	k += stp;

	// and depth-tests
	add.x = step(tex2D(g_samShadow, vPosLight.xy).x, vPosLight.z);
	add.y = step(vPos.z, v_d);

	d += add.x*add.y*stp;
}

Интересно, что всего в цикле нам нужно 4 координаты: текстурные координаты на карте теней, z-координата из light-space, и z-координата view-space. Все их можно объединить в один вектор, операции сравнения также можно связать, а умножение на константу вынести из цикла. Получаем достаточно быстрый код для построения карты шафтов. Вот так он выглядит целиком:

//-----------------------------------------------------------------------------
// Vertex Shader: VertShaft
// Desc: Process vertex for shaft
//-----------------------------------------------------------------------------
void VertShaft( float4 iPos : POSITION,
                float3 iNormal : NORMAL,
                float2 iTex : TEXCOORD0,
                out float4 oPos : POSITION,
                out float4 tPos1 : TEXCOORD3,
                out float4 tPos2 : TEXCOORD4,
                out float4 scrPos : TEXCOORD5 )
{
	// Transform position to view space
	float4 vPos1 = mul( iPos, g_mWorldView );
	float4 vPos2 = mul( iPos, g_mWorldView2 );

	// Transform to screen coord
	oPos = mul( vPos1, g_mProj );
	scrPos = oPos;
    
	// Transform to texture coord
	float2 Tex3 = scrPos.xy/scrPos.w;
	scrPos.xy = scrPos.xy/scrPos.w;
	Tex3.y = -Tex3.y;
	Tex3 = (Tex3 - 1.0f)*0.5f;
	scrPos.zw = Tex3;

	// Transform the position to light projection space
	float4 vPosLight1 = mul( vPos1, g_mViewToLightProj );
	float4 vPosLight2 = mul( vPos2, g_mViewToLightProj );   

	//transform from RT space to texture space.
	float2 ShadowTexC = 0.5 * vPosLight1.xy/vPosLight1.w + float2( 0.5, 0.5 );
	ShadowTexC.y = 1.0f - ShadowTexC.y;
	tPos1.xy = ShadowTexC;
	tPos1.z = vPosLight1.z;
        
	ShadowTexC = 0.5 * vPosLight2.xy/vPosLight2.w + float2( 0.5, 0.5 );
	ShadowTexC.y = 1.0f - ShadowTexC.y;
	tPos2.xy = ShadowTexC;  
	tPos2.z = vPosLight2.z;
        
	tPos1.w = -vPos1.z;
	tPos2.w = -vPos2.z;
}

//-----------------------------------------------------------------------------
// Pixel Shader: PixShaft
// Desc: Process pixel for enabled Shaft
//-----------------------------------------------------------------------------
float4 PixShaft( float4 tPos1 : TEXCOORD3,
                 float4 tPos2 : TEXCOORD4,
                 float4 scrPos : TEXCOORD5 ) : COLOR
{
	float d = 0.0;    
	float4 tPos;
    
	float depth = tex2D(g_samDepth, scrPos.zw);

	float stp = 1.5f/80.0f;  //step of k - 80 samples
	float k = 0.0f;
    
	// g_vZTrans.zw = float2(1/N,(N-F)/(F*N));
	float v_d = -1.0f/dot(float2(1.0,depth),g_vZTrans.zw);

	for (int i = 0; i < 80; i++)
	{
		// interpolation
		tPos = lerp(tPos1,tPos2,k += stp);
 
		// and depth-tests
		float2 add = step(float2(tex2D(g_samShadow,tPos.xy).x,v_d),tPos.zw); 
		d += add.x*add.y;
	}
    
	d = 1.0f-d*stp*0.5;
	return float4(d,1,1,1);
}

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

Так как картинка шафтов получена посредством аппроксимации пространства плоскостями, результат будет выглядеть немного «ступенчатым». Чтобы уменьшить заметность этого побочного эффекта, мы используем обычный gaussian blur, последовательно «смазывая» картинку по горизонтали и по вертикали.

Напоследок должен отметить, что вовсе необязательно использовать попиксельное представление карты шафтов. Другими словами, можно сделать размеры shaft-map меньше размеров backbuffer’а, скажем, 128х128 или 256х256. Это ведет к значительному росту производительности при сравнительно небольших потерях в качестве, особенно, если результат грамотно «смазывать». В демонстрационной программе применен именно такой метод. Однако перед «смазыванием» приходится масштабировать уменьшенную карту шафтов до размеров backbuffer’а – используя еще один проход рендеринга в текстуру. При попиксельном расчете этот шаг можно не делать.

Рендеринг сцены и тумана с саншафтами

Ну чтож, карта шафтов готова, теперь дело за малым. Для начала мы рисуем сцену, причем можно использовать уже готовую карту теней – почти бесплатно. Далее идет рисование саншафтов и тумана. Почему тумана? Потому что именно в тумане сильнее всего заметны саншафты – в ясную погоду их разглядеть практически невозможно. Как уже отмечалось ранее, для визуализации карты шафтов можно применить разные методы, однако я предлагаю использовать туман с экспоненциальным спадом по высоте. Формула для прозрачности будет иметь следующий вид:

Саншафты в outdoor сценах

Здесь O-позиция камеры, dir-вектор, идущий из камеры к точке сцены, ближайшей к камере на выбранном луче, с и b – константы, характеризующие спад и плотность тумана.

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

//-----------------------------------------------------------------------------
// Vertex Shader: QuadRenderVS
// Desc: Process vertex for the shadow map
//-----------------------------------------------------------------------------
void QuadRenderVS( float4 Pos : POSITION,
                   float2 UV : TEXCOORD,
                   out float4 oPos : POSITION,
                   out float2 OriginalUV : TEXCOORD0,
                   out float4 oPos2 : TEXCOORD1 )
{
	// Compute the projected coordinates
	oPos = Pos;
	OriginalUV = UV;
}

//-----------------------------------------------------------------------------
// Pixel Shader: QuadRenderPS
// Desc: Renders the shafts to the SA-Quad
//-----------------------------------------------------------------------------

float4 QuadRenderPS( float2 OriginalUV : TEXCOORD0,
                     float4 oPos2 : TEXCOORD1 ) : COLOR
{
	float d = tex2D(g_samShafts, OriginalUV).x + CustomMult[4]-1;
	
	float depth = tex2D(g_samDepth, OriginalUV);
	float4 scrPos;
	
	//translate texcoord to the world space
	OriginalUV.x = OriginalUV.x - 0.5;
	OriginalUV.y = 0.5 - OriginalUV.y;
	scrPos = float4(OriginalUV*2.0,depth,1.0);

	float4 wrdPos = mul( scrPos, g_mInvProj);
	wrdPos = wrdPos/wrdPos.w;
    
	float c = c0;
	float3 direction = wrdPos.xyz-g_vCamPos.xyz;
    
	// CPUpart is b*exp(-c*vCamPos.y)/c			
	d = d*saturate(CPUpart*length(direction)*(1-exp(-c*direction.y))/direction.y);
	float d1 = d*(1-CustomMult[2]*0.5)+CustomMult[2]*0.5;
	
	return float4(FogColor*d1*CustomMult[0],d1);
};

Массив CustomMult имеет чисто отладочное значение – он связывает с шейдерами значения, считываемые с бегунков в интерфейсе, что позволяет регулировать параметры эффекта, не перезапуская программу.

А так может выглядеть картина саншафтов при использовании других методов визуализации shaft-map:

Саншафты в outdoor сценах
Pис 4. Другие варианты отображения шафтов

Результаты и перспективы

Итак, у нас получился сравнительно простой и быстрый метод рисования саншафтов, вполне даже подходящий для работы в реальном времени. Так как основная нагрузка лежит на пиксельных конвейерах, то их количество на видеокарте во многом определяет скорость работы алгоритма. Современные карты уровня GF8800 показывают отличные результаты даже при попиксельном расчете саншафтов.

Отмечу, что описанный метод имеет ряд недостатков. Во-первых, если лучи от солнца падают перпендикулярно направлению камеры (т.е. параллельно плоскостям-сэмплам), то Можно заметить артефакты, особенно на шафтах от сравнительно небольших объектов, размеры которых сравнимы с расстоянием между сэмплами. Этот недостаток можно устранить либо заменив сэмпл на что-либо отличное от плоскости, либо поворачивать их в зависимости от угла между лучами и направлением камеры.

Второй недостаток связан с визуализацией: если сцена сильно затенена, то объекты, находящиеся близко к камере, могут оказаться окруженными «облаком» тумана. Однако при установке более реалистичных, непреувеличенных параметров смешивания этот эффект пропадает, хотя и в ущерб общей эффектности картинки. Однако подобных проблем, опять же, можно избежать, заменив или модернизировав алгоритм визуализации карты шафтов. Вот пример избавления от артефактов за счет изменения параметров:

Саншафты в outdoor сценах
Pис 5. Засвечивание

К статье прилагается демонстрационная программа, выполненная в стиле DirectX-примера, позволяющая управлять некоторыми параметрами рендеринга саншафтов прямо из интерфейса. Так как основная ее задача – демонстрация вышеописанного алгоритма в деле, то я не стал добавлять лишние эффекты – красивые тени, небо, воду, постпроцессинг. Однако думаю, что вместе с саншафтами позволят получить еще более атмосферный результат.

Что касается дальнейших перспектив развития метода, то тут в первую очередь стоит упомянуть изменение сэмплов – если заставить их поворачиваться, учитывая изменение угла между направлениями камеры и лучей света, то можно добиться уменьшения артефактов при любом расположении камеры. Распределение сэмплов в пространстве можно сделать нелинейным – увеличить расстояние между ними по мере отдаления от камеры, естественно, изменяя и их влияние на «плотность» саншафта. Это позволит получать более четкие шафты от небольших объектов около камеры и, одновременно с этим, от крупных, но отдаленных объектов. Да и объем, который охватывает эффект, тоже удастся увеличить. И еще одна важная область – алгоритм визуализации. Здесь особые надежды возлагаются на работу в широком динамическом диапазоне – «в одной связке» с HDR. А раз есть еще пути для наступления, то, возможно, будет и версия 2.0.

Напоследок несколько скриншотов, снятых с разными параметрами визуализации:

Саншафты в outdoor сценах
Pис 6. Cкриншоты

Исходный код

Исполняемый файл и исходники: sun_shaft.zip (1.6 Мб)

 


 

Статья участвует в конкурсе статей по программированию #2 (2007).