Trying to calculate uv coordinates for a ray casted floor plane made up from rows of pixels

I am drawing a series of rows from the bottom of the screen towards the top. Each row contains single row of pixels that span the entire width of the screen (number of pixels is equal to the x resolution). I would like the rows to combine and represent a floor plane at the player’s feet. The problem is that I am unable to correctly calculate the uv coordinates for each row and could use some help.

The approach is as follows

  • For each row
  • Calculate the world coordinates at the bottom left of the row
  • Calculate the world coordinates at the bottom right of the row
  • Use a dot product to find the uv coordinates on the plane surface

This results in the following image which does not give the effect of standing on a flat plane but instead looking up a steep surface

The underlying code is

            const float numRays = 320; // One ray for each column of pixels denoted by X resolution
            const float inverseNumRaysMinusOne = 1f / (numRays - 1f);
            var numRows = 256f;  // Number of rows that make up the plane
            var distance = 100f; // Distance of farthest row
            var dz = distance / numRows; // Delta distance between each row

            // Calculate vector to far left column on the screen
            var col = 0;
            var offset1 = 2f * col * inverseNumRaysMinusOne - 1f;
            var dir1 = forward + perpendicular * (halfWidth * offset1);
            dir1 *= dz;

            // Calculate vector to far right column on the screen
            col = 319;
            var offset2 = 2f * col * inverseNumRaysMinusOne - 1f;
            var dir2 = forward + perpendicular * (halfWidth * offset2);
            dir2 *= dz;
          
            var rowPoint1 = camera.Position;  // Ray starts at camera pos
            var rowPoint2 = camera.Position;  //
            var grid = 128f;  // Used to tile texture over a 128x128 grid
          
            for (var i = 0; i < numRows; ++i) // Start from bottom of the screen (closest to camera position)
            {
                rowPoint1 += dir1;
                rowPoint2 += dir2;

                // Tile uv over grid
                var texCoordinates = new Vector4
                {
                    x = Vector2.Dot(rowPoint1, camera.right) / grid,
                    y = Vector2.Dot(rowPoint1, camera..up) / grid,
                    z = Vector2.Dot(rowPoint2, camera.right) / grid,
                    w = Vector2.Dot((rowPoint2 + dir2), camera.up) / grid,
                };

                DrawRow(i, SCREEN_X_MIN, SCREEN_X_MAX, texCoordinates, texName);
            }

Where have my calculations gone wrong?

I like this. It reminds me of my first experiments with 3D graphics. This is a great way to learn.

There are a few ways to do this, but first you’ll need a ray that goes through the pixels in the first and last column. Do do this, I would create the tip of the ray through the pixel in NDC space and convert it to world space with the inverse view-projection matrix.

// NDC space goes from (-1, -1, -1) to (1, 1, 1) in OpenGL convention (which Unity uses on C# side).
// If you consider (0, 0) to be the top left of the screen, you’ll have to flip y:
float4 ndcPosition = float4(-1 /* or +1 for right column /, y * -2 / (height - 1) + 1, 1 / far plane /, 1 / w == 1 means it’s a position */);

// Now convert it to world space. This will be the tip of your ray
float4 worldPosition = mul(viewProjectionInv, ndcPosition);
worldPosition /= worldPosition.w;

// You can get the camera position from the view-projection matrix but usually, you already have it
float3 rayOrigin = camera.Position;
float3 rayDirection = worldPosition.xyz - rayOrigin; // tip minus tail

// Intersect it with your floor plane
if (rayDirection.y == 0) return; // looking at horizon
float t = (plane.y - rayOrigin.y) / rayDirection.y;
if (t < 0) return; // ray points away from the plane

// Calculate world space intersection with the plane
float3 worldSpaceIntersection = rayOrigin + t * rayDirection;

// The simplest way to calculate uv coordinates from world coordinates for a floor plane is to use a top-down projection
float tilingFactor = 0.1f;
float2 uv = worldSpaceIntersection.xz * tilingFactor;

This will be the uv coordinates of the pixel in the first column. Repeat it with ndcPosition.x == +1 for the uv coordinates of the pixel in the right column
Hope this helps. I wrote this from memory, so it is possible that there is a bug somewhere.