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:
Ryan Brucks has some nice write up here where he describes some workarounds. I'm specifically trying to understand the plane alignment technique:
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!

