Brad Woods Digital Garden

Notes / JavaScript / three.js / Shaders / Shaders 102 sending data

The Warhammer 40k Adeptus Mechanicus symbol

Table of contents

    Shaders 102 - sending data

    Planted: 

    Status: seed

    Hits: 1192

    Intended Audience: Creative coders, Front-end developers

    There are three different ways to send data to shaders; attributes, uniforms and varyings.

    Attributes

    Purpose: to send unique data to each vertex.
    Convention: prefix variable name with 'a' (example: aMyAttribute).
    How: store the data in a buffer using BufferAttribute(<array>, <itemSize>) and send using the geometry's setAttribute method.

    /index.js

    geometry.setAttribute("aMyAttribute", new THREE.BufferAttribute(values, 1))

    <array> size should be <itemSize> multiplied by number of vertices.
    <itemSize> is the number of items you want to send per vertex.

    Access the value in the shader using keyword in.

    vertexShader.glsl

    in float aMyAttribute;
    ...

    Example: send a random value to each vertex to change z-position. Creating a terrain-like surface.

    Uniforms

    Purpose: to send the same data to all vertices and fragments (all data is equal, aka uniform).
    Convention: prefix variable name with 'u' (example: uMyUniform).
    How: use the material's uniforms property.

    /index.js

    // set initial value
    const material = new THREE.ShaderMaterial({
    uniforms: {
    uMyUniform: {
    value: ...
    }
    },
    ...
    // change value
    material.uniforms.uMyUniform.value = ...;

    Example: set the same color for all fragments.

    Varyings

    Purpose: to send data from the vertex shader to the fragment shader. Useful because if a primitive (like a triangle) has two vertices with different data, the fragment shader will smoothly interpolate the data across the surface. For example, if a triangle has one red and two blue vertices, the surface will be a red to blue gradient.
    Convention: prefix variable name with 'v' (example: vMyVarying).
    How: declare a varying variable in the vertex shader (keyword: out) and fragment shader (keyword: in). Set its value in the vertex shader and read it in the fragment shader.

    /vertexShader.glsl

    out float vMyVarying;
    void main() {
    ...
    vMyVarying = ...
    }

    /fragmentShader.glsl

    in float vMyVarying;
    void main() {
    gl_FragColor = vec4(vec3(vMyVarying), 1.0);
    }

    Example: set each vertex color depending on its z-position. The lower, the more black, the higher the more white.

    Debugging

    Shaders don't provide many debugging tools. We don't have console.log to confirm data in a shader is correct. Instead, we can use grayscale color. Normalize the data, send it to the fragment shader and use it as the color to confirm data is correct. This is used in the Pointer Position example below.

    Pointer Position

    To send the pointer (mouse or touch) position to a shader, we can use a Raycaster. This will signal if the pointer is intersecting with a mesh. If intersecting, convert the coordinates and send them as a uniform.

    /index.js

    const raycaster = new THREE.Raycaster()
    const pointer = new THREE.Vector2()
    function onPointerMove(event) {
    // Convert screen coords to clip-space coords (go from -1 to +1, left to right, top to bottom)
    pointer.x = (event.clientX / sizes.width) * 2 - 1
    pointer.y = (-event.clientY / sizes.height) * 2 + 1
    // Update the ray with a new origin and direction.
    raycaster.setFromCamera(pointer, camera)
    const intersections = raycaster.intersectObjects(scene.children)
    if (intersections.length) {
    const intersection = intersections[0]
    material.uniforms.uUvPointer.value = intersection.uv
    }
    }

    To confirm the data is correct in the shader, I'm using the distance(...) function. It calculates the distance between two vec2 positions. The first is the position of the current vertex the shader is processing (uv). The second is the mouse position (uUvPointer). Using 1.0 - distance(uv, uUvPointer) returns a higher value the smaller the distance. Using this value for color render a gradient radiating from the pointer.

    Feedback

    Have any feedback about this note or just want to comment on the state of the economy?

    Where to next?

    JavaScript
    three.js
    Shaders
    Arrow pointing downYOU ARE HERE
    A Johnny Cab pilot