Hello !
Voilà cela fait quelques jours que je me bats avec les ombres dans Nazara, j'ai enfin réussi à en obtenir mais elles ne sont pas correctes.
Il y a divers bugs, notamment le fait que l'ombre ne semble pas vraiment correspondre, disparaît parfois, que le frustum de shadow mapping est plus petit que la lumière produite par le spotlight (et là j'avoue je suis toujours un peu wtf).
Voici une vidéo pour montrer tout ça (une image valant mille mots, cette vidéo vaut 3300 000 mots ! ): http://youtu.be/B-GXQSSr7_A
Alors côté implémentation, c'est du shadow mapping bête et méchant pour l'instant, avec la génération de la shadow map ici:
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 | void RenderSystem::UpdateShadowMaps() { if (!m_shadowRT.IsValid()) m_shadowRT.Create(); for (const Ndk::EntityHandle& light : m_lights) { LightComponent& lightComponent = light->GetComponent<LightComponent>(); NodeComponent& lightNode = light->GetComponent<NodeComponent>(); if (!lightComponent.IsShadowCastingEnabled() || lightComponent.GetLightType() != nzLightType_Spot) continue; NzVector2ui shadowMapSize(lightComponent.GetShadowMap()->GetSize()); m_shadowRT.AttachTexture(nzAttachmentPoint_Depth, 0, lightComponent.GetShadowMap()); ///TODO: Cache the matrices in the light NzRenderer::SetMatrix(nzMatrixType_Projection, NzMatrix4f::Perspective(lightComponent.GetOuterAngle(), 1.f, 1.f, 1000.f)); NzRenderer::SetMatrix(nzMatrixType_View, NzMatrix4f::ViewMatrix(lightNode.GetPosition(), lightNode.GetRotation())); NzRenderer::SetTarget(&m_shadowRT); NzRenderer::SetViewport(NzRecti(0, 0, shadowMapSize.x, shadowMapSize.y)); NzAbstractRenderQueue* renderQueue = m_shadowTechnique.GetRenderQueue(); renderQueue->Clear(); for (const Ndk::EntityHandle& drawable : m_drawables) { GraphicsComponent& graphicsComponent = drawable->GetComponent<GraphicsComponent>(); NodeComponent& drawableNode = drawable->GetComponent<NodeComponent>(); graphicsComponent.AddToRenderQueue(renderQueue); } NzSceneData sceneData; sceneData.ambientColor = NzColor(0, 0, 0); sceneData.background = nullptr; sceneData.viewer = nullptr; //< Depth technique doesn't require any viewer m_shadowTechnique.Draw(sceneData); } } |
Lors du rendu normal, les lumières sont ajoutées à la RenderQueue avec la shadow map et les matrices qui vont avec (oui il y a duplication, de toute façon mon objectif est surtout d'avoir un truc qui marche là ):
1 2 | light.viewMatrix = NzMatrix4f::ViewMatrix(transformMatrix.GetTranslation(), transformMatrix.GetRotation()); light.projectionMatrix = NzMatrix4f::Perspective(m_outerAngle, 1.f, 1.f, 1000.f); |
Qui sont envoyés un peu plus loin au shader:
1 2 3 4 5 6 7 8 9 | NzTextureSampler sampler; sampler.SetFilterMode(nzSamplerFilter_Bilinear); sampler.SetWrapMode(nzSamplerWrap_Clamp); NzRenderer::SetTexture(8, light.shadowMap); NzRenderer::SetTextureSampler(8, sampler); shader->SendMatrix(shader->GetUniformLocation("LightProjMatrix"), light.projectionMatrix); shader->SendMatrix(shader->GetUniformLocation("LightViewMatrix"), light.viewMatrix); shader->SendInteger(shader->GetUniformLocation("ShadowMap"), 8); |
Le Vertex Shader s'occupe ensuite de refaire la projection:
1 | vLightSpacePos = LightProjMatrix * LightViewMatrix * WorldMatrix * vec4(VertexPosition, 1.0); |
Et le Fragment Shader s'occupe ensuite de projeter la shadow map:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | if (vLightSpacePos.w > 0.0) { // Perspective vec3 ProjectionCoords = vLightSpacePos.xyz / vLightSpacePos.w; // Bias vec2 UVCoords; UVCoords.x = 0.5 * ProjectionCoords.x + 0.5; UVCoords.y = 0.5 * ProjectionCoords.y + 0.5; if (UVCoords.x >= 0.0 && UVCoords.x < 1.0 && UVCoords.y >= 0.0 && UVCoords.y < 1.0 && ProjectionCoords.z >= 0.0 && ProjectionCoords.z < 1.0) { float Depth = texture(ShadowMap, UVCoords).x; if (Depth < ProjectionCoords.z - 0.005) { lightDiffuse = vec3(0.0); lightSpecular = vec3(0.0); } } } |
Voilà, tout ceci donne le résultat en vidéo, je donne aussi le code responsable du calcul d'éclairage (j'ai renommé quelques variables pour rendre ça plus compréhensible):
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 | case LIGHT_SPOT: { vec3 lightDir = LightPos - vWorldPos; float lightDirLength = length(lightDir); lightDir /= lightDirLength; // Normalisation float att = max(LightAttenuation - LightInvRadius*lightDirLength, 0.0); // Ambient lightAmbient += att * LightColor * LightAmbientFactor * (MaterialAmbient.rgb + SceneAmbient.rgb); // Modification de l'atténuation pour gérer le spot float curAngle = dot(LightDirection, -lightDir); float innerMinusOuterAngle = LightInnerAngle - LightOuterAngle; // Ce sont des cosinus att *= max((curAngle - LightOuterAngle) / innerMinusOuterAngle, 0.0); // Diffuse float lambert = max(dot(normal, lightDir), 0.0); lightDiffuse += att * lambert * LightColor * LightDiffuseFactor; // Specular vec3 reflection = reflect(-lightDir, normal); float specularFactor = max(dot(reflection, eyeVec), 0.0); specularFactor = pow(specularFactor, MaterialShininess); lightSpecular += att * specularFactor * LightColor; break; } |
Si quelqu'un pouvait me donner un coup de main pour le coup, ça m'arrangerait beaucoup, ce problème va me rendre fou.
Je poste aussi le code, si quelqu'un veut voir quelle fonction fait quoi: Dépôt Github (Branche shadow sans une partie du code plus haut). Matrix4.inl (algorithme de génération des matrices).
Pour le reste (RTT) c'est basiquement de l'OpenGL (m_shadowRT.AttachTexture fait appel à glFrameBufferTexture2D par exemple), voilà.
Si besoin de plus d'information, aucun souci n'hésitez pas à demander