3
$\begingroup$

I'm implementing a volume raymarcher and the unlit basic version seems to be working fine. This article helped a ton grokking the view-to-volume-space transform: http://www.lebarba.com

However, it quickly gets ugly when reducing the ray step count to something that's feasible for doing a lighting nested loop.

exaggerated sampling artifacts, 32 steps:

steppy ray cube

Ryan Brucks has some nice write up here where he describes some workarounds. I'm specifically trying to understand the plane alignment technique:

Ryan Brucks plane alignment

his relevant shader snippet:

//bring vectors into local space to support object transforms
float3 localcampos = mul(float4( ResolvedView.WorldCameraOrigin,1.00000000), (Primitive.WorldToLocal)).xyz;
float3 localcamvec = -normalize( mul(Parameters.CameraVector, Primitive.WorldToLocal) );

//make camera position 0-1
localcampos = (localcampos / (Primitive.LocalObjectBoundsMax.x * 2)) + 0.5;

float3 invraydir = 1 / localcamvec;

float3 firstintersections = (0 - localcampos) * invraydir;
float3 secondintersections = (1 - localcampos) * invraydir;
float3 closest = min(firstintersections, secondintersections);
float3 furthest = max(firstintersections, secondintersections);

float t0 = max(closest.x, max(closest.y, closest.z));
float t1 = min(furthest.x, min(furthest.y, furthest.z));

float planeoffset = 1-frac( ( t0 - length(localcampos-0.5) ) * MaxSteps );

t0 += (planeoffset / MaxSteps) * PlaneAlignment;
t0 = max(0, t0);

float boxthickness = max(0, t1 - t0);
float3 entrypos = localcampos + (max(0,t0) * localcamvec);

return float4( entrypos, boxthickness ); 

For the life of me, I don't get how this works. I'm not familiar with Unreal Engine at all and doing my version in Metal.

here's my version without plane offset adjustment:

constant bool cam_inside        [[ function_constant(FogVolumeCamInsideIndex) ]];

vertex VolumeVertexInOut vert_volume_raymarch(VolumeCubeVertex                   in             [[ stage_in   ]],
                                              constant float4x4                  &mvp           [[ buffer(10) ]],
                                              constant float4x4                  &model_inv     [[ buffer(11) ]],
                                              constant float4                    &cam_pos       [[ buffer(12) ]]) {
    VolumeVertexInOut out;
    out.local_cam_pos         = model_inv * cam_pos;
    float4 world_pos          = cam_inside ? out.local_cam_pos : float4(in.position, 1.0);
    out.position              = mvp * float4(in.position, 1);
    out.volume_space_coords   = float4(world_pos.xyz + 0.5, 1).xyz;
    out.projected_coords      = mvp * (cam_inside ? float4(in.position, 1) : world_pos);
    return out;
}

constant float MAX_STEPS = 512;
constexpr sampler depth_sampler(filter::nearest);

fragment float4 frag_volume_raymarch_compute_rays(VolumeVertexInOut                  in                           [[ stage_in     ]],
                                                  texture2d<float, access::sample>   tex_back_pos                 [[ texture(1)   ]],
                                                  texture2d<float, access::sample>   tex_front_pos                [[ texture(2)   ]],
                                                  texture2d<float, access::sample>   scene_depth_buffer           [[ texture(3)   ]],
                                                  texture2d<float, access::sample>   volume_depth_buffer_back     [[ texture(4)   ]],
                                                  texture2d<float, access::sample>   volume_depth_buffer_front    [[ texture(5)   ]],
                                                  constant float                     &zNear_scene                 [[ buffer(1)    ]],
                                                  constant float                     &zNear_local                 [[ buffer(2)    ]],
                                                  constant float                     &zFar                        [[ buffer(3)    ]],
                                                  constant uint                      &plane_alignment             [[ buffer(4)    ]]) {

    float2 texcoord         = (in.projected_coords.xy / in.projected_coords.w + 1) / 2;
    texcoord.y              = 1-texcoord.y;

    float3 furthest         = tex_back_pos.sample(linearSampler, texcoord).xyz;
    float3 entry_pos        = in.volume_space_coords;

    float scene_depth       = scene_depth_buffer.sample(depth_sampler, texcoord).r;
    float closest_depth     = cam_inside ? 0 : volume_depth_buffer_front.sample(depth_sampler, texcoord).r;
    float furthest_depth    = volume_depth_buffer_back.sample(depth_sampler, texcoord).r;

    scene_depth             = linearize_depth(scene_depth, zNear_scene, zFar);
    closest_depth           = linearize_depth(closest_depth, zNear_local, zFar);
    furthest_depth          = linearize_depth(furthest_depth, zNear_local, zFar);
    closest_depth           = min(closest_depth, scene_depth);

    float thickness         = length(furthest - entry_pos);
    scene_depth             = map(scene_depth, closest_depth, furthest_depth, 0.0, thickness);
    scene_depth             = clamp(scene_depth, 0.0, thickness);
    thickness               = min(scene_depth, thickness);

    return float4(entry_pos, thickness);
}

I think the method of computing the ray start/end points differs a bit. The scene depth bits are for interacting with classic vertex geometry.

So the question is, how do I change my shader code to include that plane alignment thing? Or if there are other ways to do this right... I'm all ears. thanks!

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.