LOD, suite et fin v2 (les poils)
Par Narann le samedi, 17 août 2019, 17:52 - Infographie 3D - Boulot - Lien permanent
J’espère qu’avec ce billet je vais enfin terminer cette longue et indigeste série sur les LOD. :vomit:
Attaquons enfin la densité des poils au plan. Ce dernier billet va être un peu technique. On va faire un peu de trigonométrie (espace caméra, toussa) et parler de farm.
Si vous avez suivi un des liens de mon premier billet, vous avez peut-être remarqué que la méthode stochastique est utilisée pour diminuer la densité des poils sur Ratatouille à la volée, au moment du rendu, en prenant en compte la distance, le motion blur et le DOF. Je ne reviendrais pas sur les raisons pour lesquelles ce n’est plus facilement faisable de nos jours (il faut lire le billet :IFuckTheWorld: ), mais l’idée générale reste inspirante et on peut tenter de se l’approprier pour bricoler quelque chose. :dentcasse:
Cette méthode est un peu brouillonne et les habitués de trigonométrie auront sûrement une approche plus fine du problème.
Sur un plan, on cherche à calculer la densité et l’épaisseur optimale des poils d’un personnage sur ce plan.
C’est ça qu’on cherche à calculer.
Un même système de poil, différentes densités et épaisseurs.
Ces valeurs ne peuvent pas être animées au risque d’engendrer des inconsistances de nombre de poils entre deux échantillons temporels, ce qui mettrait le moteur dans une bien mauvaise passe au moment où ce dernier calcule les vecteurs de mouvement des poils. D’après mes tests (sous Guerilla et Arnold, je suppose que Renderman fait pareil) les moteurs désactivent simplement les vecteurs de mouvement du système de poil incriminé et lèvent un warning.
Il va donc falloir déterminer ces deux valeurs pour chaque système de poil sur l’ensemble du plan.
Il nous faut deux choses (suivant son pipeline, on peut utiliser de l’Alembic ou la combinaison Rig + ATOM) :
- La caméra du plan.
- La géométrie du personnage à exporter.
Une fois ces deux éléments dans notre scène, le bonheur est à notre porte. En effet, les mathématiques commencent. :baffed:
Pour chaque image du plan (et pour chaque step de motion blur), nous allons identifier le sommet géométrique qui soit à la fois le plus proche de la caméra et présents en son tronc (frustrum).
Finalement, nous calculons le diamètre, en espace caméra, du disque projeté d’une sphère de 1 unité de diamètre.
Nous récupérons ainsi le plus grand diamètre du plan.
Notez que quand, comme moi, on ne sait pas gérer ce mécanisme de façon totalement mathématique ( :seSentCon: ), on peut fabriquer une sphère et projeter chaque vertex via ce bout de code (honteusement inspiré de ce billet) :
import maya.cmds as cmds
import maya.OpenMaya as om
# retrieve MObject from node
sel = om.MSelectionList()
sel.add("myCamera")
sel.add("myGeo")
cam_dag_path = om.MDagPath()
geo_dag_path = om.MDagPath()
sel.getDagPath(0, cam_dag_path)
sel.getDagPath(1, geo_dag_path)
# generate projection matrix
cam_inv_world_mtx = cam_dag_path.inclusiveMatrixInverse()
fn_cam = om.MFnCamera(cam_dag_path)
proj_mtx = fn_cam.projectionMatrix()
proj_mtx = om.MMatrix(proj_mtx.matrix) # convert MFloatMatrix te MMatrix
post_proj_mtx = fn_cam.postProjectionMatrix()
post_proj_mtx = om.MMatrix(post_proj_mtx.matrix)
view_proj_mtx = cam_inv_world_mtx * proj_mtx * post_proj_mtx
# iterate over each geo vertex
vtx_iter = om.MItGeometry(geo_dag_path)
# init 2D bounding box values
proj_x_min = 999999.0
proj_x_max = -999999.0
proj_y_min = 999999.0
proj_y_max = -999999.0
while not vtx_iter.isDone():
# get vertex position in world space
geo_pos = vtx_iter.position(om.MSpace.kWorld)
# compute projected position on cam
proj_pos = geo_pos * view_proj_mtx
proj_pos_x = (proj_pos.x / proj_pos.w) / 2.0 + 0.5
proj_pos_y = (proj_pos.y / proj_pos.w) / 2.0 + 0.5
proj_x_min = min(proj_pos_x, proj_x_min)
proj_x_max = max(proj_pos_x, proj_x_max)
proj_y_min = min(proj_pos_y, proj_y_min)
proj_y_max = max(proj_pos_y, proj_y_max)
vtx_iter.next()
À ce stade, on a, pour un plan et un personnage, un diamètre de la partie la plus proche. :sourit:
Mais vous vous doutez qu’il va falloir la comparer avec une autre valeur. Cette valeur, c’est le diamètre du disque projeté de la sphère sur une caméra donnée quand l’asset est a une densité de 1 (densité nominale : 100 % de poils).
Ce diamètre se récupère au niveau de la modé/lookdev.
On se retrouve donc avec deux diamètres. On fait ensuite un simple ratio qui peut être utilisé pour calculer la valeur de densité.
Super : Reste l’épaisseur : En admettant que facteur soit la valeur de densité (1.0 pour 100 %, 0.5 pour 50 %, etc.), j’applique la formule suivante :
Épaisseur nominale * (1 + (−ln(facteur)÷ln(2)))^2
Neuh, que, nan, mais, de quoi ? Mais… Mais… Mais… Dorian ! Ça n’a aucun putain d’sens !!!
T’es mauvais ! Je savais bien qu’il n’y avait pas que dans les yeux que tu avais de la merde !!!
Je ne vais pas risquer de me vautrer en démonstrations. Les plus matheux d’entre vous auront peut-être remarqué la présence de X = ln(A)/ln(2) pour résoudre l’équation 2x = A. L’idée est de calculer une puissance, plus d’information ici.
En gros j’avais remarqué que la formule de calcul d’épaisseur suivante fonctionnait bien :
- Exposant à 7 (100 % de densité), Épaisseur nominale * 1^2 = 0.002
- Exposant à 6 (50 % de densité), Épaisseur nominale * 2^2 = 0.008
- Exposant à 5 (25 % de densité), Épaisseur nominale * 3^2 = 0.018
- Exposant à 4 (12.5 % de densité), Épaisseur nominale * 4^2 = 0.032
- Exposant à 3 (6.25 % de densité), Épaisseur nominale * 5^2 = 0.05
- Exposant à 2 (3.125 % de densité), Épaisseur nominale * 6^2 = 0.072
- Exposant à 1 (… % de densité), Épaisseur nominale * 7^2 = 0.098
Pour info, à chaque fois qu’on baisse l’Exponent de un, on divise la densité par deux.
Il fallait donc que je réussisse à trouver les valeurs en gras pour chaque facteur de densité. En gros, il faut trouver une formule pour convertir 0.03125 en 6… J’avoue ne pas avoir trop le niveau et je ne me fais aucun doute sur le fait qu’une méthode plus sérieuse existe. Le graph est là pour ceux qui veulent faire mumuse.
Il n’empêche que je voudrais quand même vous montrer les résultats en rendu. :redface:
On part sur ces valeurs :
Et le rendu de référence :
Density 50, Exponent 7, Width 0.002
Et en commence à descendre :
Density 50, Exponent 6, Width 0.008
Density 50, Exponent 5, Width 0.018
Density 50, Exponent 4, Width 0.032
Density 50, Exponent 3, Width 0.05
Density 50, Exponent 2, Width 0.072
Density 50, Exponent 1, Width 0.098
« Le monsieur fait mine que ça marche, mais il dit qu’il sait pas comment… » :neutral:
Mouaih…
Comme vous vous en rendez compte, cette méthode est limitée (pas de prise en compte du DOF, ni du motion blur, ni de la réfraction), mais avant de crier au scandale, il faut bien comprendre qu’on ne peut, de toutes façons, pas arriver à un résultat parfait sans que ce soit le moteur qui nous donne lui-même les informations. C’est sûrement faisable en calculant un mini rendu d’une AOV de l’aire de la surface au pixel combiné à une AOV de motion vector, mais je n’ai jamais mis cela en place. :seSentCon:
Le but est d’arriver à proposer une valeur au graphiste qu’il pourra, de toutes façons, modifier si les besoins du plan l’impose. Il y a fort à parier que la plupart des plans sur lesquels vous travaillerez n’auront rien d’exotique et avoir une valeur cohérente sous le coude vous évite de perdre du temps en essais/erreurs. :laClasse:
Toujours plus loin ?
On doit pouvoir améliorer ce mécanisme. Par exemple, si votre logiciel de lookdev supporte les valeurs animées, vous pourriez extraire les valeurs par frame (en regroupant les steps)et animer la densité. Je n’ai jamais testé, je ne peux donc pas vous dire si la « saute » de densité à chaque frame est visible à l’écran. :reflechi:
Conclusion
Les LOD en début de prod :
Vous, à deux semaines de la livraison :
J’espère que cette suite de billets, bien trop verbeux, j’en ai conscience, vous auront tout de même intéressés et élargie vos horizons techniques sur le sujet.
:marioCours: