Basic (Fake) Raycasting on a 2D Heightmap
Basically what I'm trying to do is shade a 2D heightmap using a very very basic raycasting system that basically just checks if the ray is intercepted before it should be to shade it. However it's not working correctly and I've been banging my head for several hours now on this so I figured it couldn't hurt to turn it over to you guys, because I think it's probably something either so blindingly obvious that I won't see it or so complex that I'll never wrap my head around it.
I have a map like this:
And the raycasting is giving me this (keep in mind it's just debug colors; red is ray interception, but before intended position (so shading), blue would be ray interception in the correct place (so highlights or just as-is), and yellow means that point had no ray interaction at all before the while loop cut-out).
The result should be with red on backfacing slopes and areas behind large mountains (shadows) and blue on sun-facing slopes (highlights). There should not be any yellow. So this image indicates that either all of the rays are hitting the wrong place, or the rays are being intersected ALWAYS somewhere else before they reach their target, which is impossible.
At this point I highly suspect the problem is with my trig.
Here's the Ray class:
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;
}
}
Also just as a formality, the function that is casting each ray and how I am calling that:
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 开发者_如何学Cy = 0; y < Height; y++)
ray.CastTo(x, y);
}
I ended up using a much simpler approach with Bresenham's Line Algorithm to find the intercept point; I imagine it's much faster and more efficient than the way I was trying to do it would have been.
My guess is that when your Direction
vector is applied to Position
, it oversteps the lower limit (Position.Y > -1
) before it has a chance to hit the surface (Position.Y <= Terrainmap[(int)Position.X, y].Height
).
You could try to decrease the lower limit, or re-order your if
/while
tests.
Another problem might be that the Direction
Vector is too large in comparison to your height-range. The distance between two neighboring pixels is 1, while the whole range of height differences is contained in the range (-1,1). This gives a very flat surface from the ray-casters point of view. When the Direction
vector is applied to the Position
vector is takes a relatively small step over the length, and a relatively large step over the height.
@Maltor: I actually wanted to comment your own answer, but due to my reputation am not currently able to.
I also used the bresenham's line approach and decreased calculation time to 1/10!
A running example of that can be viewed at my github project TextureGenerator-Online. The terrain tool uses this approach.
See function setTerrainShadow()
at tex_terrain.js
精彩评论