18.2 C
New York
Wednesday, June 12, 2024

Form Lens Blur Impact with SDFs and WebGL


signed distance field shape lens blur

Ever since discovering them via The E book of Shaders, I’ve been captivated by the facility of Signed Distance Features (SDFs). These features are extremely environment friendly for rendering complicated geometries and shapes, and for creating dynamic visible results. They simplify needed calculations for edge detection and form manipulation, making them very best for superior graphic functions.

Though I’ll do my finest to elucidate it additional via software, I extremely suggest testing shapes half of The E book of Shaders and the glorious assets from Inigo Quilez to be taught extra about SDFs.

On this tutorial, we are going to discover ways to leverage the facility of SDFs by drawing a easy form (a rounded rectangle) and discover how we will manipulate the properties and parameters of SDF features to create a “Lens Blur” impact on interplay. This requires some data of Three.js or any WebGL framework, in addition to familiarity with GLSL shading language for writing shaders.

Setup

To start, we’ll arrange a fundamental WebGL scene utilizing Three.js. This scene will embody a airplane, which is able to function our canvas the place the shader and subsequent results will probably be utilized.

const scene = new THREE.Scene();

let width = window.innerWidth;
let top = window.innerHeight;

const side = width / top;
const digicam = new THREE.OrthographicCamera(-aspect, side, 1, -1, 0.1, 1000);

const renderer = new THREE.WebGLRenderer();
doc.physique.appendChild(renderer.domElement);

const geo = new THREE.PlaneGeometry(1, 1);  // Scaled to cowl full viewport
const mat = new THREE.ShaderMaterial({
  vertexShader: /* glsl */`
    various vec2 v_texcoord;
    void predominant() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4(place, 1.0);
        v_texcoord = uv;
    }`,
  fragmentShader: /* glsl */`
    various vec2 v_texcoord;
    void predominant() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }`
});
const quad = new THREE.Mesh(geo, mat);
scene.add(quad);

digicam.place.z = 1;  // Set appropriately for orthographic

const animate = () => {
  requestAnimationFrame(animate);
  renderer.render(scene, digicam);
};
animate();

Then we will begin writing our fragment shader.

various vec2 v_texcoord;
void predominant() {
    vec2 st = v_texcoord;
    vec3 shade = vec3(st.x, st.y, 1.0);
    gl_FragColor = vec4(shade.rgb, 1.0);
}
Our first quad with a fundamental shader

Draw form with SDF perform

At its core, SDF relies on the idea of calculating the shortest distance from any level in area to the floor of a form. The worth returned by an SDF is optimistic if the purpose is outdoors the form, destructive if inside, and 0 precisely on the floor.

Visible instance of pixel size for circle SDF (from this video)

Right here is how we outline an SDF perform for a rounded rectangle, tailored from Inigo Quilez’s strategies discovered on his distance features tutorials:

/* sdf perform for spherical rectangle */
float sdRoundRect(vec2 p, vec2 b, float r) {
  vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);
  return min(max(d.x, d.y), 0.0) + size(max(d, 0.0)) - r;
}
void predominant() {
    vec2 st = v_texcoord;
    float roundness = 0.4;
    float sdf = sdRoundRect(st, vec2(dimension), roundness);;    
    vec3 shade = vec3(sdf);
    gl_FragColor = vec4(shade.rgb, 1.0);
}`

With the rounded rectangle SDF outlined, we will now manipulate its look by making use of stroke or fill results based mostly on the space values returned by the SDF:

various vec2 v_texcoord;

float sdRoundRect(vec2 p, vec2 b, float r) {
  vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);
  return min(max(d.x, d.y), 0.0) + size(max(d, 0.0)) - r;
}

/* Signed distance drawing strategies */
float stroke(float x, float dimension, float w, float edge) {
    float d = smoothstep(dimension - edge, dimension + edge, x + w * 0.5) - smoothstep(dimension - edge, dimension + edge, x - w * 0.5);
    return clamp(d, 0.0, 1.0);
}
float fill(float x, float dimension, float edge) {
    return 1.0 - smoothstep(dimension - edge, dimension + edge, x);
}

void predominant() {
    vec2 st = v_texcoord;
    
    /* sdf Spherical Rect params */
    float dimension = 1.0;
    float roundness = 0.4;
    float borderSize = 0.05;
    
    float sdf = sdRoundRect(st, vec2(dimension), roundness);
    sdf = stroke(sdf, 0.0, borderSize, 0.0);
    
    vec3 shade = vec3(sdf);
    gl_FragColor = vec4(shade.rgb, 1.0);
}`

This permits dynamic interplay, akin to adjusting the fill stage of the form in response to mouse motion. By means of these steps, you will notice how manipulating the ‘cut-off’ distance of the SDF can alter the visible output to create our impact.

Bringing Interactivity

To make our SDF drawing interactive, we go the normalized mouse place into the shader. By calculating the space between the mouse coordinates and the place inside the shader, we will dynamically regulate the fill parameters of the SDF drawing. The objective is that because the mouse strikes additional away, the form turns into much less sharp, revealing extra of the distinct gradient attribute of the SDF:

const vMouse = new THREE.Vector2();

/* retailer mouse coordinates */
doc.addEventListener('mousemove', (e) => vMouse.set(e.pageX, e.pageY));

/* add uniforms within the shader */
uniforms: {
  u_mouse: { worth: vMouse },
  u_resolution: { worth: vResolution }
}

Simply as we drew our SDF rectangle, we will equally introduce a SDF circle, utilizing the mouse coordinates inside our shader to dynamically affect its place:

various vec2 v_texcoord;
uniform vec2 u_mouse;
uniform vec2 u_resolution;
uniform float u_pixelRatio;

float sdCircle(in vec2 st, in vec2 middle) {
  return size(st - middle) * 2.0;
}
float fill(float x, float dimension, float edge) {
    return 1.0 - smoothstep(dimension - edge, dimension + edge, x);
}
void predominant() {
    vec2 st = v_texcoord;
    vec2 pixel = 1.0 / u_resolution.xy * u_pixelRatio;
    vec2 posMouse = vec2(1., 1.) - u_mouse * pixel;
  
    float circleSize = 0.3;
    float circleEdge = 0.5;
    float sdfCircle = fill(
        sdCircle(st, posMouse),
        circleSize,
        circleEdge
    );
    
    float sdf = sdfCircle;
    vec3 shade = vec3(sdf);
    gl_FragColor = vec4(shade.rgb, 1.0);
}

By combining the outcomes of the SDF circle with the parameters of the border, we obtain our remaining visible impact:

/* sdf spherical rectangle with stroke params adjusted by sdf circle */
float sdf;
sdf = sdRoundRect(st, vec2(dimension), roundness);
sdf = stroke(sdf, 0.0, borderSize, sdfCircle) * 4.0;

vec3 shade = vec3(sdf);
gl_FragColor = vec4(shade.rgb, 1.0);

Totally different shapes and past

Utilizing the identical ideas utilized in our preliminary examples, this method may be prolonged to just about any form, providing limitless prospects with SDFs. I once more suggest revisiting Inigo Quilez’s articles for a wide range of shapes that may be simply applied. Moreover, these may be creatively utilized to icons or textual content, additional increasing the probabilities.

Try some totally different variations within the demo.



Supply hyperlink

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles