1

<noob>Hello,

j'ai voulu reprendre quelques bouts de code que j'avais commencés pour apprendre quelques trucs en 3D, mais il me manque trop de connaissances de base pour avancer correctement. Du coup j'ai quelques questions de compréhension de base, mais de façon plus générale je serais intéressé par tous les liens vers des articles / tutos ou bouquins que vous pourriez me conseiller (le but des questions étant surtout de définir le contexte de ce que j'aimerais apprendre).

La première question est une question de vocabulaire : qu'est-ce qu'un shader, précisément ? En voulant refaire de l'OpenGL récemment, je me suis rendu compte que le fixed pipeline était complètement obsolète, et qu'il fallait maintenant écrire soi-même ses vertex et pixel shaders. Il y a 12 ans, à l'époque de Quake 3, on parlait déjà de shaders pour définir tous les petits effets graphiques à apporter aux niveaux (déformations de textures, lumières, etc). Aujourd'hui je regarde cette vidéo sur des nouveaux effets dans Skyrim, et à 00:40 il est question de "Flow-based water shader", un effet qui semble ajouter une vraie mécanique des fluides aux cours d'eau. Bref, toutes ces choses qui s'appellent shader me semblent assez différentes, j'ai du mal à croire qu'il s'agisse à chaque fois de trucs codés dans les vertex et pixels shaders, que j'évoquais en premier, et du coup la définition m'échappe complètement. Concrètement c'est quoi, un shader ?

De cette première question découle peut-être la seconde : en supposant que tous ces effets soient codés en vertex/pixel shaders (j'ai du mal à comprendre comment ça pourrait être possible, mais admettons), ça fait un paquet de choses différentes à gérer. J'ai cru comprendre qu'il valait mieux éviter les branchements dans le code d'un shader, donc un énorme shader qui gère tous les effets en même temps me semble difficilement envisageable. Mais est-ce qu'il est possible d'utiliser plusieurs shaders entre lesquels on alterne au cours du rendu d'une seule frame ? Ne sachant pas si c'était techniquement valide j'ai voulu tenter (plusieurs appels à "useProgram" au cours du rendu), et ça n'a rien donné de bon.

Troisième et dernière question, sans rapport avec la précédente : j'ai un petit code de test qui m'affiche une scène avec quelques objets, le tout éclairé par 3 lumières. Les trois lumières sont gérées dans le GLSL, avec trois fois l'ensemble de "uniforms" qui définissent une source lumineuse. Bon, ça marche, mais si je voulais faire un "vrai" projet avec non pas 3 sources lumineuses mais 20, 30 ou 50, ça se passe comment ? Je pourrais toujours en gérer 5 ou 10 dans le GLSL et n'activer que les plus proches de la caméra, quitte à fusionner puis faire progressivement disparaître les autres au fur et à mesure que la caméra s'en éloigne, mais j'ai l'impression que les jeux actuels n'ont aucun problème à gérer beaucoup de lumières simultanément, donc il doit y avoir des solutions bien plus efficaces que les hacks que j'envisage. Où est-ce que je peux trouver des infos là-dessus ? Quand je cherche sur Google, je ne tombe sur des scènes de démo qui gèrent un (petit) nombre constant de lumières, mais jamais sur des implémentations plus réalistes qui ont à gérer ce problème.

Voilà, c'est à nouveau un peu décousu, mais comme il me manque à peu près toutes les bases et que mes recherches Google me font tourner en rond avec des programmes de test qui n'apprennent pas grand-chose, je suis preneur de vos sources d'info smile

Merci !</noob>
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

2

1) À la base, comme tu l'indiques, il y a surtout des vertex et pixel (fragment en OpenGL) shaders... Microsoft a introduit avec DirectX 10 les geometry shaders (GS), qui, si je ne dis pas de bêtises, ont leur équivalent en OpenGL. Concernant ceux-ci, je ne m'en suis jamais servi, n'ayant pas de matériel suffisamment puissant pour faire mumuse avec. (Plus récemment encore il y a les "Compute shader" un peu dans le rôle de OpenCL/CUDA mais je pense que ce n'est pas la peine de trop s'étendre là dessus)
Globalement c'est juste un bout de programme specialisé qui s'exécute sur la carte graphique, comme tu le savais déjà.
Seulement avec l'évolution des technologies, les différents types de shaders (effectuant des boulots bien spécifiques) partagent un grand nombre de fonctionnalités et s'exécutent (sur les cartes modernes) sur les mêmes unités de calculs. Le driver et le materiel vont s'occuper de bien configurer les différentes unités avec les bonnes entrées et les bonnes sorties.
Mais au final tu écris toujours des vertex et pixel shader comme avant. (Plus geometry shader pour des effets plus avancés qui créent de nouveaux points...)

2) Après quand on te parle de shader en général, c'est un abus de langage. Ce qui est important c'est plutôt un effet dans sa globalité, c'est à dire une combinaison intelligente des différents états de rendu (textures, alpha blending, color write, z write, z enable, etc.) avec des shaders et assez souvent du code specifique à exécuter par le CPU. (Pour gérer les différentes passes de ton effet par exemple)
Tu peux bien évidemment (C'est assez courant, par exemple l'effet de rendu d'eau que tu as mentionné utilise forcément des shaders spécifiques et différents du reste de la scène) changer de shaders à plusieurs fois dans le rendu d'une même image, tout comme tu peux changer les autres paramètres du rendu (alpha blending, etc.)
Idéalement tu veux quand même que ça arrive le moins souvent possible, donc tu regrouperais au possible le rendu de tous les éléments utilisant les mêmes shader et paramètres de rendu.

3) Pour appliquer un nombre variable de sources de lumières, tu peux procéder en plusieurs passes: (En supposant une scène composée d'éléments totalement opaques... Les particules peuvent être dessinées en dernière étape)
Tu dessines la scène sans aucun éclairage autre que la lumière ambiante.
Tu desactives le z-write, tu définis la comparaison du z-buffer à inférieur ou égal (glDepthFunc(GL_LEQUAL)), et tu passes en additive blending. (glBlendFunc(GL_ONE, GL_ONE))
Tu dessines la scène avec uniquement la contribution de la première lumière.
Tu dessines la scène avec uniquement la contribution de la seconde lumière.
Et ainsi de suite.
Ça c'est le schéma type non optimisé. Après il faut adapter. (Et éliminer systématiquement les sources de lumière qui sont hors de portée n'est pas un luxe ^^)
En général tu voudras gérer plus d'une lumière par passe, sachant qu'une passe est quand même assez coûteuse en temps de calcul. Et si tu as des éléments transparents ça va compliquer un peu la donne.
Tu peux aussi gérer les ombres de chaque source lumineuse à chaque passe, si tu veux avoir de très belles (et très gourmandes) ombres dynamiques.

J'espère avoir répondu à peu près correctement à tes questions. De toutes façons je suis certains que momotte saura faire un post plus complet encore smile (Tu devrais peut-être le !call si pas déjà fait ?)

Édit: J'avais marqué effacer le z-buffer sans trop réfléchir, mais c'est complètement con... C'est corrigé tongue
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

3

OK, merci pour les précisions de 1) et 2), ça explique effectivement ma confusion smile

Pour 3), je ne pensais pas avoir à faire un truc aussi bourrin grin Du coup si j'ai une scène avec 30 lumières actives, et que mon shader est capable d'en traiter 5 par passe, la solution c'est de faire 6 rendus et d'additionner les résultats ? Comment on détermine le nombre de lumières gérables en une seule passe du coup ? Parceque je pourrais en coller 20 en série, je ne sais pas trop quel critère retenir pour avoir le meilleur ratio (peut-être le nombre de lumières moyen dans une scène, pour qu'en majorité une seule passe soit suffisante par frame ?)

Merci beaucoup pour ces explications en tout cas smile (tu as appris ça comment, sur le tas, ou bien tu as des sources à me conseiller ?)

Je ne voulais pas spécifiquement déranger bearbecue qui en a peut-être assez d'être callé à chaque fois qu'un titre de topic contient "3D", mais c'est vrai que... grin

!call bearbecue
--- Call : bearbecue appelé(e) sur ce topic ...
Ne réponds pas si ça te gonfle ^^
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

4

bon heu ca risque d'etre un pave, jvais essayer de repondre du mieux que je peux grin
(ie: pas dans l'immediat, enfin peut etre, ca depend de combien de rebuilds jvais me taper triso)
avatar
HURRRR !

5

N'y passe pas trop de temps non plus, si t'as quelques pistes je suis bien sûr preneur, mais je veux pas te bouffer un bout de ta soirée smile
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

6

deja, pour les deux premieres questions, quelques precisions en plus:

1)
Un shader, ca peut s'apparenter effectivement aux "shaders" de q3. sauf que c'est pas execute au meme endroit, par le meme truc, de la meme facon. bref, en fait ca a rien a voir, sauf dans le concept. (#trihum#)
Un shader, c'est un binaire pour le GPU, qu'il va etre executer, dans un ou plusieurs pipelines differents suivant le type de shader et le shader model supporte (plus de details plus bas)
c'est comme un .exe, mais pour le GPU quoi. Comme pour un programme CPU, tu peux ecrire un shader directement en ASM, ou avec un des langages haut niveau: HLSL, GLSL, Cg, CUDA, ou OpenCL.
Apres il se fait compiler en ASM, assembler en bytecode, et uploader sur le GPU, qui va l'executer dans ses (nombreuses) unites d'execution.

d'un point de vue historique, d'abord, y avait le vieux truc classique : le fixed function pipeline, qui etait plus ou moins juste un grosse collection de bourrin de flags et valeurs diverses.

ensuite, t'as eu les register combiners qui se sont greffes la dessus. ils permettaient un peu plus de souplesse dans les combinaisons d'etats de rendus en te donnant quelques fonctions de base avec lesquelles tu pouvais combiner les valeurs composant ton rendu (genre la couleur de ta texture, un vertex stream donne, une valeur uniforme, etc..). mais sont morts quasiment dessuite avec l'arrivee des premiers shaders.

ensuite, les premiers shaders... Petite precision: ce n'est pas DirectX10 qui a "introduit les geometry shaders". Mais plutot: les geometry shaders ne sont supportes par DirectX qu'a partir de la version 10, ils existaient bien avant, et etaient accessible en Cg ou en ASM avec des extensions OpenGL a l'epoque de DX9.
pour parler de versions de shaders, on parle de shader models (abbrevies "SM", genre: SM1.2, SM4.0, etc..).
les geometry shaders, c'est a partir du SM4.0

au debut, les premiers shaders etaient super limites, en instructions, en nombre de texture fetches, de registres uniformes que tu pouvais setter. Les tout premiers ne geraient pas les texture-reads dependants (genre tu sample une texture, et tu sample une 2eme texture avec des texcoords calcules avec le resultat du premier texture fetch).

par exemple les vertex shaders en shader model 2.0 sont limites a 96 instructions.
aucun shader model avant le 3.0 ne supportaient pas le sampling de textures dans le vertex shader (!)
et d'autres limitations du genre.

tu peux voir l'evolution des shader models comme un perfectionnement des unites d'execution des GPUs.

certaines vieilles cartes supportaient a la fois le fixed-function pipeline et les shaders. maintenant, les API graphiques qui gerent encore le fixed-function pipeline (genre OpenGL sur les cartes recentes) l'emulent en construisant des shaders qui font la meme chose derriere ton dos. bref.

et avant le SM3.0 (il me semble bien que c'est le 3..), les pixel et vertex shaders etaient executes par des unites d'execution bien separees. maintenant c'est unifie: c'est les memes unites d'execution qui font tourner les deux.
t'as un resume des limitations ici: http://en.wikipedia.org/wiki/High_Level_Shader_Language

la actuellement on en est au SM5.0, qui introduit la tesselation hardware. Il rajoute 3 nouveaux types de shaders en plus de vertex, geometry, et pixel shaders, entre le vertex et le geometry shader:

- hull shader
- tesselation shader
- domain shader

details ici: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476340(v=vs.85).aspx

du coup le pipeline devient:
vertex -> hull -> tesselation -> domain -> geometry -> fragment

ouai tiens d'ailleurs... DirectX utilise la terminologie "pixel shader", et OpenGL "fragment shader", mais la, c'est OpenGL qui a raison, parceque le "pixel" shader peut etre execute plusieurs fois par pixel, suivant tes settings d'antialiasing notamment, on parle de "fragment", le pixel final est une combinaison de tous ces fragments. donc c'est bien un fragment shader, pas un pixel shader...

mais bon, tout le monde dit pixel shader, alors on va dire pixel shader aussi... embarrassed

btw, les compute shaders, j'en ai jamais utilise, mais c'est a priori comme les kernels CUDA ou OpenCL.


(question 2 incoming)
avatar
HURRRR !

7

boarf, t'inquiete, jsuis en mode "etalage de science" ces derniers jours triso
(et j'ai des rebuilds de 35 minutes qui tournent quasiment en boucle zzz)
avatar
HURRRR !

8

\o/ merci pour cette première partie déjà, j'ai de la lecture pour demain au boulot grin
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

9

2)
bah la GoldenCrystal a bien resume, pas grand chose a dire de plus en fait smile

T'as certains qui aiment bien utiliser ce qu'ils appellent des 'uber-shader', qui sont des gros shaders de porc, faisables depuis les shader models qui limitent plus trop le nombre d'instructions, et qui gerent tous les types de materiaux et de lights de leurs scenes...

mais rien ne t'empeche d'avoir plein de shaders differents que tu switche, evidemment.

aussi, t'as typiquement ce qui s'appelle un 'render state' associe a chaque draw-call. (1 draw-call = ce qui affiche une liste de triangles un appel a par exemple Draw(Indexed)Primitive dans DirectX, ou glDraw(Range)Elements / glDrawArrays dans OpenGL).

pour afficher un truc, il te faut:
- un framebuffer binde
- les renderstates qui vont bien
- les shaders bindes qui vont bien.
- les uniforms bindes aux shaders
- les texture samplers bindes aux shaders (texture + sampler state)
- les vertex buffers bindes aux shaders
- un draw call

on appelle tout ce qui n'est pas un draw call dans la liste d'au dessus, un state change.

les state changes, c'est couteux, certains plus que d'autres.
le cout peut aussi varier en fonction de la nature des donnees que tu set, en plus du type de state-change.

par exemple quand tu binde un vertex buffer, suivant son mode de residence en memoire, ca va declencher un upload depuis la system-memory jusqu'en video-memory, et ca, c'est.. heu.. super lent grin si t'as un gros vertex buffer.
mais ca c'est un sujet a part entiere, alors jvais ptet pas trop m'attarder dessus grin

BREF

or donc, tout ca, ca coute cher. notamment l'upload d'un nouveau shader. pas trop parceque le shader represente beaucoup de donnees a uploader, mais surtout parceque ca invalide plein de trucs.
idem pour les textures.

rebinder a chaque drawcall le shader, les textures, les uniforms, les vertex buffers, bah.. ca fait mal niveau perfs.

c'est entre autres pour ca qu'un seul gros shaders de porc, dans certains cas, ca a ses avantages.

ceci dit c'est particulierement goret, et les perfs du dit shader peuvent etre particulierement mauvaises si t'as envie de gerer des effets sympas.

donc typiquement, la plupart des moteurs trient leurs drawcalls par state-change.
genre ils groupent tous les drawcalls qui ont le meme shader ensembles.
puis au sein de tous les draw calls d'un meme shader, ceux qui partagent les memes textures, etc...
du coup ils changent de shaders qu'une fois tous les 'n' drawcalls.

une version plus elegante, et a la mode, est d'avoir un gros ID pour chaque drawcall, 32 ou 64 bits:

par exemple, hh utilise ce layout la (copier/coller direct du header)

//----------------------------------------------------------------------------
//
//	Key layout:
//
//	64              48                                              0
//	|---------------------------------------------------------------|
//	|        |      |           24          |           24          |
//	|---------------------------------------------------------------|
//	   Scene   Pass           Depth                Material ID
//
//	depths are the distance from the viewpoint to the object. they are always positive.
//	they are packed in cropped fp32 format in the 24 bits as follows:
//
//	hh_u32	depth24 = (FpToBits(fpVal) & 0x7FFFFF80) >> 7
//	float	depth = FpFromBits<float>(depth24 << 7);
//
//	static const hh_u64	depthKeyMask = 0x0000FFFFFF000000ULL;
//	hh_u64	depth24_64 = ((hh_u64)(FpToBits(fpVal) & 0x7FFFFF80)) << (1 + 16);
//
//	update depth:
//	key = (key & ~depthKeyMask) | depth24_64;
//
//----------------------------------------------------------------------------


bon, c'est pas non plus _forcement_ un bon exemple a reprendre tel quel grin

la du coup, ca trie tous les batch d'abord par scene, puis par passe (opaques/transparentes), puis par profondeur (d'avant en arriere pour les batch opaques, d'arriere en avant pour les batch transparents), puis par ID de materiau (qui comprend les renderstates, les textures, les uniforms..)

ca pourrait etre mieux en utilisant moins de bits pour les scenes/pass, et puis de bits pour le material ID, et en utilisant certains bits du material ID pour encoder des renderstates partagees par != materiaux.. enfin bref.

(question 3 incoming /o/)
avatar
HURRRR !

10

Ah ouais, va falloir que je revoie quelques trucs dans ma façon de m'y prendre si je veux avoir un espoir de m'en sortir grin

(et il va falloir aussi que je comprenne pourquoi je n'ai jamais pu changer de shader pendant le rendu d'une scène, si ça doit être possible...)

Merci encore smile
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

11

3)
La c'est la plus interessante des 3.
alors, oui, tu peux effectivement faire ca, 1 shader qui gere jusqu'a 5 lights, et accumuler en additif les memes batch tant que toutes les lights ont pas ete processees.
mais ca va etre tres lent... que dis-je.. atrocement lent.. IGNOBLEMENT lent...
des que t'aura plus de 10 objets et 3 lights, et une resol decente embarrassed
bref, des que t'aura une vraie scene.

Theoriquement, le "rayon d'action" des lights est infini, vu que l'intensite percue varie en 1/(d^2), avec d la distance du pixel a la light.
Une optim courante est de tricher sur la fonction d'attenuation des lights, et pas utiliser 1/(d^2), mais une fonction qui intersecte y=0 au bout d'une certaine distance, tout en donnant l'impression d'une courbe assez smooth, qui pourrait ressembler a la courbe d'attenuation normale. du coup tes lights ont une distance d'influence max.
tu peux ensuite utiliser la bounding sphere de chaque light pour determiner quel batch est affecte par quelle light.
ca t'apporte rien si t'as une seule piece avec 200 lights dedans qui eclairent chacune toute la piece, mais dans des scenes "normales", ca aide deja un peu.

que tu peux aussi calculer le rayon d'influence au dela duquel l'influence de la light n'affecte plus l'image de facon perceptible (hint: couleurs 8-bits affichees, intensite < 1/255 sert a rien, toussa).
ceci dit, si tu utilise un buffer floating-point pour faire ton rendu avant de le blitter a l'ecran, tu pourra avoir des differences en accumulant beaucoup de lights de faible intensite.
ca pete aussi le HDR. apres tu peux toujours te demmerder pour augmenter dynamiquement le rayon d'influence de toutes tes lights en fonction de l'expose de ta camera, mais bon.. t'augmente l'expose, et ca se met a ramer tritop


en pratique, ca ca va bien quand t'as que 2/3 lights par objet.
ca s'appelle le forward-rendering.

foreach(light)
{
  color += draw(object, light);
}


mais en l'etat, sans optims tres rusees ou concessions chiantes, ca pue un peu du zgeg. complexite a la louche: O(G*L), avec G le nombre de batch geometriques a rendre, et L le nombre de lights moyennes par objet



Apres, t'as le deffered-rendering.

l'idee c'est de decoupler lumieres et geometrie, et de descendre a O(G + L)
evidemment les notations de complexite algorithmiques sont pas forcement tres pertinentes, vu que t'as plein d'autres facteurs qui peuvent fausser l'estimation, genre l'overdraw, l'overhead d'avoir de multiples render targets, les plusieurs passes geometriques eventuelles, la distribution des lights, etc.. mais en pratique c'est quand meme bon.

du coup, l'idee du deffered rendering, c'est de dire que tous tes materiaux, pour etre rendus, ont besoin des memes infos, typiquement, pour chaque pixel:

- albedo (la couleur diffuse de l'objet)
- normale
- coeffs speculaires divers, suivant le modele d'eclairage choisi.
- eventuellement depth accessible depuis le shader
- potentiellement d'autres trucs, genre: couleur d'emission, ou trucs du genre

apres, tu te demmerde pour packer tout ca dans un framebuffer avec le nombre le plus faible de rendertargets possibles.
le dit framebuffer s'appelle un G-buffer. (comme Geometry-buffer)

par exemple, tu peux avoir:

rendertarget1 (RGBA) : { diffuse.r, diffuse.g, diffuse.b, specular.power }
rendertarget2 (RGBA) : { normal.x, normal.y, normal.z, specular.intensity }

donc, dans une premiere passe, t'as un shader tout simple qui ecrit dans ces deux render targets (oui, tu peux outputter plusieurs colors dans des render targets differentes dans le meme pixel shader)
il sample la diffuse, eventuellement la normalmap si yen a une, eventuellement une specmap, bref.

a la fin de cette passe, t'as deux rendertargets qui contiennent toutes les normales, diffuse colors, spec coeffs, de ta scene, que tu peux rebinder a un autre shader en tant que textures.

ensuite, dans une deuxieme passe, tu prends toutes les lights de ta scene, et tu les affiche avec un shader qui prend les deux rendertargets d'au dessus en tant que textures, la depth map, et calcule l'eclairage.
si ta light couvre toute la scene, tu la rend avec un fullscreen quad, sinon, t'affiche une primitive basique qui recouvre les zones d'influence de la light a l'ecran (une sphere, ou un carre/rectangle en 2D a l'ecran).

et au final t'as tous les objets de ta scene rendus eclaires.
du coup le shader de lighting est vachement plus simple (une seule light a gerer), le shader des objets aussi, et c'est cool grin

Evidemment, t'es oblige d'utiliser quand meme du forward rendering pour tes objets transparents eclaires :/
avec les derniers shader models, tu peux faker en utilisant un G-buffer avec multisampling. tu le multisample 4x, et t'as 4 layers de geometrie eclairee par pixel possibles (donc 3 layers transparents), ou utiliser un G-buffer supplementaire pour la geometrie transparente, mais dans ce cas t'as une seule couche eclairable.


apres, le deffered rendering, c'est bien, mais c'est un peu relou quand meme, parceque c'est vachement rigide niveau materiaux.

tu as donc une AUTRE option \o/



le light-prepass rendering, qui est une forme de deffered rendering, mais legerement differente.

ca t'oblige a faire deux passes de geometrie, mais:

- c'est plus flexible niveau materiau
- c'est plus leger niveau memoire / framebuffers
- ca peut gerer une putain de chiee de lights, c'est ce qu'on utilise dans hh, et on affiche des lights qui sont en fait des particules, on peut en avoir 50 000 par frame sans probleme... et sur des machines pas si terribles. (du moment que l'overdraw est raisonnable tongue)
- ca marche en DX9 (mais bon c'est pas forcement un argument grin)

le G-buffer ressemble a ca:

- normales
- parametres de spec

une fois le G-buffer rendu, comme au dessus, tu fais une passe sur toutes les lights.
mais c'est pas le materiau final qu'elles vont rendre.
c'est l'eclairage recu par chaque pixel de la scene : le light-buffer
(avec la composante speculaire optionellement dans l'alpha. apres, si tu veux un rendu exact, il te faut du RGB pour le spec aussi, donc il te faut un light-buffer et un spec-buffer separe.)

une fois que t'as le light buffer, tu le file au vrai shader de rendu de tes objets.
pour chaque pixel, il a l'intensite recue, et son lobe de spec, donc il a plus besoin des normales, des specmaps, et autres conneries, sauf pour certains types de materiaux, mais peut quand meme faire des effets tordus si il a envie, rajouter une couleur d'emission si ya besoin, combiner la diffuse comme il veut avec ce qu'il veut, attenuer la spec comme il le sent, absorber et diffuser la lumiere recue comme il veut, etc, etc..

meme combat que le deffered-rendering classique pour les objets transparents.

tiens jvais te faire des screens du G-Buffer de hh smile
avatar
HURRRR !

12

Tiens en fait regarde cet article sur le Deferred Shading c'est mieux que ce que j'ai expliqué (peut-être un poil plus compliqué à visualiser, mais au final le principe est le même)
En fait il me semblait que le deferred shading c'était ce que j'expliquais, mais visiblement non. (Du coup je me demande comment je connaissais le nom confus) En tout cas c'est clairement mieux (toujours plusieurs passes, mais beaucoup moins de calculs).

Sinon pour le nombre de lumière gérables en une passe, c'est surtout fonction de la taille du programme que tu acceptes d'utiliser (les cartes modernes n'ont plus vraiment de limitation de taille, mais c'est pas pour autant qu'il faut en abuser), et surtout du nombre de constantes que tu peux utiliser pour paramétrer le shader. (ça dépend de la version des shaders utilisé et du matériel ça aussi)
Zeph (./3) :
tu as appris ça comment, sur le tas, ou bien tu as des sources à me conseiller ?
Sur le tas, mais au départ j'ai beaucoup appris avec quelques exemplaires de l'excellent (et défunt sad) PC team… (Surtout le hors série n°16, mais bonne chance pour le trouver… J'en ai un et je le garde bien au chaud grin)
La documentation de DirectX (c'est certes moins applicable pour OpenGL) est également d'une grande utilité, et en dehors de ça, je pense qu'il faut surtout mentionner gamedev.net qui regorge(ait ?) de tutos parfois très intéressant. (Du genre, comment faire un gaussian blur avec des shaders, comment fonctionne le geomipmapping, etc.)
Plus orienté vers XNA il y avait Ziggyware (site web), avec ses forums et ses tutos, certains plus utiles que d'autres, mais le site est mort aujourd'hui, et je pense qu'il y a peu de chance qu'il ressurgisse un jour.
Sinon, voilà un blog très intéressant (toujours tourné vers DirectX/XNA, mais les techniques restent en général les mêmes que ce soit DirectX ou OpenGL) de l'auteur de Allegro (sisi grin), qui a énormément travaillé dans le domaine de la 3D, et jusqu'à dernièrement, sur XNA. (Grâce à lui j'ai découvert les merveilles du premultiplied alpha… cheeky)

[edit] j'ai méga-cross, du coup je vais aller lire les posts de bearbecue… cheeky
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

13

ha ouai pas mal l'article wikipedia sur le deffered shading oui

(sauf que, voila, ce qu'ils disent sur le deffered-lighting, ya pas besoin pour la passe deffered d'outputter dans _deux_ rendertargets si on pack la spec dans l'alpha du light-buffer, cf ce que je disais au dessus oui)

y'a meme la liste des jeux qui utilisent telle ou telle technique dis donc... classe grin
avatar
HURRRR !

14

bon en attendant que je finisse mon rebuild et que je fasse quelques screenshots.. zzz

en fait tu peux aller voir la bas: http://www.hhvfx.com/wiki/index.php/Debug_render_buffers

c'est des details, avec screens, sur le light-prepass rendering de hh oui
mais dans l'exemple pris en screenshot ya qu'une seule light, donc on visualise pas des masses le light splatting... jvais en faire des plus potables, de screens grin

ah, c'est aussi vachement cool pour les ombres, le light-prepass rendering oui
ca simplifie pas mal ^^

Edit: dans les buffers d'au dessus, la depth est rendue explicitement dans un buffer RGBA8 a part, parceque tu peux pas sampler la depth-map directement en DX9 (et tout le rendu de hh est fait en DX9 :/ (enfin, c'est avec un plugin DX9 qu'on distribue l'editeur en tout cas, ya aussi un plugin GL qui a pas ces limitations la, mais.. bref..))

a noter aussi que le rendu final output des couleurs dans une 2eme rendertarget, pour les postprocess (deformations screenspace et blur/depth of field)
avatar
HURRRR !

15

ho classe le drag/drop de fichiers pour l'upload sur mirari grin


bon, donc... (t'as dja du voir un screen de cette scene la bob grin)



PASSE 1

normales encodees en coordonnees ecran spheriques (2 composantes R, G) + parametres du modele de lighting (2 composantes B, A):

tromb Fichier joint : cqUA (Sanctum_NormalsBRDF.png)



decomposition:
normales reconverties en XYZ classique, balance dans les canaux RGB:

tromb Fichier joint : HbZ6 (Sanctum_Normals.png)

parametre 'roughness' du materiau... c'est grosso-modo l'inverse de la 'specular power' classique. La dans cette scene ya une valeur uniforme par objet, mais ca peut tout a fait etre une texture (dans le lien wiki que j'ai balance au dessus, avec l'anneau, ca varie en fonction d'une texture)

tromb Fichier joint : DX86 (Sanctum_Roughness.png)




rendu de la depth dans un buffer BGRA8 pour pouvoir la lire dans le shader en DX9, sinon on peut reutiliser la depthmap non-lineaire classique.

tromb Fichier joint : 0a9I (Sanctum_LinearDepthBGRA8.png)

depth lineaire remappee:

tromb Fichier joint : CdR9 (Sanctum_LinearDepth.png)





PASSE 2

Splatting des lights. la on voit bien les quads en screenspace qui englobent la zone d'influence des lights:

tromb Fichier joint : qZ6H (Sanctum_LightSplatting.png)




Light-buffer genere:

tromb Fichier joint : YkpW (Sanctum_Irradiance.png)

Spec dans le canal alpha du light-buffer:

tromb Fichier joint : 7odE (Sanctum_Specular.png)






PASSE 3

Rendu final, avec utilisation du light-buffer pour eclairer la geometrie. C'est la que la couleur diffuse, l'emission, l'intensite de spec, etc.. sont prises en compte.

tromb Fichier joint : SMRp (Sanctum_FullRender.png)


output dans une 2eme rendertarget du meme framebuffer des vecteurs de distortion pour un postprocess de deformation en screenspace, pour simuler la refraction de l'air due a la chaleur (ca peut servir aussi pour la refraction de surfaces diverses, y compris de la flotte)

tromb Fichier joint : sDBF (Sanctum_DistortionAndBlur.png)



valaaaa \o/
avatar
HURRRR !

16

Bon, pendant que j'y suis, au cas ou tu t'en foutrais pas, des details sur le dernier buffer (Distortion and blur):

La distortion screenspace plus flagrante, vue de pres:
(les vecteurs de distortion en pixels sont stockes dans les canaux rouge et verts (pour X et Y, et le postprocess va additionner ces vecteurs aux coordonnees de textures avec lesquelles il va aller sampler la texture du rendu final)
oN8S 6yT0 3AOo

le blur visualise avec du depth-of-field:
(le canal bleu donne le rayon du blur (j'ai augmente l'intensite dans toshop, sinon on voyait pas grand chose))
kVMU E0rd mF5U

(clic sur image pour mieux voir, la les thumbnails sont un peu faibles, on voit pas des masses de trucs... (ouai, le DOF est moche, je sais))



bon voila, j'espere que ca repond a tes questions grin
avatar
HURRRR !

17

./11 > Hmm, juste pour être sûr (car du coup je doute… grin), il faut toujours une passe par lumière différente avec le deffered rendering, n'est-ce pas ? (Ou, demandé autrement, est-ce que la "Passe 2" se fait vraiment en une seule fois ?)

En tout cas, jolis screens en ./15 smile
(J'ai bien envie de faire un test d'implémentation du coup… Il faut que je résiste à la tentation grin)

Sinon (je squatte un peu le topic de Zeph du coup… tongue), comment tu gères les ombres avec tout ça ? J'avais utilisé une fois une shadow texture (il me semble que c'est comme ça que ça s’appelle), et ça déchirait grave, mais avec plein de lumières à gérer, ça ne doit pas être très efficace… :/
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

18

par passe, j'entendais pas drawcall hein, evidemment... mais serie de drawcalls dans un framebuffer.
a chaque passe ==> switch de framebuffer.

donc la, mettons t'as 100 objets visibles, et 30 lights visibles:

dans le cas du forward:
passe 1:
3000 drawcalls, 30 rendus par objet, 1 pour chaque light, 100 objets. rendu dans la rendertarget finale.
(ok tu peux diminuer un peu si tu gere plusieurs lights par shader, mais c'est vachement plus chiant a gerer, et en plus ca te fait des shaders super lourds...)

dans le cas du deffered shading:
passe 1:
100 drawcalls, 1 pour chaque objet, rendu dans le G-Buffer
passe 2:
30 drawcalls, 1 pour chaque light, rendu dans la rendertarget finale <- lighting + shading

dans le cas du deffered lighting:
passe 1:
100 drawcalls, 1 pour chaque objet, rendu dans le G-Buffer
passe 2:
30 drawcalls, 1 pour chaque light, rendu dans le light-buffer (ce que j'appelle l'irradiance cache dans les screens d'au dessus) <- lighting
passe 3:
100 drawcalls, 1 pour chaque objet, rendu dans la rendertarget finale. <- shading
avatar
HURRRR !

19

Oki, merci smile
(C'est vrai que vu de cette manière ça fait bien une seule passe, c'est juste moi qui avait pas regardé ça sous le bon angle tongue)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

20

ah merde heu oui les ombres...

bah, admettons que t'aie deja toutes tes shadowmaps (grin) (non mais en fait pour les generer de toutes facons ca change pas entre du forward et du deffered, donc bon)
tu bind chaque shadowmap au shader de rendu de chaque light.
ca implique que chaque light doit savoir rendre ses propres ombres, mais ca parait logique. c'est particulierement simple a gerer.
apres, les shaders de rendu finaux de tes materiaux n'ont absolument pas a se preoccuper de savoir si telle light avait ou pas des ombres, quel type d'ombres c'etait, etc...
ils recuperent tout aplati dans le light-buffer.

de leur point de vue, ils connaissent meme pas la notion d'ombres. pour eux, ils recuperent dans le light-buffer l'irradiance pour chaque pixel, c'est a dire la lumiere qui arrive sur chaque pixel, ombres comprises, lumiere ambiante comprise, SSAO compris, sperical harmonics compris, lightmaps comprises, illumination globale temps-reel comprise (cheeky)

bref.. c'est beau, c'est chaud... mangez-en grin

tandisqu'en forward, non seulement tu dois faire plein de versions de tes shaders de rendu, pour des nombres differents de lights si tu veux des shaders qui gerent plus d'une light (y se passe quoi si t'as qu'une light? 2? 5? embarrassed), MAIS en plus faut que tu prevoie toutes les combinaisons de types de lights differentes (spotlight?, pointlight? lumiere directionnelle genre soleil? lights exotiques genre des "lignes" (neons), area-lights, whatever..), ET les combinaisons de leurs differents types d'ombres possibles...

bref, c'est un bordel innomable...
avatar
HURRRR !

21

GoldenCrystal (./17) :
./11 > Hmm, juste pour être sûr (car du coup je doute… grin), il faut toujours une passe par lumière différente avec le deffered rendering, n'est-ce pas ? (Ou, demandé autrement, est-ce que la "Passe 2" se fait vraiment en une seule fois ?)


tiens, en fait, j'ai oublie un detail a propos de ca...

ce qui est bien aussi, c'est que tu peux batcher les lights similaires ensembles.
(en gros, avec un seul drawcall, rendre toutes tes lights d'un coup).
donc malgre le probleme de terminologie avec "passe" (ou pour toi passe == drawcall), tu peux effectivement avoir un seul draw call pour 'n' lights trilovetrioui

typiquement dans hh, avec les particules-lights, on a qu'un ou deux gros vertex-buffers, qui contiennent chacun plein (genre ~ 5000 / 10000) de lights.

pour 5000 lights, ca te fait un vertex buffers qui contient 5000 quads, donc 10 000 triangles.

et tu le rend en un seul draw call.

du coup au lieu de:

pass1: 100 drawcalls (objects/GBuffer)
pass2: 5000 drawcalls (lights/LightBuffer)
pass3: 100 drawcalls (objects/FinalRender)

ca te fait:

pass1: 100 drawcalls (objects/GBuffer)
pass2: 1 drawcall (lights/LightBuffer)
pass3: 100 drawcalls (objects/FinalRender)

... #Awesome#
avatar
HURRRR !

22

bearbecue (./20) :
ah merde heu oui les ombres...

bah, admettons que t'aie deja toutes tes shadowmaps (grin) (non mais en fait pour les generer de toutes facons ca change pas entre du forward et du deffered, donc bon)
Ok donc quoi qu'il arrive à ce niveau là c'est toujours aussi peu efficace tongue
tandisqu'en forward, non seulement tu dois faire plein de versions de tes shaders de rendu, pour des nombres differents de lights si tu veux des shaders qui gerent plus d'une light (y se passe quoi si t'as qu'une light? 2? 5? embarrassed), MAIS en plus faut que tu prevoie toutes les combinaisons de types de lights differentes (spotlight?, pointlight? lumiere directionnelle genre soleil? lights exotiques genre des "lignes" (neons), area-lights, whatever..), ET les combinaisons de leurs differents types d'ombres possibles...
Hmm, ouais je visualise assez bien le merdier grin

Merci ^^
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

23

ouai, toujours aussi peu efficace, pour la generation des shadowmaps seulement, mais leur utilisation est quand meme vachement simplifiee smile
tfacons les shadowmaps sur beaucoup de lights ca a toujours un peu ete la merde grin
avatar
HURRRR !

24

bearbecue (./21) :
donc malgre le probleme de terminologie avec "passe" (ou pour toi passe == drawcall)
En fait, j'ai surtout buggué en assimilant une passe à un rendu de l'écran entier… Alors qu'en fait tu n'es pas obligé de terminer la scène à chaque rendu triso
tu peux effectivement avoir un seul draw call pour 'n' lights trilovetrioui
Sauf si tu veux gérer les ombres, exact ?
typiquement dans hh, avec les particules-lights, on a qu'un ou deux gros vertex-buffers, qui contiennent chacun plein (genre ~ 5000 / 10000) de lights.
Ah oui je crois que je vois… En plus des coordonnées du quad, tu stockes les informations de chaque source de lumière, c'est ça ?
Énorme love

Merci^2
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

25

Sauf si tu veux gérer les ombres, exact ?

oui
(sauf si tu les packe dans une texture de taille plus grande, et/ou que tu les rends dans une volume-texture, ou dans des texture-arrays en DX10 #clindoeilclindoeil#trigni)
Ah oui je crois que je vois… En plus des coordonnées du quad, tu stockes les informations de chaque source de lumière, c'est ça ?
Énorme love

oui !
et tu peux aussi utiliser les geometry-shaders pour creer le quad directement sur la carte.
donc en fait au final dans ton vertex-buffer, t'as qu'une liste de points, meme plus de quads (1 vertex par light quoi), avec genre:

- 1 vertex stream float4 qui contient positions XYZ + rayon de la light dans le W
- 1 vertex stream float4 qui contient : intensite RGB + 1 parametre d'attenuation quelconque dans le A
-> deux float4 par lights top
avatar
HURRRR !

26

wow grin

bon, il va me falloir quelques jours pour lire, digérer, tenter d'extraire des infos, comprendre, revenir, poser des questions et boucler à l'étape 1 sur tous ces posts grin Mais merci pour ton temps smile

Je reprends dans l'ordre des posts les trucs qui m'échappent mais pour lesquels je peux encore me raccrocher à quelque chose (parcequ'il y a aussi des trucs hors d'atteinte pour le moment, chaque chose en son temps grin). Ça peut être juste des problèmes de vocabulaires, vu que j'en suis encore à essayer de mettre des noms sur des trucs que j'ai pu lire à droite à gauche ^^
apres, tu te demmerde pour packer tout ca dans un framebuffer avec le nombre le plus faible de rendertargets possibles.

Une "rendertarget", c'est quoi, une image de la taille du rendu final dans laquelle un pixel shader peut enregistrer une info (RGBA) par pixel ? Si oui, je ne savais même pas qu'on pouvait faire autre chose que la rendre, justement, et donc a fortiori qu'on pouvait en avoir plus d'une grin

Faut que je regarde en OpenGL/GLSL comment on fait smile

[edit] bon, dans le lien de Golden ça parle de multi-render target, donc je suppose que c'est bien ça, je saurai quoi chercher quand j'essaierai d'implémenter ça ^^
a la fin de cette passe, t'as deux rendertargets qui contiennent toutes les normales, diffuse colors, spec coeffs, de ta scene, que tu peux rebinder a un autre shader en tant que textures.

Bon d'après ça je vais supposer que ma compréhension du point précédent était la bonne. Du coup c'est surpuissant ce truc, en effet ^^
ensuite, dans une deuxieme passe, tu prends toutes les lights de ta scene, et tu les affiche avec un shader qui prend les deux rendertargets d'au dessus en tant que textures, la depth map, et calcule l'eclairage.si ta light couvre toute la scene, tu la rend avec un fullscreen quad, sinon, t'affiche une primitive basique qui recouvre les zones d'influence de la light a l'ecran (une sphere, ou un carre/rectangle en 2D a l'ecran).

Hey mais c'est énorme ça, donc pour toutes les lumières vraiment éloignées on a potentiellement une toute petite zone à retraiter, j'imagine que le gain doit rapidement pouvoir être énorme... par contre si on veut faire cette optimisation, j'imagine que c'est mort d'envisager gérer plusieurs lumières simultanément dans le shader des lumières (à moins d'avoir deux lumières qui affectent exactement la même zone, mais c'est improbable, sauf pour celles qui prennent tout l'écran, bref). Si je résume, la première passe crée tes deux rendertargets, puis on applique les lumières une à une en partant de ... ? le rendu initial sans lumières, qu'on a aussi calculé lors de la première passe ?

Pour la transparence, je saute pour le moment, j'y reviendrai quand le reste sera moins flou ^^
À propos du light-prepass rendering :
une fois le G-buffer rendu, comme au dessus, tu fais une passe sur toutes les lights.
mais c'est pas le materiau final qu'elles vont rendre.
c'est l'eclairage recu par chaque pixel de la scene : le light-buffer(avec la composante speculaire optionellement dans l'alpha. apres, si tu veux un rendu exact, il te faut du RGB pour le spec aussi, donc il te faut un light-buffer et un spec-buffer separe.)

Ayé je commence déjà à être largué. La composante spéculaire, c'est bien le produit scalaire entre le vecteur "caméra" et le vecteur de direction de la lumière, élevé à une puissance qui dépend du matériau ? Du coup c'est quoi les autres paramètres dont on a besoin et pour lesquels il faut utiliser un buffer RGB supplémentaire ?
Edit: dans les buffers d'au dessus, la depth est rendue explicitement dans un buffer RGBA8 a part, parceque tu peux pas sampler la depth-map directement en DX9 (et tout le rendu de hh est fait en DX9 :/

Heu j'ai du louper un truc, mais vous vous en servez pour quoi de la depth-map ? Je n'arrive pas à voir à quel moment il vous faut la réutiliser ? D'ailleurs je ne comprends pas du tout le screenshot Sanctum_LinearDepthBGRA8.png grin (la valeur de profondeur est codée dans les composante GB ? lapin compris o_O)
Screenshots du post ./15:
normales encodees en coordonnees ecran spheriques (2 composantes R, G) + parametres du modele de lighting (2 composantes B, A)

Hep hep hep, c'est quoi cette méthode pour faire tenir les normales en deux composantes ? grin (et les deux autres c'est quoi ?)
output dans une 2eme rendertarget du meme framebuffer des vecteurs de distortion pour un postprocess de deformation en screenspace, pour simuler la refraction de l'air due a la chaleur (ca peut servir aussi pour la refraction de surfaces diverses, y compris de la flotte)

Rah putain, ça non plus je ne comprends pas à partir de quoi ils sont générés, mais c'est trop classe grin
Bon, pendant que j'y suis, au cas ou tu t'en foutrais pas, des details sur le dernier buffer (Distortion and blur)

Non justement, et j'aimerais bien savoir d'où ils sortent les paramètres de déformation ^^
ah merde heu oui les ombres...

Bon là je zappe, ça fait trop pour le moment, je vais juste bookmarquer tout ça pour le jour où j'aurai compris jusqu'ici grin

Merci encore, beaucoup, vraiment, vous deux smile
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

27

pour les rendertargets > ouaip, c'est exactement ca...

en gros, jette un oeil du cote de l'extension GL_FRAMEBUFFER_EXT. (ca a ete integre en OpenGL natif dans les dernieres versions de GL il me semble)

un framebuffer, c'est une collection de surfaces (de meme taille), dans lesquelles tu peux rendre : les rendertargets.

de base y a qu'une seule rendertarget par framebuffer.
typiquement, le backbuffer OpenGL dans lequel tu fais tes rendus, ya qu'une seule rendertarget (enfin, deux si tu compte la depth-map wink)

le vieux hardware gere qu'une seule rendertarget par framebuffer, mais depuis quelques annees, il y a des cartes qui gere les MRTs (multiple rendertargets). y en a un nombre max, genre 8, ca depend de la carte, tu peux le recuperer via une fonction prevue pour en GL... et une caps en Direct3D.

et depuis un certain shader-model (jme souviens plus lequel grin) tu peux effectivement ecrire de facon selective dans chacune des rendertargets.

exemple:

tu cree un framebuffer avec 3 rendertargets, dans ton pixel shader, tu peux ecrire un truc du genre:

void	main()
{
	gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0);	// ecrit du rouge dans la MRT0
	gl_FragData[1] = vec4(0.0, 1.0, 0.0, 1.0);	// ecrit du vert dans la MRT1
	gl_FragData[2] = vec4(0.0, 0.0, 1.0, 1.0);	// ecrit du bleu dans la MRT2
}


\o/
Hey mais c'est énorme ça, donc pour toutes les lumières vraiment éloignées on a potentiellement une toute petite zone à retraiter, j'imagine que le gain doit rapidement pouvoir être énorme...

exactement oui !
par contre si on veut faire cette optimisation, j'imagine que c'est mort d'envisager gérer plusieurs lumières simultanément dans le shader des lumières (à moins d'avoir deux lumières qui affectent exactement la même zone, mais c'est improbable, sauf pour celles qui prennent tout l'écran, bref). Si je résume, la première passe crée tes deux rendertargets, puis on applique les lumières une à une en partant de ... ? le rendu initial sans lumières, qu'on a aussi calculé lors de la première passe ?


bah "gerer plusieurs lumieres dans le shader" n'a plus vraiment de sens la... c'est justement un avantage du truc. tu rends une primitive geometrique _par light_, donc t'as pas de question de gerer plusieurs lights dans le shader de la light...

t'as une spotlight, tu rends la geometrie d'un cone, qui represente le cone de la spotlight, avec le shader de lighting. t'as une pointlight? tu rends un mesh de cube ou de sphere, ou un quad en screenspace, avec le shader de lighting pour pointlight.

apres, comme je disais dans un des posts au dessus, t'es pas non plus oblige de faire un drawcall par light, tu peux avoir, dans un seul vertex buffer, la geometrie pour 10 lights differentes. tes 10 lights sont aux 4 coins de ton level, bah dans ton vertex buffer, t'aura un mesh qui, lorsqu'il est rendu, donne 10 grosses spheres la ou sont les lights, avec le rayon de chaque sphere = le rayon d'influence de la light, au dela de laquelle elle n'eclaire plus oui.

et non, pour remplir le lightbuffer, tu pars d'une couleur noire...
c'est une nouvelle rendertarget le light-buffer...
au debut de la passe 2, ou tu rends les lights, tu le clear a 0 (ou a ta couleur de lumiere ambiante, si t'as de la lumiere ambiante)

en gros:
glClearColor(ambientColor.r, ambientColor.g, ambientColor.b, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);

si ta lumiere ambiante est geree par materiau, et que chaque objet a la sienne, c'est dans la passe 3, lorsque tu lis l'irradiance du lightbuffer, que tu fais:

irradiance = texture2D(lightBuffer, pixelCoords) + ambientColor;
Ayé je commence déjà à être largué. La composante spéculaire, c'est bien le produit scalaire entre l'angle de vue et l'angle d'incidence de la lumière, élevé à une puissance qui dépend du matériau ? Du coup c'est quoi les autres paramètres dont on a besoin et pour lesquels il faut utiliser un buffer RGB supplémentaire ?

ouai, c'est ca. sauf que les lights peuvent avoir une couleur.

la composante speculaire, a la base, c'est rien de plus que la reflection de la light sur ta surface. tu vois surtout le lobe speculaire parceque le reste de l'environnement est pas assez intense pour que tu voie vraiment la reflection, mais si tu fais l'experience et que tu mets un objet a cote d'un mur blanc, sauf si c'est un objet tres diffus (qui de toutes facons aura quasiment pas de spec), tu vas voir un lobe speculaire qui correspond a la reflection du mur. apres tout, le mur est une lumiere aussi, il renvoie de la lumiere sur l'objet smile

bref. donc, le lobe speculaire doit avoir la couleur de la light.
sauf que la couleur du lobe speculaire peut etre different de l'irradiance locale, vu que c'est justement une reflection, ca vient d'ailleurs que l'irradiance locale.

exemple:
t'as une sphere blanche eclaire par une light rouge, elle apparait rouge.
si t'as une light verte loin derriere la sphere, qui apparait a incidence rasante, tu vas voir son lobe speculaire.
ce sera un lobe speculaire vert, sur la sphere rouge (suivant les intensites respectives des deux lights, ca va te faire du coup un lobe speculaire entre le vert, jaune, orange...)

tiens en fait on le voit la: MW0F
(tire d'une excellente pres crytek... faudrait que je la retrouve, c'est tres bien explique smile)

donc pour un rendu correct, t'es oblige de stocker non pas juste l'intensite speculaire, mais la couleur speculaire.

typiquement dans un pipeline forward classique, pour chaque light tu fais:

diffuse_lighting = max(0, dot(surface_normal, surface_to_light)) * distance_attenuation;
specular_lighting = pow(max(0, dot(surface_normal, half_angle_vector)), specular_power);

lighting = (diffuse_lighting + specular_lighting) * light_color + ambient_lighting;


bon apres, t'as quelques rares materiaux qui peuvent modifier la specular color (genre l'or), mais on s'en fout, si t'as envie de faire ca, c'est pas dans le light-buffer, c'est apres, lors de la derniere passe, lorsque le vrai materiau est rendu.


donc voila, pour bien faire, t'es oblige de stocker dans le light buffer le RGB de la lumiere diffuse, et le RGB de la lumiere speculaire.
sauf qu'en pratique, tu fais rarement la difference, sauf si tu t'amuse a faire des scenes avec des cas extremes expres pour demontrer le probleme.

donc, tu peux faker ca en stockant juste l'intensite speculaire, et en reutilisant la couleur de la lumiere diffuse en tant que couleur speculaire (ce qui est totalement faux, evidemment).

mais du coup ca te permet de packer les deux dans une seule rendertarget en RGBA : RGB=diffuse_lighting, A=specular_intensity
mais si t'es large niveau memoire, rien ne t'empeche d'avoir deux rendertargets dans ton light buffer, et de faire les choses bien smile

par exemple dans le cryengine 3, ils supportent les deux methodes, j'imagine qu'ils switchent de l'une a l'autre suivant les settings de qualite que tu choisis grin

Heu j'ai du louper un truc, mais vous vous en servez pour quoi de la depth-map ? Je n'arrive pas à voir à quel moment il vous faut la réutiliser ? D'ailleurs je ne comprends pas du tout le screenshot Sanctum_LinearDepthBGRA8.png grin (la valeur de profondeur est codée dans les composante GB ? lapin compris o_O)


alors, le shader des lights, en gros il doit faire ca:

vec3	pixel_normal = ???;
vec3	pixel_position_in_viewspace = ???;
float	specular_power = ???;

vec3	pixel_to_light = light_position_in_viewspace - pixel_position_in_viewspace;
vec3	light_direction = normalize(pixel_to_light);
vec3	diffuse_lighting = max(0, dot(light_direction, pixel_normal)) * light_color;

vec3	view_vector = normalize(pixel_position_in_viewspace);
vec3	half_angle_vector = normalize(pixel_to_light + view_vector);
float	specular_lighting = max(0, dot(half_angle_vector, pixel_normal)), specular_power);

gl_FragColor[0] = vec4(diffuse_lighting.x, diffuse_lighting.y, diffuse_lighting.z, specular_lighting);


il connait 'light_position_in_viewspace', forcement, c'est le shader de la light, si tu fais un drawcall par light, la position de la light peut juste etre un parametre uniforme du shader.


(je poste dja ca avant que chrome crashe, ca me ferait chier de tout retaper triso)
avatar
HURRRR !

28

ouai donc, en fait, pourquoi faut la depth... bah...

il faut trouver comment calculer les trois valeurs au debut du snippet d'au dessus:

- pixel_normal
- pixel_position_in_viewspace
- specular_power

pour pixel_normal et specular_power, c'est pas complique, on les a dans les textures du GBuffer rendu a la passe precedente.

par contre, pixel_position_in_viewspace... bah.. soit tu rajoute une rendertarget dans le GBuffer pour stocker la position du pixel ... en viewspace \o/ donc float4, au mieux half4
soit tu utilise la depthmap oui

t'as les coordonnees x, y du pixel a l'ecran (gl_FragCoord.xy), et la profondeur du pixel -> tu peux reconstruire sa position.

Hep hep hep, c'est quoi cette méthode pour faire tenir les normales en deux composantes ? grin (et les deux autres c'est quoi ?)


tu prends la normale en screenspace.

vu que les normales sont normalisees, tu as:

1 = sqrt(x^2 + y^2 + z^2) = x^2 + y^2 + z^2;

z = sqrt(x^2 + y^2);

donc deja, si tu stocke juste x et y, tu peux reconstruire le z de la normale oui
(enfin, si tu stocke x*0.5 + 0.5 et y*0.5 + 0.5 en R, G)
aussi, vu que c'est une projection d'hemisphere, la normale peut etre soit dans l'hemisphere qui te fait face, soit dans l'autre, typiquement elle sera dans l'hemisphere qui te fait face, MAIS..
- avec les normalmaps, tu peux avoir des normales qui pointent dans l'autre sens (DANS l'ecran, pas en dehors), et concretement ca te fout plein d'artefacts de merde dans ton rendu)
- la precision des normales sur les bords de l'hemisphere est nulle a chier...

tu peux grandement ameliorer ces deux points en faisant:

RG = sqrt(z * 0.5 + 0.5) * normalize(xy);

ca remappe la normale dans une cercle qui est le depliage de la sphere des normales, dont la seule singularite se trouve au pole le plus eloigne de l'ecran, celui qui pointe dans l'ecran, et que tu vois en gros jamais.. hmm bref.

ca fait chier faudrait vraiment remettre la main sur ce talk de crytek, ils expliquent ca avec des images et tout, c'est quand meme plus clair...

bon bref...


les deux autres, c'est:

- ce qu'on appelle pour hh la shinyness (une valeur entre 0 et 1, plus ou moins 1/spec_power. 0 represente une surface parfaitement reflective, 1 une surface parfaitement diffuse... bon ok en fait ca devrait plutot s'appeller diffuseness, ou diffusitiveness, ou.. bref.. voila quoi grin)

- le "normal reflectance factor", c'est un parametre du modele d'approximation de BRDF qu'on utilise (les approximations de BRDF, c'est genre phong, blinn/phong, cook-torrance, ward, beckmann, etc... c'est differentes approximations du comportement d'une surface soumise a de la lumiere. un BRDF, c'est une fonction a 4 dimensions, la "Bidirectional Reflectance Distribution Function" (go google si ca t'interesse, c'est un topic a part entiere ca grin))

bref, donc voila, pour notre modele d'approximation de BRDF, on a besoin de ca en plus.
le normal reflectance factor, ca va dire en gros quel pourcentage de lumiere est reflechie par la surface lorsque le reflection_vector.light_vector == 1. ca permet de simuler les surfaces comme la peau (et beaucoup d'autres), qui sont speculaires pour les angles de vues rasants, mais tres diffus pour les angles de vue perpendiculaires)
Rah putain, ça non plus je ne comprends pas à partir de quoi ils sont générés, mais c'est trop classe Non justement, et j'aimerais bien savoir d'où ils sortent les paramètres de déformation ^^


grin
c'est plus ou moins un fake. dans les exemples d'au dessus, le shader refracte le view-vector sur cette normalmap:

4Mpw

va chercher la depth dans le depth buffer a l'endroit du pixel rendu (c'est une approximation tres moche), et calcule la distance d'intersection entre le plan a cette profondeur et le rayon refracte. (au final niveau calcul c'est tres simple, c'est tout des plans alignes a l'ecran), et ecrit les composantes x,y du vecteur refracte * cette distance / distance du pixel a l'oeil, dans le buffer de deformation oui

pour les surfaces de flotte, ca serait juste la normale de la surface au lieu de la texture au dessus...
avatar
HURRRR !

29

bearbecue (./27) :
lighting = (diffuse_lighting + specular_lighting) * light_color + ambient_lighting;

Ah ok, en effet, depuis le début je conserve la specular color sans m'être posé la question de son importance, donc "diffuse_lighting * diffuse_light_color + specular_lighting * specular_light_color + ambient_lighting". Mais effectivement garder seulement une intensité c'est une bonne idée, j'ai même pas réfléchi sur le coup... ^^
tiens en fait on le voit la: MW0F
(tire d'une excellente pres crytek... faudrait que je la retrouve, c'est tres bien explique smile)

Bon ok, autant là ça se voit très bien, autant je risque ne pas avoir besoin de cette précision avant un moment (peut-être le jour où le reste marchera... grin).
bearbecue (./28) :
vu que les normales sont normalisees, tu as:

1 = sqrt(x^2 + y^2 + z^2) = x^2 + y^2 + z^2;

z = sqrt(x^2 + y^2);

donc deja, si tu stocke juste x et y, tu peux reconstruire le z de la normale oui

Ah oui tiens, en effet, là aussi j'aurais pu réfléchir un peu avant de poser la question grin Par contre la solution pour limiter les problèmes de cette technique j'aurais pu chercher longtemps sans explication ^^

Bon, je vais surement lutter un moment avant d'avoir un truc qui marche (et je viendrai upper mon topic à ce moment-là ^^), mais merci encore pour toutes ces précisions, j'ai de quoi m'occuper un moment avant de comprendre et d'appliquer tout ça smile
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

30

ok top
you're welcome smile
avatar
HURRRR !