chore(feature/orb): add shader and noise maps
Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
225
feature/orb/src/main/assets/shaders/Orb.wgsl
Normal file
225
feature/orb/src/main/assets/shaders/Orb.wgsl
Normal file
@@ -0,0 +1,225 @@
|
||||
// ============================================================================
|
||||
// Orb.wgsl - Raymarched Displaced Sphere Shader
|
||||
// ============================================================================
|
||||
//
|
||||
// A WebGPU shader that renders an animated, noise-displaced sphere using
|
||||
// raymarching (sphere tracing). The sphere surface is perturbed by sampling
|
||||
// a noise texture, creating an organic, fluid-like appearance.
|
||||
//
|
||||
// Inspired by and ported from:
|
||||
// "Glazed Orb" by vochsel - https://www.shadertoy.com/view/4scSW4
|
||||
//
|
||||
// Techniques used:
|
||||
// - Sphere tracing (raymarching) for rendering implicit surfaces
|
||||
// - Signed distance functions (SDF) for geometry definition
|
||||
// - Noise-based displacement for organic surface deformation
|
||||
// - Fresnel reflections for realistic rim lighting
|
||||
// - Environment mapping via cubemap for reflections
|
||||
//
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Surface intersection threshold - rays closer than this are considered hits
|
||||
const EPS: f32 = 0.01;
|
||||
|
||||
// Maximum raymarching iterations to prevent infinite loops
|
||||
const MAX_ITR: i32 = 100;
|
||||
|
||||
// Maximum ray travel distance before giving up (ray miss)
|
||||
const MAX_DIS: f32 = 10.0;
|
||||
|
||||
// Pre-normalized light direction vector: normalize(vec3(0.0, 0.7, -2.0))
|
||||
// Light comes from slightly above and in front of the sphere
|
||||
const LIGHT_DIR: vec3<f32> = vec3<f32>(0.0, 0.329, -0.944);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Uniform Data Structures
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Per-frame uniform data passed from the application
|
||||
struct Uniforms {
|
||||
resolution: vec2<f32>, // Viewport resolution in pixels
|
||||
time: f32, // Elapsed time in seconds (drives animation)
|
||||
amplitude: f32, // Displacement strength (0.0 = smooth, higher = more bumpy)
|
||||
color: vec4<f32>, // Base color tint (RGB) and opacity (A)
|
||||
aspectRatio: f32, // Width / Height ratio for correct projection
|
||||
}
|
||||
|
||||
// Vertex shader output / Fragment shader input
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>, // Clip-space position
|
||||
@location(0) uv: vec2<f32>, // Texture coordinates [0,1]
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Bindings
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
||||
@group(0) @binding(1) var noiseTexture: texture_2d<f32>; // Displacement noise (cellular/Voronoi)
|
||||
@group(0) @binding(2) var cubeTexture: texture_cube<f32>; // Environment cubemap for reflections
|
||||
@group(0) @binding(3) var texSampler: sampler; // Texture sampler (linear filtering)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Vertex Shader
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Generates a fullscreen quad from 6 vertices (2 triangles).
|
||||
// Uses vertex index to determine position - no vertex buffer needed.
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
||||
// Fullscreen quad vertex positions in NDC
|
||||
var pos = array<vec2<f32>, 6>(
|
||||
vec2<f32>(-1.0, -1.0), // Triangle 1
|
||||
vec2<f32>(1.0, -1.0),
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(-1.0, 1.0), // Triangle 2
|
||||
vec2<f32>(1.0, -1.0),
|
||||
vec2<f32>(1.0, 1.0)
|
||||
);
|
||||
|
||||
var output: VertexOutput;
|
||||
output.position = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
|
||||
output.uv = pos[vertexIndex] * 0.5 + 0.5; // Convert [-1,1] to [0,1]
|
||||
return output;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Signed Distance Functions (SDF)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Returns the signed distance from point `p` to a sphere of radius `r`
|
||||
// centered at the origin. Negative inside, positive outside.
|
||||
fn sd_sph(p: vec3<f32>, r: f32) -> f32 {
|
||||
return length(p) - r;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Scene Distance Function
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Evaluates the distance field at point `p`.
|
||||
// Combines a base sphere with animated noise displacement.
|
||||
fn map(p: vec3<f32>) -> f32 {
|
||||
// Base UV from world position (scaled down for tiling)
|
||||
let u = p.xy * 0.2;
|
||||
|
||||
// Animated UV for large-scale displacement
|
||||
// Creates slow, flowing motion across the surface
|
||||
var um = u * 0.3;
|
||||
um.x = um.x + uniforms.time * 0.1; // Horizontal drift
|
||||
um.y = um.y - uniforms.time * 0.025; // Slower vertical drift
|
||||
um.x = um.x + um.y * 2.0; // Shear for diagonal motion
|
||||
|
||||
// Sample noise at two frequencies:
|
||||
// - hlg: Large-scale, animated features (the "goo" blobs)
|
||||
// - hfn: Fine detail, static relative to surface
|
||||
let hlg = textureSampleLevel(noiseTexture, texSampler, um, 0.0).x;
|
||||
let hfn = textureSampleLevel(noiseTexture, texSampler, u, 0.0).x;
|
||||
|
||||
// Combine noise samples with amplitude control
|
||||
// Fine detail is reduced where large features are prominent
|
||||
let amp = max(uniforms.amplitude, 0.15);
|
||||
let disp = (hlg * 0.4 + hfn * 0.1 * (1.0 - hlg)) * amp;
|
||||
|
||||
// Return displaced sphere distance
|
||||
return sd_sph(p, 1.5) + disp;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Lighting Utilities
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Schlick-style Fresnel approximation.
|
||||
// Returns higher values at grazing angles (rim lighting effect).
|
||||
//
|
||||
// Parameters:
|
||||
// bias - Minimum reflectivity at perpendicular view
|
||||
// scale - Reflectivity multiplier
|
||||
// power - Controls falloff sharpness
|
||||
// I - Incident ray direction (normalized)
|
||||
// N - Surface normal (normalized)
|
||||
fn fresnel(bias: f32, scale: f32, power: f32, I: vec3<f32>, N: vec3<f32>) -> f32 {
|
||||
return bias + scale * pow(1.0 + dot(I, N), power);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Fragment Shader
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Flip Y to match expected coordinate system
|
||||
let uv = vec2<f32>(input.uv.x, 1.0 - input.uv.y);
|
||||
|
||||
// Convert UV [0,1] to centered coordinates [-1,1] with aspect correction
|
||||
var d = 1.0 - 2.0 * uv;
|
||||
d.x = d.x * uniforms.aspectRatio;
|
||||
|
||||
// -------------------------
|
||||
// Camera Setup
|
||||
// -------------------------
|
||||
let campos = vec3<f32>(0.0, 0.0, -2.9); // Camera position (in front of sphere)
|
||||
let raydir = normalize(vec3<f32>(d.x, -d.y, 1.0)); // Ray direction per pixel
|
||||
|
||||
// -------------------------
|
||||
// Raymarching Loop
|
||||
// -------------------------
|
||||
var pos = campos;
|
||||
var tdist: f32 = 0.0; // Total distance traveled
|
||||
var dist: f32 = EPS; // Current step distance
|
||||
|
||||
for (var i: i32 = 0; i < MAX_ITR; i = i + 1) {
|
||||
if (dist < EPS || tdist > MAX_DIS) {
|
||||
break;
|
||||
}
|
||||
dist = map(pos);
|
||||
tdist = tdist + dist;
|
||||
pos = pos + dist * raydir;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Shading (on hit)
|
||||
// -------------------------
|
||||
if (dist < EPS) {
|
||||
// Compute surface normal via central differences
|
||||
let eps = vec2<f32>(0.0, EPS);
|
||||
let normal = normalize(vec3<f32>(
|
||||
map(pos + eps.yxx) - map(pos - eps.yxx),
|
||||
map(pos + eps.xyx) - map(pos - eps.xyx),
|
||||
map(pos + eps.xxy) - map(pos - eps.xxy)
|
||||
));
|
||||
|
||||
// Diffuse lighting (Lambertian)
|
||||
let diffuse = max(0.0, dot(LIGHT_DIR, normal));
|
||||
|
||||
// Specular highlight (Blinn-Phong style, high exponent for tight highlight)
|
||||
let specular = pow(diffuse, 256.0);
|
||||
|
||||
// Fresnel term for rim lighting / reflection intensity
|
||||
let R = fresnel(0.2, 1.4, 2.0, raydir, normal);
|
||||
|
||||
// Environment reflection from cubemap
|
||||
let reflectDir = reflect(raydir, normal);
|
||||
let envReflection = textureSampleLevel(cubeTexture, texSampler, reflectDir, 0.0).rgb;
|
||||
|
||||
// Combine lighting components
|
||||
let col = vec3<f32>(
|
||||
diffuse * uniforms.color.rgb + // Base diffuse with color tint
|
||||
specular * 0.1 + // Subtle specular highlight
|
||||
envReflection * 0.1 + // Subtle environment reflection
|
||||
R * 0.5 // Fresnel rim glow
|
||||
);
|
||||
|
||||
// Output with premultiplied alpha
|
||||
return vec4<f32>(col * uniforms.color.a, uniforms.color.a);
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Background (ray miss)
|
||||
// -------------------------
|
||||
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
BIN
feature/orb/src/main/assets/textures/noise_map.png
Normal file
BIN
feature/orb/src/main/assets/textures/noise_map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Reference in New Issue
Block a user