Deferred Rendering: 3D positie uit screen space en depth map

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • roy-t
  • Registratie: Oktober 2004
  • Laatst online: 08-09 11:33
Ik heb 3 Textures (rendertargets) met data
ColorRT
NormalRT
DepthRT

In een vierde RT wordt de lighting data opgeslagen.

Voor het maken van een point light gebruikt een tutorial die ik volg het renderen van sphere mesh met speciale culling settings om te berekenen waar wel en geen licht zou moeten zijn. Hoewel het renderen van 1 sphere niet zwaar is, lijkt het me dat het nog makkelijker moet kunnen.

Je zou immers in de vertex shader of pixel shader de 3D positie van een pixel kunnen uitrekenen, dan zou je belichting in de pixel shader kunnen uitreken aan de hand van de afstand tussen de 3D positie van het licht en de 3D positie van die pixel. Voordeel hiervan is het niet nodig hebben van een sphere model en de veel hogere kwaliteit.

Nu denk ik dat alle data aanwezig is in de shader om dit te doen. de DepthRT kan gewoon uitgelezen worden om de diepte van een pixel te bepalen (laten we even aannemen dat de camera recht naar voren kijkt, dan is dit de Z).

De X en Y worden normaal berekend door de input positie te vermenigvuldigen met enkele matrices (world matrix, projectie matrix, etc..).

In de pixel shader heb ik het screen coordinate (van -1 tot 1) beschikbaar, dus als ik een manier zou kunnen vinden om dit terug te rekenen naar world coordinates. Helaas kan ik zelf niet een manier bedenken om dit te doen.

Dus uiteindelijk vraag is dus eigenlijk: hoe kom je van (screenX, screenY, depth) naar -> (worldX, worldY, depth(aka worldZ))?

(Een van de oplossingen is om ook een render target te hebben met 3D posities gecodeerd in XYZ->RGB, maar dat zou weer extra geheugen kosten).

Een vriend van me meent ooit wel eens zo'n techniek gelezen te hebben, maar kon het niet meer vinden :/, dus dat schiet ook niet op. Heeft iemand anders een idee, tip, of kent hij de paper waar dit in gebeurt?

~ Mijn prog blog!


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
roy-t schreef op dinsdag 21 december 2010 @ 10:55:
Je zou immers in de vertex shader of pixel shader de 3D positie van een pixel kunnen uitrekenen,
Ik snap niet helemaal wat je bedoelt. In je vertex shader heb je toch gewoon alle 3D vertexen. Dan kun je die informatie toch gewoon meegeven naar de pixel shader, en kun je per pixel de distance naar je point light berekenen?

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • roy-t
  • Registratie: Oktober 2004
  • Laatst online: 08-09 11:33
Woy schreef op dinsdag 21 december 2010 @ 11:25:
[...]

Ik snap niet helemaal wat je bedoelt. In je vertex shader heb je toch gewoon alle 3D vertexen. Dan kun je die informatie toch gewoon meegeven naar de pixel shader, en kun je per pixel de distance naar je point light berekenen?
In forward rendering is dat waar, maar in deferred rendering render je je geometry appart, het renderen van de lighting doe je door een quad te renderen met deze shader, de 3D posities die je in de vertex shader mee zou geven zouden dan de 3d positie van de quad zijn (z=0, variabele x en y) en niet van je geometry. Op deze manier is je lighting pass minder afhankelijk van je geometry, het is als het ware een image techniek geworden. Extra data die je nodig hebt zoals normals en depth worden meegenomen via MRT bij het renderen van je originele geometry.

~ Mijn prog blog!


Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

Volgens mij ligt het niet zo makkelijk als (WVP)^-1 te pakken, gezien projectie matrices over het algemeen niet zo werken.

Ik heb hier niet heel diep over nagedacht, maar kan het volgende niet werken:
Tijdens het daadwerkelijk gebruik van je geo-buffers gebruik je wellicht een fullscreen-quad, en met de UV coordinaten daarvan kan je isoleren in welke pixel je bezig bent (of eigenlijk, wje indexeert je geo-buffer op een manier). Je kan dan een lijn trekken vanaf je camera-positie, door het camera-frustrum (waarvan je door die UV weet waar exact hij door de screen-plane gaat), en dan die lijn doortrekken tot hij "Z" lang is (waarvan die Z dus uit je geo-buffer komt). Dat punt is dan de locatie van je fragment in 3D.

Misschien niet echt super-simpel, maar volgens mij mogelijk, mits je natuurlijk je hele geo-buffer met 1 camera rendert :)

-niks-


Acties:
  • 0 Henk 'm!

  • DroogKloot
  • Registratie: Februari 2001
  • Niet online

DroogKloot

depenisvanjezus

roy-t schreef op dinsdag 21 december 2010 @ 10:55:
In de pixel shader heb ik het screen coordinate (van -1 tot 1) beschikbaar, dus als ik een manier zou kunnen vinden om dit terug te rekenen naar world coordinates. Helaas kan ik zelf niet een manier bedenken om dit te doen.

Dus uiteindelijk vraag is dus eigenlijk: hoe kom je van (screenX, screenY, depth) naar -> (worldX, worldY, depth(aka worldZ))?
Denk eens logisch na (daar helpen tutorials je helaas niet mee). De standaard 3D world->screen transformatie ziet er zo uit:

[x', y', z', w']^T = P * V * M * [x, y, z, w]^T

Dus de screen->world transform is z'n inverse:

[x, y, z, w]^T = (P * V * M)^{-1} * [x', y', z', w']^T

Omdat lighting toch in eyespace berekend wordt is het handig om hier M = I te nemen, dus je hebt alleen (P * V)^{-1} = V^{-1} * P^{-1} nodig. Typisch is V orthonormaal (zodat V^{-1} = V^T), voor P^{-1} moet je iets meer moeite doen als het een perspective matrix is.

[ Voor 5% gewijzigd door DroogKloot op 21-12-2010 13:31 ]


Acties:
  • 0 Henk 'm!

  • roy-t
  • Registratie: Oktober 2004
  • Laatst online: 08-09 11:33
Damn DroogKloot, gewoon wat rauwe wiskunde er tegen aan en je komt al heel ver zo te zien, op zo'n "laag niveau"had ik er nog niet over nagedacht.

Wat ik me wel afvraag is of je rekening gehouden hebt met dat een inverse van een matrix anders gedefineerd is als een inverse van een formule.

B = A^{-1} als A*B= I.

Of haal ik nu dingen door elkaar :).

MLM:

Jij geeft een hele mooi manier die meer 'in mijn denken' licht, die ga ik zeker ook onderzoeken. Vanavond ga ik er eens even flink mee klooien en dan zien we wel welke manier ik uit kom. Mocht iemand nog pseudo code willen toedragen, of een andere manier, schroom niet :).

Voor de liefhebbers: dit is de shader die ik ongeveer in gedachten heb:
HLSL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
float3 lightPosition;
float3 lightColor;
float lightIntensity;
float3 cameraPosition;

float4x4 worldMatrix;


// diffuse color, and specularIntensity in the alpha channel
texture colorMap; 
// normals, and specularPower in the alpha channel
texture normalMap;
//depth
texture depthMap;

sampler colorSampler = sampler_state
{
    Texture = (colorMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
};
sampler depthSampler = sampler_state
{
    Texture = (depthMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};
sampler normalSampler = sampler_state
{
    Texture = (normalMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

struct VertexShaderInput
{
    float3 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
    float3 Position3D : TEXCOORD2;
};

float2 halfPixel;

float DotProduct(float3 lightPos, float3 pos3D, float3 normal)
{
    float3 lightDir = normalize(pos3D - lightPos);
    return dot(-lightDir, normal);    
}

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
    output.Position =  mul(float4(input.Position, 1), worldMatrix);
    //align texture coordinates
    output.TexCoord = input.TexCoord - halfPixel;


    //Die gaan we dus bereken
    output.Position3D = ??; 
    return output;
}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{     
    //get normal data from the normalMap
    float4 normalData = tex2D(normalSampler,input.TexCoord);
    //tranform normal back into [-1,1] range
    float3 normal = 2.0f * normalData.xyz - 1.0f;

    float diffuseLightingFactor = DotProduct(lightPosition, input.Position3D, normal);
    diffuseLightingFactor = saturate(diffuseLightingFactor);
    diffuseLightingFactor *= lightIntensity;
    return float4(lightColor * diffuseLightingFactor,1); 
}

technique Technique0
{
    pass Pass0
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

~ Mijn prog blog!


Acties:
  • 0 Henk 'm!

  • The Flying Dutchman
  • Registratie: Mei 2000
  • Laatst online: 29-07 21:57
Ik heb een tijdje geleden hetzelde gedaan. Aangezien ik op internet vele formule's tegenkwam, maar er nooit duidelijk bij werd vermeld van welk coordinaat systeem naar welk coordinaat systeem werd gerekend, ben ik zelf maar aan de slag gegaan.

Heb de OpenGL documentatie erbij gepakt, gekeken welke transformaties allemaal uitgevoerd werden en van welke parameters deze afhangen. Vervolgens heb ik de omgekeerde serie transformaties uitgerekend. Zie voor een overzicht:

(let op: initiele berekening klopt niet omdat ik een aantal verkeerde parameters had gebruikt, het staat verderop in dezelfde thread uitgelegd wat ik fout deed en hoe het wel moest)
http://www.opengl.org/dis...&Number=277897#Post277897

edit: het uiteindelijke resultaat is overigens op youtube te zien. Mede dankzij deze demo ben ik aan een baan als game programmeur gekomen! :)

[ Voor 13% gewijzigd door The Flying Dutchman op 21-12-2010 15:11 ]

The Flying Dutchman


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Volgens mij zie je hier vooralsnog je perspective divide over het hoofd.

Acties:
  • 0 Henk 'm!

  • roy-t
  • Registratie: Oktober 2004
  • Laatst online: 08-09 11:33
The Flying Dutchman schreef op dinsdag 21 december 2010 @ 14:57:
Ik heb een tijdje geleden hetzelde gedaan. Aangezien ik op internet vele formule's tegenkwam, maar er nooit duidelijk bij werd vermeld van welk coordinaat systeem naar welk coordinaat systeem werd gerekend, ben ik zelf maar aan de slag gegaan.

Heb de OpenGL documentatie erbij gepakt, gekeken welke transformaties allemaal uitgevoerd werden en van welke parameters deze afhangen. Vervolgens heb ik de omgekeerde serie transformaties uitgerekend. Zie voor een overzicht:

(let op: initiele berekening klopt niet omdat ik een aantal verkeerde parameters had gebruikt, het staat verderop in dezelfde thread uitgelegd wat ik fout deed en hoe het wel moest)
http://www.opengl.org/dis...&Number=277897#Post277897

edit: het uiteindelijke resultaat is overigens op youtube te zien. Mede dankzij deze demo ben ik aan een baan als game programmeur gekomen! :)
Ik kom helaas niet helemaal uit je code.
mat4 m = gl_ProjectionMatrix;
float Z = m[3].z / (texture2DRect(G_Depth, gl_FragCoord.xy).x
* -2.0 + 1.0 - m[2].z);
vec3 modelviewpos = vec3(pos.xy/pos.z*Z,Z);
Wat doet texture2DRect(..) hier? Of sample je op die manier van je depthtexture (ik ken alleen een klein beetje HLSL en geen GLSL, dus het is nogal verwarrend?

Wat stellen de camera ranges voor? Zijn dat gewoon de culling afstanden?

Waar heb jij trouwens op gegoogled om dit te vinden?

~ Mijn prog blog!


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
texure2DRect is equivalent aan texRECT en volgens mij gaan daar gewoon pixel coordinaten in ipv uvs.

[ Voor 4% gewijzigd door PrisonerOfPain op 21-12-2010 16:15 ]


Acties:
  • 0 Henk 'm!

  • The Flying Dutchman
  • Registratie: Mei 2000
  • Laatst online: 29-07 21:57
roy-t schreef op dinsdag 21 december 2010 @ 15:50:
[...]


Ik kom helaas niet helemaal uit je code.
Het ging vooral om hoe je zelf de formule zou kunnen achterhalen als je weet welke stappen er genomen worden in de DirectX render pipeline (en deze stappen dan omgekeerd uitvoeren), niet om de code die ik had gequote. Maar ik zal zo even kijken of ik het belangrijkste gedeelte even uit mijn code kan vissen.
[...]


Wat doet texture2DRect(..) hier? Of sample je op die manier van je depthtexture (ik ken alleen een klein beetje HLSL en geen GLSL, dus het is nogal verwarrend?
Ja, daar wordt uit de depth texture gelezen. GLSL en HLSL lijken overigens heel veel op elkaar.
Wat stellen de camera ranges voor? Zijn dat gewoon de culling afstanden?

Waar heb jij trouwens op gegoogled om dit te vinden?
De links naar de sources staan erbij (in mijn post op het OpenGL forum). Volgens mij heb ik op het OpenGL forum zelf gezocht ipv met google.

De daadwerkelijke code die ik gebruik om de 3D positie van een pixel te bepalen met behulp van de depth uit de depth buffer (in OpenGL non-lineair met waarden tussen 0 en 1) is als volgt:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
uniform vec4 projection; // <-- contains camera settings
uniform sampler2DMS depth_sampler; // <-- sampler for the depth texture


/**
 * Retrieves 3d position of the pixel with given text coordinates.
 * @param s: the sample of the pixel.
 * @param int_textcoord: the screen coordinate.
 * @return: the 3d position of the pixel.
 */
vec3 get_position(int s, ivec2 int_texcoord)
{
    // read the depth from the depth texture (value between 0 and 1)
    float depth = texelFetch(depth_sampler, int_texcoord, s).x;

    // fragment coordinate (sample is ignored for now)
    vec2 fragcoord = gl_FragCoord.xy;

    // assume scaling was 1
    float w = 1.0;

    float h = w * projection.y / projection.x;
    
    /**
     * layout of projection:
     *
     * projection.x: width of viewport in pixels
     * projection.y: height of viewport in pixels
     * projection.z: far point of projection matrix
     * projection.w: near point of projection matrix
     */


    // calculate the position, using the given camera settings
    vec3 pos = vec3(-w / projection.w * (gl_FragCoord.x / projection.x - 0.5),
        -h / projection.w * (gl_FragCoord.y / projection.y - 0.5),
        -(2.0f * projection.z * projection.w) / ((projection.z + projection.w)
            - (projection.z - projection.w) * (2.0f * depth - 1)));
        
    return pos * vec3(pos.z, pos.z, 1);
}


Het kan zijn dat dit hier en daar nog wel wat geoptimaliseerd kan worden en dat je met bepaalde aannamen de berekening nog wat verder kunt vereenvoudigen (ik meen me te herinneren dat er ook een veel eenvoudigere berekening bestaat, wat ik heb gedaan is exact het omgekeerde doen ten opzichte van wat er in de OpenGL render pipeline gebeurt). Let op: dit is dus voor OpenGL. OpenGL en DirectX doen een aantal dingen anders, daardoor kan de berekening ook net wat anders zijn.

Maar in principe, kun je voor DirectX hetzelfde doen als wat ik voor OpenGL heb gedaan: ga na welke stappen er genomen worden om van eye space (of world space, wat je wilt) coordinaten naar window coordinaten te komen. In OpenGL zijn dat de volgende stappen:

eye coordinates --> [apply projection matrix] --> clip coordinates --> [normalize: clip coordinate w should be equal to 1] --> normalized device coordinates --> [apply viewport transformation] --> window coordinates

Om nu terug te gaan van window coordinates naar eye coordinates moet je precies de omgekeerde stappen doen. Dus, reken de inverse matrices uit en ga daarmee van window coordinates terug naar eye coordinates. Vereenvoudig deze stappen zoveel mogelijk en je vind de formule om van pixels + waarde uit de depth buffer terug te gaan naar de eye coordinates (om vervolgens naar world space coordinates te gaan kun je deze nog weer met de inverse camera matrix vermenigvuldigen).

De exacte render pipeline van DirectX ken ik niet, maar aangezien DirectX en OpenGL heel veel met elkaar gemeen hebben en het van dezelfde onderliggende hardware gebruik maakt zullen er veel overeenkomsten zijn.

Veel informatie over deferred rendering vind je overigens in onderstaande documenten:Eigenlijk zou ik die omgekeerde berekening nog eens netjes moeten uitwerken in een pdf'je ofzo. Maarja, je weet wel hoe dat soort dingen gaan.

The Flying Dutchman


Acties:
  • 0 Henk 'm!

  • roy-t
  • Registratie: Oktober 2004
  • Laatst online: 08-09 11:33
Hey, nogmaals bedankt voor je posts, ik denk dat ik er nu toch wel uit zou moeten kunnen komen. Anders zal ik op 'directx.com' (zoiets) nog even een specifiekere vraag stellen als ik er echt niet uit kom.

Ik hoop dat dit voor mij gaat werken, want het werken met geometry voor belichting is juist iets wat ik niet wil :).

Edit iig de volgende stappen moeten waarschijnlijk ongedaan worden

[code]
float4 eyePos = mul(position, View); //transform to eye/camera space
float4 hpos = mul(eyePos, Projection); //transform to projection space

hpos.xyz = hpos.xyz / w; //perform perspective divide to transform to NDC coordinates

hpos.x = .5f * hpos.x + .5f;
hpos.y = .5f * hpos.y + .5f;
//hpos.y = -.5f * hpos.y + .5f; //if you want to correctly sample a texture with these, y needs to be flipped [/]


Ben er nog even mee aan het klooien, heb eerst de code van The Flying Dutchman vrij letterlijk in een shader gezet, maar daar komt helaas niets zinnigs uit (ik had gehoopt iig iets te zien om op het goede spoor te zijn), dus dan nu maar even een wat gestructureerdere aanpak.

Edit2:

Ik heb het. De volgende shader geeft netjes de afstand in wereld coordinaten. Uiteindelijk heb ik met alle tips hier, en wat uit elkaar slopen van een andere shader de juiste manier gevonden.

Het bleek uiteindelijk een stuk simpeler dan ik dacht:
HLSL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    //perspective divide
    input.ScreenPosition.xy /= input.ScreenPosition.w;

    float depthVal = tex2D(depthSampler,texCoord).r;

    //compute screen-space position
    float4 position;
    position.xy = input.ScreenPosition.xy;
    position.z = depthVal;
    position.w = 1.0f;
    
    //transform to world space
    position = mul(position, InvertViewProjection);
    position /= position.w; 


Harstikke bedankt voor alle tips hier! Nu kan ik nog weer wat verder met leuke lampen bereken. Eigenlijk elke vorm die je in een formule kunt uitdrukken is nu mogelijk zonder geometry te renderen in de lighting stap.

Volledige shader:
HLSL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
float3 lightPosition;
float3 lightColor;
float lightIntensity;
float lightRadius;
float3 cameraPosition;

float4x4 World;
float4x4 View;
float4x4 Projection;
float4x4 InvertViewProjection;


// diffuse color, and specularIntensity in the alpha channel
texture colorMap; 
// normals, and specularPower in the alpha channel
texture normalMap;
// depth
texture depthMap;

sampler colorSampler = sampler_state
{
    Texture = (colorMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
};
sampler depthSampler = sampler_state
{
    Texture = (depthMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};
sampler normalSampler = sampler_state
{
    Texture = (normalMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

struct VertexShaderInput
{
    float3 Position : POSITION0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;    
    float4 ScreenPosition : TEXCOORD0;    
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;  
    output.Position = mul(float4(input.Position, 1), World);    
    output.ScreenPosition = output.Position;            
    return output;
}


float2 halfPixel;
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    //obtain screen position
    input.ScreenPosition.xy /= input.ScreenPosition.w;

    float2 texCoord = 0.5f * (float2(input.ScreenPosition.x,-input.ScreenPosition.y) + 1);
    //allign texels to pixels
    texCoord -=halfPixel;
    //read depth
    float depthVal = tex2D(depthSampler,texCoord).r;

    //compute screen-space position
    float4 position;
    position.xy = input.ScreenPosition.xy;
    position.z = depthVal;
    position.w = 1.0f;
    //transform to world space
    position = mul(position, InvertViewProjection);
    position /= position.w; 
        
    //calculate distance
    float xd = lightPosition.x - position.x;
    float yd = lightPosition.y - position.y;
    float zd = lightPosition.z - position.z;
    float worldDistance = sqrt(xd*xd + yd*yd + zd*zd);

    if(worldDistance < lightRadius)
    {
        return float4(1,0,0,1);
    }
    else
    {
        return float4(0,1,0,1);
    }

}

technique Technique0
{
    pass Pass0
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

[ Voor 115% gewijzigd door roy-t op 22-12-2010 00:19 ]

~ Mijn prog blog!

Pagina: 1