web-dev-qa-db-fra.com

Générer une carte normale à partir d'une carte de hauteur?

Je travaille sur la génération de correctifs procéduraux de saleté en utilisant des fractales aléatoires pour un jeu vidéo. J'ai déjà généré une carte de hauteur à l'aide de l'algorithme de déplacement à mi-chemin et l'ai enregistrée dans une texture. J'ai quelques idées sur la façon de transformer cela en une texture de normales, mais certains commentaires seraient très appréciés.

Ma texture de hauteur est actuellement une image en niveaux de gris de 257 x 257 (les valeurs de hauteur sont mises à l'échelle à des fins de visibilité):

enter image description here

Ma pensée est que chaque pixel de l'image représente une coordonnée de réseau dans une grille de 256 x 256 (d'où la raison pour laquelle il y a 257 x 257 hauteurs). Cela signifierait que la normale à la coordonnée (i, j) est déterminée par les hauteurs à (i, j), (i, j + 1), (i + 1, j) et (i + 1, j + 1 ) (appelez respectivement A, B, C et D).

Donc, étant donné les coordonnées 3D de A, B, C et D, serait-il logique de:

  1. diviser les quatre en deux triangles: ABC et BCD
  2. calculer les normales de ces deux faces via un produit croisé
  3. divisé en deux triangles: ACD et ABD
  4. calculer les normales de ces deux faces
  5. moyenne des quatre normales

... ou y a-t-il une méthode beaucoup plus simple qui me manque?

34
Dawson

Exemple de code GLSL de mon shader de rendu de surface d'eau:

#version 130
uniform sampler2D unit_wave
noperspective in vec2 tex_coord;
const vec2 size = vec2(2.0,0.0);
const ivec3 off = ivec3(-1,0,1);

    vec4 wave = texture(unit_wave, tex_coord);
    float s11 = wave.x;
    float s01 = textureOffset(unit_wave, tex_coord, off.xy).x;
    float s21 = textureOffset(unit_wave, tex_coord, off.zy).x;
    float s10 = textureOffset(unit_wave, tex_coord, off.yx).x;
    float s12 = textureOffset(unit_wave, tex_coord, off.yz).x;
    vec3 va = normalize(vec3(size.xy,s21-s01));
    vec3 vb = normalize(vec3(size.yx,s12-s10));
    vec4 bump = vec4( cross(va,vb), s11 );

Le résultat est un vecteur de relief: xyz = normal, a = hauteur

42
kvark

Ma pensée est que chaque pixel de l'image représente une coordonnée de réseau dans une grille de 256 x 256 (d'où la raison pour laquelle il y a 257 x 257 hauteurs). Cela signifierait que la normale à la coordonnée (i, j) est déterminée par les hauteurs à (i, j), (i, j + 1), (i + 1, j) et (i + 1, j + 1 ) (appelez respectivement A, B, C et D).

Non. Chaque pixel de l'image représente un sommet de la grille, donc intuitivement, par symétrie, sa normale est déterminée par les hauteurs des pixels voisins (i-1, j), (i + 1, j), (i, j- 1), (i, j + 1).

Étant donné une fonction f: ℝ2 → ℝ qui décrit une surface en ℝ3, une unité normale à (x, y) est donnée par

v = (−∂f/∂x, −∂f/∂y, 1) et n = v/| v |.

On peut prouver que la meilleure approximation de tof/∂x par deux échantillons est archivée par:

∂f/∂x (x, y) = (f (x + ε, y) - f (x − ε, y))/(2ε)

Pour obtenir une meilleure approximation, vous devez utiliser au moins quatre points, donc l'ajout d'un troisième point (c'est-à-dire (x, y)) n'améliore pas le résultat.

Votre hightmap est un échantillonnage de certaines fonctions f sur une grille régulière. En prenant ε = 1, vous obtenez:

2v = (f (x − 1, y) - f (x + 1, y), f (x, y − 1) - f (x, y + 1), 2)

16
ybungalobill

Une méthode courante utilise un filtre Sobel pour une dérivée pondérée/lisse dans chaque direction.

Commencez par échantillonner une zone de hauteur 3x3 autour de chaque Texel (ici, [4] est le pixel pour lequel nous voulons la normale).

[6][7][8]
[3][4][5]
[0][1][2]

Alors,

//float s[9] contains above samples
vec3 n;
n.x = scale * -(s[2]-s[0]+2*(s[5]-s[3])+s[8]-s[6]);
n.y = scale * -(s[6]-s[0]+2*(s[7]-s[1])+s[8]-s[2]);
n.z = 1.0;
n = normalize(n);

scale peut être ajusté pour correspondre à la profondeur du monde réel de la carte de hauteur par rapport à sa taille.

12
jozxyqk

Si vous considérez chaque pixel comme un sommet plutôt que comme une face, vous pouvez générer un maillage triangulaire simple.

+--+--+
|\ |\ |
| \| \|
+--+--+
|\ |\ |
| \| \|
+--+--+

Chaque sommet a une coordonnée x et y correspondant aux x et y du pixel dans la carte. La coordonnée z est basée sur la valeur de la carte à cet endroit. Les triangles peuvent être générés explicitement ou implicitement par leur position dans la grille.

Ce dont vous avez besoin est la normale à chaque sommet .

Un sommet normal peut être calculé en prenant une moyenne pondérée par zone de la surface normales pour chacun des triangles qui se rencontrent à ce point.

Si vous avez un triangle avec des sommets v0, v1, v2, vous pouvez alors utiliser un produit vectoriel croisé (de deux vecteurs qui se trouvent sur deux des côtés du triangle) pour calculer un vecteur dans la direction de la normale et mis à l'échelle proportionnellement à l'aire du triangle.

Vector3 contribution = Cross(v1 - v0, v2 - v1);

Chacun de vos sommets qui ne sont pas sur le bord sera partagé par six triangles. Vous pouvez parcourir ces triangles, additionner les contributions, puis normaliser la somme des vecteurs.

Remarque: Vous devez calculer les produits croisés de manière cohérente pour vous assurer que les normales pointent toutes dans la même direction. Choisissez toujours deux côtés dans le même ordre (dans le sens horaire ou antihoraire). Si vous en mélangez certains, ces contributions pointeront dans la direction opposée.

Pour les sommets sur le bord, vous vous retrouvez avec une boucle plus courte et beaucoup de cas spéciaux. Il est probablement plus facile de créer une bordure autour de votre grille de faux sommets, puis de calculer les normales pour les intérieurs et de supprimer les fausses bordures.

for each interior vertex V {
  Vector3 sum(0.0, 0.0, 0.0);
  for each of the six triangles T that share V {
    const Vector3 side1 = T.v1 - T.v0;
    const Vector3 side2 = T.v2 - T.v1;
    const Vector3 contribution = Cross(side1, side2);
    sum += contribution;
  }
  sum.Normalize();
  V.normal = sum;
}

Si vous avez besoin de la normale à un point particulier sur un triangle (autre que l'un des sommets), vous pouvez interpoler en pesant les normales des trois sommets par les coordonnées barycentriques de votre point. C'est ainsi que les rastériseurs graphiques traitent la normale pour l'ombrage. Il permet à un maillage triangulaire d'apparaître comme une surface lisse et incurvée plutôt que d'un tas de triangles plats adjacents.

Astuce: Pour votre premier test, utilisez une grille parfaitement plate et assurez-vous que toutes les normales calculées pointent vers le haut.

9
Adrian McCarthy