Skip to content

Instantly share code, notes, and snippets.

@pema99
Created May 5, 2024 22:31
Show Gist options
  • Save pema99/092cd7e8bca4c0e3cae3a0e84e96d698 to your computer and use it in GitHub Desktop.
Save pema99/092cd7e8bca4c0e3cae3a0e84e96d698 to your computer and use it in GitHub Desktop.

Revisions

  1. pema99 created this gist May 5, 2024.
    242 changes: 242 additions & 0 deletions SlangMarcher.shader
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,242 @@
    Shader "pema99/SlangMarcher"
    {
    Properties
    {
    _Iterations ("Max iterations", Float) = 128
    _MaxDist ("Max distance", Float) = 50
    _MinDist ("Min distance", Float) = 0.001
    }
    SubShader
    {
    Tags { "Queue"="Transparent" "DisableBatching"="True" }

    Pass
    {
    Cull Front

    CGPROGRAM
    #pragma target 5.0
    #include "UnityCG.cginc"

    bool _WorldSpace;
    float _Iterations;
    float _MinDist;
    float _MaxDist;

    struct v2f
    {
    float4 vertex : SV_POSITION;
    float3 camera_position : TEXCOORD0;
    float3 surface_position : TEXCOORD1;
    UNITY_VERTEX_OUTPUT_STEREO
    };

    [shader("vertex")]
    v2f vert (appdata_base v)
    {
    v2f o;

    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_INITIALIZE_OUTPUT(v2f, o);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    o.vertex = UnityObjectToClipPos(v.vertex);
    o.camera_position = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1)).xyz;
    o.surface_position = v.vertex.xyz;

    return o;
    }

    // Highlighted feature: Interfaces
    interface ISignedDistanceFunction
    {
    [Differentiable]
    float distance(float3 p);
    }

    struct Sphere : ISignedDistanceFunction
    {
    float radius;

    // Highlighted feature: Constructors
    __init(float radius)
    {
    this.radius = radius;
    }

    [Differentiable]
    float distance(float3 p)
    {
    return length(p) - radius;
    }
    }

    struct Torus : ISignedDistanceFunction
    {
    float2 size;

    __init(float2 size)
    {
    this.size = size;
    }

    [Differentiable]
    float distance(float3 p)
    {
    let q = float2(length(p.xz) - size.x, p.y);
    return length(q) - size.y;
    }
    }

    // Highlighted feature: Heterogeneous arrays
    static const ISignedDistanceFunction scene[] =
    {
    Sphere(0.25),
    Torus(float2(0.35, 0.08)),
    };

    [Differentiable]
    float scene_distance(float3 position)
    {
    var dist = _MaxDist;
    [MaxIters(16)]
    for (int i = 0; i < scene.getCount(); i++)
    {
    dist = operator_smooth_union(dist, scene[i].distance(position), 0.05);
    }
    return dist;
    }

    [Differentiable]
    float operator_smooth_union(float d1, float d2, float k)
    {
    let h = saturate(0.5 + 0.5 * (d2-d1) / k);
    return lerp(d2, d1, h) - k * h * (1.0 - h);
    }

    // Highlighted feature: Autodifferentation
    float3 normal(float3 position)
    {
    // Use reverse-mode autodiff to calculate the gradient
    var diffPosition = diffPair(position);
    bwd_diff(scene_distance)(diffPosition, 1);
    let gradient = normalize(diffPosition.d);
    return gradient;

    // Just to demonstrate: We could also have used forward mode.
    /*let diffX = diffPair(position, float3(1, 0, 0));
    let diffY = diffPair(position, float3(0, 1, 0));
    let diffZ = diffPair(position, float3(0, 0, 1));
    float3 gradient = float3(
    fwd_diff(scene_distance)(diffX).d,
    fwd_diff(scene_distance)(diffY).d,
    fwd_diff(scene_distance)(diffZ).d
    );
    return normalize(gradient);*/
    }

    struct MarchResult
    {
    float distance;
    int steps;

    __init(float distance, int steps)
    {
    this.distance = distance;
    this.steps = steps;
    }
    }

    // Highlighted feature: Enums
    enum StepResult
    {
    Continue,
    Hit,
    Miss
    }

    struct MarchIterator
    {
    MarchResult result;

    float3 ray_origin;
    float3 ray_direction;

    // Highlighted feature: Properties
    property int steps { get { return result.steps; } }

    __init(float3 ray_origin, float3 ray_direction)
    {
    this.ray_origin = ray_origin;
    this.ray_direction = ray_direction;
    this.result = MarchResult(0, 0);
    }

    // Highlighted feature: Mutating member functions
    [mutating]
    StepResult step()
    {
    float3 current_position = ray_origin + ray_direction * result.distance;
    float current_distance = scene_distance(current_position);
    result.distance += current_distance;
    result.steps++;

    if (result.distance > _MaxDist)
    return StepResult.Miss;
    else if (current_distance < _MinDist)
    return StepResult.Hit;
    else
    return StepResult.Continue;
    }
    }

    // Highlighted feature: Optional types
    Optional<MarchResult> march(float3 ray_origin, float3 ray_direction)
    {
    var iter = MarchIterator(ray_origin, ray_direction);

    while (iter.steps < _Iterations)
    {
    switch (iter.step())
    {
    case StepResult.Hit:
    return Optional<MarchResult>(iter.result);
    case StepResult.Miss:
    return none;
    case StepResult.Continue:
    break;
    }
    }

    return Optional<MarchResult>(iter.result);
    }

    [shader("fragment")]
    float4 frag (v2f i, out float depth : SV_Depth) : SV_Target
    {
    let ray_origin = i.camera_position;
    let ray_direction = normalize(i.surface_position - i.camera_position);

    let result = march(ray_origin, ray_direction);
    if (result == none)
    {
    discard;
    }

    let hit_position = ray_origin + ray_direction * result.value.distance;
    let hit_normal = normal(hit_position);

    let clip_position = UnityObjectToClipPos(float4(hit_position, 1.0));
    depth = clip_position.z / clip_position.w;

    let base_color = hit_normal * 0.5 + 0.5;
    let ambient_occlusion = 1.0 - (result.value.steps / _Iterations);

    return float4(base_color * ambient_occlusion, 1.0);
    }
    ENDCG
    }
    }
    }