Forums Développement Multimédia

Aller au contenu

- - - - -

Eclairage de Phong

CODE Actionscript

3 réponses à ce sujet

#1 damien.git

    Ceinture Blanche

  • Members
  • Pip
  • 14 messages

Posté 08 February 2012 - 17:29 PM

Bonjour à tous,

Je suis en train de regarder Minko pour en cibler plus précisément les fonctionnalités et je suis extrêmement interessé par sa gestion des shaders. J'ai un peu de mal pour l'instant mais je suis très impressionné par ses possibilités.

J'essaye actuellement de faire une ombre sur les "bords" de mon objet 3D, j'ai donc codé une sous classe de ActionScriptShader. Les zones non ombrées seront face à la caméra tandis que les zones ombrées auront leur surface très inclinée si on les regarde depuis la caméra.

Voici la méthode que j'ai retouché :

override protected function getOutputColor() : SValue
{
   // présence de l'ombre
   var shadow:SValue = subtract( dotProduct3( vertexNormal, normalize( subtract(_light.normalize(), cameraLocalPosition) ) ), -0.5 );
   shadow = min( shadow, 1 );
   shadow = max( shadow, 0 );

   // texture
   var uv : SValue = interpolate( vertexUV );
   var diffuse:SValue = sampleTexture( BasicStyle.DIFFUSE, uv );

   // mixage de la texture et de l'ombre
   return float4(
      multiply(diffuse.rgb, add( 1.0, multiply( -1.0, interpolate(shadow)) ) ),
      diffuse.a
   );
}
 

Le problème est que mon objet 3D contient très peu de polygones, du coup on voit sur l'objet des dégradés linéaires pour l'ombre. Si j'augmente le nombre de polygones ce problème disparaît. J'imagine que cela vient du paramètre vertexNormal qui correspond à une interpolation linéaire entre deux normales de sommet. Savez vous comment obtenir un vecteur, correspondant à une interpolation de Phong entre deux normales de sommet ?

Pour vous montrer voici une illustration ( prise sur tpeinfographie3d.wordpress.com )
Image IPB
A gauche, sphère sous ombrage plat ; au milieu, sphère sous ombrage de Gouraud (ce que j'obtiens) ; à droite, sphère sous ombrage de Phong (ce que je voudrais).


J'espère avoir été assez clair, merci d'avance pour votre aide
// Damien
http://namide.com/

#2 damien.git

    Ceinture Blanche

  • Members
  • Pip
  • 14 messages

Posté 09 February 2012 - 19:06 PM

J'ai ajouté sur mon objet 3D une texture de normalMap générée avec Blender (bake mode : normal, normal space : object).
Les interpolations entre les sommets des normales ne sont maintenant plus linéaire (comme je le souhaite) mais les vecteurs 3D obtenus avec la texture de normaleMap n'ont rien à voir avec ceux du paramètre vertexNormal.
Je sens que j'y suis presque, il me faudrait juste un petit coup de pouce !
// Damien
http://namide.com/

#3 Jean-Marc Le Roux

    Ceinture Noire

  • Minko
  • PipPipPipPipPipPipPip
  • 210 messages

Posté 10 February 2012 - 18:45 PM

Bonjour,

Citation

Le problème est que mon objet 3D contient très peu de polygones, du coup on voit sur l'objet des dégradés linéaires pour l'ombre.

le problème c'est que tu calcules l'éclairage par vertex et non pas par pixel. Comment ça se voit ? C'est assez simple:


return float4(
          multiply(diffuse.rgb, add( 1.0, multiply( -1.0, interpolate(shadow)) ) ),
          diffuse.a
   );
 

Tu "interpolate" la valeur que tu as dans shadow.

1. Que fais "interpolate()" ?

La méthode interpolate sert à récupérer des calculs faits par vertex pour les utiliser par pixel. Tu peux avoir plus de détails en lisant le tutoriel suivant:

"Work with vertex attributes in the fragment shader" sur le Hub

En résumé: le vertex shader calcule des valeurs "par vertex". Mais le fragment shader utilise des valeurs "par pixel". Ex: j'ai une normale par vertex, mais j'ai besoin de la valeur de la normale par pixel pour mon éclairage. Ce que fait le GPU, c'est qu'il va "interpoler" certaines valeur en les passant du vertex au fragment shader. En assembleur, ces valeurs spéciales sont stockées dans les registres "varying".

La méthode interpolate() permet de signaler que la valeur prise en argument doit être récupérée dans le fragment shader. Pour comprendre il faut savoir que le programme du shader est derrière représenté sous forme d'un graphe. Chaque opération AS3 (add, multiply, etc...) va ajouter un ou plusieurs noeuds au graphe. C'est ensuite ce graphe qui est compilé par Minko pour créer le bytecode du shader. Mais il n'y a qu'un seul graphe, qui contient toutes les opérations du vertex et du fragment shader.

Donc un shader, bien qu'il soit - sur le GPU, dans la logique et dans le code - séparé en deux fonctions, n'est en fait considéré que comme étant un seul et unique programme. C'est un peu compliqué d'expliquer pourquoi, mais ça permet de simplifier beaucoup la liaison entre les deux programmes, l'allocation mémoire et aussi de les optimiser plus facilement.

Du coup, peu importe dans quelle fonction tu appelles "interpolate()". Tout ce qui compte, c'est que l'appel à cette fonction va ajouter dans le graph du shader un noeud qui va dire "après moi, c'est le fragment shader". A partir de là, le compilateur fait tout le travail à ta place, et notamment gère toutes les problématiques d'allocation/optimisation.

2. Comment corriger ton shader ?

En interpolant la valeur de l'éclairage, tu interpoles bien trop tard: tu fais tous les calculs par vertex, et donc ce n'est pas du Phong, mais du Gouraud.

Pour faire du phong, il faut que tu fasses les calculs dans le fragment shader. Ca veut dire que tu dois directement interpoler la normale du vertex, et faire tes calculs à partir de ça:


private var _vertexNormal : SValue;
private var _vertexUv : SValue;

override protected function getOutputPosition() : SValue
{
  _vertexNormal = vertexNormal;

  return vertexClipspacePosition;
}

override protected function getOutputColor() : SValue
{
  var normal : SValue = normalize(interpolate(_vertexNormal));
  var diffuse : SValue = sampleTexture(BasicStyle.DIFFUSE, interpolate(_vertexUv));
  var lambert : SValue = saturate(negate(dotProduct3(
        normal,
        normalize(cameraLocalDirection)
  );

  return float4(multiply(diffuse.rgb, lambert), diffuse.a);
}
 

2. Pourquoi utiliser des "private var" ?

En voyant ce code, tu dois te demander pourquoi stocker vertexNormal dans _vertexNormal et vertexUv dans _vertexUv pour ensuite les récupérer avec interpolate. Notamment par ce qu'on pourrait écrire interpolate(vertexNormal) et que ça marcherait pareil. Et tu as raison!

Pour savoir pourquoi je crée des "private var" plutôt que de faire des "interpolat()" directement... c'est juste par ce que c'est plus facile à comprendre :) En faisant comme ça, on voit bien que le vertex shader remplit des valeurs et que le fragment shader les lit en les interpolant. Je conseille de procéder ainsi pour tous tes shaders :)

3. Comment savoir où utiliser interpolate() ?

Quand utiliser "interpolate()" ? Par définition, tu dois l'utiliser dés que tu veux faire passer des données du vertex shader vers le fragment shader. Mais du coup, comment sais-tu quelles données doivent être calculées dans l'un ou dans l'autre ? Pire, le vertex shader va beaucoup plus vite que le fragment shader: il ne passe que sur quelques milliers de vertices contre plusieurs millions de pixels! Alors comment optimiser au mieux et ne faire que les bonnes opérations au bon endroit et être sur de ne pas faire trop de calculs inutiles dans le fragment shader ?

C'est très simple : tous les calculs qui peuvent être interpolés linéairement peuvent être faits dans le vertex shader puis interpolés/passés au fragment shader avec "interpolate()".

Par exemple l'addition ou la multiplication sont des opérations linéaires. Donc le mieux c'est de les faire dans le vertex shader et de passer le résultat au fragment shader ensuite:


private var _vertexColor : SValue;

protected function getOutputPosition() : void
{
  _vertexColor = multiply(add(float4(1), vertexNormal), 0.5);
}

protected function getOutputColor() : void
{
  return interpolate(_vertexColor);
}
 

est plus optimisé que :


private var _vertexNormal : SValue;

protected function getOutputPosition() : void
{
  _vertexColor = vertexNormal;
}

protected function getOutputColor() : void
{
  return multiply(add(float4(1), interpolate(_vertexNormal)), 0.5);
}
 

Mais les deux donnent (normalement) le même résultat à l'écran :) Normalement, le nouveau compilateur JIT introduit dans Minko 2 sera capable de déplacer vos opérations interpolate() pour optimiser vos shaders, vous facilitant ainsi la tâche !

Par contre, le dotProduct3() ou length() (la longueur d'un vecteur) ne sont pas des opérations linéaires, ce qui explique donc la différence d'interpolation - et donc de résultat - que tu obtiens :)

N'hésite pas si tu as d'autre questions !

Modifié par JMLR, 10 February 2012 - 18:49 PM.


#4 damien.git

    Ceinture Blanche

  • Members
  • Pip
  • 14 messages

Posté 12 February 2012 - 14:04 PM

Merci beaucoup JMLR,

Ta réponse/tuto/cour m'a beaucoup aidé ; notamment pour comprendre certain concepts de la gestion des shaders dans Minko.
J'ai réussi à faire à peu près ce que je souhaite. Je dis à peu près car le résultat low poly n'est pas aussi bon que le high poly, mais j'imagine que c'est normal :smile:

Je vais continuer de creuser les shaders de Minko maintenant que j'y vois un peu plus clair. Je suis très impressionné par les possibilités que ce moteur 3D semble offrir.
Quand j'aurais finis celui que je suis en train de développer je le posterais probablement sur mon lab : lab.namide.com
// Damien
http://namide.com/



1 utilisateur(s) li(sen)t ce sujet

0 membre(s), 1 invité(s), 0 utilisateur(s) anonyme(s)

authorised training centre

Centre de Formation Mediabox - Adobe et Apple Authorised Training Center.

Déclaré auprès de la Direction du Travail et de la Formation Professionnelle

Mediabox : SARL au capital de 62.000€ - Numéro d'activité : 11 75 44555 75 - SIRET : 49371646800035

MEDIABOX, 23, rue de Bruxelles, 75009 PARIS

FFP