7 8 9 10 11 12 13

First triangle

28

Finally it's time to put everything together and draw our first triangle! That's quite a large HTML page, however we've already seen almost all of it parts. Take a closer look:

<!DOCTYPE html>
<html>
    <body>
        <style type="text/css">
            textarea
            {
                display: none;
            }
        </style>
        <canvas id="canvas" width="600" height="600"></canvas>
        <textarea id="vs">
            #version 300 es
            precision highp float;

            in vec2 position;

            void main(void)
            {
                gl_Position = vec4(position, 0.0, 1.0);
            }
        </textarea>
        <textarea id="fs">
            #version 300 es
            precision highp float;

            out vec4 frag_color;

            void main(void)
            {
                frag_color = vec4(1.0, 0.5, 0.25, 1.0);
            }
        </textarea>
        <script type="text/javascript">
            const canvas = document.getElementById("canvas");
            const gl = canvas.getContext("webgl2");

            function text(id)
            {
                return document.getElementById(id).value;
            }

            function compile(shader, source)
            {
                gl.shaderSource(shader, source);
                gl.compileShader(shader);

                if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
                {
                    alert(gl.getShaderInfoLog(shader));
                }
            }

            const vs = gl.createShader(gl.VERTEX_SHADER);
            const fs = gl.createShader(gl.FRAGMENT_SHADER);
            const program = gl.createProgram();

            compile(vs, text("vs"));
            compile(fs, text("fs"));

            gl.attachShader(program, vs);
            gl.attachShader(program, fs);
            gl.linkProgram(program);

            if(!gl.getProgramParameter(program, gl.LINK_STATUS))
            {
                alert(gl.getProgramInfoLog(program));
            }

            const h = Math.sqrt(0.75);

            const vertices =
            [
               -0.5,     -h / 3,
                0.0,  2 * h / 3,
                0.5,     -h / 3
            ];

            const buffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

            const position = gl.getAttribLocation(program, "position");
            gl.enableVertexAttribArray(position);
            gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);

            function draw(timestamp)
            {
                gl.clearColor(0, 0, 0, 1);
                gl.clear(gl.COLOR_BUFFER_BIT);

                gl.useProgram(program);
                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.drawArrays(gl.TRIANGLES, 0, 3);

                window.requestAnimationFrame(draw);
            }

            window.requestAnimationFrame(draw);
        </script>
    </body>
</html>

Our position input value is a vertex attribute. We need to get its location, enable it, and bind a data to it:

const position = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(position);
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);

First two lines are obvious. Let's take a closer look at the last one:

gl.vertexAttribPointer
(
    position, // The attrubute location
    2,        // The size of the attribute. The size is 2 because position is 2-components vector
    gl.FLOAT, // A type of each component
    false,    // True if values are normalized. In our case they're not
    0,        // A stride between the vertex data. In our case the data is packed tightly, that's why the stride is 0
    0         // The offset of the data in the buffer. 0 in our case since the data is started right away
);

So, vertexAttribPointer is a function that tells the GPU how the data is presented in a GPU buffer.

And the last step is:

gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.drawArrays(gl.TRIANGLES, 0, 3);

Finally, we used our GLSL program. After that we told GPU that we're going to work with buffer GPU buffer. And right after that we called drawArrays which has drawn our first triangle. Whew! Put the example above into a file and open it in your web browser. You should see a picture like this:

WebGL triangle

drawArrays takes three parameters. The first one sets a type of the geometry topology. In the example above it's gl.TRIANGLES which means that every three vertices represent a triangle. The second parameter is a vertex offset in the GPU buffer. And the third parameter is a count of vertices that should be rendered. One triangle consists of three vertices — that's why the third parameter is 3.

Congratulations! You've just drawn your very first triangle with WebGL!

Rate this post:
Lesson 9
Lesson 11
Share this page:

Learning plan

Now let's create a fragment shader, compile it and check for errors
It's time to link our shaders into a completed GLSL program
Now we need to create a GPU buffer and transfer a geometry data into it
10. First triangle
Everything is ready to draw our first triangle
11. Uniforms
How to use draw call level parameters to control the shading process
Let's add more attributes to vertices and use them to enhance our triangle
How to evaluate a vertex-specified data and interpolate it between vertices