jeudi 22 décembre 2011

Changement d'atmosphère !

Je n'était pas du tout satisfait du système de rendu d'atmosphère de mon générateur de planète; ce dernier était composé de deux éléments, cohabitant avec plus ou moins de bonheur:

- un meshe toujours orienté dans le plan de la caméra, représentant le "halo" atmosphérique vu de l'espace
- une skybox invisible de l'espace, mais se coloriant en bleu lorsque la camera se trouve sur la surface, coté jour

Lorsque la camera vient de l'espace pour pénetrer dans l'atmosphère, le halo s'efface progressivement, tandis que la skybox se colorie progressivement de la couleur du ciel ...

Par ailleurs, une fois sur la surface, lorsque le soleil se couche, l'effet n'est pas très réaliste : le bleu vire progressivement au noir; j'aimerais avoir les couleurs flamboyantes des vrais couchers de soleils... (j'ai tenté d'ajouter une touche de rouge violet, l'effet était sympa, mais sans plus...).

Sans parler du fait que d'avoir ses deux élements à synchroniser complexifie le code.

Bref ça marche, mais c'est pas terrible... Je me suis donc mis à la recherche d'un meilleur algo, en fouillant sur mon site favori :

http://vterrain.org

J'y ait trouvé la doc suivante, dans laquelle T.Nishita propose un algo se basant sur l'équation de Rayleigh:

http://nis-lab.is.s.u-tokyo.ac.jp/~nis/cdrom/sig93_nis.pdf

et un exemple d'implémentation de cet algo est proposé par S.O'Neil; les sources sont dispos ici :

http://sponeil.net/

Et une explication de son implémentation ici :

http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/real-time-atmospheric-scattering-r2093

L'idée est de rendre l'atmosphere via un modele 3D de sphère, rendue coté INTERIEUR (normales tournées vers le centre de la sphère), en déterminant la couleur pour chaque vertice de la sphère.


Ci dessous, le modèle de sphère que je vais utiliser pour le rendu:





I. Coté CPU:
J'ai repris l'implémentation de S.O'Neil, en l'adaptant à mon API 3D, sous forme d'une classe C++ : 

class cOpticalLengthLookupTable
{

public:

cOpticalLengthLookupTable( IMReal p_InnerRadius, IMReal p_OuterRadius );
~cOpticalLengthLookupTable( void );

void Create( IMReal p_RayleighScaleHeight, IMReal p_MieScaleHeight );
void SetVertexScatteringColor( const cIMVector& p_vertexpos, const cIMVector& p_viewpos, const cIMVector& p_lightdir, cIMVector& p_outcolor );
};


La méthode Create() consiste à précalculer la table des chemins optiques; OuterRadius represente l'altitude Max de la couche atmosphérique de la planète; cela correspond donc au rayon du meshe 'sphere' utilise pour le rendu de l'atmosphere. Le paramètre p_InnerRadius correspond au rayon de la planète.

La méthode SetVertexScatteringColor() calcule pour chaque vertice sa couleur propre, en fonction de la position camera et de la direction du vecteur lumineux.
Dans le cas de mon moteur 3D, le scenegraph peut comporter jusqu'à 3 sources lumineuses; On effectue donc pour chaque vertice au max 3 appels à cOpticalLengthLookupTable::SetVertexScatteringColor(), puis on somme les 3 couleurs résultantes, en prenant soin de clamper le résultat entre 0.0 et 1.0 :

if( islight1 )
{
m_scattering->SetVertexScatteringColor( vertpos, m_viewer_relative_pos, lightdir1, outcol1 );
}


if( islight2 )
{
m_scattering->SetVertexScatteringColor( vertpos, m_viewer_relative_pos, lightdir2, outcol2 );
}


if( islight3 )
{
m_scattering->SetVertexScatteringColor( vertpos, m_viewer_relative_pos, lightdir3, outcol3 );
}


outcolsum[0] = cIMMaths::Clamp( 0.0, 1.0, outcol1[0] + outcol2[0] + outcol3[0] );
outcolsum[1] = cIMMaths::Clamp( 0.0, 1.0, outcol1[1] + outcol2[1] + outcol3[1] );
outcolsum[2] = cIMMaths::Clamp( 0.0, 1.0, outcol1[2] + outcol2[2] + outcol3[2] );


La méthode cOpticalLengthLookupTable::SetVertexScatteringColor() est relativement couteuse en temps CPU; aussi, pour ne pas mettre le FPS à genou, j'ai adopté la méthode suivante : l'ensemble des vertex de la sphere n'est pas traité sur une frame, mais étalé sur plusieurs d'entres elles; le nombre max de vertices traités sur une frame est stocké dans la constante ATMOSCATTERINGVERTEXPERFRAME:
for( long j = 0; j < ATMOSCATTERINGVERTEXPERFRAME; j++ )
{

       // traiter le vertice d'index 'm_atmovertexindex' ...

m_atmovertexindex++;

if( m_atmovertexindex == meshe->m_vertices_final_size )
{
m_atmovertexindex = 0;
}
}

II. Coté GPU:

Il suffit de se munir de shaders capables de restituer pour chaque pixel la couleur interpolée en fonction des couleurs calculées coté CPU pour chaque Vertices : difficile de faire plus simple !! 

Le code HLSL de mes shaders:

float4x4 matViewProjection: register(c0);

struct VS_INPUT 
{
  float4 Position : POSITION0;
  float4 Color    : TEXCOORD0;
};

struct VS_OUTPUT 
{
  float4 Position : POSITION0;
  float4 Color    : TEXCOORD0;
};

VS_OUTPUT vs_main( VS_INPUT Input )
{
  VS_OUTPUT Output;

  Output.Position = mul( Input.Position, matViewProjection );
  Output.Color = Input.Color;
     
  return( Output );   
}

NB : Noter que coté vertex shader je stocke la couleur calculée du vertice dans les coords textures...

 struct PS_INTPUT 
{
  float4 Position : POSITION0;
  float4 Color    : TEXCOORD0;
};


float4 ps_main( PS_INTPUT input ) : COLOR0
{   
  return input.Color;
}

Quelques screenshots:








On aperçoit ici et là quelques artefacts "triangles" : c'est dû à la tesselation du meshe "sphère", peut être un peu faible, mais augmenter le nombre de vertex de la sphère revient à consommer un peu plus de temps CPU; je pense avoir trouvé ici un compromis satisfaisant entre vitesse et rendu :)





Une voie d'amélioration possible serait de "shaderiser" le code de cOpticalLengthLookupTable::SetVertexScatteringColor(), en fournissant au shader la table précalculée des chemins optiques sous la forme d'une texture... 

Aucun commentaire:

Enregistrer un commentaire