Bonjour à tous,
Je suis en train d'implémenter un algo de parallel split shadow mapping. Tout fonctionne presque bien, sauf que... Sauf que le frustrum de ma lumière change en fonction de la position et l'orientation de ma caméra.
Voici des screenshot qui illustrent mon problème:
Premier cas, je me positionne à un endroit quelconque de ma scène:
Vous ne pouvez pas le voir mais deja le frustrum de la lumière n'est pas bon.
A partir de cette position, j'effectue un déplacement latéral vers la droite sans changer l'orientation de ma caméra et voici ce que j'obtient:
Le 2e screenshot montre clairement que le frustrum de la lumière a changé.
Second cas, je me positionne en dessous de la lumière:
Le frustrum de la lumière semble nul, cad que chancun des point du frustrum ont la meme position. Et voici ce que j'obtient lorsque j'effectue un déplacement vers l'arrière et que je ne suis plus sous la lumière:
Tout est correct.
Troisième cas, pour montrer cette "annulation du frustrum de la lumière":
Je me positionne à un endroit de ma scène:
Ensuite, je ne change pas de position, mais je fais varier l'orientation de la caméra par un "mouvement de tete" vers la droite:
Le frustrum (la partie encore claire) s'est considérablement reduit.
Je continue mon mouvement de tete vers la droite et je tombe sur ma fameuse annulation du frustrum:
Enfin, pour terminer, voila un screenshot ou tout est correct:
Pour ce qui est des sources, les voici:
Méthode d'initialisation de la shadow map:
La méthode qui calcule chaque split:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 glClearColor(0.5f, 0.5f, 1.0f, 1.0f); //On cree la texture qui va contenir la shadow map glGenTextures(1, &TextureId); glActiveTextureARB(GL_TEXTURE3_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, TextureId); glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadow_map_size, shadow_map_size, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//LINEAR pour avoir un PCF x2 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//LINEAR pour avoir un PCF x2 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, /*GL_CLAMP*/GL_CLAMP_TO_BORDER);//PB DE PERF POSSIBLE AVEC CLAMP_TO_BORDER glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, /*GL_CLAMP*/GL_CLAMP_TO_BORDER);//PB DE PERF POSSIBLE AVEC CLAMP_TO_BORDER glActiveTextureARB(GL_TEXTURE3_ARB); glDisable(GL_TEXTURE_2D); //Creation du FBO glGenFramebuffersEXT(1, &FboId); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FboId); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); //Associe le FBO à la texture de shadow map. //Comme ca quand on ecrit dans le FBO, le résultat se trouve dans la texture glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, TextureId, 0); GLenum FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if(FBOstatus != GL_FRAMEBUFFER_COMPLETE_EXT) printf("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n"); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); //glActiveTextureARB(GL_TEXTURE0_ARB); //Use the color as the ambient and diffuse material glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); //White specular material color, shininess 16 glMaterialfv(GL_FRONT, GL_SPECULAR, white); glMaterialf(GL_FRONT, GL_SHININESS, 16.0f);
La méthode qui calcule le frustrum de la caméra:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 void CalculateSplitDistances(int iNumOfSplits, float fNear, float fFar) { // The lambda must me between 0.0f and 1.0f float fLambda = m_fSplitLambda; for(int i = 0; i < iNumOfSplits; i++) { float fIDM = i / (float)iNumOfSplits; float fLog = fNear * powf((fFar/fNear), fIDM); float fUniform = fNear + (fFar - fNear)*fIDM; m_fSplitDistances[i] = fLog * fLambda + fUniform*(1-fLambda); } // This is used to improve the correctness of the calculations. Our main near- and farplane // of the camera always stay the same, no matter what happens. m_fSplitDistances[0] = fNear; m_fSplitDistances[iNumOfSplits] = fFar; }
Celle qui calcule la crop matrix:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 void CalculateFrustumCorners(GSEPOINT3D pPoints[8], GSEPOINT3D vPos, GSEPOINT3D vView, GSEPOINT3D vUp, float fNear, float fFar, float fScale, float fFOV, float fAspect) { GSEPOINT3D vZ= vView - vPos; vZ.Normalize(); GSEPOINT3D vX; vX.CrossProduct(vUp, vZ); vX.Normalize(); GSEPOINT3D vY; vY.CrossProduct(vZ, vX); float fNearPlaneHeight = tanf(GSEGET_RADIANS(fFOV) * 0.5f) * fNear; float fNearPlaneWidth = fNearPlaneHeight * fAspect; float fFarPlaneHeight = tanf(GSEGET_RADIANS(fFOV) * 0.5f) * fFar; float fFarPlaneWidth = fFarPlaneHeight * fAspect; vZ *= fNear; GSEPOINT3D vNearPlaneCenter = vPos + vZ; vZ *= fFar; GSEPOINT3D vFarPlaneCenter = vPos + vZ; pPoints[0] = GSEPOINT3D(vNearPlaneCenter - (vX*fNearPlaneWidth) - (vY*fNearPlaneHeight)); pPoints[1] = GSEPOINT3D(vNearPlaneCenter - (vX*fNearPlaneWidth) + (vY*fNearPlaneHeight)); pPoints[2] = GSEPOINT3D(vNearPlaneCenter + (vX*fNearPlaneWidth) + (vY*fNearPlaneHeight)); pPoints[3] = GSEPOINT3D(vNearPlaneCenter + (vX*fNearPlaneWidth) - (vY*fNearPlaneHeight)); pPoints[4] = GSEPOINT3D(vFarPlaneCenter - (vX*fFarPlaneWidth) - (vY*fFarPlaneHeight)); pPoints[5] = GSEPOINT3D(vFarPlaneCenter - (vX*fFarPlaneWidth) + (vY*fFarPlaneHeight)); pPoints[6] = GSEPOINT3D(vFarPlaneCenter + (vX*fFarPlaneWidth) + (vY*fFarPlaneHeight)); pPoints[7] = GSEPOINT3D(vFarPlaneCenter + (vX*fFarPlaneWidth) - (vY*fFarPlaneHeight)); GSEPOINT3D vCenter(0.0f, 0.0f, 0.0f); for(int i = 0; i < 8; i++) vCenter = vCenter + pPoints[i]; vCenter /= 8.0f; for(int i = 0; i < 8; i++) pPoints[i] = pPoints[i] + ((pPoints[i]-vCenter)*(fScale-1.0f)); }
Pour finir, la boucle de jeu:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127 //pFrustum est le frustrum de la caméra calculé dans // CalculateFrustumCorners (nom du paramètre du frustrum:pPoints) void CalculateLightForFrustum(GSEPOINT3D pFrustum[8]) { // Get the light projection matrix, without altering the current one // !!!ATTENTION!!! // We use 45 degrees as the light's FOV in OpenGL. Other values produce erronous results // from time to time. Anyway, it has the advantage of improving precision, so there's no // reason not to use it. // We chose a small value for the initial farplane to keep precision high in the early // calculations. It doesn't make a big difference, but everything is welcome. // !!!ATTENTION!!! // Remember, the depthtexture uses an aspect ratio of 1.0f (e.g. 2048x2048) glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPerspective(45.0f, 1.0f, 1.0f, 50.0f); glGetFloatv(GL_PROJECTION_MATRIX, m_lightProjection); glPopMatrix(); // Get the light modelview matrix, without altering the current one glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadIdentity(); // We render from the light's position, looking at the origin with the y-axis as upvector GSEPOINT3D LightLookAt=LightPosition+LightDirection; gluLookAt(LightPosition.x, LightPosition.y, LightPosition.z, LightLookAt.x, LightLookAt.y, LightLookAt.z, LightUpVector.x, LightUpVector.y, LightUpVector.z ); glGetFloatv(GL_MODELVIEW_MATRIX, m_lightLookAt); glPopMatrix(); // Next we will find the min and max values of the current frustum split in the lights // post-projection space (where coordinate range is from -1.0f to 1.0f) // !!!ATTENTION!!! // In OpenGL the Z coordinates in post projection space range from [-1,1], // in Direct3D they range from [0,1] float fMaxX = -1.0f; float fMaxY = -1.0f; float fMinX = 1.0f; float fMinY = 1.0f; float fMaxZ = -1.0f; // We multiply the current projection matrix with the modelview matrix of the light // !!!ATTENTION!!! // In Direct3D, the order of multiplication would be different: // --> CMatrix4x4 lightviewproj = m_LookAtMatrix * m_lightProjection; // The OpenGL FAQ says the following: "Note that post-multiplying with column-major // matrices produces the same result as pre-multiplying with row-major matrices". GSEMATRIX4X4 lightviewproj = m_lightProjection * m_lightLookAt; // We transform the camera's frustum into post-projection space and get the // min and max values for X,Y and Z. // The X and Y values are used to create a zoom-in matrix and the farthest Z value // is used as the new farplane for the light to improve precision. for(int i = 0; i < 8; i++) { // We transform the camera's frustum into post-projection space by multiplying it's // vertices with the lightviewproj matrix. // !!!ATTENTION!!! // The w-coordinate of the frustum vertices MUST be set to 1.0f!!! // I do this in my CVector4() contructor. GSEVECTOR4D vTransformed; vTransformed.w=1.0; vTransformed = lightviewproj.VectorMatrixMultiply(GSEVECTOR4D(pFrustum[i])); // We project the x and y values prior to determining the max values vTransformed.x /= vTransformed.w; vTransformed.y /= vTransformed.w; // We find the min and max values for X, Y and Z if(vTransformed.x > fMaxX) fMaxX = vTransformed.x; if(vTransformed.y > fMaxY) fMaxY = vTransformed.y; if(vTransformed.y < fMinY) fMinY = vTransformed.y; if(vTransformed.x < fMinX) fMinX = vTransformed.x; if(vTransformed.z > fMaxZ) fMaxZ = vTransformed.z; } // We clamp the max X and Y values (post projection space: X and Y between [-1,1]) fMaxX = Clamp(fMaxX, -1.0f, 1.0f); fMaxY = Clamp(fMaxY, -1.0f, 1.0f); fMinX = Clamp(fMinX, -1.0f, 1.0f); fMinY = Clamp(fMinY, -1.0f, 1.0f); // We use the max Z-value + the light's nearplane + some bias as the new farplane float fLightFar = fMaxZ + 1.0f + 1.5f; // We set a new projection matrix for the light with the new farplane // !!!ATTENTION!!! // Most of the time, we're able to set the nearplane a little bit further to improve // our precision even more. // Remember, the depthtexture uses an aspect ratio of 1.0f (e.g. 2048x2048) // Again we use a FOV of 45 degrees with OpenGL, as it improves precision and doesn't // give erronous results. glMatrixMode( GL_PROJECTION ); glPushMatrix(); glLoadIdentity(); gluPerspective(45.0f, 1.0f, 1.0, fLightFar); glGetFloatv(GL_PROJECTION_MATRIX, m_lightProjection); glPopMatrix(); // We make an appropriate matrix for zooming in on the current split, just as in the paper float fScaleX = 2.0f / (fMaxX-fMinX); float fScaleY = 2.0f / (fMaxY-fMinY); float fOffsetX = -0.5f * (fMaxX+fMinX) * fScaleX; float fOffsetY = -0.5f * (fMaxY+fMinY) * fScaleY; // We set the matrix to the identity and fill in the important values GSEMATRIX4X4 CropView; CropView.LoadIdentity(); // !!!ATTENTION!!! // This does not need to be the transpose matrix of the Direct3D version! CropView.entries[0] = fScaleX; CropView.entries[5] = fScaleY; CropView.entries[12] = fOffsetX; CropView.entries[13] = fOffsetY; // !!!ATTENTION!!! // Due to row-major and column major notation of matrices, in Direct3D the multiplication // order would be different: --> m_lightProjection = m_lightProjection * CropView; // The OpenGL FAQ says the following: "Note that post-multiplying with column-major // matrices produces the same result as pre-multiplying with row-major matrices". m_lightProjection = CropView * m_lightProjection; }
Il est très probable que mon problème se situe dans la méthode de calcul du frustrum de la lumière mais je ne vois pas ou donc je me permets d'abuser de votre savoir :p.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 Camera->GetKeyboardControl(); Camera->GetMouseControl(); Camera->Update(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); float fCameraFar =GSEApplication->GetFar(); CalculateSplitDistances(iNumOfSplits, 1.0f, fCameraFar); for(int iSplit = 0; iSplit < iNumOfSplits; iSplit++) { float fNear =m_fSplitDistances[iSplit]; float fFar = m_fSplitDistances[iSplit+1]; GSEPOINT3D pFrustum[8]; float iWidth, iHeight; iWidth = GSEApplication->GetSizeX(); iHeight = GSEApplication->GetSizeY(); float fAspect = iWidth / iHeight; CalculateFrustumCorners(pFrustum, GSECamera->GetPosition(), GSECamera->GetTarget(), GSECamera->GetUpVector(), GSEApplication->GetNear(), fFar, 1.0f, GSEApplication->GetFOV(), fAspect); CalculateLightForFrustum(pFrustum); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,FboId); glViewport(0, 0, shadow_map_size, shadow_map_size); glDepthRange(0.0f, 1.0f); // We only have to clear the depth buffer, since we're only interested in the depth glClear(GL_DEPTH_BUFFER_BIT); // Disable all superfluous states to enable fast Z-only rendering on new graphics cards glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glDisable(GL_ALPHA_TEST); // We use some offset to hide ugly selfshadowing artifacts // Unfortunately, my geometry isn't closed or convex, so I can't render only backfaces // into the depthtexture // !!! ATTENTION !!! // Bear in mind that more bias causes the shadows to pop with PSSMs. See for yourself // what I mean by altering these values. glPolygonOffset( 2.0f, 8.0f ); glEnable( GL_POLYGON_OFFSET_FILL ); // We keep depth writes enabled glDepthMask(1); glDepthFunc(GL_LESS); // Use the appropriate zoomed-in matrix for the current split glMatrixMode(GL_PROJECTION); glLoadMatrixf(m_lightProjection); // Use the appropriate modelview matrix for the current split glMatrixMode(GL_MODELVIEW); glLoadMatrixf(m_lightLookAt); // Enable culling of the backfaces and render the scene geometry to the depthtexture glEnable(GL_CULL_FACE); DrawScene(); // restore all the states again for regular drawing glDepthFunc(GL_LEQUAL); glEnable(GL_LIGHTING); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glEnable(GL_TEXTURE_2D); glDisable( GL_POLYGON_OFFSET_FILL ); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glViewport(0, 0, GSEApplication->GetSizeX(), GSEApplication->GetSizeY()); glDepthRange(iSplit/(float)iNumOfSplits, (iSplit+1)/(float)iNumOfSplits); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(GSEApplication->GetFOV(), fAspect, fNear, fFar); glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_PROJECTION); glGetFloatv(GL_PROJECTION_MATRIX, m_ProjMatrixCamera); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); GSEPOINT3D CameraPosition=GSECamera->GetPosition(); GSEPOINT3D CameraTarget=GSECamera->GetTarget(); GSEPOINT3D CameraUpVector=GSECamera->GetUpVector(); gluLookAt(CameraPosition.x, CameraPosition.y, CameraPosition.z, CameraTarget.x, CameraTarget.y, CameraTarget.z, CameraUpVector.x, CameraUpVector.y, CameraUpVector.z); glGetFloatv(GL_MODELVIEW_MATRIX, m_LookAtMatrixCamera); //tmpPos sert juste à faire une conversion rapide entre un GSEPOINT3D et un GLfloat GLfloat tmpPos[4]={LightPosition.x,LightPosition.y,LightPosition.z,1.0}; //tmpWhite permet de définir la noirceur de l'ombre GLfloat tmpWhite[4]={white[0]*Shadowness,white[1]*Shadowness,white[2]*Shadowness,white[3]*Shadowness}; glLightfv(GL_LIGHT1, GL_POSITION, tmpPos); glLightfv(GL_LIGHT1, GL_AMBIENT, tmpWhite); glLightfv(GL_LIGHT1, GL_DIFFUSE, tmpWhite); glLightfv(GL_LIGHT1, GL_SPECULAR, black); glEnable(GL_LIGHT1); glEnable(GL_LIGHTING); glActiveTextureARB(GL_TEXTURE3_ARB); glEnable(GL_TEXTURE_2D); float x[] = { 1.0f, 0.0f, 0.0f, 0.0f }; float y[] = { 0.0f, 1.0f, 0.0f, 0.0f }; float z[] = { 0.0f, 0.0f, 1.0f, 0.0f }; float w[] = { 0.0f, 0.0f, 0.0f, 1.0f }; const GLfloat bias[] = {0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f}; glTexGenfv( GL_S, GL_EYE_PLANE, x ); glTexGenfv( GL_T, GL_EYE_PLANE, y ); glTexGenfv( GL_R, GL_EYE_PLANE, z ); glTexGenfv( GL_Q, GL_EYE_PLANE, w ); glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR ); glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR ); glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR ); glTexGeni( GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR ); glEnable( GL_TEXTURE_GEN_S ); glEnable( GL_TEXTURE_GEN_T ); glEnable( GL_TEXTURE_GEN_R ); glEnable( GL_TEXTURE_GEN_Q ); glMatrixMode(GL_TEXTURE); glLoadMatrixf(bias); glMultMatrixf(m_lightProjection); glMultMatrixf(m_lightLookAt); glMatrixMode(GL_MODELVIEW); DrawScene(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); glDisable( GL_TEXTURE_2D ); glDisable( GL_TEXTURE_GEN_S ); glDisable( GL_TEXTURE_GEN_T ); glDisable( GL_TEXTURE_GEN_R ); glDisable( GL_TEXTURE_GEN_Q ); }
Merci.
PS: Je me suis basé sur un document de nvidia pour ce code:
http://http.developer.nvidia.com/GPU...ems3_ch10.html
Partager