I'm trying to apply raymarching on top of 3D rasterization, ordering what 3D object gets displayed based on the depth of a rasterization pass and the raymarch depth. After getting the transform right for position (using the information here) I was able to have the position of the raymarched spheres coherent with the rasterized cubes. I ran into issues however with depth being different at the edges of the screen compared to the middle, despite position never changing.
These gifs should show what I'm talking about
higher res versions https://ibb.co/ebji0y https://ibb.co/dhUGLy
Something odd is going on with depth, but I don't understand what I'm doing wrong? Here is my fragment shader for my depth texture render pass:
#version 330 core
layout(location = 0) out vec4 FragColor;
layout(location = 1) out float DepthColor;
in vec2 TexCoord;
// texture samplers
uniform sampler2D texture0;
uniform sampler2D texture1;
uniform float near;
uniform float far;
float LinearizeDepth(float depth)
{
float z = depth * 2.0 - 1.0; // back to NDC
return (2.0 * near * far) / (far + near - z * (far - near));
}
void main()
{
float depth = LinearizeDepth(gl_FragCoord.z);
DepthColor = depth;
FragColor = mix(texture(texture0, TexCoord), texture(texture1, TexCoord), 0.3);
}
and here is my shader for the final raymarch pass:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D u_screen_texture;
uniform sampler2D u_depth_texture;
uniform float u_fov;
uniform vec3 u_rotation;
uniform vec3 u_origin;
uniform vec3 u_resolution;
const float inf = uintBitsToFloat(0x7F800000u);
vec3 rotVecX(in vec3 vector, in float theta){
mat3 rotmat = mat3(1, 0, 0,
0, cos(theta), -sin(theta),
0, sin(theta), cos(theta));
return vector * rotmat;
}
vec3 rotVecY(in vec3 vector, in float theta){
mat3 rotmat = mat3(cos(theta), 0, sin(theta),
0, 1, 0,
-sin(theta), 0, cos(theta));
return vector * rotmat;
}
vec3 calculateFragementRay(in vec2 fragCoord, in vec3 resolution){
vec2 uv = fragCoord;
uv.x = (uv.x * 2.0) - 1.0;
uv.y = (2.0 * uv.y) - 1.0;
if(resolution.x >= resolution.y){
uv.x *= resolution.x/resolution.y;
}else{
uv.y *= resolution.y/resolution.x;
}
float tan_fov = tan(u_fov/2.0);
vec2 pxy = uv * tan_fov;
vec3 ray_dir = normalize(vec3(pxy, 1));
return ray_dir;
}
vec3 getRayDirection(in vec2 fragCoord, in vec3 resolution, in vec3 rotation){
// Normalized pixel coordinates (from -1 to 1)
vec3 ray_dir = calculateFragementRay(fragCoord, resolution);
ray_dir = rotVecX(ray_dir, rotation.x);
ray_dir = rotVecY(ray_dir, rotation.y);
return ray_dir;
}
struct Ray{
vec3 origin;
vec3 direction;
};
Ray getRay(in vec2 fragCoord, in vec3 resolution, in vec3 rotation, in vec3 origin){
vec3 ray_dir = getRayDirection(fragCoord, resolution, rotation);
vec3 ray_origin = origin;
return Ray(ray_origin, ray_dir);
}
struct RayMarchSettings{
int max_steps;
float max_distance;
float min_distance;
float depth_weight;
};
struct Sphere{
vec3 origin;
float radius;
};
float signedDistance(vec3 p, in Sphere sphere){
return (length(p - sphere.origin) - sphere.radius);
}
const int NULL_ID = -1;
const int SPHERE_ID = 0;
const int VSPHERE_ID = 1;
Sphere test_sphere = Sphere(vec3(0.0, 4.0, 4.0),1.0);
Sphere volume_sphere = Sphere(vec3(0.0, 1.0, 0.0),2.0);
float signedDistance(vec3 ray_endpoint, out int obj_id){
float d0 = signedDistance(ray_endpoint, test_sphere);
float d1 = signedDistance(ray_endpoint, volume_sphere);
float minval = min(d0, d1);
obj_id = minval == d0 ? SPHERE_ID : VSPHERE_ID;
return minval;
}
bool rayMarch(Ray ray, in RayMarchSettings settings, in float rasterized_depth, out float out_depth, out int out_obj_id){
float depth = 0.0;
for( int i = 0; i < settings.max_steps; ++i){
vec3 ray_endpoint = ray.origin + ray.direction * depth;
int obj_id;
float ray_distance = signedDistance(ray_endpoint, obj_id);
depth += ray_distance * settings.depth_weight;
if( depth > rasterized_depth){
break;
}
if(ray_distance <= settings.min_distance){
out_depth = depth;
out_obj_id = obj_id;
return true;
}
if(depth > settings.max_distance){
break;
}
}
out_depth = inf;
out_obj_id = NULL_ID;
return false;
}
float checkerBoardTexture(in vec2 uv){
vec2 q = floor(uv);
return clamp(mod( q.x+q.y, 2.0 ), 0.25, 1.0);
}
float fogify(in float dist){
return 1.0/ (1.0 + dist*dist * 0.1);
}
RayMarchSettings rm_settings = RayMarchSettings(128, 100000.0, 0.0001, 0.5);
void main()
{
vec3 origin = vec3(u_origin.x, u_origin.y, u_origin.z);
origin = origin.zyx;
Ray ray = getRay(TexCoords, u_resolution, u_rotation, origin);
vec3 texture_color = texture(u_screen_texture, TexCoords).rgb;
float rasterized_depth = texture(u_depth_texture, TexCoords).r;
float depth = inf;
int obj_id = NULL_ID;
vec3 color;
if(rayMarch(ray, rm_settings,rasterized_depth, depth, obj_id)){
vec3 endpoint = ray.origin + ray.direction * depth;
float pattern_value = checkerBoardTexture(endpoint.xz * 4.0);
if(obj_id == SPHERE_ID){
color = vec3(1.0)*pattern_value;
}
if(obj_id == VSPHERE_ID){
color = vec3(0.0, 1.0, 0.0)*pattern_value;
}
switch(obj_id){
case SPHERE_ID:
color = vec3(1.0)*pattern_value;
break;
case VSPHERE_ID:
color = vec3(0.0, 1.0, 0.0)*pattern_value;
break;
default:
color = vec3(1.0,0.0,1.0)*pattern_value;
}
}else{
float re_rasterized_depth = rasterized_depth == inf ? 0.0 : rasterized_depth;
color = texture_color +re_rasterized_depth/100.f;
}
FragColor = vec4(color, 1.0);
}
The u_depth_texture
is where the depth comes from (and what DepthColor
corresponds to) The u_screen_texture
is just a texture of the rasterization pass information (output of FragColor
).
UPDATE:
So this answer outlined exactly my problem. I had to make the following changes:
vec3 getRotatedRayDirection(vec3 ray_dir, in vec3 rotation){
// Normalized pixel coordinates (from -1 to 1)
//vec3 ray_dir = calculateFragementRay(fragCoord, resolution);
ray_dir = rotVecX(ray_dir, rotation.x);
ray_dir = rotVecY(ray_dir, rotation.y);
return ray_dir;
}
struct Ray{
vec3 origin;
vec3 direction;
float cosA;
};
Ray getRay(in vec2 fragCoord, in vec3 resolution, in vec3 rotation, in vec3 origin){
vec3 ray_dir = calculateFragementRay(fragCoord, resolution);
vec3 rot_ray_dir = getRotatedRayDirection(ray_dir, rotation);
vec3 ray_origin = origin;
float cosA = ray_dir.z; // a.z/length(a), already normalized so length(a) == 1.0.
return Ray(ray_origin, rot_ray_dir, cosA);
}
Above I basically added cosA as a seperate value onto the ray, as suggested by the linked answer, with the appropriate code changes to avoid re-running calculateFragmentRay
. However I needed to figure out which cosine corresponded to the one in thier picture, I knew it was a lateral dimension, it happened to be Z in this case, and I used the standard method of retrieving cosine from a normalized vector.
After that I did exactly what the linked answer said and made the following change:
if( (depth * ray.cosA) > rasterized_depth){
break;
}
And here are the results! No depth artifacts!