# Fishbowl effect correction in raycasting engine suggestions (pseudo 3d projection)

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:

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):

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

• It's likely your raycast method is not returning a correct perpendicular distance. Can you show us how you're computing your distance? Commented Mar 16, 2020 at 0:35
• 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? :\) Commented Mar 16, 2020 at 18:23
• Much better! If you've solved your problem, please feel free to post your solution as an Answer below. Commented Mar 16, 2020 at 18:49

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:

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
}

• 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 Commented Mar 16, 2020 at 19:06