www.xbdev.net
xbdev - software development
Sunday June 1, 2025
Home | Contact | Support | WebGPU Graphics and Compute ...
     
 

WebGPU/WGSL Tutorials and Articles

Graphics and Compute ...

 

SDF Normals (More Than One Way)


When working with visualizing 3-dimensional sdf (signed ditance functions) - you'll need to calculate the surface normal for a point on the surface.


SDF Normal Estimation Comparison - Visualizing different methods with labeled diagrams.
SDF Normal Estimation Comparison - Visualizing different methods with labeled diagrams.


This is easy - however, the old saying goes 'there is more than one way to skin a cat' - as with calculating the normal - there are lots of ways of doing this. Each having pros and cons.

Table comparing different methods for calculating normals for Signed Distance Functions (SDFs), focusing on their advantages and trade-offs:

Method Description Pros Cons
Finite Differences Computes normals by taking differences along axis-aligned directions. Simple and easy to implement
Works with any SDF function
equires 6 samples per evaluation
Can produce small artifacts due to numerical precision
Tetrahedral Sampling Uses four predefined directional vectors to estimate the gradient. Fewer samples (4 instead of 6)
Less axis-aligned bias
Slightly more stable in some cases
Can introduce slight asymmetry
Less intuitive compared to finite differences
Analytical Gradient Uses symbolic differentiation or known gradient formulas for specific SDFs. Exact normals
No numerical approximation needed
More efficient when available
Requires explicit gradient expressions
Not always available for complex or composite SDFs
Central Differences A variation of finite differences using a centered approach. More accurate compared to forward/backward difference
Fewer artifacts than simple finite difference
Requires 6 samples
Still subject to precision issues
Dual Number Gradient Uses automatic differentiation through dual numbers to compute exact gradients. Precise for analytical functions
Efficient in certain cases
Harder to implement for general SDFs
Requires specialized math handling


There are many other ones - including Sobel-like Sampling (for stylization or extra robustness) and Stochastic Normal Estimation (Monte Carlo-style gradient).

Each method has trade-offs between precision, performance, and ease of implementation. If you're working on ray marching or procedural rendering, the choice depends on your optimization needs!

Theory To Working Implementations

We'll implement a simple WGSL function for the different normal calculation methods using Signed Distance Functions (SDFs).


Screenshots from our test scene for the different normal calculations. The implementation algorithms are given below and the te...
Screenshots from our test scene for the different normal calculations. The implementation algorithms are given below and the test code is included at the bottom (WebGPU Lab).


What To Look For?

When looking at the generated output for the scene using the different normal algorithms - you should look for key differences in shading, smoothness, and edge definition. Finite Differences often produce a subtle graininess or minor artifacts due to numerical precision, especially on curved surfaces. Central Differences may yield slightly more stable shading but can still show sampling-related inconsistencies. Tetrahedral Sampling tends to avoid axis-aligned bias, resulting in smoother gradients, but might introduce asymmetries in certain areas. Pay close attention to how light interacts with the surface—are highlights correctly positioned, do shadows behave naturally, and is there any visible noise or distortion at sharp transitions? These comparisons will help you evaluate which method best suits your needs in terms of quality versus performance trade-offs.

1. Finite Differences Normal


This method samples the SDF at small offsets along the x, y, and z axes.

fn normal(ptvec3<f32>) -> vec3<f32> {
    
let epsilonf32 0.001;
    return 
normalize(vec3<f32>(
        
map(pt vec3<f32>(epsilon0.00.0)) - map(pt vec3<f32>(epsilon0.00.0)),
        
map(pt vec3<f32>(0.0epsilon0.0)) - map(pt vec3<f32>(0.0epsilon0.0)),
        
map(pt vec3<f32>(0.00.0epsilon)) - map(pt vec3<f32>(0.00.0epsilon))
    ));
}

• Simple to implement
• Works for any SDF
• Requires 6 samples


2. Tetrahedral Sampling Normal


This method samples along tetrahedral directions rather than axis-aligned directions.

fn normal(ptvec3<f32>) -> vec3<f32> {
    var 
nvec3<f32> = vec3<f32>(0.0);
    for (var 
ii32 041) {
        
let e 0.5773 * (2.0 vec3<f32>(
            
f32(((3) >> 1) & 1),
            
f32((>> 1) & 1),
            
f32(1)
        ) - 
1.0);
        
map(pt 0.0005 e).x;
    }
    return 
normalize(n);
}

• Fewer samples (4 instead of 6)
• Reduces axis-aligned bias
• Can introduce slight asymmetry


3. Analytical Gradient Normal (if available)


This is the best-case scenario where an explicit derivative formula exists.

fn normal(ptvec3<f32>) -> vec3<f32> {
    
let rf32 1.0// Example: Sphere SDF
    
return normalize(pt r); // Exact gradient of a sphere
}

• No numerical approximation
• Most accurate and efficient
• Requires explicit formula, not always available


4. Central Differences Normal


Similar to finite differences, but symmetrically samples in both positive and negative directions.

fn normal(ptvec3<f32>) -> vec3<f32> {
    
let epsilonf32 0.001;
    
let dx = (map(pt vec3<f32>(epsilon0.00.0)) - map(pt vec3<f32>(epsilon0.00.0))) * 0.5;
    
let dy = (map(pt vec3<f32>(0.0epsilon0.0)) - map(pt vec3<f32>(0.0epsilon0.0))) * 0.5;
    
let dz = (map(pt vec3<f32>(0.00.0epsilon)) - map(pt vec3<f32>(0.00.0epsilon))) * 0.5;
    return 
normalize(vec3<f32>(dxdydz));
}

• Slightly more accurate than finite differences
• Still requires 6 samples


5. Dual Number Gradient (Automatic Differentiation)


This method uses specialized algebra to get exact gradients. WGSL does not natively support dual numbers, but we can implement helper functions to manage the mathematics.

Implementing a normal function for an SDF (signed distance function) using dual numbers in WGSL (WebGPU Shading Language) allows you to compute exact gradients via automatic differentiation. This is especially valuable when analytical gradients are difficult or cumbersome to derive.

We compute the normal at a point on a surface defined by an SDF
map(p: vec3<f32>) -> f32
, using dual numbers for gradient estimation.

Background on Dual Numbers

A dual number is of the form:

where ε^and ε ≠ 0


For a function
f(x + ε) = f(x) + f'(x)ε
, it lets us extract derivatives automatically.

We simulate dual numbers by creating a struct that carries a value and its partial derivatives (dx, dy, dz). Each arithmetic operation must propagate these components correctly.

Step-by-step Details Dual-Number Implementation


1. Define a
Dual3
struct representing a 3D dual number.
2. Overload math functions to operate on
Dual3
.
3. Define
map_dual
that takes
Dual3
and returns a dual number representing the SDF value and gradient.
4. Extract the gradient to get the normal.

Dual Number Implementation (WGSL)

struct Dual3 {
    
valf32,
    
dxf32,
    
dyf32,
    
dzf32,
};

fn 
dual_add(aDual3bDual3) -> Dual3 {
    return 
Dual3(
        
a.val b.val,
        
a.dx b.dx,
        
a.dy b.dy,
        
a.dz b.dz
    
);
}

fn 
dual_sub(aDual3bDual3) -> Dual3 {
    return 
Dual3(
        
a.val b.val,
        
a.dx b.dx,
        
a.dy b.dy,
        
a.dz b.dz
    
);
}

fn 
dual_mul(aDual3bDual3) -> Dual3 {
    return 
Dual3(
        
a.val b.val,
        
a.dx b.val a.val b.dx,
        
a.dy b.val a.val b.dy,
        
a.dz b.val a.val b.dz
    
);
}

fn 
dual_dot(aDual3bDual3) -> Dual3 {
    return 
Dual3(
        
a.val b.val,
        
a.dx b.val a.val b.dx,
        
a.dy b.val a.val b.dy,
        
a.dz b.val a.val b.dz
    
);
}

// Create a Dual3 from a constant value with derivatives
fn make_dual3(vvec3<f32>, axisu32) -> Dual3 {
    return 
Dual3(
        
v[axis],
        
select(0.01.0axis == 0),
        
select(0.01.0axis == 1),
        
select(0.01.0axis == 2)
    );
}

// Example SDF: sphere at origin
fn sdf(pvec3<f32>) -> f32 {
    return 
length(p) - 1.0;
}

// Dual version of SDF
fn sdf_dual(pvec3<f32>) -> Dual3 {
    
// Construct dual inputs
    
let x make_dual3(p0u);
    
let y make_dual3(p1u);
    
let z make_dual3(p2u);

    
// Length: sqrt(x^2 + y^2 + z^2)
    
let x2 dual_mul(xx);
    
let y2 dual_mul(yy);
    
let z2 dual_mul(zz);
    
let sum dual_add(dual_add(x2y2), z2);
    
let len Dual3(
        
sqrt(sum.val),
        
0.5 sum.dx sqrt(sum.val),
        
0.5 sum.dy sqrt(sum.val),
        
0.5 sum.dz sqrt(sum.val)
    );

    
// Sphere: length(p) - radius
    
return Dual3(
        
len.val 1.0,
        
len.dx,
        
len.dy,
        
len.dz
    
);
}

// Normal function
fn get_normal(pvec3<f32>) -> vec3<f32> {
    
let d sdf_dual(p);
    return 
normalize(vec3<f32>(d.dxd.dyd.dz));
}

Benefits of Dual-Number Approach


• Accurate gradients even for complex analytic SDFs.
• No need for finite difference hacks or tuning epsilon.
• Can be generalized to more complex SDF trees.


Resources & Links


WebGPU Lab (SDF Test Scene) Normals

Visualizing Normal Calculation (simple 2d canvas sketch)












101 WebGPU Programming Projects. WebGPU Development Pixels - coding fragment shaders from post processing to ray tracing! WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Games WGSL 2d 3d interactive web-based fun learning WebGPU Compute WebGPU API - Owners WebGPU Development Cookbook - coding recipes for all your webgpu needs! WebGPU & WGSL Essentials: A Hands-On Approach to Interactive Graphics, Games, 2D Interfaces, 3D Meshes, Animation, Security and Production Kenwright graphics and animations using the webgpu api 12 week course kenwright learn webgpu api kenwright programming compute and graphics applications with html5 and webgpu api kenwright real-time 3d graphics with webgpu kenwright webgpu for dummies kenwright webgpu wgsl compute graphics all in one kenwright webgpu api develompent a quick start guide kenwright webgpu by example 2022 kenwright webgpu gems kenwright webgpu interactive compute and graphics visualization cookbook kenwright wgsl webgpu shading language cookbook kenwright WebGPU Shader Language Development: Vertex, Fragment, Compute Shaders for Programmers Kenwright WGSL Fundamentals book kenwright WebGPU Data Visualization Cookbook kenwright Special Effects Programming with WebGPU kenwright WebGPU Programming Guide: Interactive Graphics and Compute Programming with WebGPU & WGSL kenwright Ray-Tracing with WebGPU kenwright



 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.