0
\$\begingroup\$

I'm writing a simple raycaster in golang and I have some problems understanding perspective correction. The code is simple, the main rendering loop is this:

    curVector := playerVector.NewRotated(-curFov / 2)
    rotateStep := curFov / float64(screen.Width()) // Angles per screen row
    // Traverse each row of our screen, cast a ray and render it to screen buffer
    for i := 0; i <= screen.Width(); i++ {
        curVector.Rotate(rotateStep)

        hit, distance, tile, tileP := rayCast(curX, curY, curVector, viewDistance)

        if hit {
            // distToHeight is basically linear (screen.Height()/distance)
            drawTexturedWallColumn(screen, tile, i, distToHeight(distance, screen.Height()), tileP) // Project walls on screen
        }
        // drawSpritesColumn(screen, i, curVector, distance) // Project sprites on screen
    }

So I just cast rays with even angle intervals and get this fishbowl effect: enter image description here

So, I understand that the problem is with angle intervals and sphere projected rays with the same length. I found this question How can I correct an unwanted fisheye effect when drawing a scene with raycasting? and tried to implement the same logic:

leftVector := playerVector.NewRotated(90)
    // Traverse each row of our screen, cast a ray and render it to screen buffer
    for i := 0; i <= screen.Width(); i++ {
        progress := float64(i)/float64(screen.Width()) - 0.5 // -0.5 to 0.5
        stepX := (playerVector.X + progress*(leftVector.X*2)) // *2 to make 90 FOV
        stepY := (playerVector.Y + progress*(leftVector.Y*2))
        curVector := Vector{X: stepX, Y: stepY}

        hit, distance, tile, tileP := rayCast(curX, curY, curVector, viewDistance)

        if hit {
            // distToHeight is basically linear (screen.Height()/distance)
            drawTexturedWallColumn(screen, tile, i, distToHeight(distance, screen.Height()), tileP) // Project walls on screen
        }
        // drawSpritesColumn(screen, i, curVector, distance) // Project sprites on screen
    }

But the result is almost the same (FOV variable is not used here but it's around 90, it's not important at the moment). The difference is that this one has a more "linear" fishbowl effect because of evenly spaced intervals (progress variable): enter image description here

So, I don't know how to fix this, I also tried to use perpendicular distance (distance*cos).

\$\endgroup\$
3
  • \$\begingroup\$ It's likely your raycast method is not returning a correct perpendicular distance. Can you show us how you're computing your distance? \$\endgroup\$
    – DMGregory
    Mar 16, 2020 at 0:35
  • \$\begingroup\$ Yeah, you are right. I played with this for a while and came up with this result (i updated the question). I guess the perspective is as it should be (right? :\) \$\endgroup\$
    – Stanislav
    Mar 16, 2020 at 18:23
  • \$\begingroup\$ Much better! If you've solved your problem, please feel free to post your solution as an Answer below. \$\endgroup\$
    – DMGregory
    Mar 16, 2020 at 18:49

1 Answer 1

2
\$\begingroup\$

Here's a solution I got for now (using the second one with vectors above). Before, i calculated the distance using vector's angle, i corrected it to increase starting ray position using direction vector like this:

func rayCast(x0, y0 float64, dir Vector, distance float64) (hit bool, dist float64, tile int, tileP float64) {
    length := 0.0 // Length of hit check
    step := 0.01  // Interval of collision checking
    for length <= distance {
        if hit, tile, tileP := IntersectsWithMap(x0, y0); hit {
            return true, length, tile, tileP
        }
        x0 += dir.X * step
        y0 += dir.Y * step
        length += step
    }
    return false, distance, 0.0, 0.0
}

Results so far: enter image description here


The previous rayCast is a bit messy but I'll include it for historic purposes:

func rayCast(x0, y0 float64, dir Vector, distance float64) (hit bool, dist float64, tile int, tileP float64) {
    length := 0.0 // Length of hit check
    step := 0.01  // Interval of collision checking
    sX, sY := x0-dir.X, y0-dir.Y
    for length <= distance {
        if hit, tile, tileP := IntersectsWithMap(increaseVector(sX, sY, x0, y0, length)); hit {
            return true, length, tile, tileP
        }
        length += step
    }
    return false, distance, 0.0, 0.0
}

// Gets next point for vector by length
func increaseVector(x0, y0, x1, y1, length float64) (x, y float64) {
    dX, dY := x1-x0, y1-y0
    mg := math.Sqrt(dX*dX + dY*dY)
    uX, uY := dX/mg, dY/mg

    return x1 + uX*length, y1 + uY*length
}
\$\endgroup\$
1
  • \$\begingroup\$ The fix given in this answer would be clearer if you also showed your previous raycasting method in your question, so users can compare and contrast the two approaches \$\endgroup\$
    – DMGregory
    Mar 16, 2020 at 19:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .