PDA

Prikaži potpunu verziju : O svetlu u igrama


Lucic Nemanja
24.11.2013, 17:01
Naiđoh na konkretan problem, ali reko zašto u sto vreme i ne otvoriti temu koja se generalno bavi algoritmima za osvetljenje i shading. Izvinjavam se ako već postoji :).

Elem, moj problem je sledeći. Radio sam ovih dana Light Pre-Pass i uspeo nakon dugog lupanja glavom o zid da ga implementiram, i nakon toga čitav sistem za osvetljenje scene da ispeglam sa snekama. I sve je išlo koliko toliko ok, dok nisam odlučio da implementiram i specular highlights. Koristio sam običnu Phong formulu i račun sam radio u pixel shaderu gde računam teksturu za sveto. Međutim dobijao sam čudne rezultate...


http://www.sk.rs/forum/attachment.php?attachmentid=47728&stc=1&d=1385304973


Ok, highlight je na mestu ali nešto očigledno nije uredu sa njim. Zašto je tako "iseckan"? Je l' neko možda upoznat sa ovim problemom?

Belphegor
24.11.2013, 17:15
Koji ti je format za buffer normala?
Pokazi vertex/pixel shader za taj prolaz i pixel shader gde samplujes taj buffer za kalkulacije svetla.

Lucic Nemanja
24.11.2013, 17:24
Koji ti je format za buffer normala?
Pokazi vertex/pixel shader za taj prolaz i pixel shader gde samplujes taj buffer za kalkulacije svetla.

32-bit ARGB, 8 bita po kanalu.



VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;

float4x4 viewProjection = mul(View, Projection);
float4x4 worldViewProjection = mul(World, viewProjection);

output.Position = mul(input.Position, worldViewProjection);
output.Normal = mul(input.Normal, World);
output.Depth.xy = output.Position.zw;

return output;
}


PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)
{
PixelShaderOutput output;

output.Depth = input.Depth.x / input.Depth.y;
output.Normal.xyz = (normalize(input.Normal).xyz / 2) + .5;

output.Depth.a = 1;
output.Normal.a = 1;

return output;
}
}




float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float3 color = 1;
// Find the pixel coordinates of the input position in the depth
// and normal textures
float2 texCoord = postProjToScreen(input.LightPosition) + halfPixel();

// Extract the depth for this pixel from the depth map
float4 depth = tex2D(depthSampler, texCoord);

// Recreate the position with the UV coordinates and depth value
float4 position;
position.x = texCoord.x * 2 - 1;
position.y = (1 - texCoord.y) * 2 - 1;
position.z = depth.r;
position.w = 1.0f;

// Transform position from screen space to world space
position = mul(position, InvViewProjection);
position.xyz /= position.w;

float3 viewDirection = position - CameraPosition;

// Extract the normal from the normal map and move from
// 0 to 1 range to -1 to 1 range
float4 normal = (tex2D(normalSampler, texCoord) - .5) * 2;

// Perform the lighting calculations for a point light
if (IsPointLight)
{
float3 pointLightDirection = normalize(PointLightPosition - position);
float pointLighting = 1;
pointLighting = clamp(dot(normal, pointLightDirection), 0, 1);

float d = distance(PointLightPosition, position);
float att = clamp(1 - pow(d / PointLightAttenuation, 6), 0, 1);

float3 pointLightAmount = pointLighting * att * LightPower;

// Add specular highlights
float3 refl = reflect(pointLightDirection, normal);
float3 view = normalize(viewDirection);

pointLightAmount += pow (saturate(dot(refl, view)), SpecularPower) * SpecularColor;

color *= pointLightAmount;
}

//Preform the lighting calculation for a directional light
if (IsDirectionalLight)
{
normalize(DirectionalLightDirection);
float directionalLightAmount = clamp(dot(normal, -DirectionalLightDirection), 0, 1) * LightPower;

// Add specular highlights
float3 refl = reflect(DirectionalLightDirection, normal);
float3 view = normalize(viewDirection);

directionalLightAmount += pow (saturate(dot(refl, view)), SpecularPower) * SpecularColor;

color *= directionalLightAmount;
}


return float4(LightColor * color, 1);
}





float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
// Sample model's texture
float3 basicTexture = tex2D(basicTextureSampler, input.UV);

if (!TextureEnabled)
basicTexture = float4(1, 1, 1, 1);

// Extract lighting value from light map
float2 texCoord = postProjToScreen(input.PositionCopy) + halfPixel();
float3 light = tex2D(lightSampler, texCoord);

light += AmbientColor;

// Calculating Projective Texturing
float3 projection = float3(0, 0, 0);
if (ProjectorEnabled)
projection = sampleProjector(postProjToScreen(input.ProjectorSc reenPosition) + halfPixel());

// Calculating Shadows
float shadow = 1;
if (DoShadowMapping)
{
float2 shadowTexCoord = postProjToScreen(input.ShadowScreenPosition) + halfPixel();
//float mapDepth = sampleShadowMap(shadowTexCoord).r;

float realDepth = input.ShadowScreenPosition.z / ShadowFarPlane - ShadowBias;

if (realDepth < 1)
{
float2 moments = sampleShadowMap(shadowTexCoord);
float lit_factor = (realDepth <= moments.x);
float E_x2 = moments.y;
float Ex_2 = moments.x * moments.x;
float variance = min(max(E_x2 - Ex_2, 0.0) + 1.0f / 10000.0f, 1.0);
float m_d = (moments.x - realDepth);
float p = variance / (variance + m_d * m_d);

shadow = clamp(max(lit_factor, p), ShadowMult, 1.0f);
}
}

return float4(basicTexture * DiffuseColor * light * shadow + projection, 1);
}



Da napomenem za specular nisam ništa posebno kreirao, samo sam ga ubacio u light shader i dodaje se isti na čitavu scenu.

Belphegor
24.11.2013, 17:36
1.

32-bit ARGB, 8 bita po kanalu.


8 bita po kanalu je malo za normale pa moze biti razlog za artefakte koje vidis, probaj sa floating point texturom kao npr. ARGB32F (mada je dovoljno i ARGB16F, kao u STALKER-u).

2.

output.Normal = mul(input.Normal, World);


Normal je directional vektor, prema tome moras da ukines translation part tako sto postavljas w komponentu na 0

output.Normal = mul(float4(input.Normal, 0.0f), World);

ili transformacijom sa 3x3 matrixom:

output.Normal.xyz = mul(input.Normal.xyz, float3x3(World));



To je sto za sad vidim da mi smrdi.

Lucic Nemanja
24.11.2013, 18:23
Povećanje kanala je rešilo problem :D. Stavio sam 16x4 po kanalu. Hvala!
A čisto da napomenem da sam imao i bug sa senkama i usput odlučim da pogledam i format za shadow depth buffer kad ono i on RGBA po 8 bita, a koristim efektivno samo dva kanala, red i green!!! Em što sam imao problem sa kvalitetom senki jer sam smeštao u samo 8 bita, em što mi je 16 bita otišlo ni na šta :facepalm. Promenio sam ga na 32 bita, 2x16 i senke rade perfektno :D!
Sve sam radio po nekoj knjizi koja je budi bog s nama...

Belphegor
24.11.2013, 18:29
Mozes da pakujes normale i u 32 bita ali onda povecavas broj instrukcija i kvalitet je upitan. Imas ovde (http://aras-p.info/texts/CompactNormalStorage.html) zanimljiv artikal o raznim semama i pakovanja/raspakivanja za normale.

Lucic Nemanja
24.11.2013, 19:14
Proučiću ovih dana. Nego, imam još jedno pitanje, u vezi senki. Iz nekog razloga mi ne crta senke kada mnogo udaljim virtuelnu kameru koja predstavlja svetlo koje ih baca. Podesio sam far clip da bude dovoljno velik, i pogledao sam i iz same kamere da li pokriva sve što treba. Sve je ok. Međutim kada je mnogo odaljim neće da crta senke. Sačuvao sam buffere za shadowDepth i depth i dobio sam ovo:


http://www.sk.rs/forum/attachment.php?attachmentid=47730&stc=1&d=1385312213



http://www.sk.rs/forum/attachment.php?attachmentid=47731&stc=1&d=1385312223


Kapiram da je problem u depth baferu. Stavio sam format depth bafera 2x32. Morao sam to da stavim pošto koristim multilply render targets za depth i normal bafere, a normale su 4x16, pa mi je prijavljivao grešku da moraju biti iste veličine.

Shader je isti kao onaj gore, a evo koji mu je format output-a:


struct PixelShaderOutput
{
float4 Normal : COLOR0;
float4 Depth : COLOR1;
};


Hvala unapred! :)

Belphegor
24.11.2013, 20:16
Postoji vise implementacija za shadow mape, ja cu probati da napisem o nacinu koji je "standardan" i koji uglavnom koriste svi jer radi na svakom hw-u.
Za shadow mape ti treba poseban RT (render target, odnosno shadow buffer) i DS (depth stencil surface)*.
Format za shadow buffer najidealnije treba da bude floating point textura, R32F (samo red kanal).

r = z/w
Postavke near i far clip plan-ova postavljas "razumno", odnosno near da bude sto dalje od kamere moguce a far sto blize (a da ne klipujes objekte koje crtas), inace ces imati losiji kvalitet senki.

* Ne znam koji jezik koristis, sa C++-som kad kreiras uredjaj imas opciju dali da automatski kreira i DS povrsinu. Kad menjas RT (SetRenderTarget funkcija), RT koji postavis mora imati iste ili manje od DS dimenzije koja je trenutno postavljena (SetDepthStencilSurface funkcija).
Recimo ako imas 1024x768 frejm (client prostor na koji se crta) a shadow mape se obicno postavljaju da budu POT (power of two, 512x512-1024x1024-2048x2048...) pa moze da se desi da hoces recimo shadow buffer dimenzija 2048x2048, zato ti treba posebna DS povrsina (tako zahteva DX api).
Znaci imas nesto ovako:


void DrawFrame() {
// DS je vec postavljen, ako si izabrao automatsko kreiranje i njegova velicina je kao i backbuffer (1024x768)
...
LPDIRECT3DSURFACE backBuffer;
device->GetRenderTarget(0, backBuffer); // "bekapujemo" backBUffer povrsinu jer nam treba kasnije da se postavi

// iz pogleda igraca
device->SetRenderTarget(0, DepthSurface);
device->SetRenderTarget(1, NormalSurface);
device->Clear( ..., TARGET | ZBUFFER | STENCIL...);

DrawScene(playerView, playerProjection);
...

// iz pogleda svetla
LPDIRECT3DSURFACE oldDS;
device->GetDepthStencilSurface(&oldDS);// "bekapujemo" DS povrsinu jer u sledecem koraku postavljamo novu za shadow buffer

device->SetDepthStencilSurface(shadowDS); // bez ovoga ces imati gresku ako je dimenzija shadow buffera veca od DS povrsine
device->SetRenderTarget(0, shadowSurface);// shadow buffer
device->SetRenderTarget(1, nullptr);// "deaktiviramo" MRT (multiple render targets) posto treba da crtamo samo na jedan RT
device->Clear( ..., TARGET | ZBUFFER | STENCIL...);

DrawScene(lightView, lightProjection);// crtamo na shadow buffer
...

// ovde smo zavrsili sa shadow bufferom i vracamo "stare" povrsine
oldDS->Release(); // Get... pozivi inkrementuju ref count pa se ovde poziva Release da ne bi doslo do curenja memorije
backBuffer->Release();// -||-
device->SetDepthStencilSurface(oldDS);
device->SetRenderTarget(0, backBuffer);

DrawSceneWithShadowMap();
...
}

Lucic Nemanja
25.11.2013, 14:03
Hvala. Uspeo sam da sredim sve sa senkama, uglavnom mi je razumno postavljane far i near ravni rešilo sve probleme. Do sad sam near ravan postavljao odmah ispred kamere, a far daleko, pa se onda u količniku gubila informacija jer je broj težio nuli. Još mi samo jedan problem postoji koji nikako ne mogu da provalim zašto.


http://www.sk.rs/forum/attachment.php?attachmentid=47733&stc=1&d=1385380917


Senke su mi malo ofsetovane. Sve mi se čini dobro, ne mogu ni da namirišem grešku... :confused:

Belphegor
25.11.2013, 14:49
Posto sam predhodni post napamet pisao zaboravio sam gore da navedem da treba da se izmenjaju podesavanja i za viewport kad se menjaju RT.

Ofset koji vidis je odavno poznat problem sa shadow mapama i zove se peter panning, osim ako nemas negde drugde gresku u kodu.
Eventualno mozes da "maskiras" problem na licima koja ne "gledaju" prema "suncu" jer ionako nisu osvetljena, mozda ovako:

// l - light direction (normalized)
// n - face normal (normalized)
float NdotL = saturate( dot( n, l ) );
...
atten *= NdotL;
s'tim da treba podesiti i ambijentalno osvetljenje da se podudara sa senkama. U glavnom ne postoji generalno resenje, na dosta mesta ces morati koristiti razne "hakove" da sredis neke stvari.
Na zalost, za vece scene/objekte ne moze se dobiti zadovoljavajuci kvalitet senki sa jednom texturom/shadow bufferom, za takve stvari ljudi koriste CSM (cascaded shadow maps) ili PSSM (parallel split shadow maps), ne pitaj me za razliku jer ne znam ni sam. Za CSM imas primer kod u DirectX sdk samples folderima, doduse primer je za dx11 ali moze da se iskoristi i za dx9 jer je sva matematika nevezana za konkretan api (ovo sam i ja koristio).
I imam jedan sample demo koji sam sacuvao sa jedne stranice/artikla koja nazalost vise ne postoji, ali moze da posluzi za studiranje. U ovom demou nije adresirano "plivanje ivica" (edge shimmering) senki kad se kamera rotira za razliku od CSM dx demoa u kome je to reseno.

https://drive.google.com/file/d/0B-h-AQ2Uqh8hY0xEaDNWV1N4QWM/edit?usp=sharing

Nikad nisam koristio google drive pa ne znam da li ce ovo raditi. Javi mi da li moze da se skine, postavio sam fajl kao "public".

Andross
25.11.2013, 15:39
Sto se tice PSSM-a evo poredjenja:

http://media.indiedb.com/images/games/1/14/13077/Screenshot021.1.jpg

http://media.indiedb.com/images/games/1/14/13077/Screenshot022.1.jpg

Uglavnom, za istu scenu, distancu i velicinu teksture senki se dobija kvalitetniji rezultat ali je ujedno i sporija tehnika naravno. Sto ti je veca distanca crtanja senki to ce one biti sve losije jer se renderuju na manjoj povrsini istog render targeta, tako da verujem da ti je distanca bila toliko velika da je shader jednostavno odbijao da spakuje senku na toliko malu povrsinu :D