|
|
Введение
Иногда разработчикам игровых приложений могут потребоваться создание динамической среды окружения – к примеру – смена дня и ночи, снег, дождь, облачность.
Существует множество методов достижения желаемого результата. Некоторые быстрые, другие медленные и сложные в понимании простыми разработчиками.
Изучив большинство методов реализаций динамического окружения, мы пришли к выводу, что они все не удовлетворяли нашим критериям – быть довольно быстрыми, качественными, а главное простыми. В методе Харриса[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
|
|