Di base (falso) raycasting su un heightmap 2D
-
25-10-2019 - |
Domanda
In pratica quello che sto cercando di fare è ombreggiare una heightmap 2D utilizzando un sistema raycasting molto molto di base che fondamentalmente solo controlla se il raggio è stato intercettato prima che dovrebbe essere quello di ombra esso. Tuttavia non funziona correttamente e sono stato sbattere la testa per diverse ore ora in poi questo così ho pensato che non poteva male a capovolgerlo a voi ragazzi, perché penso che sia probabilmente qualcosa sia così assolutamente ovvio che non lo farò vederlo o così complesso che non sarò mai avvolgere la mia testa intorno ad esso.
Ho una mappa simile a questo:
E la raycasting mi sta dando questa (tenere a mente che è solo colori di debug; rosso è raggi intercettazione, ma posizione prima intesa (così ombreggiatura), blu sarebbero intercettazioni ray nel posto giusto (quindi mette in evidenza o solo come-è ), e mezzi di gialli che punto aveva alcuna interazione ray a tutti prima del ciclo while cut-out).
Il risultato dovrebbe essere con rosso sul backfacing piste e le aree dietro le grandi montagne (ombre) e blu sui pendii di fronte al sole (luci). Non ci dovrebbe essere alcun giallo. Quindi questa immagine indica che o tutti i raggi stanno colpendo il mal collocati o raggi vengono intersecati sempre altrove prima di raggiungere il loro obiettivo, che è impossibile.
A questo punto ho altamente sospetto che il problema è con il mio Trig.
Ecco la classe Ray:
class Ray
{
public Vector2 Position;
public Vector2 Direction; // Think in XZ coordinates for these (they are on a perpendicular plane to the heightmap)
// Angle is angle from horizon (I think), and height is height above zero (arbitrary)
public float Angle, Height;
private TerrainUnit[,] Terrainmap;
private float U, V;
public Ray(ref TerrainUnit[,] Terrainmap, float height, float angle)
{
this.Terrainmap = Terrainmap;
this.Angle = angle;
this.Height = this.V = height;
// Create new straight vector
this.Direction = new Vector2(0, 1);
// Rotate it to the values determined by the angle
this.Direction = Vector2.Transform(Direction, Matrix.CreateRotationX(Angle));
//this.Direction = new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle));
// Find the horizontal distance of the origin-destination triangle
this.U = V / (float)Math.Tan(Angle);
// Bleh just initialize the vector to something
this.Position = new Vector2(U, V);
}
public void CastTo(int x, int y)
{
// Get the height of the target terrain unit
float H = (float)Terrainmap[x, y].Height;
// Find where the ray would have to be to intersect that terrain unit based on its angle and height
Position = new Vector2(x - U, H + V);
float Z = 1000 * (float)Terrainmap[0, y].Height;
// As long as the ray is not below the terrain and not past the destination point
while (Position.Y > Z && Position.X <= x)
{
// If the ray has passed into terrain bounds update Z every step
if (Position.X > 0) Z = 1000 * (float)Terrainmap[(int)Position.X, y].Height;
Position.X += Direction.X;
Position.Y += Direction.Y;
}
Terrainmap[x, y].TypeColor = Color.Yellow;
if ((int)Position.X == x) Terrainmap[x, y].TypeColor = Color.Blue;
else Terrainmap[x, y].TypeColor = Color.Red;
}
}
Anche solo come una formalità, la funzione che sta gettando ogni raggio e come sto chiamando che:
if (lighting) CastSunRays(1f, MathHelper.PiOver4);
private void CastSunRays(float height, float angle)
{
Ray ray = new Ray(ref Terrainmap, height, angle);
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
ray.CastTo(x, y);
}
Soluzione
Ho finito per usare un approccio molto più semplice con l'algoritmo Linea di Bresenham per trovare l'intercetta punto; Immagino che è molto più veloce e più efficiente rispetto al modo in cui stavo cercando di farlo sarebbe stato.
Altri suggerimenti
La mia ipotesi è che quando il vostro vettore Direction
viene applicato a Position
, si oltrepassa il limite inferiore (Position.Y > -1
) prima che abbia la possibilità di colpire la superficie (Position.Y <= Terrainmap[(int)Position.X, y].Height
).
Si potrebbe provare a diminuire il limite inferiore, o ri-ordinare il vostro if
/ test while
.
Un altro problema potrebbe essere che il Direction
Vector è troppo grande in confronto alla tua altezza-gamma. La distanza tra due pixel adiacenti è 1, mentre l'intera gamma di differenze di altezza è contenuta nell'intervallo (-1,1). Questo dà un molto superficie piana dal punto di vista ray-rotelle. Quando il vettore Direction
viene applicato al vettore Position
è prende un relativamente piccolo passo sulla lunghezza, e una relativamente grande passo sull'altezza.
@Maltor: in realtà ho voluto commentare la propria risposta, ma a causa della mia reputazione non sono attualmente in grado di
.ho usato anche l'approccio linea del Bresenham e diminuzione del tempo di calcolo per 1/10!
Un esempio di esecuzione di che può essere visionato a mio progetto github TextureGenerator-Online . Lo strumento del terreno utilizza questo approccio.
vedi funzione setTerrainShadow()
a tex_terrain.js