webglmeshvertex

How to translate, append, and loop vertices to create a WebGL mesh?


I can generate a cube in WebGL using a simple vertices array. I want to generate a larger cube comprised of many of the smaller cubes. What is wrong with my programmatic approach to this and/or with the peripheral WebGL syntax which I am not familiar with?

Desired

What I see

        let canvas = document.getElementById(name);
        let gl = canvas.getContext("webgl");
        canvas.width = 300;
        canvas.height = 300;


        // grid spacing
        const N = 3;
        const h = 1.5/N;

        // vertices used to create box
        const box = [

            // Front
            h, h, h,
            h, -h, h,
            -h, h, h,
            -h, h, h,
            h, -h, h,
            -h, -h, h,
        
            // Left
            -h, h, h,
            -h, -h, h,
            -h, h, -h,
            -h, h, -h,
            -h, -h, h,
            -h, -h, -h,
        
            // Back
            -h, h, -h,
            -h, -h, -h,
            h, h, -h,
            h, h, -h,
            -h, -h, -h,
            h, -h, -h,
        
            // Right
            h, h, -h,
            h, -h, -h,
            h, h, h,
            h, h, h,
            h, -h, h,
            h, -h, -h,
        
            // Top
            h, h, h,
            h, h, -h,
            -h, h, h,
            -h, h, h,
            h, h, -h,
            -h, h, -h,
        
            // Bottom
            h, -h, h,
            h, -h, -h,
            -h, -h, h,
            -h, -h, h,
            h, -h, -h,
            -h, -h, -h,
        ];

        const vertices = [];
        for (let i = 0; i < 2; i++) {
            let out = box;
            vertices.push(...mat4.translate(out, box, [i*h, i*h, i*h]));
            out = null;
        }



        // assigning color to each cube
        let colorData = [];
        // divide by N and then by faces
        for (let cube = 0; cube < vertices.length/N; cube++) {
            let faceColor = randomColor();
            for (let face = 0; face < vertices.length/(N*6); face++) {
                colorData.push(...faceColor);
            }
        }


        // load buffers
        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // bind to current array buffer
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); // load vertex data into buffer and choose draw mode

        const colorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); // bind to current array buffer
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorData), gl.STATIC_DRAW); // load vertex data into buffer and choose draw mode



        // routine to output xyz coordinates from buffer into vertex shader
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, `
        precision mediump float;

        attribute vec3 position;
        attribute vec3 color;
        varying vec3 vColor;

        uniform mat4 matrix;

        void main() {
            vColor = color;
            gl_Position = matrix * vec4(position, 1);
        }
        `);
        gl.compileShader(vertexShader)



        //routine to assign color shader
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, `
        precision mediump float;

        varying vec3 vColor;
        void main() {
            gl_FragColor = vec4(vColor, 1);
        }
        `);
        gl.compileShader(fragmentShader);


        
        // "link" vertex and color shaders
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);

        // assign position, color, and uniform locations
        const positionLocation = gl.getAttribLocation(program, `position`); // attribute index
        gl.enableVertexAttribArray(positionLocation);
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);

        const colorLocation = gl.getAttribLocation(program, `color`); // attribute index
        gl.enableVertexAttribArray(colorLocation);
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false, 0, 0);

        const uniformLocation = {
            matrix : gl.getUniformLocation(program, `matrix`)
        };

        gl.useProgram(program);
        gl.enable(gl.DEPTH_TEST);



        let matrix = mat4.create();
        mat4.translate(matrix, matrix, [0, 0, -3]);
        mat4.rotateY(matrix, matrix, Math.PI/4);

        let projectionMatrix = mat4.create();
        mat4.perspective(projectionMatrix,
            90 * Math.PI/180,   // vertical fov
            canvas.height/canvas.width, // aspect ratio
            1e-4,   // near cull distance
            1e4 // far cull distance
        );

        let outMatrix = mat4.create();


        // animate
        animate();

        function animate() {
            requestAnimationFrame(animate);

            mat4.multiply(outMatrix, projectionMatrix, matrix);
            gl.uniformMatrix4fv(uniformLocation.matrix, false, outMatrix);
            gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3); // triangle, first vertex, draw all three
            // divide length of vertices array by 3 to get the number of vertices. vertices = coordinateComponents/componentsPerCoordinate(x,y,z)
        }

I believe the issue is in the first 75-100 lines, from the initialization of the box to the end of the loop that appends translated vertices to the vertices array. I thought it may have to do with the location of the final vertex of one cube and the first of the following, but because each triangle is a closed shape, I no longer think that is it. Please share your insights, and thank you in advance for your help.


Solution

  • There are a few issues with your approach. Let's visited them one-by-one:

    mat4.translate(out, box, [i*h, i*h, i*h]);
    

    According to the documentation, mat4.translate does not actually transform your box vertices. Instead it generates a translation matrix.

    let translation = [];
    mat4.translate(translation, mat4.identity([]), [i * h, i * h, i * h]);
    

    You must then multiply each box vertex by this translation matrix to get the transformed vertex:

    for (let j = 0; j < box.length / N; j += N) {
      let translatedVertex = [];
      const vertex = [box[j], box[j + 1], box[j + 2]];
      vec3.transformMat4(translatedVertex, vertex, translation);
      vertices.push(...translatedVertex);
    }
    

    Translating by i*h for each dimension will only offset your boxes along all three dimensions, so you will need to change your loop to translate along just the x-axis, then the y-axis, then the z-axis.

    const vertices = [];
    for (let x = 0; x < 3; x++) {
        for (let y = 0; y < 3; y++) {
            for (let z = 0; z < 3; z++) {
                let translation = [];
                mat4.translate(translation, mat4.identity([]), [x * h, y * h, z * h]);
                for (let j = 0; j < box.length / N; j += N) {
                    let translatedVertex = [];
                    const vertex = [box[j], box[j + 1], box[j + 2]];
                    vec3.transformMat4(translatedVertex, vertex, translation);
                    vertices.push(...translatedVertex);
                }
            }
        }
    }