Search Unity

Raymarcher with depth buffer

Discussion in 'Shaders' started by highFlylowFlow, Apr 27, 2020.

  1. highFlylowFlow

    highFlylowFlow

    Joined:
    Nov 27, 2019
    Posts:
    15
    I found some similar examples and tutorials but i can't understand them enough to make a transparency queued raymarching shader that properly draws with standard mesh-based objects in scene.

    I managed to make a shader from this Writing a raymarcher in Unity by ArtOfCode .

    There is a nice github tutorial on how to make a raymarcher which properly interacts with standard meshes in scene, but it relies on a C# script and i tried to make a self-contained shader that does not rely on any outside code to function. Is it possible ?

    I found something about depth buffers and eyespace but i'm still confused.

    @bgolus, can you help me with this ?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    That ArtOfCode tutorial is great. Goes through a lot of the reasons why stuff is being done, things that could be easy mistakes, etc.

    Now, there's two ways to go about what you're trying to do.

    One is to sample the camera depth texture, get the max linear view space depth, and limit the ray distance by that. Lots of shaders have the code for sampling the depth texture to get the linear depth, so I'm not going to into that. But there's some nuance to transforming a depth into a max ray distance since it's not as straightforward as multiplying a normalized ray by the depth. Instead of normalizing the ray you need to scale it to have a 1 unit view space depth. That is easier than it sounds, but basically you need to divide the ray by
    dot(ray, viewSpaceForwardDir)
    , where the
    viewSpaceForwardDir
    is
    mul(float3(0,0,-1), (float3x3)UNITY_MATRIX_V)
    . That's the equivalent of getting the
    .forward
    vector from the camera's transform in c#. Then you multiply that by the depth, and now you have the max ray position.

    The alternative is you use
    SV_Depth
    to modify the output the depth from the fragment shader. This has the advantage of not needing a camera depth texture, meaning it works with deferred rendering and mobile. However this means you have to transform your raytraced hit location from object space into a proper window depth value (I have posted some of the code to do that elsewhere). It also only works with opaque objects, so if this is something with semi-transparency / volume the depth texture option is the way to go.
     
    TwinCrab and highFlylowFlow like this.
  3. highFlylowFlow

    highFlylowFlow

    Joined:
    Nov 27, 2019
    Posts:
    15
    Thanks for the in-depth explanation :)
    I'm trying to go for transparency because i will try to make a volumetric raymarcher, basically a math sdf primitive that is semi-transparent and properly interacts with normal UV-ed meshes in scene. The C# was not a problem by itself, the .shader will be loaded into another closed-source software made with Unity so i (probably) don't have access to camera scripts. This self-contained route is best in my opinion.
     
  4. highFlylowFlow

    highFlylowFlow

    Joined:
    Nov 27, 2019
    Posts:
    15
    I'm sorry to bug you again @bgolus, but i'm stuck again. I made effort to complete the code and add pictures to best review the problems i have.

    Here is the code (there a lot of comments which should be enough to get the idea what i did). I tried to understand how volumetric raymarching works, but the examples i found weren't either beginner-friendly or they discussed the matter mostly as theory. So i tried to build the VolumetricRaymarch() function as generic as possible (there are more improvements to be made), which for now looks okay to me.

    Depth detection remains the problem, i tried to make the code as per your help, but i did not understand it well enough to make it work right.

    Code (CSharp):
    1. Shader "VolumetricShaderTest"
    2. {
    3.     SubShader
    4.     {
    5.         Tags
    6.         {
    7.             "Queue" = "Transparent"
    8.             "IgnoreProjector" = "True"
    9.             "RenderType" = "Transparent"
    10.         }
    11.  
    12.         ZWrite Off Lighting Off Cull Off Fog { Mode Off } Blend One Zero
    13.  
    14.         GrabPass {}
    15.  
    16.         Pass
    17.         {
    18.             CGPROGRAM
    19.  
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.             //make fog work
    23.             #pragma multi_compile_fog
    24.  
    25.             #include "UnityCG.cginc"
    26.  
    27.             struct appdata
    28.             {
    29.                 float4 vertex : POSITION;
    30.             };
    31.  
    32.             struct v2f
    33.             {
    34.                 UNITY_FOG_COORDS(1)
    35.                 float4 vertex : SV_POSITION;
    36.                 float3 ro : TEXCOORD3;
    37.                 float3 hitPosition : TEXCOORD4;
    38.                 float4 screenPos : TEXCOORD5;
    39.                 float4 uvgrab : TEXCOORD6;
    40.             };
    41.  
    42.             sampler2D _GrabTexture;
    43.             sampler2D _CameraDepthTexture;
    44.  
    45.             v2f vert (appdata v)
    46.             {
    47.                 v2f o;
    48.                 o.vertex = UnityObjectToClipPos (v.vertex);
    49.                 o.ro = mul (unity_WorldToObject, float4 (_WorldSpaceCameraPos, 1));
    50.                 o.hitPosition = v.vertex;
    51.                 o.uvgrab = ComputeGrabScreenPos (o.vertex);
    52.                 o.screenPos = ComputeScreenPos (o.vertex);
    53.                 UNITY_TRANSFER_FOG (o, o.vertex);
    54.                 return o;
    55.             }
    56.  
    57.             float GetTorus (float3 p) //torus primitive
    58.             {
    59.                 #define MAIN_RADIUS 0.3
    60.                 #define SECTION_RADIUS 0.1
    61.                 float d = length (float2 (length (p.xz) - MAIN_RADIUS, p.y)) - SECTION_RADIUS; //torus oriented in xz plane, y is main axis
    62.                 return d;
    63.             }
    64.  
    65.             float GetDensity (float3 p) //density calculation, actual code will go in here
    66.             {
    67.                 return 0.05;
    68.             }
    69.  
    70.             #define SURFACE_DISTANCE 1e-3
    71.  
    72.             //volumetric raymarcher()
    73.             //raymarch until ray intersects with SDF sphere, record that point in p and then collect density
    74.             //ro = ray origin
    75.             //rd = ray direction (normalized vector)
    76.             //maxdepth = compare if raymarched point is intersecting with background geometry
    77.             float VolumetricRaymarch (float3 ro, float3 rd, float maxdepth)
    78.             {
    79.                 float dS = 0.0; //distance to surface
    80.                 float dO = 0.0; //total distance travelled along the ray
    81.                 float3 p; //point to sample from
    82.                 //first raymarcher seeks point on a SDF surface
    83.                 //this is required to ensure consistent sampling in second raymarch, indepedent of camera world position
    84.                 for (int i = 0; i < 60; i++) //finite count of loops. tweak the number of steps to minimum
    85.                 {
    86.                     p = ro + dO * rd;
    87.                     dS = GetTorus (p); //get distance to surface of primitive found along the ray
    88.                     if (dS < SURFACE_DISTANCE) break; //near surface ? stop loop
    89.                     dO += dS; //advance along the ray
    90.                     //if sampling point intersects with background geometry, stop immediately and return 0 density
    91.                     if (dO > maxdepth) return 0.0;
    92.                 }
    93.                 //second raymarcher does the density accumulation once inside volume of primitive
    94.                 float accumulatedOcclusion = 0.0; //accumulated density for all occluding particles along the ray
    95.                 dO = 0.0; //reset travelling distance back to 0
    96.                 ro = p; //set the starting point for second raymarch at surface of primitive
    97.                 for (i = 0; i < 60; i++) //finite count of loops. more loops + smaller increments = finer detail
    98.                 {
    99.                     if (dO > maxdepth) break; //if sampling point intersects with background geometry, stop accumulating density
    100.                     //if density is close to 1, stop. some transparency must be preserved
    101.                     if (accumulatedOcclusion > 0.9) break;
    102.                     //crude speedup by using sphere primitive slightly larger than mesh object, consider it entire-volume-checking
    103.                     //ray itself goes beyond unity object rendered because of ray origin, this puts limit on that
    104.                     if (dS <= (length (p) - 1.0)) break;
    105.                     //get distance to primitive surface
    106.                     //for dS < 0, point is inside volume
    107.                     //at dS = 0 point is at the surface (rare)
    108.                     //and for dS > 0 point is outside primitive
    109.                     dS = GetTorus (p);
    110.                     //if point is inside volume, sample density at point and accumulate it
    111.                     if (dS < SURFACE_DISTANCE) accumulatedOcclusion += GetDensity (p);
    112.                     dO += 1.0 / 60; //advance the sampling step
    113.                     p = ro + dO * rd; //and calculate new sampling position
    114.                 }
    115.                 return accumulatedOcclusion; //return collected occlusion along the ray
    116.             }
    117.  
    118.             fixed4 frag (v2f i) : SV_Target
    119.             {
    120.  
    121.                 //raymarch inputs
    122.                 float3 ro = i.ro;
    123.                 float3 rd = normalize (i.hitPosition - ro);
    124.  
    125.                 //grabbed coolor for transparency effect
    126.                 fixed4 grabCol = tex2Dproj (_GrabTexture, UNITY_PROJ_COORD (i.uvgrab));
    127.  
    128.                 //grabbed for depth calculation
    129.                 // taken from https://github.com/IronWarrior/ToonWaterShader
    130.                 float cameraDepth = tex2Dproj (_CameraDepthTexture, UNITY_PROJ_COORD (i.screenPos)).r;
    131.                 float eyeDepthLinear = LinearEyeDepth (cameraDepth);
    132.  
    133.              
    134.                 //tried to my best to understand the suggestion, but i failed. i couldn't make it work
    135.                 float3 ray = i.hitPosition - ro;
    136.                 float3 viewSpaceForwardDir = mul (float3 (0, 0, -1), (float3x3)UNITY_MATRIX_V);
    137.                 ray = ray / dot (ray, viewSpaceForwardDir);
    138.                 float3 maxray = eyeDepthLinear * ray;
    139.              
    140.  
    141.                 float maxdepth = length (ro + rd * eyeDepthLinear); //raymarcher limited by this maximum depth
    142.                 float occlusion = VolumetricRaymarch (ro, rd, maxdepth); //raymarch inside primitive and return occlusion factor
    143.                 fixed4 col = lerp (grabCol, fixed4(1,1,1,1), occlusion); //simple gradient between background and opaque white using above occlusion
    144.                 //apply fog
    145.                 UNITY_APPLY_FOG (i.fogCoord, col);
    146.                 return col;
    147.             }
    148.             ENDCG
    149.         }
    150.     }
    151. }
    152.  
    And here are the images showing the problems with rendering https://imgur.com/a/zruIiVD
    It appears that problems are related to camera angle when shaded object is near edges of field of view and when camera "enters" the shaded object.

    Also i tried to find out how to properly add imgur album to forum post but had no luck.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Your ray is in object space.
    The
    viewSpaceForwardDir
    is in world space (I named that variable badly, it’s extracting the world space view forward dir from the view space matrix).
    The linear depth is in view space. However view space is always a uniformly scaled matrix, meaning the scale of view and world matches, so there’s some tricks that can be used when mixing values between the two, like how I’m extracting the forward direction with a transposed
    mul
    .

    The issue is object space is ... who knows what. Could be rotated, could be scaled, could even be skewed compared to world space!

    So really you need to calculate the max ray in world space first, then transform it back to object space, and get the length from that.
     
  6. highFlylowFlow

    highFlylowFlow

    Joined:
    Nov 27, 2019
    Posts:
    15
    Ok just to make sure : at bottom of frag there is a block of unused code that i wrote as per your instructions. Is it written properly ? Is ray properly calculated as i.hitPosition - ro ?
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Yes, it's just that it's in object space.