:longBar:
Sommaire:
:longBar:

Avant de commencer

Bien entendu, il est fortement conseillé d'avoir lu (et compris) la partie 1 de ce tutorial. Autrement, il y a de fortes chances pour que vous soyez largué assez vite... :baffed:

Deuxième chose: je ne prétends pas ici vous apprendre à coder un shader pour le jeu vidéo. L'écriture de ce code est "Maya oriented". Ainsi, la méthode n'est pas très rigoureuse (au niveau du passage des argument dans les shader par exemple) mais simplifiée au possible pour ne pas passer de temps sur des détails techniques et avoir le plus rapidement possible quelque chose dans sont viewport.

:longBar:

Quoi c'est qu'on va faire exactement?

Ça:

cgfx_part2_002.png

Avec plein de paramètres!

cgfx_part2_003.png

:longBar:

Le code de base

Voici le code minimal duquel nous allons partir:

/*--- Matrice ---*/
float4x4 wvpMatrix : WorldViewProjection;	// On récupère la matrice de la projection de la vue dans le monde
 
/*--- Structures ---*/
struct vIN
{
	float3 Position	: POSITION;	// La position du vertex en entrée
};
 
struct vOUT
{
	float4 Position	: POSITION;	// La position du vertex en sortie (doit être de type float4)
};
 
/*--- Vertex Shader ---*/
vOUT mainVS(vIN IN)
{
	// Création de la strucure de sortie
	vOUT OUT;
 
	// Convertion du float3 IN.Position en un float4
	float4 Position = float4(IN.Position, 1);
 
	// On multiplie la nouvelle position par la matrice de la vue. Puis on remplis la structure de sortie
	OUT.Position = mul(wvpMatrix, Position);
 
	// On renvoit la nouvelle structure de sortie
	return OUT;
}
 
/*--- Pixel Shader ---*/
float4 mainPS(vOUT IN) : COLOR
{
	// Renvoit la couleur du pixel: RGBA
	return float4(1, 0, 0, 1);
}
 
/*--- Techniques ---*/
technique Main
{
	pass p0	// Une seule passe
	{
		// Compilation des shaders
		VertexProgram = compile arbvp1 mainVS();
		FragmentProgram = compile arbfp1 mainPS();
	}
}

cgfx_part2_0031.png

cgfx_tuto_001.7z

:longBar:
La matrice
float4x4 wvpMatrix : WorldViewProjection;	// On récupère la matrice de la projection de la vue dans le monde

Ici nous initialisons la matrice qui va nous permettre de calculer la position du vertex en fonction de notre vue.

:longBar:
Les structures d'entrées et de sorties
struct vIN
{
	float3 Position	: POSITION;	// La position du vertex en entrée
};
 
struct vOUT
{
	float4 Position	: POSITION;	// La position du vertex en sortie (doit être de type float4)
};
  • vIN, ce sont les informations qu'on a besoin en entrée.
  • vOUT c'est ce qu'on à en sortie du Vertex Shader. Notre vertex "modifié" en fait.

Pour l'instant, nous n'utilisons que les positions des vertex. C'est pourquoi vIN et vOUT contiennent les mêmes choses. Le mot POSITION (en majuscule spécifie le "buffer" dans lequel on stock les informations).

:longBar:
Le vertex shader
vOUT mainVS(vIN IN)
{
	// Création de la strucure de sortie
	vOUT OUT;
 
	// Convertion du float3 IN.Position en un float4
	float4 Position = float4(IN.Position, 1);
 
	// On multiplie la nouvelle position par la matrice de la vue. Puis on remplis la structure de sortie
	OUT.Position = mul(wvpMatrix, Position);
 
	// On renvoit la nouvelle structure de sortie
	return OUT;
}

C'est le shader (par shader, entendez "program") qui modifie la position (et éventuellement d'autres choses: Normal, UV, vecteur de la lumière, etc..) des vertex. Il prend comme argument la structure d'entrée (vIN) et renvoie la structure de sortie modifiée (vOUT). Si ce shader est très utile pour le jeu vidéo (notamment pour calculer du skinning en temps réel), il l'est beaucoup moins dans notre cas. Il servira surtout à calculer les matrices.

:longBar:
Le pixel shader
float4 mainPS(vOUT IN) : COLOR
{
	// Renvoit la couleur du pixel: RGBA
	return float4(1, 0, 0, 1);
}

Ce shader renvoie une couleur de type float4 (je ne sais pas à quoi sert le dernier composant. Je suppose que c'est l'alpha mais il ne semble pas très bien supporter par Maya). Ici il est très simple (On renvoie du rouge) mais c'est là que beaucoup de choses vont se passer.

:longBar:
Les techniques et les passes
technique Main
{
	pass p0	// Une seule passe
	{
		// Compilation des shaders
		VertexProgram = compile arbvp1 mainVS();
		FragmentProgram = compile arbfp1 mainPS();
	}
}

Les "techniques" permettent de coder plusieurs comportements dans un seul shader. Ici, il n'y en aura qu'une seule. Les passes permettent de calculer plusieurs passes dans un shader (je suppose que pour des choses comme la réfraction ça peut être utile). Idem, nous n'utiliserons qu'une seule passe.

:longBar:

On commence à modifier: L'illumination de base

Bon, le rouge ça a beau être sexy sur une femme, ça l'est beaucoup moins dans un viewport. :sourit: Nous allons "illuminer" tout ça. Pour pouvoir illuminer, il faut une pointLight. Il n'y a pas (à ma connaissance) de moyen de reconnaitre automatiquement les pointLights de vos scènes Maya. Il faut les déterminer dans le shader. Nous allons donc créer un attribut "pointLightPos0":

:longBar:
Création des attributs

La convention veut que les attributs soit créés juste en dessous des matrices et en au dessus des structures.

//Light 0
float4 pointLightPos0 : POSITION
<
	string UIName =  "PointLight0 Position";
	string Space	= "World";
>;
 
float3 pointLightColor0
<
	string type		= "color";
	string UIName	= "PointLight0 Color";
> = {1.0, 1.0, 1.0};
 
float pointLightIntensity0
<
	string UIName =  "PointLight0 Intensity";
	string UIWidget = "slider";
	float UIMin = 0.0;
	float UIMax = 1.0;
	float UIStep = 0.001;
> = 1;
 
bool bUsePointLight0
<
	string UIName = "Use PointLight0";
	string UIWidget = "RadioButton";
> = true;

On s'arrête un peu sur les différents paramètres utilisés lors des déclarations des attributs (Je tiens à vous prévenir, je ne suis pas hyper calé là dessus):

string UIName =  "PointLight0 Position";

C'est le nom que prendra l'attribut dans Maya.

string Space	= "World";

J'ai une idée mais impossible de bien l'expliquer. En gros c'est l'espace utilisé pour ladite position.

string type		= "color";

Si vous ne mettez pas ça, dans Maya, vous aurez 3 valeurs à rentrer à la main pour la couleur au lieu d'un widget de Color de Maya. C'est pour simplifier disons.

> = {1.0, 1.0, 1.0};

Vous l'aurez compris, c'est la valeur par défaut.

string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.001;

Le type de widget, ainsi que ses paramètres, toujours dans l'optique d'avoir quelque chose d'assez facile à manipuler dans Maya. :siffle:

Voila, les attributs ont été créés.

:longBar:
Ajout de déclaration de matrices

Pour la suite, nous allons avoir besoin de déclarer deux nouvelles matrices dont nous allons avoir besoin:

float4x4 wMatrix : World;
float4x4 wvMatrixIT : WorldInverseTranspose;

Maintenant, direction les déclarations des structures!

:longBar:
Modification des déclarations des structures

Pour vIN, on précise qu'on va avoir besoin de la normal des vertex:

struct vIN
{
	float3 Position	: POSITION;	// La position du vertex en entrée
	float4 Normal	: NORMAL;		// La normal du vertex en entrée
};

Et on calculera la normal du vertex par rapport au monde (WorldNormal) ainsi que le vecteur de la pointLight (PointLight0Vec). On doit alors aussi modifier la structure de sortie du vertex:

struct vOUT
{
	float4 Position	: POSITION;	// La position du vertex en sortie (doit être de type float4)
	float3 PointLight0Vec	: TEXCOORD0;
	float3 WorldNormal	: TEXCOORD1;
};

C'est maintenant que ça ce gâte! Direction le vertex shader!

:longBar:
Modification du vertex shader

On commence par le plus simple. Juste après avoir créé la structure de sortie (vOUT), remplissez la WorldNormal:

// Création de la strucure de sortie
vOUT OUT;
 
OUT.WorldNormal = mul(wvMatrixIT, IN.Normal).xyz;

On est en plein dans les maths: On multiplie la wvMatrixIT par la normal du vertex. La pointLights maintenant:

// Convertion du float3 IN.Position en un float4
float4 Position = float4(IN.Position, 1);
 
// Convertion en "world" space de la position du vertex
float4 PositionWorld = mul(wMatrix, Position);

Et enfin

// Calcul du vecteur de la pointLight
OUT.PointLight0Vec = pointLightPos0.xyz - PositionWorld.xyz;

On s'accroche et on va maintenant au pixel shader!

:longBar:
Modification du pixel shader

Dès le début du shader, on normalise les vecteurs en entrée:

float4 mainPS(vOUT IN) : COLOR
{
	// On normalise les vecteurs
	float3 Nn = normalize(IN.WorldNormal);
	float3 pL0n = normalize(IN.PointLight0Vec);
 
	// Renvoit la couleur du pixel: RGBA
	return float4(1, 0, 0, 1);
}

pL0n pour "pointLight0 normalisé".

Et maintenant, c'est les opérations de calcul de l'illumination:

//Illumination Declarations
float3 illumPointLight0 = 0;
 
if(bUsePointLight0)
{
	float pL0dn = dot(pL0n,Nn);
	illumPointLight0 = max(0, pL0dn);
	illumPointLight0 *= pointLightColor0*pointLightIntensity0;
}
 
float3 result = illumPointLight0;
 
// Renvoit la couleur du pixel: RGBA
return float4(result, 1);

Sauvegardez votre fichier .cgfx et rechargez le:

cgfx_part2_004.png

Vous verrez qu'il vous faut remplir un paramètre pour que tout fonctionne bien:

cgfx_part2_005.png

Créez une point light, copiez collez le nom du node de transform de cette pointLight:

cgfx_part2_006.png

Et paf!

cgfx_part2_007.png

Bougez votre pointLight, changez la couleur, l'illumination devrait suivre!

cgfx_tuto_002.7z

Si à ce stade rien ne fonctionne, la meilleure méthode est de prendre mon shader, vérifier qu'il fonctionne bien chez vous et comparer mon fichier avec le votre (via un logiciel comme winmerge pour voir ou il y a des différences). N'hésitez surtout pas à laisser un commentaire pour me prévenir si un de mes fichiers sources ne fonctionnent pas.

:longBar:

Ajout de la seconde pointLight

Bon, maintenant qu'on à une pointLight, ça serait bien d'en mettre une autre! :hehe:

Vous allez voir que ça n'a rien de bien compliqué.

:longBar:
Ajout des attributs

La c'est de la logique pure. Dans:

/* --- Déclarations des paramètres */

On ajoute:

//Light 1
float4 pointLightPos1 : POSITION
<
	string UIName =  "PointLight1 Position";
	string Space	= "World";
>;
 
float3 pointLightColor1
<
	string type		= "color";
	string UIName	= "PointLight1 Color";
> = {1.0, 1.0, 1.0};
 
float pointLightIntensity1
<
	string UIName =  "PointLight1 Intensity";
	string UIWidget = "slider";
	float UIMin = 0.0;
	float UIMax = 1.0;
	float UIStep = 0.001;
> = 1;
 
bool bUsePointLight1
<
	string UIName = "Use PointLight1";
	string UIWidget = "RadioButton";
> = true;

Vous l'aurez compris, on copie tous les attributs de la light déjà existante (zero) et on remplace les zéros par des un...

:longBar:
Modification de la structure

Idem pour la structure vOUT:

struct vOUT
{
	float4 Position	: POSITION;	// La position du vertex en sortie (doit être de type float4)
	float3 PointLight0Vec	: TEXCOORD0;
	float3 PointLight1Vec	: TEXCOORD1;
	float3 WorldNormal	: TEXCOORD2;
};

Notez le petit changement de numéro sur les TEXCOORD pour garder un truc clean.

:longBar:
Modification du vertex shader

Ici rien de compliqué non plus, dans:

/*--- Vertex Shader ---*/
// Calcul du vecteur de la pointLight
	OUT.PointLight0Vec = pointLightPos0.xyz - PositionWorld.xyz;
	OUT.PointLight1Vec = pointLightPos1.xyz - PositionWorld.xyz;
:longBar:
Modification du pixel shader

C'est là que c'est le plus compliqué (et vous allez voir que ça ne l'est pas non plus réellement). Dans:

/*--- Pixel Shader ---*/
float4 mainPS(vOUT IN) : COLOR
{
	// On normalise les vecteurs
	float3 Nn = normalize(IN.WorldNormal);
	float3 pL0n = normalize(IN.PointLight0Vec);
	float3 pL1n = normalize(IN.PointLight1Vec);
 
 
	//Illumination Declarations
	float3 illumPointLight0 = 0;
	float3 illumPointLight1 = 0;
 
	if(bUsePointLight0)
	{
		float pL0dn = dot(pL0n,Nn);
		illumPointLight0 = max(0, pL0dn);
		illumPointLight0 *= pointLightColor0*pointLightIntensity0;
	}
 
	if(bUsePointLight1)
	{
		float pL1dn = dot(pL1n,Nn);
		illumPointLight1 = max(0, pL1dn);
		illumPointLight1 *= pointLightColor1*pointLightIntensity1;
	}
 
	float3 result = illumPointLight0 + illumPointLight1;
 
	// Renvoit la couleur du pixel: RGBA
	return float4(result, 1);
	return float4(1,0,0, 1);
}

En fait, on calcule les valeurs d'illumination de chaque pointLight séparément (illumPointLight0 et illumPointLight1).

Puis on les additionne:

float3 result = illumPointLight0 + illumPointLight1;

Rechargez:

cgfx_part2_008.png

cgfx_part2_009.png

Et voila le travail!

Rien de bien compliqué, vous en conviendrez. :hihi:

Voici le code source, au cas où:

cgfx_tuto_003.7z

Fin de ce billet. N'hésitez pas à laisser un commentaire si vous avez des questions ou si je n'ai pas été assez clair sur certains points

Quand vous vous sentez prêt, passez à la suite!

:marioCours: