Brad Woods Digital Garden

Notes / JavaScript / three.js / Shaders / Shaders 101

The Warhammer 40k Adeptus Mechanicus symbol

Table of contents

    A flat surface wireframe.

    Shaders 101

    Planted: 

    Tended: 

    Status: sprout

    Hits: 3994

    Intended Audience: Creative coders, front-end developers with basic knowledge of three.js

    Mesh

    In three.js, an object like a cube or sphere is called a mesh. This mesh is a plane. A flat, rectangular surface. Every mesh is made of a geometry and a material.

    /index.js

    Geometry

    The geometry defines the initial shape. The plane is made up of segments. A segment consists of two triangles. A triangle is a primitive, a basic building block.

    Triangle with edges and vertices labeled

    /index.js

    Material (shader)

    The material defines how the geometry surface appears. Material is a shader.

    /index.js

    A line with labels: JavaScript -> Vertex Shader -> Fragment Shader -> Pixels

    Pipeline

    The pipeline that transforms three.js code to pixels looks like this:

    1. 1. three.js JavaScript code is executed on the CPU
    2. 2. Shader code is executed on the GPU
    3. 3. Pixels are rendered on the screen

    A shader has two parts, a vertex and fragment shader. They work like this:

    1. 1. Each vertex runs through the vertex shader to get its viewport space position.
    2. 2. Primitives are converted into fragments. Fragments are data corresponding to a potential pixel.
    3. 3. Each fragment runs through the fragment shader to get its color.

    Vertex shader

    three.js provides some shaders but to open up creativity possibilities, I need to understand how to make my own. The starting point is using THREE.ShaderMaterial, a material that renders custom shader code. Below is boilerplate code.

    /index.js

    The vertex shader's main function is called for every vertex.

    • The purpose of the vertex shader is to set the value for gl_Position to the current vertex's clip space coordinates.
    • gl_Position is a reserved global variable
    • position, projectMatrix and modelViewMatrix are being passed in to the shader by three.js
    • position is the current vertex local space coordinates
    • multiplying position by projectMatrix and modelViewMatrix converts it to clip space coordinates

    GLSL

    Shaders are written in a C-like language called GLSL (OpenGL Shading Language). A data type commonly used is vectors. Similar to arrays in JavaScript.

    • vec2 myVec = vec2(1.0, 0.5) is like const myVec = [1.0, 0.5], vec3 myVec = vec3(1.0, 0.5, 1.0) is like const myVec = [1.0, 0.5, 1.0], ...
    • A value can be accessed using myVec.x or myVec.y which is like myVec[0] or myVec[1]
    • A shorthand for creating a vector with the same values is vec2 myVec = vec2(0.0), which is the same as vec2 myVec = vec2(0.0, 0.0)

    UVs

    UV is a 2D coordinate system used for the surface of the geometry. Similar to the XY coordinate system except all values are normalized (between 0 and 1). In the vertex shader, the UV coords of the current vertex are passed in by three.js.

    /index.js

    Fragment shader

    The fragment shader's main function is called for every fragment.The purpose of the fragment shader is to set the value for gl_FragColor to the current fragment's color. It accepts a normalized rgba color value.

    /index.js

    Note:

    • three.js only sends uv coords to the vertex shader. To access the current fragment's UV coords, they have to be sent from the vertex shader. See the sandbox below on how to do this. They are labeled vUv by convention.
    • transparent: true needs to be set to use an alpha value less than 1.0.

    Strength / weaknesses

    Besides low-level control, shaders are also performant. JavaScript runs on a CPU thread using sequential processing. Capable of only doing one task at a time, like rendering one pixel at a time. Shader code runs on the GPU using parallel processing. Capable of doing many tasks at once, like rendering all pixels at once. The GPU also has hardware accelerated angle, trigonometric and exponential functions. Functions used to create shader effects.

    Their weakness is writing in native WebGL. Things aren't abstracted and simplified like in three.js. Making code complex. Also, the only output is color. There is no console.log for debugging.

    Sandbox

    Feedback

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