Главная Новости Статьи Книги Обзоры Проекты Обо мне
Поиск:

меню
главная
новости
статьи
книги
обзоры
проекты
обо мне
 

 
Создание динамического окружения в ваших проектах



Введение

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

Часть первая. Небо

Для начала нам потребуется небо. Что же это такое и как его рисовать? Небо – это газовый шар на планете (атмосфера), через который, лучи от солнца проходят путь и рассеиваются в нем, на составляющие волны (цвета) – чем большее расстояние хода луча – тем больше рассеются синий и зеленый компоненты – поэтому закаты красные.
Хватит лирики. Переходим к практике.
Для начала нам понадобится геометрия неба. Для этого в любом удобном редакторе создайте сферу. Да, обычную сферу)).  После этого можно немножко сжать ее по вертикали. Размер сферы я выбрал 1000-2000 радиус. Сфера должна иметь текстурные координаты. Итак – в своем приложении загружаем и выводим сферу в позиции камеры, для того чтобы игрок был всегда в центре сферы.

D3DXMATRIX skyBW;
D3DXMatrixIdentity(&skyBW);
D3DXMatrixTranslation(&skyBW,cam_pos.x,cam_pos.y,cam_pos.z);

 

Рисуем сферу с шейдером неба – для него передаем такие константы:

-матрицу мира (World)
-матрицу вида (View)
-матрицу проекции (Proj)
-вектор позиции камеры (CamPos)
-вектор направления источника света (LightDir)
-текстуру неба (txScatt)

Это все.

Текстура неба выглядит так:

По сути – это три текстуры – дня, заката, ночи. Она служит для выборки цвета из нее согласно положению источника света.

В шейдере определяем константы:

float SunLightness = 0.2;
float sunRadiusAttenuation = 4096;
float largeSunLightness = 0.2;
float largeSunRadiusAttenuation = 1024;
float dayToSunsetSharpness = 4;
float hazeTopAltitude = 50; //высота тумана
float fDensity = 0.00028; //плотность тумана

Так выглядит вершинный шейдер неба :
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
PS_INPUT VS( VS_INPUT input )
{
PS_INPUT output = (PS_INPUT)0;
output.Pos = mul( input.Pos, World );
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Proj );
output.Tex = input.Tex;

    output.WorldEyeDirection =  CamPos.xyz - mul( input.Pos, World ).xyz;

    float dist = length(output.WorldEyeDirection);
output.Fog = (1.f/exp(pow(dist * fDensity, 2)));
return output;
}

Как видим ничего нового и не понятного. Преобразуем координаты вершин в экранные координаты, передаем текстурные координаты, передаем направление взгляда (WorldEyeDirection – луч от камеры к точке неба, в мировых координатах), и вычисляем значение переменной тумана.

Теперь пиксельный шейдер неба:

float4 PS( PS_INPUT input) : SV_Target
{
float4 colorOutput = float4(0,0,0,1);

// Считаем лучи вида, света и высоту камеры
float eyeAlt = CamPos.y;
float3 eyeVec = normalize(input.WorldEyeDirection);
float3 lightVec = normalize(LightDir.xyz);

// Само солнце
float sunHighlight = 0.3*pow(max(0, dot(lightVec, -eyeVec)), sunRadiusAttenuation) * SunLightness;        

// Подсветка солнцем атмосферы – ореол света вокруг диска
float largeSunHighlight = pow(max(0, dot(lightVec, -eyeVec)), largeSunRadiusAttenuation) * largeSunLightness;

// Считаем 2Д угол между вектором взгляда и вектором солнце-камера
float3 flatLightVec = normalize(float3(lightVec.x, 0, lightVec.z));
float3 flatEyeVec = normalize(float3(eyeVec.x, 0, eyeVec.z));
float diff = dot(flatLightVec, -flatEyeVec);           

float val = lerp(0.25, 1.25, min(1, hazeTopAltitude / max(0.0001, eyeAlt)));
// Применяем силу тумана между голубым небом и горизонтом
float YAngle = pow(max(0, -eyeVec.y), val);      

// Получаем 3 цвета основываясь на угле YAngle и углом между вектором вгляда и вектором солнца
float4 fogColorDay = txScatt.Sample( samLinear, float2( (1 - (diff + 1) * 0.5)*0.32, 1-YAngle));
float4 fogColorSunset = txScatt.Sample( samLinear, float2( (1 - (diff + 1) * 0.5)*0.32+0.34, 1-YAngle));
float4 fogColorNight = txScatt.Sample( samLinear, float2( (1 - (diff + 1) * 0.5)*0.32+0.68, 1-YAngle));

float4 fogColor;

// Если солнце над горизонтом, интерполируем цвет дня и заката
// Иначе интерполируем цвет заката и ночи
if (lightVec.y > 0)
{
// Смягчаем переход цвета дня к туману – позволяя получить более реалистичные значение чем при //использовании линейной интерполяции
fogColor = lerp(fogColorDay, fogColorSunset, min(1, pow(1 - lightVec.y, dayToSunsetSharpness)));
}
else
{
// Такая же схема для заката/ночи.
fogColor = lerp(fogColorSunset, fogColorNight, min(1, -lightVec.y * 4));
}

// подсветка атмосферы от солнца
fogColor += sunHighlight + largeSunHighlight;

// Применяем туман
colorOutput = lerp(fogColor, colorOutput, input.Fog);

colorOutput = fogColor+ sunHighlight;

// Делаем ночь «не абсолютно темной»
colorOutput.rgb += max(0,(1 - colorOutput.rgb)) * float3( 0.15, 0.15, 0.2 );

return colorOutput;
}

Стоит отметить режим семплера для текстуры. Он такой:


SamplerState samLinear
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = mirror;
AddressV = mirror;
};

 

Т.е. адресация установлена в режим зеркальности – т.к. геометрия сфера.
В результате мы должны получить такую картинку:

Результат работы шейдера неба.

 

В дополнение скажу – этот шейдер является некоей модификацией шейдера взятого из примера атмосферы от компании nVidia (FxComposer – Atmosphere Scattering)
Единственный минус данного метода – слишком медленные lerp`ы – в будущем буду думать над оптимизацией.
Теперь расскажу, как быстро получить вектор солнца. Для этого мы будем передавать сферические координаты, а на выходе получим вектор источника света:


D3DXVECTOR4 GetDirection(float Theta, float Phi)
{
float y = (float)cos((double)Theta);
float x = (float)sin((double)Theta) * cos(Phi);
float z = (float)sin((double)Theta) * sin(Phi);
float w = 1.0f;
return D3DXVECTOR4(x,y,z,w);
}

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

 

 

>>Следующая страница

 

 

 

Copyright CSS PSV 2009 Kherson
Hosted by uCoz