Страницы

вторник, 9 августа 2011 г.

Advanced Ambient Lighting

Амбиентному освещению традиционно уделяется наименьшее внимание в начале изучения программирования графики и шейдеров. Большинство примеров подставляют константное значение амбиентного света в результирующую формулу освещения. Тем не менее, в современных играх амбиентное освещение играет очень важную роль в формировании финальной картинки. С увеличением мощности железа появляются новые техники для эмуляции рассеянного освещения, такие как Global Illumination, Ambient Occlusion, Radiance Transfer, Indirect Lighting.
В этом примере будут рассмотрены несколько техник амбиентного освещения и будет показано, как они влияют на результат финальной картинки.
Вышеописанный случай константного амбиента, формула L = Ld + La, где L - результат вычисления, Ld - диффузная составляющая, La - амбиентная составляющая.
При детальном рассмотрении картинки заметно, что карта нормалей совсем потерялась на неосвещенных участках - они выглядят плоскими.
Чтобы сделать рельеф нормалей заметным, подсветим его дополнительным источником света, направленным сверху. В шейдере это выглядит так:
// vertex shader
OUT.SkyVector = mul(float3(0, 1, 0), TBN);

// pixel shader
float ambientPart = 0.5 + 0.5 * dot(IN.SkyVector, BumpNormals);


т.е. мы передаем из вершинного шейдера в пиксельный направление на вектор {0,1,0} - SkyVector в пространстве TBN. В пиксельном шейдере вычисляем результат амбиента как обычный источник света и смещаем результат в диапазон [0, 1]. Дальше делаем с результатом все то же самое, что и с обычной константой амбиента. Результат:

Этот результат можно еще улучшить. Представим себе дневное освещение в реальном мире. В солнечную погоду часть солнечных лучей преломляется в атмосфере и дает голубоватый оттенок. В пасмурную погоду оттенок ближе к серому. При закатном солнце - розово-оранжевый.
Более того, снизу объекты подсвечиваются светом, отраженным от поверхности, на которой они находятся. Если это асфальт или земля - оттенок темно-серый. Если это трава - зеленый. Вода - синий.
Используем это в шейдере. Передадим туда значения "верхнего" и "нижнего" освещения. Используем уже имеющийся результат для интерполяции между ними:
float3 ComputeAmbientColor(float3 skyVector, float3 normalVector)
{
float factor = 0.5 + 0.5 * dot(skyVector, normalVector);
return lerp(g_AmbientBottom.xyz, g_AmbientTop.xyz, factor);
}


Результат (только ambient):

Результат (diffuse + ambient):

Результат (с текстурой):




воскресенье, 7 августа 2011 г.

Fog with multipass lighting

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


C1 = L1 * 0.3 + F * 0.7 // первый проход
C2 = L2 * 0.3 + F * 0.7 // второй проход
C = C1 + C2 = L1 * 0.3 + F * 0.7 + L2 * 0.3 + F * 0.7 = (L1 + L2) * 0.3 + F * 1.4


Где L1 - результат вычисления цвета в первом проходе
L2 - во втором
C1 - суммарный результат освещения с туманом в первом проходе
C2 - во втором

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


Если выключить туман во втором проходе, результат будет так же неправильный:

C1 = L1 * 0.3 + F * 0.7
C2 = L2
C = C1 + C2 = L1 * 0.3 + L2 + 0.7 * F

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


Можно посчитать фактор тумана таким образом, чтобы в каждом проходе накладывалась только часть тумана
Fi = F / N
, где N - число проходов освещения. Этот подход возможен, но достаточно сложный в реализации, потому как при отсечении источников света рисуется только та часть объектов, которая попала в зону влияния источника света.

Самым простым решением проблемы является обнуление цвета тумана (до черного). Результат вычисления:

C1 = L1 * 0.3 + F * 0.7
C2 = L2 * 0.3 + F * 0.7 = L2 * 0.3 + 0.0 * 0.7 = L2 * 0.3
C = L1 + L2 = L1 * 0.3 + 0.7 * F + L2 * 0.3 = (L1 + L2) * 0.3 + F * 0.7

Что и требовалось. Результат:



Код рендера выглядит примерно так:


// сохраняем настройки цвета тумана
D3DXCOLOR saveFogColor = _fogColor;
for(size_t lightIndex = 0; lightIndex < _lights.size(); ++lightIndex)
{
context.light = _lights[lightIndex];
context.renderPass = RenderPass_Lighting;

// если включены тени, рисуем карту теней
if (context.light->enableShadows)
RenderShadowMap(&context);

// рисуем все модели в сцене
for(size_t entityIndex = 0; entityIndex < _entities.size(); ++entityIndex)
{
context.entity = _entities[entityIndex];
context.entity->model->Render(context);
}

// обнуляем туман в последующих проходах
_fogColor = D3DXCOLOR(0, 0, 0, 0);

// включаем аддитивное наложение
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
}

// восстанавливаем исходный цвет тумана
_fogColor = saveFogColor;