Amélioration de L'éclairage de la zone dans WebGL & ThreeJS
J'ai travaillé sur une implémentation d'éclairage de zone dans WebGL similaire à cette démo:
Http://threejs.org/examples/webgldeferred_arealights.html
La mise en œuvre ci-dessus en trois.js a été porté à partir du travail de ArKano22 sur le 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 ArKano22 la mise en œuvre initiale est que le calcul du terme diffus ne tient pas compte des normales de surface.
J'ai augmenté cette solution Depuis quelques semaines maintenant, en travaillant avec les améliorations de redPlant pour résoudre ce problème. Actuellement, j'ai des calculs normaux incorporés dans la solution, mais le résultat est également défectueux.
Voici un aperçu de ma mise en œuvre actuelle:
Introduction
Les étapes de calcul du diffus le terme pour chaque fragment est le suivant:
- projeter 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.
- vérifiez que le sommet est du bon côté du plan lumineux de la zone en comparant le vecteur de projection avec la normale de la lumière.
- calculez le décalage 2D de ce point projeté sur le plan à partir du centre / de la position de la lumière.
- serrez ce vecteur de décalage 2D pour qu'il se trouve à l'intérieur de la zone de la lumière (définie par sa largeur et sa hauteur).
- dériver la position du monde 3D du point 2D projeté et serré. C'est le point le plus proche sur la lumière de la zone au sommet.
- effectuez les calculs diffus habituels que vous feriez pour une lumière ponctuelle en prenant le produit de point entre le vecteur du sommet au point le plus proche (normalisé) et le Sommet normal.
Problème
Le problème avec cette solution est que l'éclairage les calculs sont effectués à partir du point le plus proche {[38] } et ne tiennent pas compte des autres points de la surface des lumières qui pourraient éclairer le fragment encore plus. Laissez-moi essayer et expliquer pourquoi ...
Considérons le diagramme suivant:
La Lumière de zone est à la fois perpendiculaire à la surface et la croise. Chacun des fragments sur la surface retournera toujours un point le plus proche sur la lumière de la zone où la surface et la lumière se croisent. Depuis le surface normale et les vecteurs vertex-à-lumière sont toujours perpendiculaires, le produit dot entre eux est nul. Par la suite, le calcul de la contribution diffuse est nul malgré qu'il y ait une grande surface de lumière qui se profile sur la surface.
Solution Potentielle
JE propose QUE plutôt que de calculer la lumière à partir du point le plus proche sur la lumière de la zone, nous le calculons à partir d'un point sur la lumière de la zone qui donne le plus grand produit de points entre le vecteur vertex-lumière (normalisé) et le vertex normal. Dans le diagramme ci-dessus, ce serait le point violet, plutôt que le point bleu.
Aider!
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 Shader fragment:
- position du sommet
- sommet normal (vecteur unitaire)
- position de la lumière, largeur et Hauteur
- lumière normale (vecteur unitaire)
- lumière droite (vecteur unitaire)
- 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, je créé ce diagramme (j'espère que cela aide):
Pour tester ma proposition, j'ai besoin du point de coulée sur la lumière de la zone-représentée par les points rouges, afin que je puisse effectuer le produit de point entre le sommet-à-point de coulée (normalisé) et le Sommet normal. Encore une fois, cela devrait donner la valeur de contribution maximale possible.
Mise à jour!!!
J'ai créé une esquisse interactive sur CodePen qui visualise les mathématiques que j'ai actuellement mise en œuvre:
Http://codepen.io/wagerfield/pen/ywqCp
Le code relavent sur lequel vous devez vous concentrer est line 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-celles-ci sont mises à jour dynamiquement pour afficher le produit dot entre les vecteurs pertinents.
J'imagine que la solution nécessiterait un autre pseudo avion cela s'aligne avec la direction du sommet normal et est perpendiculaire au plan de la lumière, mais je pourrais me tromper!
5 réponses
, La bonne nouvelle est qu'il existe une solution; mais d'abord la mauvaise nouvelle.
Votre approche consistant à utiliser le point qui maximise le produit dot est fondamentalement erronée et n'est pas physiquement plausible.
Dans votre première illustration ci-dessus, supposons que votre lumière de zone ne se composait que de la moitié gauche.
Le "purple" point -- celui qui maximise le produit scalaire pour la moitié gauche -- est le même que le point qui maximise le produit scalaire de deux moitiés combiné.
Par conséquent, si l'on devait utiliser la solution proposée, on pourrait conclure que la moitié gauche de la lumière de la zone émet le même rayonnement que la lumière entière. Évidemment, c'est impossible.
La solution pour calculer la quantité totale de lumière que la lumière de surface jette sur un point donné est plutôt compliquée, mais pour référence, vous pouvez trouver une explication dans l'article de 1994 L'Irradiance Jacobienne pour les Sources polyédriques partiellement occlus ici.
Je vous suggère de regarder à Figure 1, et quelques paragraphes de Section 1.2 -- et puis s'arrêter. :-)
Pour le rendre facile, j'ai codé un shader très simple qui implémente la solution en utilisant les trois.js WebGLRenderer
-- pas le différé.
EDIT: voici un violon mis à jour: http://jsfiddle.net/hh74z2ft/1/
Le noyau du shader 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 Bon Nouvelles:
- cette approche est physiquement correcte.
- L'atténuation est gérée automatiquement. (Sachez que les lumières plus petites nécessiteront une valeur d'intensité plus grande. )
- en théorie, cette approche devrait fonctionner avec des polygones arbitraires, pas seulement des polygones rectangulaires.
Mises en garde:
- j'ai seulement implémenté le composant diffus, parce que c'est ce que votre question aborde.
- vous devrez implémenter le composant spéculaire en utilisant un heuristique raisonnable-similaire à ce que vous avez déjà codé, Je m'attends.
- cet exemple simple ne traite 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 de la face.
- puisque
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ée. (WebGLDeferredRenderer
prend en charge les lumières de la zone, bien sûr. ) - Les ombres ne sont pas soutenu.
Trois.js R. 73
Hm. Question étrange! Il semble que vous ayez commencé avec une approximation très spécifique et que vous travailliez maintenant vers la bonne solution.
Si nous nous en tenons à seulement diffuse et une surface qui est plat (a seulement une 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 simplement allin = integral ( lightin) ((lightin).(normal))*lumière c'est dur. donc, tout le problème est de résoudre cette intégrale. avec la lumière de point de vous tricher en faisant une somme et en tirant la lumière. Cela fonctionne très 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 une sorte de sondes de lumière, des harmoniques sphériques ou de nombreuses 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 voulez éclairer. Vous avez besoin que toute la lumière entre. Certains sont moins importantes, certains plus. C'est à ça que sert ta routine. 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 vite. Et c'est ce que fait votre code de bibliothèque: un choix rapide pour une bonne estimation (mais imparfaite).
Et c'est là que je pense que vous allez en arrière: vous avez réalisé qu'ils font une supposition, et que ça craint parfois (c'est la nature de deviner). Maintenant, n'essayez pas de corriger leur conjecture, mais trouvez-en une meilleure! Et peut-être essayer de comprendre pourquoi ils ont choisi que deviner. Une bonne approximation ne consiste pas à être bon dans les cas de coin, mais à bien se dégrader. C'est à quoi ça ressemble pour moi. (Encore une fois, désolé, je suis paresseux pour lire les trois.code js maintenant).
Donc, pour répondre à votre question:
- je pense que vous allez à ce sujet dans le mauvais sens. Vous commencez avec une idée hautement optimisée, et essayez de résoudre ce problème. Il vaut mieux commencer par le problème.
- résolvez une chose à la fois. Votre capture d'écran a beaucoup de spéculaire, qui est sans rapport avec votre problème, mais très visuel et était probablement une grande influence pour les gens 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. Lisez 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 aide. Je pourrais avoir totalement tort ici et parler à quelqu'un qui cherche juste des Maths Rapides, dans ce cas, je m'excuse.
Convenons que le point de coulée est toujours sur le bord.
Disons que "partie éclairée" est la partie de l'espace qui est représentée par le quad de la lumière extrudée le long de sa normale.
Si le point de surface se trouve dans la partie éclairée, alors 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 la lumière vous donnerait deux points comme options (seulement deux, car le point de coulée est toujours sur le bord). Alors testez ces deux pour voir lequel contribue plus.
Si le point n'est pas dans la partie éclairée, alors vous pouvez calculer quatre plans, chacun a un point de surface, sa normale et l'un des sommets du quad de la lumière. Pour chaque sommet light-quad, vous auriez deux points (sommet + un point d'intersection de plus) à tester qui contribue le plus.
Cela devrait faire l'affaire. Veuillez me donner vos commentaires si vous rencontrez un contre-exemple.
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(a^2+x0^2)
L = somme(k/(sqrt(a^2+x0^2))^2), y=0..infini
L = somme(k/(y ^ 2 + x0^2)), y = 0..l'infini, x > 0, y > 0
L = intégrale(k/(y^2+x0^2)), y=0..infinité = k * Pi/(2*x0)
Http://s5.hostingkartinok.com/uploads/images/2013/06/6dbb7b6d3babc092d3daf18bb3c6e6d5.png
Réponse:
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 de l'éclairage pour les lumières de Zone:
Http://gpupro.blogspot.com/2014/03/gpu-pro-5-physically-based-area-lights.html