Using Web-GL with Typescript

Sample 4 - Textured Quad utilising a Texture Buffer.


This page extends the previous example by a texture to the quad, the texture used from one of the OpenGL Ne-He texturing examples.
At the bottom of the code sections, you can see an example of the code in action.
The new code sections are shown in bold, for the sake of brevity, unchanged sections are omitted and denoted by ...
The example can be downloaded from Github Git

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />

    <script id="shader-vs" type="x-shader/x-vertex">
        attribute vec3 aVertexPosition;
        attribute vec2 aTextureCoord;

        uniform mat4 mVMatrix;
        uniform mat4 pMatrix;

        varying vec2 vTextureCoord;

        void main(void) {
            gl_Position = pMatrix * mVMatrix * vec4(aVertexPosition, 1.0);
            vTextureCoord = aTextureCoord;
        }
        </script>

    <script id="shader-fs" type="x-shader/x-fragment">
        precision mediump float;
        varying highp vec2 vTextureCoord;
        uniform sampler2D uSampler;

        void main(void) {
            gl_FragColor = texture2D(uSampler, vTextureCoord);
        }
    </script>

    <script src="gl-matrix.js"></script>
    <script src="app.js"></script>
</head>

<body>
    <canvas id="CanvasGL" style="border: none;" width="500" height="500"></canvas>
</body>
</html>
The Typescript code below initialises the canvas, shaders, buffers and sets up the camera to draw a simple coloured Quad.

declare var mat4: any;
declare var vec3: any;

class TexturedPlane {

    ...

    private vertexPositionBuffer;
    private vertexIndexBuffer;
    private cubeVertexTextureCoordBuffer;

    private vertices;
    private indices;
    private neheTexture;

    constructor() {

        ...

        this.InitBuffers();
        this.InitTexture();
        this.SetUpCamera();

        ...

    }

    ...

    private InitTexture() {
        this.neheTexture = this.gl.createTexture();
        this.gl.bindTexture(this.gl.TEXTURE_2D, this.neheTexture);

        // Because images have to be download over the internet
        // they might take a moment until they are ready.
        // Until then put a single pixel in the texture so we can
        // use it immediately. When the image has finished downloading
        // we'll update the texture with the contents of the image.
        const level = 0;
        const internalFormat = this.gl.RGBA;
        const width = 1;
        const height = 1;
        const border = 0;
        const srcFormat = this.gl.RGBA;
        const srcType = this.gl.UNSIGNED_BYTE;
        const pixel = new Uint8Array([0, 0, 255, 255]);  // opaque blue
        this.gl.texImage2D(this.gl.TEXTURE_2D, level, internalFormat,
            width, height, border, srcFormat, srcType,
            pixel);

        this.neheTexture.image = new Image();
        var __this = this;
        this.neheTexture.image.onload = function () {

            __this.gl.bindTexture(__this.gl.TEXTURE_2D, __this.neheTexture);
            __this.gl.texImage2D(__this.gl.TEXTURE_2D, 0, __this.gl.RGBA, __this.gl.RGBA, __this.gl.UNSIGNED_BYTE, __this.neheTexture.image);

            // WebGL1 has different requirements for power of 2 images vs non power of 2 images so check if the image is a
            // power of 2 in both dimensions.
            if (__this.IsPowerOf2(__this.neheTexture.image.width) && __this.IsPowerOf2(__this.neheTexture.image.height)) {
                // Yes, it's a power of 2. Generate mips.
                __this.gl.generateMipmap(__this.gl.TEXTURE_2D);
            } else {
                // No, it's not a power of 2. Turn of mips and set wrapping to clamp to edge
                __this.gl.texParameteri(__this.gl.TEXTURE_2D, __this.gl.TEXTURE_WRAP_S, __this.gl.CLAMP_TO_EDGE);
                __this.gl.texParameteri(__this.gl.TEXTURE_2D, __this.gl.TEXTURE_WRAP_T, __this.gl.CLAMP_TO_EDGE);
                __this.gl.texParameteri(__this.gl.TEXTURE_2D, __this.gl.TEXTURE_MIN_FILTER, __this.gl.LINEAR);
            }

            // Enable transparency
            __this.gl.blendFunc(__this.gl.SRC_ALPHA, __this.gl.ONE_MINUS_SRC_ALPHA);
            __this.gl.enable(__this.gl.BLEND);
        }
        this.neheTexture.image.src = "nehe.gif";
    }

    private IsPowerOf2(value) {
        return (value & (value - 1)) == 0;
    }

    private InitShaders() {
        ...

        // Bind the texture attribute
        this.shaderProgram.textureCoordAttribute = this.gl.getAttribLocation(this.shaderProgram, "aTextureCoord");
        this.gl.enableVertexAttribArray(this.shaderProgram.textureCoordAttribute);

        this.shaderProgram.pMatrixUniform = this.gl.getUniformLocation(this.shaderProgram, "pMatrix");
        this.shaderProgram.mvMatrixUniform = this.gl.getUniformLocation(this.shaderProgram, "mVMatrix");
    }

    ...

    private InitBuffers() {
        ...

        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.vertices), this.gl.STATIC_DRAW);
        this.vertexPositionBuffer.itemSize = 3;
        this.vertexPositionBuffer.numItems = 4;

        this.cubeVertexTextureCoordBuffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexTextureCoordBuffer);
        var textureCoords = [
            0.0, 0.0, //uv0
            1.0, 0.0, //uv1
            1.0, 1.0, //uv2
            0.0, 1.0  //uv3
        ];
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(textureCoords), this.gl.STATIC_DRAW);
        this.cubeVertexTextureCoordBuffer.itemSize = 2;
        this.cubeVertexTextureCoordBuffer.numItems = 4;

        this.vertexIndexBuffer = this.gl.createBuffer();

        ...
    }

    private DrawScene() {
        this.gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
        this.gl.clearDepth(1.0);                 // Clear everything
        this.gl.enable(this.gl.DEPTH_TEST);      // Enable depth testing
        this.gl.depthFunc(this.gl.LEQUAL);       // Near things obscure far things

        // Clear the canvas before we start drawing on it.
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
        mat4.rotate(this.mvMatrix, this.mvMatrix, 0.01, vec3.fromValues(1, 0, 0));

        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexPositionBuffer);
        this.gl.vertexAttribPointer(this.shaderProgram.vertexPositionAttribute, this.vertexPositionBuffer.itemSize, this.gl.FLOAT,
            false, 0, 0);
        this.gl.enableVertexAttribArray(this.shaderProgram.vertexPositionAttribute);

        // Tell WebGL how to pull out the texture coordinates from
        // the texture coordinate buffer into the textureCoord attribute.
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexTextureCoordBuffer);
        this.gl.vertexAttribPointer(this.shaderProgram.textureCoordAttribute, this.cubeVertexTextureCoordBuffer.itemSize, this.gl.FLOAT, false, 0, 0);
        this.gl.enableVertexAttribArray(this.shaderProgram.textureCoordAttribute);

        // Tell WebGL we want to affect texture unit 0
        this.gl.activeTexture(this.gl.TEXTURE0);
        // Bind the texture to texture unit 0
        this.gl.bindTexture(this.gl.TEXTURE_2D, this.neheTexture);
        // Tell the shader we bound the texture to texture unit 0
        this.gl.uniform1i(this.shaderProgram.samplerUniform, 0);

        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.vertexIndexBuffer);

        ...

    }

    ...

}

window.onload = () => {
    var texturedPlane = new TexturedPlane();
}