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:
Les étapes de calcul du terme diffus pour chaque fragment sont les suivantes:
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:
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.
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.
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:
Pour mettre toutes ces informations dans un contexte visuel, j'ai créé ce diagramme (j'espère que cela aide):
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.
J'ai créé un croquis interactif sur CodePen qui visualise les mathématiques que j'ai actuellement implémentées:
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!
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/
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:
Avertissements:
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.)three.js r.73
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:
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.
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
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
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.