Explain Simply | VBA

WebGL Milkdrop-style Visualizer Code

Detailed explanation and implementation of a WebGL Milkdrop-style visualizer using HTML, CSS, and JavaScript to create intricate visual effects in real-time.


Empty image or helper icon

Prompt

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL Milkdrop-style Visualizer</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { width: 100%; height: 100%; }
    </style>
</head>
<body>
    <canvas id="glCanvas"></canvas>
    <script>
        const canvas = document.getElementById('glCanvas');
        const gl = canvas.getContext('webgl');

        if (!gl) {
            alert('WebGL not supported');
            throw new Error('WebGL not supported');
        }

        const vertexShaderSource = `
            attribute vec4 a_position;
            void main() {
                gl_Position = a_position;
            }
        `;

        const fragmentShaderSource = `
            precision mediump float;
            uniform vec2 u_resolution;
            uniform float u_time;

            vec3 palette(float t) {
                vec3 a = vec3(0.5, 0.5, 0.5);
                vec3 b = vec3(0.5, 0.5, 0.5);
                vec3 c = vec3(1.0, 1.0, 1.0);
                vec3 d = vec3(0.263, 0.416, 0.557);
                return a + b * cos(6.28318 * (c * t + d));
            }

            void main() {
                vec2 uv = (gl_FragCoord.xy * 2.0 - u_resolution.xy) / u_resolution.y;
                vec2 uv0 = uv;
                vec3 finalColor = vec3(0.0);

                for (float i = 0.0; i < 4.0; i++) {
                    uv = fract(uv * 1.5) - 0.5;

                    float d = length(uv) * exp(-length(uv0));

                    vec3 col = palette(length(uv0) + i*.4 + u_time*.4);

                    d = sin(d*8. + u_time)/8.;
                    d = abs(d);

                    d = pow(0.01 / d, 1.2);

                    finalColor += col * d;
                }

                gl_FragColor = vec4(finalColor, 1.0);
            }
        `;

        function createShader(gl, type, source) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        }

        const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);

        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('Program linking error:', gl.getProgramInfoLog(program));
            throw new Error('Program linking error');
        }

        const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

        const positions = [
            -1, -1,
            1, -1,
            -1, 1,
            1, 1,
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

        const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
        const timeUniformLocation = gl.getUniformLocation(program, 'u_time');

        function resizeCanvasToDisplaySize(canvas) {
            const displayWidth = canvas.clientWidth;
            const displayHeight = canvas.clientHeight;

            if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
                canvas.width = displayWidth;
                canvas.height = displayHeight;
            }
        }

        function render(time) {
            resizeCanvasToDisplaySize(gl.canvas);
            gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);

            gl.useProgram(program);

            gl.enableVertexAttribArray(positionAttributeLocation);
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

            gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
            gl.uniform1f(timeUniformLocation, time * 0.001);

            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

            requestAnimationFrame(render);
        }

        requestAnimationFrame(render);
    </script>
</body>
</html>

Answer

Explanation of the WebGL Milkdrop-style Visualizer Code

HTML Structure

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL Milkdrop-style Visualizer</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { width: 100%; height: 100%; }
    </style>
</head>
<body>
    <canvas id="glCanvas"></canvas>
    <script src="script.js"></script>
</body>
</html>
  • <!DOCTYPE html>: Indicates that the document is HTML5.
  • <html lang="en">: Starts the HTML document, specifying the language as English.
  • <head>: Contains meta-information about the document.
    • <meta charset="UTF-8">: Specifies the character encoding.
    • <meta name="viewport" content="width=device-width, initial-scale=1.0">: Ensures the website scales correctly on all devices.
    • <title>: Sets the title of the webpage.
    • <style>: Adds CSS directly within the HTML for styling purposes. It sets the body to have no margin and the canvas to take up the full width and height of the screen.
  • <body>: The main content of the document.
    • <canvas id="glCanvas"></canvas>: HTML canvas element where the WebGL rendering will occur.
    • <script src="script.js"></script>: Links to an external JavaScript file that brings the visualization to life (assuming code would be moved there).

JavaScript (WebGL Implementation)

const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');

if (!gl) {
    alert('WebGL not supported');
    throw new Error('WebGL not supported');
}

// Vertex Shader Source Code
const vertexShaderSource = `
    attribute vec4 a_position;
    void main() {
        gl_Position = a_position;
    }
`;

// Fragment Shader Source Code
const fragmentShaderSource = `
    precision mediump float;
    uniform vec2 u_resolution;
    uniform float u_time;

    vec3 palette(float t) {
        vec3 a = vec3(0.5, 0.5, 0.5);
        vec3 b = vec3(0.5, 0.5, 0.5);
        vec3 c = vec3(1.0, 1.0, 1.0);
        vec3 d = vec3(0.263, 0.416, 0.557);
        return a + b * cos(6.28318 * (c * t + d));
    }

    void main() {
        vec2 uv = (gl_FragCoord.xy * 2.0 - u_resolution.xy) / u_resolution.y;
        vec2 uv0 = uv;
        vec3 finalColor = vec3(0.0);

        for (float i = 0.0; i < 4.0; i++) {
            uv = fract(uv * 1.5) - 0.5;
            
            float d = length(uv) * exp(-length(uv0));
            
            vec3 col = palette(length(uv0) + i*.4 + u_time*.4);
            
            d = sin(d*8. + u_time)/8.;
            d = abs(d);
            
            d = pow(0.01 / d, 1.2);
            
            finalColor += col * d;
        }

        gl_FragColor = vec4(finalColor, 1.0);
    }
`;

// Function to Create Shader
function createShader(gl, type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error('Program linking error:', gl.getProgramInfoLog(program));
    throw new Error('Program linking error');
}

const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

const positions = [
    -1, -1,
    1, -1,
    -1, 1,
    1, 1,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
const timeUniformLocation = gl.getUniformLocation(program, 'u_time');

function resizeCanvasToDisplaySize(canvas) {
    const displayWidth = canvas.clientWidth;
    const displayHeight = canvas.clientHeight;

    if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
        canvas.width = displayWidth;
        canvas.height = displayHeight;
    }
}

function render(time) {
    resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(program);

    gl.enableVertexAttribArray(positionAttributeLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

    gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
    gl.uniform1f(timeUniformLocation, time * 0.001);

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    requestAnimationFrame(render);
}

requestAnimationFrame(render);

Steps Explained

  1. Canvas and WebGL Context Initialization:

    • const canvas = document.getElementById('glCanvas');: Selects the <canvas> element by its ID.
    • const gl = canvas.getContext('webgl');: Retrieves the WebGL rendering context. If WebGL is not supported, an alert is shown and an error is thrown.
  2. Shader Source Code:

    • Vertex Shader: Specifies that the position of each vertex should be passed as an attribute.
    • Fragment Shader: Handles coloring of each pixel, using a custom palette function and some mathematical operations to create visual effects.
  3. Shader Creation Function:

    • createShader: Compiles the shader source code and logs errors if present.
  4. Program Creation:

    • Compiles vertex and fragment shaders.
    • Links shaders to create a WebGL program.
  5. Attribute and Buffer Setup:

    • positionAttributeLocation: Finds the location of the a_position attribute in the shader program.
    • positionBuffer: Creates and binds a buffer for vertex positions.
    • Vertices Data: Defines the positions of the vertices forming two triangles that cover the entire canvas.
  6. Uniform Setup:

    • resolutionUniformLocation: Finds the location of the resolution uniform in the shader program.
    • timeUniformLocation: Finds the location of the time uniform in the shader program.
  7. Resize Canvas:

    • resizeCanvasToDisplaySize: Adjusts the canvas size to match its displayed size.
  8. Render Function:

    • Adjusts the viewport and clears the canvas.
    • Uses the shader program and updates attribute pointers.
    • Sets the uniform values.
    • Draws the triangles as a triangle strip.
    • Calls requestAnimationFrame to repeat the rendering process, creating the animation effect.

The given script leverages WebGL to create a dynamic, real-time visual effect by manipulating vertex and fragment shaders. Each frame is dynamically rendered based on the current time, creating complex, animated patterns.

Create your Thread using our flexible tools, share it with friends and colleagues.

Your current query will become the main foundation for the thread, which you can expand with other tools presented on our platform. We will help you choose tools so that your thread is structured and logically built.

Description

Detailed explanation and implementation of a WebGL Milkdrop-style visualizer using HTML, CSS, and JavaScript to create intricate visual effects in real-time.