I am making a procedural sdf (just a bunch of cubes) based of an image. The idea is very simple. We have a stencil image:
Each texel in the image corresponds to a 3D cube. So to ray trace what I am doing is, calculate the intersection with the plane where the image should live. Grab that point and cmpute the discrete coordinate of the texel corresponding to the collision point. e.g. if each texel corresponds to a cube of size $s$ and the plane corresponds to the union of the front faces of all squares we have:
Let $p$ be te point of intersection between the ray and the plane, the discrete cooridnate is $\lfloor p / s \rfloor$.
This allows me to find the position of the cube sdf corresponding to the face being collided with. If I raytrace this sdf I get:
Which is an infinite tiling of cubes. Now I can use the above texel coordinate and the image to figure out if the point of collission $p$ corresponds to a full texel or not. If the texel is empty then i can modify the cube sdf to return infinity, making it be equivalent to an "empty" sdf.
This did get rid of unwanted cubes, but now things look 2D. Since I am deciding which cubes to keep and which to throw based on the planar information, when we are looking from the side (or any angle induced by perspective) a ray that would have collided against a filled cube will collide first agianst an empty one and be discarded when it should actually collide.
I can correct this by not testing just the current cube sdf, but the 8 neighbours as well:
This is much better and it looks solved, except...
A steeper angle will cause the issue from before. Now the ray is colliding against an empty texel much farther away from the filled ones and beign discarded when it should collide.
I was hoping someone could suggest a way to specify my sdf such that I get soemthing like the second to last picture from any angle:
float CubeSdf( vec3 p )
{
p -= vec3(voxel_size / 2.f);
vec3 b = vec3(voxel_size / 2.f) - vec3(rounding) * 0.95;
vec3 q = abs(p) - b;
return (length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0)) - rounding;
}
float CrossSdf( vec3 p, ivec2 map_index)
{
vec3 position = vec3(-voxel_size * float(map_index.x), 0, -voxel_size * float(map_index.y));
// vec3 position = vec3(0);
float final = 1e30;
int radius = 1;
const ivec2 texture_size = textureSize(terrain_map, 0);
for(int i=-radius; i<=radius; i++)
{
for(int j=-radius; j<=radius; j++)
{
ivec2 sample_coord = ivec2(map_index.x + i, texture_size.y - map_index.y - j);
bool inside_bounds =
sample_coord.x >= 0 && sample_coord.x < texture_size.x &&
sample_coord.y >= 0 && sample_coord.y < texture_size.y ;
float val = texelFetch(terrain_map, sample_coord, 0).r * int(inside_bounds);
vec3 offset = vec3(float(i) * voxel_size, 0, float(j) * voxel_size);
final = min(final, CubeSdf(p + position - offset) + float(1e30) * float(val <= 0));
}
}
return final;
}