web-dev-qa-db-fra.com

Éclairage de zone amélioré dans WebGL et ThreeJS

Je travaille sur une implémentation d'éclairage de zone dans WebGL similaire à cette démo:

http://threejs.org/examples/webgldeferred_arealights.html

L'implémentation ci-dessus dans three.js a été transférée du travail d'ArKano22 sur gamedev.net:

http://www.gamedev.net/topic/552315-glsl-area-light-implementation/

Bien que ces solutions soient très impressionnantes, elles ont toutes deux quelques limites. Le principal problème avec l'implémentation originale d'ArKano22 est que le calcul du terme diffus ne tient pas compte des normales de surface.

J'augmente cette solution depuis quelques semaines maintenant, en travaillant avec les améliorations apportées par redPlant pour résoudre ce problème. Actuellement, j'ai des calculs normaux incorporés dans la solution, MAIS le résultat est également imparfait.

Voici un aperçu de mon implémentation actuelle:

area lighting teaser

Introduction

Les étapes de calcul du terme diffus pour chaque fragment sont les suivantes:

  1. Projetez le sommet sur le plan sur lequel se trouve la lumière de la zone, de sorte que le vecteur projeté coïncide avec la direction/normale de la lumière.
  2. Vérifiez que le sommet est du bon côté du plan lumineux de la zone en comparant le vecteur de projection à la normale de la lumière.
  3. Calculez le décalage 2D de ce point projeté sur le plan à partir du centre/de la position de la lumière.
  4. Fixez ce vecteur de décalage 2D afin qu'il se trouve à l'intérieur de la zone de la lumière (définie par sa largeur et sa hauteur).
  5. Dérivez la position mondiale 3D du point 2D projeté et serré. Il s'agit du point le plus proche de la zone lumineuse au sommet.
  6. Effectuez les calculs diffus habituels comme vous le feriez pour une lumière ponctuelle en prenant le produit scalaire entre le vecteur de sommet au point le plus proche (normalisé) et la normale de sommet.

Problème

Le problème avec cette solution est que les calculs d'éclairage sont effectués à partir du point le plus proche et ne tiennent pas compte d'autres points sur la surface de la lumière qui pourraient éclairer le fragment encore plus. Laissez-moi essayer d'expliquer pourquoi…

Considérez le diagramme suivant:

problematic area lighting situation

La lumière de zone est à la fois perpendiculaire à la surface et l'intersecte. Chacun des fragments sur la surface renverra toujours un point le plus proche sur la zone de lumière où la surface et la lumière se croisent. Puisque la surface normale et les vecteurs sommet-lumière sont toujours perpendiculaires, le produit scalaire entre eux est nul. Par la suite, le calcul de la contribution diffuse est nul malgré la présence d'une grande zone de lumière sur la surface.

Solution potentielle

Je propose qu'au lieu de calculer la lumière à partir du point le plus proche sur la lumière de la zone, nous la calculons à partir d'un point de la lumière de la zone qui donne le plus grand point produit entre le vecteur sommet à lumière (normalisé) et le sommet normal. Dans le diagramme ci-dessus, ce serait le point violet, plutôt que le point bleu.

Aidez-moi!

Et donc, c'est là que j'ai besoin de votre aide. Dans ma tête, j'ai une assez bonne idée de la façon dont ce point peut être dérivé, mais je n'ai pas la compétence mathématique pour arriver à la solution.

Actuellement, j'ai les informations suivantes disponibles dans mon fragment shader:

  • position du sommet
  • sommet normal (vecteur unitaire)
  • position, largeur et hauteur de la lumière
  • lumière normale (vecteur unitaire)
  • lumière droite (vecteur unitaire)
  • s'allume (vecteur unitaire)
  • point projeté du sommet sur le plan des lumières (3D)
  • décalage du point projeté par rapport au centre des lumières (2D)
  • décalage serré (2D)
  • position mondiale de ce décalage serré - le point le plus proche (3D)

Pour mettre toutes ces informations dans un contexte visuel, j'ai créé ce diagramme (j'espère que cela aide):

available lighting information

Pour tester ma proposition, j'ai besoin du point de moulage sur la zone lumineuse - représentée par les points rouges, afin de pouvoir effectuer le produit scalaire entre le sommet au point de lancer (normalisé) et au sommet normal. Encore une fois, cela devrait donner la valeur de contribution maximale possible.

METTRE À JOUR!!!

J'ai créé un croquis interactif sur CodePen qui visualise les mathématiques que j'ai actuellement implémentées:

http://codepen.io/wagerfield/pen/ywqCp

codepen

Le code relavent sur lequel vous devez vous concentrer est la ligne 318 .

castingPoint.location est une instance de THREE.Vector3 et est la pièce manquante du puzzle. Vous devriez également remarquer qu'il y a 2 valeurs en bas à gauche de l'esquisse - elles sont mises à jour dynamiquement pour afficher le produit scalaire entre les vecteurs pertinents.

J'imagine que la solution nécessiterait un autre pseudo plan qui s'aligne avec la direction du sommet normal ET est perpendiculaire au plan de la lumière, mais je peux me tromper!

59
wagerfield

La bonne nouvelle est qu'il existe une solution; mais d'abord les mauvaises nouvelles.

Votre approche consistant à utiliser le point qui maximise le produit scalaire est fondamentalement erronée et n'est pas physiquement plausible.

Dans votre première illustration ci-dessus, supposez que la lumière de votre zone ne comprenait que la moitié gauche.

Le point "violet" - celui qui maximise le produit scalaire pour la moitié gauche - est le même que le point qui maximise le produit scalaire pour les deux moitiés combinées.

Par conséquent, si l'on devait utiliser votre solution proposée, on conclurait que la moitié gauche de la zone lumineuse émet le même rayonnement que la lumière entière. De toute évidence, c'est impossible.

La solution pour calculer la quantité totale de lumière que la lumière de zone projette sur un point donné est plutôt compliquée, mais pour référence, vous pouvez trouver une explication dans l'article de 1994 The Irradiance Jacobian for Partially Occluded Polyhedral Sourcesici .

Je vous suggère de regarder Figure 1 , et quelques paragraphes de Section 1.2 - puis arrêtez. :-)

Pour le rendre facile, j'ai codé un shader très simple qui implémente la solution en utilisant le three.js WebGLRenderer - pas le différé.

EDIT: Voici un violon mis à jour: http://jsfiddle.net/hh74z2ft/1/

enter image description here

Le noyau du shader de fragment est assez simple

// direction vectors from point to area light corners

for( int i = 0; i < NVERTS; i ++ ) {

    lPosition[ i ] = viewMatrix * lightMatrixWorld * vec4( lightverts[ i ], 1.0 ); // in camera space

    lVector[ i ] = normalize( lPosition[ i ].xyz + vViewPosition.xyz ); // dir from vertex to areaLight

}

// vector irradiance at point

vec3 lightVec = vec3( 0.0 );

for( int i = 0; i < NVERTS; i ++ ) {

    vec3 v0 = lVector[ i ];
    vec3 v1 = lVector[ int( mod( float( i + 1 ), float( NVERTS ) ) ) ]; // ugh...

    lightVec += acos( dot( v0, v1 ) ) * normalize( cross( v0, v1 ) );

}

// irradiance factor at point

float factor = max( dot( lightVec, normal ), 0.0 ) / ( 2.0 * 3.14159265 );

Plus de bonnes nouvelles:

  1. Cette approche est physiquement correcte.
  2. L'atténuation est gérée automatiquement. (Sachez que des lumières plus petites nécessiteront une valeur d'intensité plus élevée.)
  3. En théorie, cette approche devrait fonctionner avec des polygones arbitraires, pas seulement rectangulaires.

Avertissements:

  1. Je n'ai implémenté que le composant diffus, car c'est à cela que répond votre question.
  2. Vous devrez implémenter le composant spéculaire en utilisant une heuristique raisonnable - similaire à ce que vous avez déjà codé, je suppose.
  3. Cet exemple simple ne gère pas le cas où la lumière de la zone est "partiellement en dessous de l'horizon" - c'est-à-dire que les 4 sommets ne sont pas tous au-dessus du plan du visage.
  4. Étant donné que WebGLRenderer ne prend pas en charge les lumières de zone, vous ne pouvez pas "ajouter la lumière à la scène" et vous attendre à ce qu'elle fonctionne. C'est pourquoi je passe toutes les données nécessaires dans le shader personnalisé. (WebGLDeferredRenderer prend en charge les éclairages de zone, bien sûr.)
  5. Les ombres ne sont pas prises en charge.

three.js r.73

40
WestLangley

Hm. Question étrange! Il semble que vous ayez commencé avec une approximation très spécifique et que vous reveniez maintenant à la bonne solution.

Si nous nous en tenons uniquement à la diffusion et à une surface plate (qui n'a qu'une seule normale), quelle est la lumière diffuse entrante? Même si nous nous en tenons à chaque lumière entrante a une direction et une intensité, et nous prenons juste allin = intégrale (lightin) ((lightin). (Normal)) * la lumière c'est dur. donc tout le problème est de résoudre cette intégrale. avec la lumière ponctuelle, vous trichez en en faisant une somme et en retirant la lumière. Cela fonctionne bien pour les lumières ponctuelles sans ombres, etc. maintenant ce que vous voulez vraiment faire est de résoudre cette intégrale. c'est ce que vous pouvez faire avec des sondes lumineuses, des harmoniques sphériques ou bien d'autres techniques. ou quelques astuces pour estimer la quantité de lumière d'un rectangle.

Pour moi, il est toujours utile de penser à l'hémisphère au-dessus du point que vous souhaitez éclairer. Vous avez besoin de toute la lumière qui entre. Certains sont moins importants, d'autres plus. C'est à ça que sert votre normale. Dans un raytracer de production, vous pouvez simplement échantillonner quelques milliers de points et avoir une bonne estimation. En temps réel, vous devez deviner beaucoup plus rapidement. Et c'est ce que fait votre code de bibliothèque: un choix rapide pour une bonne (mais imparfaite) supposition.

Et c'est là que je pense que vous allez en arrière: vous vous êtes rendu compte qu'ils font une supposition, et que ça craint parfois (c'est la nature de deviner). Maintenant, n'essayez pas de corriger leur supposition, mais trouvez-en une meilleure! Et peut-être essayer de comprendre pourquoi ils ont choisi cette supposition. Une bonne approximation ne consiste pas à être bon dans les cas d'angle, mais à bien se dégrader. C'est à ça que ça ressemble pour moi. (Encore une fois, désolé, je suis trop paresseux pour lire le code three.js maintenant).

Donc, pour répondre à votre question:

  • Je pense que vous vous y trompez. Vous commencez avec une idée hautement optimisée et essayez de résoudre ce problème. Mieux vaut partir du problème.
  • Résolvez une chose à la fois. Votre capture d'écran a beaucoup de spéculations, cela n'est pas lié à votre problème mais très visuel et a probablement été une grande influence pour les personnes qui conçoivent le modèle.
  • Vous êtes sur la bonne voie et avez une bien meilleure idée du rendu que la plupart des gens. Cela peut fonctionner pour et contre vous. Renseignez-vous sur certains moteurs de jeu modernes et leurs modèles d'éclairage. Vous trouverez toujours une combinaison fascinante de hacks et de compréhension profonde. La compréhension profonde est ce qui pousse à choisir les bons hacks :)

J'espère que cela t'aides. Je peux me tromper complètement ici et divaguer sur quelqu'un qui cherche juste des calculs rapides, dans ce cas, je m'excuse.

2
starmole

http://s3.hostingkartinok.com/uploads/images/2013/06/9bc396b71e64b635ea97725be8719e79.png

Si je comprends bien:

définir L "Lumière pour le point x0"

L ~ K/S ^ 2

S = sqrt (y ^ 2 + x0 ^ 2)

L = somme (k/(sqrt (y ^ 2 + x0 ^ 2)) ^ 2), y = 0..fini

L = somme (k/(y ^ 2 + x0 ^ 2)), y = 0..fini, x> 0, y> 0

L = intégrale (k/(y ^ 2 + x0 ^ 2)), y = 0..infinity = k * Pi/(2 * x0)

http://s5.hostingkartinok.com/uploads/images/2013/06/6dbb7b6d3babc092d3daf18bb3c6e6d5.png

Répondre:

L = k * Pi/(2 * x0)

k dépend de l'environnement

1
user2471050

cela fait un moment, mais il y a un article dans gpu gems 5 qui utilise "le point le plus important" plutôt que le "point le plus proche" pour approximer l'intégrale d'éclairage pour les lumières de zone:

http://gpupro.blogspot.com/2014/03/gpu-pro-5-physically-based-area-lights.html

1
user755921

Soyons d'accord que le point de lancement est toujours sur le bord.

Disons que la "partie éclairée" est la partie de l'espace qui est représentée par le quadruple de lumière extrudée le long de sa normale.

Si le point de surface se trouve dans la partie éclairée, vous devez calculer le plan qui contient ce point, c'est un vecteur normal et la lumière est normale. L'intersection entre ce plan et celui de la lumière vous donnerait deux points en option (seulement deux, car le point de moulage est toujours sur le bord). Alors testez ces deux pour voir lequel contribue le plus.

Si le point n'est pas dans la partie éclairée, vous pouvez calculer quatre plans, chacun ayant un point de surface, sa normale et l'un des sommets du quadruple de la lumière. Pour chaque sommet de quadruple de lumière, vous auriez deux points (sommet + un point d'intersection de plus) à tester qui contribuent le plus.

Cela devrait faire l'affaire. Veuillez me donner des commentaires si vous rencontrez un contre-exemple.

1
Abstract Algorithm