DEV Community

Cover image for Advanced Shader Techniques: Delving into Ray Tracing and Signed Distance Functions
Haider Aftab
Haider Aftab

Posted on

Advanced Shader Techniques: Delving into Ray Tracing and Signed Distance Functions

Shaders are an essential tool in modern graphics programming, allowing developers to create stunning visual effects and complex scenes. While basic shaders can handle simple transformations and coloring, advanced shader techniques like ray tracing and signed distance functions (SDFs) enable the creation of more sophisticated effects. This article delves into these advanced shader techniques and how they can be implemented.

Ray Tracing in Shaders

Ray tracing is a technique used to simulate the way light interacts with objects in a scene. It traces the path of light rays as they travel through the scene, calculating reflections, refractions, and shadows to create highly realistic images.

Basic Concepts of Ray Tracing

  • Ray Generation: Rays are cast from the camera or eye position into the scene.
  • Intersection Calculation: Determine where the rays intersect with objects.
  • Shading: Calculate the color at the intersection points based on material properties and lighting.

Implementing Ray Tracing in GLSL

Here is a simple example of ray tracing a sphere in GLSL:

precision mediump float;
uniform vec2 u_resolution;
uniform vec3 u_cameraPos;

vec3 sphereColor = vec3(1.0, 0.0, 0.0);
vec3 lightPos = vec3(10.0, 10.0, 10.0);

float sphereSDF(vec3 p) {
    return length(p) - 1.0; // Sphere of radius 1
}

vec3 rayDirection(float fov, vec2 fragCoord, vec2 resolution) {
    vec2 xy = fragCoord - resolution * 0.5;
    float z = resolution.y / tan(radians(fov) * 0.5);
    return normalize(vec3(xy, -z));
}

float rayMarch(vec3 ro, vec3 rd) {
    float dO = 0.0; // Distance from origin
    for (int i = 0; i < 100; i++) {
        vec3 p = ro + rd * dO;
        float dS = sphereSDF(p); // Distance to the sphere
        if (dS < 0.01) return dO; // Hit
        dO += dS;
    }
    return -1.0; // No hit
}

void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution;
    vec3 ro = u_cameraPos;
    vec3 rd = rayDirection(45.0, gl_FragCoord.xy, u_resolution);

    float t = rayMarch(ro, rd);
    if (t > 0.0) {
        vec3 hitPoint = ro + rd * t;
        vec3 normal = normalize(hitPoint);
        vec3 lightDir = normalize(lightPos - hitPoint);
        float diff = max(dot(normal, lightDir), 0.0);
        vec3 color = diff * sphereColor;
        gl_FragColor = vec4(color, 1.0);
    } else {
        gl_FragColor = vec4(0.0);
    }
}

Enter fullscreen mode Exit fullscreen mode

Signed Distance Functions (SDFs)

SDFs are mathematical functions that return the shortest distance from a point in space to the surface of an object. They are used in ray marching to efficiently render complex shapes and scenes.

Basics of SDFs

  • Distance Calculation: SDFs provide a distance value that indicates how far a point is from the nearest surface.
  • Surface Intersection: When the distance is zero, the point lies on the surface.

    Common SDFs

  • Sphere: float sphereSDF(vec3 p, float r) { return length(p) - r; }

  • Box: float boxSDF(vec3 p, vec3 b) { vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); }

    Combining SDFs

    Complex shapes can be created by combining SDFs using operations like union, intersection, and difference.

  • Union: min(d1, d2)

  • Intersection: max(d1, d2)

  • Difference: max(d1, -d2)

    Example of Combining SDFs

float sceneSDF(vec3 p) {
    float sphere = sphereSDF(p - vec3(0.0, 0.0, 5.0), 1.0);
    float box = boxSDF(p - vec3(2.0, 0.0, 5.0), vec3(1.0));
    return min(sphere, box); // Union of sphere and box
}
Enter fullscreen mode Exit fullscreen mode

Putting It All Together

By combining ray tracing and SDFs, you can create complex and realistic scenes. Here's an example shader that combines these techniques:

precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;

float sphereSDF(vec3 p, float r) {
    return length(p) - r;
}

float boxSDF(vec3 p, vec3 b) {
    vec3 q = abs(p) - b;
    return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}

float sceneSDF(vec3 p) {
    float sphere = sphereSDF(p - vec3(sin(u_time), 0.0, 5.0), 1.0);
    float box = boxSDF(p - vec3(2.0, cos(u_time), 5.0), vec3(1.0));
    return min(sphere, box);
}

vec3 rayDirection(float fov, vec2 fragCoord, vec2 resolution) {
    vec2 xy = fragCoord - resolution * 0.5;
    float z = resolution.y / tan(radians(fov) * 0.5);
    return normalize(vec3(xy, -z));
}

float rayMarch(vec3 ro, vec3 rd) {
    float dO = 0.0; // Distance from origin
    for (int i = 0; i < 100; i++) {
        vec3 p = ro + rd * dO;
        float dS = sceneSDF(p); // Distance to the scene
        if (dS < 0.01) return dO; // Hit
        dO += dS;
    }
    return -1.0; // No hit
}

void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution;
    vec3 ro = vec3(0.0, 0.0, -5.0);
    vec3 rd = rayDirection(45.0, gl_FragCoord.xy, u_resolution);

    float t = rayMarch(ro, rd);
    if (t > 0.0) {
        vec3 hitPoint = ro + rd * t;
        vec3 normal = normalize(hitPoint);
        vec3 lightDir = normalize(vec3(10.0, 10.0, 10.0) - hitPoint);
        float diff = max(dot(normal, lightDir), 0.0);
        vec3 color = diff * vec3(1.0, 0.5, 0.3);
        gl_FragColor = vec4(color, 1.0);
    } else {
        gl_FragColor = vec4(0.0);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Advanced shader techniques like ray tracing and signed distance functions open up a world of possibilities for creating realistic and complex graphics in WebGL. By understanding these concepts and learning how to implement them, you can significantly enhance the visual quality of your graphics applications. Experiment with different SDF shapes and lighting models to see the full potential of these techniques.

Top comments (0)