Блог. Создание эффектов окружения в реальном времени
GameDev.ru / Страницы / Блог. Создание эффектов окружения в реальном времени [Форум / Инфо]

Блог. Создание эффектов окружения в реальном времени

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

Часть вторая. Облака

Облака - взвешенные в атмосфере продукты конденсации водяного пара, видимые на небе с поверхности земли.
Именно так сказано в wikipedia.ru. Но что это нам дает? Все мы знаем как они выглядят с самого детства, т.к. видим их каждый день.
Самый простой способ нарисовать неповторяющиеся формы облаков – метод шума Перлина. Суть его такая – сперва мы создаем текстуру шума, далее увеличиваем ее размеры с помощью октав, и обрезаем цвета по экспоненте, чтобы выделить отдельные облака.
Шум Перлина - математический алгоритм по генерированию процедурной текстуры псевдо-случайным методом.
Т.е. существует некая функция – которой мы передаем случайное целое число – а она возвращает нам число от -1,0f до +1,0f. Вот она :

 //Функция двумерного шума, на вход два числа(int) на выход – шум (double)
 double Noise2D (int x,int y)
 {
 int n = x + y*57;
 n = (n << 13) ^ n;
 return ( 1.0f - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f);
 } 

Но какие числа ей передавать? Случайные! Следующая функция получает случайные числа :

int xor128(void) 
{ 
static int x = 123456789;
 static int y = 362436069;
 static int z = 521288629;
 static int w = 88675123; 
int t;
 t = x ^ (x << 11);
 x = y; y = z; z = w;
 return w = w ^ (w >> 19) ^ t ^ (t >> 8); 
} 

Итоговая функция создания текстуры с шумом Перлина будет такой :

HRESULT GenerateNoise(ID3D10Device* OurDevice, int size,ID3D10ShaderResourceView **Texture)
 { 
//ШАГ1 СОЗДАЕМ ДВУМЕРНЫЙ МАССИВ
double **NoiseR;
 double **NoiseG;
 double **NoiseB;
 double **NoiseA;

NoiseR = new double*[size];
 for (int z=0; z < size; z++){NoiseR[z] = new double[size];} 
NoiseG = new double*[size];
 for (int z=0; z < size; z++){NoiseG[z] = new double[size];} 
NoiseB = new double*[size];
 for (int z=0; z < size; z++){NoiseB[z] = new double[size];} 
NoiseA = new double*[size];
 for (int z=0; z < size; z++){NoiseA[z] = new double[size];} 

//ШАГ2 ЗАПОЛНЯЕМ МАССИВ ШУМОМ ПЕРЛИНА
for (int q=0; q<size; q++)
 for (int k=0; k<size; k++){
 NoiseR[q][k]= (Noise2D(xor128(),xor128()));
 NoiseG[q][k]= (Noise2D(xor128(),xor128()));
 NoiseB[q][k]= (Noise2D(xor128(),xor128()));
 NoiseA[q][k]= (Noise2D(xor128(),xor128()));
 }

//ШАГ3 ПЕРЕВОДИМ ЗНАЧЕНИЯ ШУМА В БИТОВЫЕ(Было -1:1, стало 0:255)
 for (int q=0; q<size; q++)
 for (int k=0; k<size; k++)
 {
 NoiseR[q][k] = (int)(255*(NoiseR[q][k]/2+0.5f));
 NoiseG[q][k] = (int)(255*(NoiseG[q][k]/2+0.5f));
 NoiseB[q][k] = (int)(255*(NoiseB[q][k]/2+0.5f));
 NoiseA[q][k] = (int)(255*(NoiseA[q][k]/2+0.5f));
 }

//ШАГ4 СОЗДАЕМ И ЗАПОЛНЯЕМ ТЕКСТУРУ
ID3D10Texture2D *PerlinTex = NULL; //PERLIN NOISE
 D3D10_TEXTURE2D_DESC desc;
 desc.MiscFlags = D3D10_RESOURCE_MISC_SHARED;
 desc.Width = size;
 desc.Height = size;
 desc.MipLevels = desc.ArraySize = 1;
 desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
 desc.SampleDesc.Count = 1;
 desc.SampleDesc.Quality = 0;
 desc.Usage = D3D10_USAGE_DYNAMIC;
 desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
 desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;

 if( FAILED(  OurDevice->CreateTexture2D( &desc, NULL, &PerlinTex )))
 {
 return S_FALSE;
 }


D3D10_MAPPED_TEXTURE2D mappedTex;
 PerlinTex->Map( D3D10CalcSubresource(0, 0, 1), D3D10_MAP_WRITE_DISCARD, 0, &mappedTex );

UCHAR* pTexels = (UCHAR*)mappedTex.pData;
 for( UINT row = 0; row < desc.Height; row++ )
 {
 UINT rowStart = row * mappedTex.RowPitch;
 for( UINT col = 0; col < desc.Width; col++ )
 {
 UINT colStart = col * 4;
 pTexels[rowStart + colStart + 0] = (UCHAR)(NoiseR[row][col]); // Red
 pTexels[rowStart + colStart + 1] = (UCHAR)(NoiseG[row][col]); // Green
 pTexels[rowStart + colStart + 2] = (UCHAR)(NoiseB[row][col]); // Blue
 pTexels[rowStart + colStart + 3] = (UCHAR)(NoiseA[row][col]); // Alpha
 }
 }

PerlinTex->Unmap( D3D10CalcSubresource(0, 0, 1) );

// Create the shader-resource view
 D3D10_SHADER_RESOURCE_VIEW_DESC srDesc;
 srDesc.Format = desc.Format;
 srDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
 srDesc.Texture2D.MostDetailedMip = 0;
 srDesc.Texture2D.MipLevels = 1;

if( FAILED( OurDevice->CreateShaderResourceView( PerlinTex, &srDesc, &(*Texture) )))
 {
 return S_FALSE;
 }


//free recources
 for (int i=0; i<size; i++) 
{
 delete [] NoiseR[i];
 delete [] NoiseG[i];
 delete [] NoiseB[i];
 delete [] NoiseA[i];
 }

delete [] NoiseR;
 delete [] NoiseG;
 delete [] NoiseB;
 delete [] NoiseA;

return S_OK;
 } 

Вызов функции:

ID3D10ShaderResourceView*    NoiseTex = NULL;
 GenerateNoise(Device,16,&NoiseTex); 

Текстура имеет размеры 16*16, теперь нужно ее преобразовать. Чем выше размеры текстуры, тем лучше, но нужно знать золотую середину. Я выбрал – 2048*2048.

Наша маленькая текстура должна быть такой:
Изображение
Увеличена в миллион раз =)


Преимущество метода Перлина в том, что данная текстура уже затайлена!

Создаем рендер таргеты и необходимые ресурсы (текстуры) для рендеринга текстуры облаков

HRESULT CreateCloudRecources(ID3D10Device* OurDevice, int size, D3D10_VIEWPORT* ViewPort, 
 ID3D10RenderTargetView** RTView, ID3D10ShaderResourceView** SRView)
 {
 ID3D10Texture2D* Texture;
 //SET UP VIEWPORT
 ViewPort->Width = size;
 ViewPort->Height = size;
 ViewPort->MinDepth = 0.0f;
 ViewPort->MaxDepth = 1.0f;
 ViewPort->TopLeftX = 0;
 ViewPort->TopLeftY = 0; 
 

D3D10_TEXTURE2D_DESC TexDesc;
 TexDesc.Width                   = size;
 TexDesc.Height                  = size;
 TexDesc.MipLevels               = 0;
 TexDesc.ArraySize               = 1;
 TexDesc.Format                  = DXGI_FORMAT_R16G16B16A16_FLOAT; 
TexDesc.SampleDesc.Count        = 1;
 TexDesc.SampleDesc.Quality      = 0;
 TexDesc.Usage                   = D3D10_USAGE_DEFAULT;
 TexDesc.BindFlags               = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
 TexDesc.CPUAccessFlags          = 0;
 TexDesc.MiscFlags               = D3D10_RESOURCE_MISC_GENERATE_MIPS;

    if( FAILED(  OurDevice->CreateTexture2D(&TexDesc, NULL, &Texture)))
 {
 return S_FALSE;
 }

       D3D10_RENDER_TARGET_VIEW_DESC RTDesc;
 RTDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
 RTDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D;
 RTDesc.Texture2D.MipSlice = 0;

       if( FAILED(  OurDevice->CreateRenderTargetView(Texture, &RTDesc, &(*RTView))))
 {
 return S_FALSE;
 }

       D3D10_SHADER_RESOURCE_VIEW_DESC SRDesc;
 SRDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
 SRDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
 SRDesc.Texture2D.MostDetailedMip = 0;
 SRDesc.Texture2D.MipLevels = 1;

       if( FAILED(  OurDevice->CreateShaderResourceView(Texture, &SRDesc, &(*SRView))))
 {
 return S_FALSE;
 }

return S_OK;
 } 

Вызов такой :

D3D10_VIEWPORT vpCloud; 
ID3D10RenderTargetView*    CloudRTView;
 ID3D10ShaderResourceView*  CloudSRView;
 bool FirstFrameCloudReady=false; 
CreateCloudRecources(Device,2048,&vpCloud, &CloudRTView, &CloudSRView); 

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

Вот так мы рендерим в текстуру :

if (!FirstFrameCloudReady)
 {
 Device->RSSetViewports( 1, &vpCloud );
 Device->OMSetRenderTargets( 1, &CloudRTView, NULL );
 Device->ClearRenderTargetView(CloudRTView, ClearColor );
 Scatter_Perlin->SetResource( NoiseTex );
 Device->IASetVertexBuffers(0, 1, &quad, &stride, &offset);
 Device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 ScatterCloudTech->GetPassByIndex( 0 )->Apply( 0 );
 Device->Draw(6,0 );
 FirstFrameCloudReady=true;
 } 

Рендерим облака в текстуру простейшим шейдером октав:

PS_INPUTCloud MakeCloudVS( VS_INPUT input )
 {
 PS_INPUTCloud output = (PS_INPUTCloud)0;
 output.Pos = input.Pos;
 output.Tex = input.Tex;
 return output;
 } 
float4 MakeCloudPS( PS_INPUTCloud input) : SV_Target
 {
 float n1=txPerlin.Sample( PerlinSampler, input.Tex*1);   
float n2=txPerlin.Sample( PerlinSampler, input.Tex*2);
 float n3=txPerlin.Sample( PerlinSampler, input.Tex*4);
 float n4=txPerlin.Sample( PerlinSampler, input.Tex*8);
 float n5=txPerlin.Sample( PerlinSampler, input.Tex*16);
 float n6=txPerlin.Sample( PerlinSampler, input.Tex*32);

float nFinal=n1+n2/2+n3/4+n4/8+n5/16+n6/32;
 float c;

float CloudCover=1.0; 
float CloudSharpness=0.001; 

c = CloudCover - nFinal;
 if (c < 0) c=0;

float CloudDensity;

CloudDensity = (1 - pow(CloudSharpness,c));
 return CloudDensity;
 } 

CloudCover – регулирует плотность покрытия облаков
CloudSharpness – четкость облаков

Возможен другой вариант шейдера октав – если кому интересно, вот он:

float2 st = input.Tex.xy;
 float g = 1.0; 
float4 r = 0.0;
 for (int i = 0.0; i < 8; i++) 
{
 r += g * (2.0 * txPerlin.Sample( PerlinSampler, st) - 1.0);
 st = st * 2.0;   
g *= 0.5; 
} 
r=saturate(r);

return r; 

Этот пример для 8 октав.

В результате получим вот такую текстуру:
Изображение

Теперь можно подготовить геометрию облаков. Открываем 3ДМакс. Создаем плоскость, размеры 2000*2000, количество сегментов 16*16. Конвертируем в Editable Mesh. В свитке выбираем Vertex Edit, в параметрах Soft Selection, выбираем Use Soft Selection. Настраиваем так:

Изображение

Выделяем центральный вертекс и вытягиваем вверх на 100-200 единиц.

Все. Экспортируем. Подгружаем в движок – теперь рисуем :

D3DXMATRIX cloudWorld,cloudScale;
 D3DXMatrixIdentity(&cloudWorld);
 D3DXMatrixIdentity(&cloudScale);
 D3DXMatrixTranslation(&cloudWorld,cam_pos.x,cam_pos.y-75,cam_pos.z);
 D3DXMatrixScaling(&cloudScale,1,0.9f,1);
 D3DXMatrixMultiply(&cloudWorld,&cloudScale,&cloudWorld);

Scatter_World->SetMatrix( ( float* )&cloudWorld);

Scatter_CloudTex->SetResource( CloudSRView );
 Device->IASetVertexBuffers(0, 1, &scape.VB[0], &stride, &offset);
 Device->IASetIndexBuffer(scape.IB[0], DXGI_FORMAT_R32_UINT, 0);
 ScatterRenderCloudTech->GetPassByIndex( 0 )->Apply(0);
 Device->DrawIndexed( scape.polygon_count[0][0]*3, scape.offset_index[0][0], 0 ); 

Понижаем на 75 единиц и немного масштабируем. Подберите эти значения сами чтобы облака смотрелись отлично.

Что же нужно нашему шейдеру? Ему нужно то же что и шейдеру неба, плюс текстуру облаков.

Вершинный шейдер облаков идентичен вершинному шейдеру неба. Он есть выше, поэтому пропустим его. Теперь главное – пиксельный шейдер облаков:

float4 PSCloud( PS_INPUTSB input) : SV_Target
 {
 //Пиксель из нашей текстуры облаков
float4 CloudTex = txCloud.Sample( PerlinSampler, input.Tex); 

//ЭТА ЧАСТЬ ИДЕНТИЧНА ШЕЙДЕРУ НЕБА – ПОЛУЧАЕМ ЦВЕТ НЕБА В ТОЧКЕ ОБЛАКА
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;

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);       

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 ); 

//ШЕЙДИНГ ОБЛАКА

float a = CloudTex.a;

if (LightDir.y<0.35) {a=lerp(a/3,a,LightDir.y*(1/0.35));}
 if (LightDir.y<0.0) {a=CloudTex.a/3.0;}

if (YAngle<0.35) {a=lerp(0,a,YAngle*(1/0.35));}
 if (YAngle<=0.0) {a=0;}

//немного глушим альфу...
a=lerp(a/2,a,CloudTex.a);

//ШЕЙДИНГ 
float3 ResCol=1;
 if (LightDir.y<=0.5) ResCol=lerp(float3(1.0,0.7,0.7),float3(1,1.0,1.0),LightDir.y*(1/0.5));
 if (LightDir.y<=0.0) {ResCol=colorOutput.rgb+float3(0.1,0.1,0.1);}

float3 SunD=LightDir.xyz;
 SunD.x+=0.85;
 float3 sunDirection   = normalize (SunD) * 0.015;//На сколько смещается текстура!
sunDirection.y = -sunDirection.z;
 sunDirection.z = 0.0;

 float2 oUv=input.Tex;     

 float Tmp=txCloud.Sample( PerlinSampler,  oUv+sunDirection.xy).a;
 float3 attenuation=(1.0 - clamp ((1-colorOutput.rgb/1.1) * Tmp, 0.0, 1.0));
 ResCol.rgb *= attenuation;

return float4(ResCol,a);
 }

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

Схематически это выглядит так:

Изображение

Точка 1 – будет самой светлой. Точка 4 – самой темной. Логично же!
Выражение - colorOutput.rgb/1.1 – в шейдере означает что тень облака будет такого же цвета как и освещенная сторона, но немного темнее.

Результаты показаны ниже :
Изображение

Изображение

Изображение

Ссылка | Комментарии [9]
27 авг. 2011

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

Изображение

Введение

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

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

Ссылка
27 авг. 2011

Пирамиды из бокалов в Москве, площадки, меню бара, цены.
2001—2018 © GameDev.ru — Разработка игр