RayMarching Unreal HLSL

This HLSL script performs ray marching to render a scene with a sphere and a torus. It calculates the distance to these objects using Signed Distance Functions (SDF), determines surface normals, and applies lighting effects to achieve diffuse and specular highlights. The script involves normalizing the light direction, iterating through ray steps, and calculating color and opacity based on intersections with the objects.


Summary of Functionality:

  1. Ray Initialization: The script initializes the ray origin and step direction based on the view direction and world position. The light direction is also normalized.
  2. Shape Definitions (SDF): A structure defines the SDFs for a sphere and a torus, returning the distance to the surface and the object type.
  3. Normal Calculation: Functions to calculate normals for the sphere and torus using numerical gradients are provided.
  4. Ray Marching Loop: The loop iterates, calculating distances to the sphere and torus. It determines the closest object and sets the diffuse color accordingly.
  5. Intersection Handling: If an intersection is found, it calculates the surface normal, diffuse lighting based on the light direction, and specular highlights. The final color is returned.
  6. Opacity Mask: If no intersection is found, the opacity mask is set to 0, and the color is set to black.

Define Sdf shapes & normals

// Structure to define Signed Distance Functions (SDF) for shapes
struct sdfShapes
{
    // SDF for a sphere
    float4 sphere(float3 p, float3 offset, float radius)
    {
        // Calculate the distance from the point to the surface of the sphere
        float dist = length(p - offset) - radius;
        // Type of object (0 for sphere)
        float type = 0; 
        return float4(dist, type, 0, 0); 
    }

    // SDF for a torus
    float4 torus(float3 p, float3 offset, float2 radius)
    {
        float3 q = p - offset;
        // Calculate the distance from the point to the surface of the torus
        float2 t = float2(length(float2(q.x, q.z)) - radius.x, q.y);
        // Type of object (1 for torus)
        float type = 1; 
        return float4(length(t) - radius.y, type, 0, 0);
    }

    // Calculate the normal for the sphere using its SDF
    float3 calculateNormalSphere(float3 p, float3 offset, float radius)
    {
        float eps = 0.001; // Small epsilon for numerical gradient
        float3 n;
        n.x = sphere(p + float3(eps, 0, 0), offset, radius).x - sphere(p - float3(eps, 0, 0), offset, radius).x;
        n.y = sphere(p + float3(0, eps, 0), offset, radius).x - sphere(p - float3(0, eps, 0), offset, radius).x;
        n.z = sphere(p + float3(0, 0, eps), offset, radius).x - sphere(p - float3(0, 0, eps), offset, radius).x;
        return normalize(n);
    }

    // Calculate the normal for the torus using its SDF
    float3 calculateNormalTorus(float3 p, float3 offset, float2 radius)
    {
        float eps = 0.001; // Small epsilon for numerical gradient
        float3 n;
        n.x = torus(p + float3(eps, 0, 0), offset, radius).x - torus(p - float3(eps, 0, 0), offset, radius).x;
        n.y = torus(p + float3(0, eps, 0), offset, radius).x - torus(p - float3(0, eps, 0), offset, radius).x;
        n.z = torus(p + float3(0, 0, eps), offset, radius).x - torus(p - float3(0, 0, eps), offset, radius).x;
        return normalize(n);
    }
};

Ray Marching Loop

sdfShapes sdf;

for (int i = 0; i < 512; i++)
{
    // Calculate the distance to the sphere
    float4 distSphere = sdf.sphere(rayOrigin, sphereCenter, sphereRadius);
    // Calculate the distance to the torus
    float4 distTorus = sdf.torus(rayOrigin, torusCenter, float2(torusRadiusMajor, torusRadiusMinor));
    // Choose the closer distance
    float dist = min(distSphere.x, distTorus.x); 

    // Set diffuse color based on the closest shape
    if (dist == distSphere.x)
        diffuseColor = sphereColor;
    if (dist == distTorus.x)
        diffuseColor = torusColor;

    if (dist < 0.01)
    {
        // Calculate Surface Normal
        float3 normal;
        if (dist == distSphere.x)
            normal = sdf.calculateNormalSphere(rayOrigin, sphereCenter, sphereRadius); 
        else
            normal = sdf.calculateNormalTorus(rayOrigin, torusCenter, float2(torusRadiusMajor, torusRadiusMinor)); 
        
        // Calculate Diffuse Based On Light Direction
        float diffuse = max(dot(normal, lightDirection), 0); 
        
        // Reflection Vector of Light Direction & Surface Normal 
        float3 reflection = reflect(lightDirection, normal); 
        // Normalized Vector From World Position To Ray Origin 
        float3 viewDirection = normalize(-worldPos - rayOrigin); 
        // Specular, Size Based on Pow Exp.
        float specular = pow(max(dot(reflection, viewDirection), 0), 16); 
        
        // Add Specular Highlight To Diffuse Result
        return (diffuseColor * diffuse + (specular * float3(1, 1, 1))); 
    }
    
    // Continue ray marching by moving the ray origin
    opacityMask = 1;
    rayOrigin += rayStep;
}

// If no intersection, set opacity mask to 0 and return black color
opacityMask = 0;
return float3(0, 0, 0);

REFERENCES

Leave a comment