openglglslgaussianbluropengl-2.0

GLSL - Gaussian Blur Artifact Issue


First of all, due to requirements constraints, I am working in OpenGL v. 2.1, and GLSL 120. I have implemented a simple fragment shader that applies a two pass (horizontal & vertical) Gaussian blur with n-kernel weights obtained from Pascal's Triangle. For the image below, I have decided to use a kernel size of 32, just for funsies:

Gaussian Kernel Size: 32, Offset: 3

As observed, the edges of the filtered blob seems to have some strange artifacts, as well as a ringing effect on its edges. For reference, here's how I am applying the blur:

if(isHorizontal)
{
    result += texture2D(tex, vec2( curFrag.x - 14.0 * xOff, curFrag.y )).rgba * 0.000000115484001;
    result += texture2D(tex, vec2( curFrag.x - 13.0 * xOff, curFrag.y )).rgba * 0.00000115484001;
    result += texture2D(tex, vec2( curFrag.x - 12.0 * xOff, curFrag.y )).rgba * 0.000008372590071;
    result += texture2D(tex, vec2( curFrag.x - 11.0 * xOff, curFrag.y )).rgba * 0.0000468865044;
    result += texture2D(tex, vec2( curFrag.x - 10.0 * xOff, curFrag.y )).rgba * 0.0002109892698;
    result += texture2D(tex, vec2( curFrag.x - 9.0 * xOff, curFrag.y )).rgba * 0.0007836744306;
    result += texture2D(tex, vec2( curFrag.x - 8.0 * xOff, curFrag.y )).rgba * 0.002448982596;
    result += texture2D(tex, vec2( curFrag.x - 7.0 * xOff, curFrag.y )).rgba * 0.006530620255;
    result += texture2D(tex, vec2( curFrag.x - 6.0 * xOff, curFrag.y )).rgba * 0.01502042659;
    result += texture2D(tex, vec2( curFrag.x - 5.0 * xOff, curFrag.y )).rgba * 0.03004085317;
    result += texture2D(tex, vec2( curFrag.x - 4.0 * xOff, curFrag.y )).rgba * 0.05257149305;
    result += texture2D(tex, vec2( curFrag.x - 3.0 * xOff, curFrag.y )).rgba * 0.08087922008;
    result += texture2D(tex, vec2( curFrag.x - 2.0 * xOff, curFrag.y )).rgba * 0.1097646558;
    result += texture2D(tex, vec2( curFrag.x - 1.0 * xOff, curFrag.y )).rgba * 0.131717587;

    result += texture2D(tex, curFrag).rgba * 0.1399499362;

    result += texture2D(tex, vec2( curFrag.x + 1.0 * xOff, curFrag.y )).rgba * 0.131717587;
    result += texture2D(tex, vec2( curFrag.x + 2.0 * xOff, curFrag.y )).rgba * 0.1097646558;
    result += texture2D(tex, vec2( curFrag.x + 3.0 * xOff, curFrag.y )).rgba * 0.08087922008;
    result += texture2D(tex, vec2( curFrag.x + 4.0 * xOff, curFrag.y )).rgba * 0.05257149305;
    result += texture2D(tex, vec2( curFrag.x + 5.0 * xOff, curFrag.y )).rgba * 0.03004085317;
    result += texture2D(tex, vec2( curFrag.x + 6.0 * xOff, curFrag.y )).rgba * 0.01502042659;
    result += texture2D(tex, vec2( curFrag.x + 7.0 * xOff, curFrag.y )).rgba * 0.006530620255;
    result += texture2D(tex, vec2( curFrag.x + 8.0 * xOff, curFrag.y )).rgba * 0.002448982596;
    result += texture2D(tex, vec2( curFrag.x + 9.0 * xOff, curFrag.y )).rgba * 0.0007836744306;
    result += texture2D(tex, vec2( curFrag.x + 10.0 * xOff, curFrag.y )).rgba * 0.0002109892698;
    result += texture2D(tex, vec2( curFrag.x + 11.0 * xOff, curFrag.y )).rgba * 0.0000468865044;
    result += texture2D(tex, vec2( curFrag.x + 12.0 * xOff, curFrag.y )).rgba * 0.000008372590071;
    result += texture2D(tex, vec2( curFrag.x + 13.0 * xOff, curFrag.y )).rgba * 0.00000115484001;
    result += texture2D(tex, vec2( curFrag.x + 14.0 * xOff, curFrag.y )).rgba * 0.000000115484001;
}
else
{
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 14.0 * yOff )).rgba * 0.000000115484001;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 13.0 * yOff )).rgba * 0.00000115484001;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 12.0 * yOff )).rgba * 0.000008372590071;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 11.0 * yOff )).rgba * 0.0000468865044;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 10.0 * yOff )).rgba * 0.0002109892698;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 9.0 * yOff )).rgba * 0.0007836744306;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 8.0 * yOff )).rgba * 0.002448982596;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 7.0 * yOff )).rgba * 0.006530620255;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 6.0 * yOff )).rgba * 0.01502042659;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 5.0 * yOff )).rgba * 0.03004085317;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 4.0 * yOff )).rgba * 0.05257149305;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 3.0 * yOff )).rgba * 0.08087922008;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 2.0 * yOff )).rgba * 0.1097646558;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y - 1.0 * yOff )).rgba * 0.131717587;

    result += texture2D(tex, curFrag).rgba * 0.1399499362;

    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 1.0 * yOff )).rgba * 0.131717587;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 2.0 * yOff )).rgba * 0.1097646558;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 3.0 * yOff )).rgba * 0.08087922008;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 4.0 * yOff )).rgba * 0.05257149305;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 5.0 * yOff )).rgba * 0.03004085317;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 6.0 * yOff )).rgba * 0.01502042659;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 7.0 * yOff )).rgba * 0.006530620255;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 8.0 * yOff )).rgba * 0.002448982596;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 9.0 * yOff )).rgba * 0.0007836744306;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 10.0 * yOff )).rgba * 0.0002109892698;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 11.0 * yOff )).rgba * 0.0000468865044;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 12.0 * yOff )).rgba * 0.000008372590071;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 13.0 * yOff )).rgba * 0.00000115484001;
    result += texture2D(tex, vec2( curFrag.x, curFrag.y + 14.0 * yOff )).rgba * 0.000000115484001;
} 

Furthermore, I am using two framebuffers. First, I draw the white blob onto a texture bound to the first framebuffer, then I apply my blur shader onto the second framebuffer for a horizontal pass, then back to the first one for a vertical pass. I have implemented a slider that repeats this process as well, see snippet below:

glUseProgram(gauss_blur_frag);
glUniform1f(glGetUniformLocation(gauss_blur_frag, "offset"), (float)radius);
glUniform2f(glGetUniformLocation(gauss_blur_frag, "resolution"), (float)fboWidth, (float)fboHeight);

for(int i = 1; i < smoothAmount; i++)
{           
    glUniform1i(glGetUniformLocation(gauss_blur_frag, "isHorizontal"), true);
    drawTexOnFBO(secondFBO, firstFBO->texId, bounds);
    glUniform1i(glGetUniformLocation(gauss_blur_frag, "isHorizontal"), false);
    drawTexOnFBO(firstFBO, secondFBO->texId, bounds);
}

The banding/ringing/artifacts get more pronounced as I increase the offset/radius of my blur, as well as increase the number of times the for-loop runs. The aim for this exercise is to simply apply a 'softening' effect on the edges of the blob, without the kernels being visible while being able to manipulate the offset. Can anyone shed some light on this issue? Thank you.


Solution

  • The banding/ringing/artifacts get more pronounced as I increase the offset/radius of my blur.

    Of course. If you increase the radius, the you increase the distance between the sample points, but you don't increase the number of sample points itself. This causes that there is a gap between to texels when you lookup the 32 samples and you don't consider the whole information of the source texture.
    Note, for a large radius, 2 adjacent points in the target texture uses completely different texels from the source texture when be processed. This causes the banding and artefacts.

    In common this effect can be decreased by using bilinear texture filtering (GL_LINEAR). For a completely smooth and free of artefact blur effect, you have to increase the number of samples. But this will decrease the performance rapidly. See also Fast Gaussian blur at pause.

    See the example, where the effect can be reproduced with decreasing blur factor and increasing radius:

    var readInput = true;
    function changeEventHandler(event){
    readInput = true;
    }
    
    (function loadscene() {
    
    var resize, gl, progDraw, progBlur, vp_size, blurFB;
    var bufCube = {};
    var bufQuad = {};
    var shininess = 10.0;
    var glow = 10.0;
    var sigma = 0.8;
    var radius = 1.0;
    
    function render(delteMS){
    
        if ( readInput ) {
            //readInput = false;
            var sliderScale = 100;
            sigma     = document.getElementById( "sigma" ).value / sliderScale;
            radius    = document.getElementById( "radius" ).value / sliderScale;
        }
    
        Camera.create();
        Camera.vp = vp_size;
            
        gl.enable( gl.DEPTH_TEST );
        gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
        gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
    
        // set up framebuffer
        gl.bindFramebuffer( gl.FRAMEBUFFER, blurFB[0] );
        gl.viewport( 0, 0, blurFB[0].width, blurFB[0].height );
        gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
    
        // set up draw shader
        ShaderProgram.Use( progDraw.prog );
        ShaderProgram.SetUniformM44( progDraw.prog, "u_projectionMat44", Camera.Perspective() );
        var viewMat = Camera.LookAt();
        //viewMat = RotateAxis( viewMat, CalcAng( delteMS, 13.0 ), 0 );
        //viewMat = RotateAxis( viewMat, CalcAng( delteMS, 17.0 ), 1 );
        ShaderProgram.SetUniformM44( progDraw.prog, "u_modelViewMat44", viewMat );
        ShaderProgram.SetUniformF1( progDraw.prog, "u_shininess", shininess );
        
        // draw scene
        VertexBuffer.Draw( bufCube );
    
        // set blur-X framebuffer and bind frambuffer texture
        gl.bindFramebuffer( gl.FRAMEBUFFER, blurFB[1] );
        gl.viewport( 0, 0, blurFB[1].width, blurFB[1].height );
        gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
        var texUnit = 1;
        gl.activeTexture( gl.TEXTURE0 + texUnit );
        gl.bindTexture( gl.TEXTURE_2D, blurFB[0].color0_texture );
    
        // set up blur-X shader
        ShaderProgram.Use( progBlur.prog );
        ShaderProgram.SetUniformI1( progBlur.prog, "u_texture", texUnit )
        ShaderProgram.SetUniformF2( progBlur.prog, "u_textureSize", vp_size );
        ShaderProgram.SetUniformF1( progBlur.prog, "u_sigma", sigma )
        ShaderProgram.SetUniformF1( progBlur.prog, "u_radius", radius )
        ShaderProgram.SetUniformF2( progBlur.prog, "u_dir", [1.0, 0.0] )
    
        // draw full screen space
        gl.enableVertexAttribArray( progBlur.inPos );
        gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
        gl.vertexAttribPointer( progBlur.inPos, 2, gl.FLOAT, false, 0, 0 ); 
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
        gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
        gl.disableVertexAttribArray( progBlur.inPos );
    
        // reset framebuffer and bind frambuffer texture
        gl.bindFramebuffer( gl.FRAMEBUFFER, null );
        gl.viewport( 0, 0, vp_size[0], vp_size[1] );
        gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
        texUnit = 2;
        gl.activeTexture( gl.TEXTURE0 + texUnit );
        gl.bindTexture( gl.TEXTURE_2D, blurFB[1].color0_texture );
    
        // set up pst process shader
        ShaderProgram.SetUniformI1( progBlur.prog, "u_texture", texUnit )
        ShaderProgram.SetUniformF2( progBlur.prog, "u_dir", [0.0, 1.0] )
    
        // draw full screen space
        gl.enableVertexAttribArray( progBlur.inPos );
        gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
        gl.vertexAttribPointer( progBlur.inPos, 2, gl.FLOAT, false, 0, 0 ); 
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
        gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
        gl.disableVertexAttribArray( progBlur.inPos );
    
        requestAnimationFrame(render);
    }
    
    function resize() {
        //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
        vp_size = [window.innerWidth, window.innerHeight]
        //vp_size = [256, 256]
        canvas.width = vp_size[0];
        canvas.height = vp_size[1];
    
        var fbsize = Math.max(vp_size[0], vp_size[1]);
        fbsize = 1 << 31 - Math.clz32(fbsize); // nearest power of 2
    
        blurFB = [];
        for ( var i = 0; i < 2; ++ i ) {
            fb = gl.createFramebuffer();
            fb.width = fbsize;
            fb.height = fbsize;
            gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
            fb.color0_texture = gl.createTexture();
            gl.bindTexture( gl.TEXTURE_2D, fb.color0_texture );
            gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
            gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
            gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, fb.width, fb.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null );
            fb.renderbuffer = gl.createRenderbuffer();
            gl.bindRenderbuffer( gl.RENDERBUFFER, fb.renderbuffer );
            gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fb.width, fb.height );
            gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.color0_texture, 0 );
            gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, fb.renderbuffer );
            gl.bindTexture( gl.TEXTURE_2D, null );
            gl.bindRenderbuffer( gl.RENDERBUFFER, null );
            gl.bindFramebuffer( gl.FRAMEBUFFER, null );
            blurFB.push( fb );
        }
    }
    
    function initScene() {
    
        canvas = document.getElementById( "canvas");
        gl = canvas.getContext( "experimental-webgl" );
        if ( !gl )
            return null;
    
        progDraw = {}
        progDraw.prog = ShaderProgram.Create( 
        [ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
            { source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
        ] );
        if ( !progDraw.prog )
            return null;
        progDraw.inPos = gl.getAttribLocation( progDraw.prog, "inPos" );
        progDraw.inNV  = gl.getAttribLocation( progDraw.prog, "inNV" );
        progDraw.inCol = gl.getAttribLocation( progDraw.prog, "inCol" );
    
        progBlur = {}
        progBlur.prog = ShaderProgram.Create( 
        [ { source : "post-shader-vs", stage : gl.VERTEX_SHADER },
            { source : "blur-shader-fs", stage : gl.FRAGMENT_SHADER }
        ] );
        progBlur.inPos = gl.getAttribLocation( progBlur.prog, "inPos" );
        if ( !progBlur.prog )
            return;    
        
        // create cube
        var cubePos = [
        -1.0, -1.0,  1.0,  1.0, -1.0,  1.0,  1.0,  1.0,  1.0, -1.0,  1.0,  1.0,
        -1.0, -1.0, -1.0,  1.0, -1.0, -1.0,  1.0,  1.0, -1.0, -1.0,  1.0, -1.0 ];
        var cubeCol = [ 1.0, 0.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ];
        var cubeHlpInx = [ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 ];  
        var cubePosData = [];
        for ( var i = 0; i < cubeHlpInx.length; ++ i ) {
        cubePosData.push( cubePos[cubeHlpInx[i]*3], cubePos[cubeHlpInx[i]*3+1], cubePos[cubeHlpInx[i]*3+2] );
        }
        var cubeNVData = [];
        for ( var i1 = 0; i1 < cubeHlpInx.length; i1 += 4 ) {
        var nv = [0, 0, 0];
        for ( i2 = 0; i2 < 4; ++ i2 ) {
            var i = i1 + i2;
            nv[0] += cubePosData[i*3]; nv[1] += cubePosData[i*3+1]; nv[2] += cubePosData[i*3+2];
        }
        for ( i2 = 0; i2 < 4; ++ i2 )
        cubeNVData.push( nv[0], nv[1], nv[2] );
        }
        var cubeColData = [];
        for ( var is = 0; is < 6; ++ is ) {
        for ( var ip = 0; ip < 4; ++ ip ) {
            cubeColData.push( cubeCol[is*3], cubeCol[is*3+1], cubeCol[is*3+2] ); 
        }
        }
        var cubeInxData = [];
        for ( var i = 0; i < cubeHlpInx.length; i += 4 ) {
        cubeInxData.push( i, i+1, i+2, i, i+2, i+3 );   
        }
        bufCube = VertexBuffer.Create(
        [ { data : cubePosData, attrSize : 3, attrLoc : progDraw.inPos },
        { data : cubeNVData,  attrSize : 3, attrLoc : progDraw.inNV },
        { data : cubeColData, attrSize : 3, attrLoc : progDraw.inCol } ],
        cubeInxData );
    
        bufQuad.pos = gl.createBuffer();
        gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
        gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0 ] ), gl.STATIC_DRAW );
        bufQuad.inx = gl.createBuffer();
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
        gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( [ 0, 1, 2, 0, 2, 3 ] ), gl.STATIC_DRAW );  
        
        window.onresize = resize;
        resize();
        requestAnimationFrame(render);
    }
    
    function Fract( val ) { 
        return val - Math.trunc( val );
    }
    function CalcAng( deltaTime, intervall ) {
        return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;
    }
    function CalcMove( deltaTime, intervall, range ) {
        var pos = self.Fract( deltaTime / (1000*intervall) ) * 2.0
        var pos = pos < 1.0 ? pos : (2.0-pos)
        return range[0] + (range[1] - range[0]) * pos;
    }    
    function EllipticalPosition( a, b, angRag ) {
        var a_b = a * a - b * b
        var ea = (a_b <= 0) ? 0 : Math.sqrt( a_b );
        var eb = (a_b >= 0) ? 0 : Math.sqrt( -a_b );
        return [ a * Math.sin( angRag ) - ea, b * Math.cos( angRag ) - eb, 0 ];
    }
    
    function IdentityMat44() {
        return [ 1, 0, 0, 0,    0, 1, 0, 0,    0, 0, 1, 0,    0, 0, 0, 1 ];
    };
    
    function RotateAxis(matA, angRad, axis) {
        var aMap = [ [1, 2], [2, 0], [0, 1] ];
        var a0 = aMap[axis][0], a1 = aMap[axis][1]; 
        var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
        var matB = matA.slice(0);
        for ( var i = 0; i < 3; ++ i ) {
            matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
            matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
        }
        return matB;
    }
    
    function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
    function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
    function Normalize( v ) {
        var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
        return [ v[0] / len, v[1] / len, v[2] / len ];
    }
    
    var Camera = {};
    Camera.create = function() {
        this.pos    = [0, 3, 0.0];
        this.target = [0, 0, 0];
        this.up     = [0, 0, 1];
        this.fov_y  = 90;
        this.vp     = [800, 600];
        this.near   = 0.5;
        this.far    = 100.0;
    }
    Camera.Perspective = function() {
        var fn = this.far + this.near;
        var f_n = this.far - this.near;
        var r = this.vp[0] / this.vp[1];
        var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
        var m = IdentityMat44();
        m[0]  = t/r; m[1]  = 0; m[2]  =  0;                              m[3]  = 0;
        m[4]  = 0;   m[5]  = t; m[6]  =  0;                              m[7]  = 0;
        m[8]  = 0;   m[9]  = 0; m[10] = -fn / f_n;                       m[11] = -1;
        m[12] = 0;   m[13] = 0; m[14] = -2 * this.far * this.near / f_n; m[15] =  0;
        return m;
    }
    Camera.LookAt = function() {
        var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
        var mx = Normalize( Cross( this.up, mz ) );
        var my = Normalize( Cross( mz, mx ) );
        var tx = Dot( mx, this.pos );
        var ty = Dot( my, this.pos );
        var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos ); 
        var m = IdentityMat44();
        m[0]  = mx[0]; m[1]  = my[0]; m[2]  = mz[0]; m[3]  = 0;
        m[4]  = mx[1]; m[5]  = my[1]; m[6]  = mz[1]; m[7]  = 0;
        m[8]  = mx[2]; m[9]  = my[2]; m[10] = mz[2]; m[11] = 0;
        m[12] = tx;    m[13] = ty;    m[14] = tz;    m[15] = 1; 
        return m;
    } 
    
    var ShaderProgram = {};
    ShaderProgram.Create = function( shaderList ) {
        var shaderObjs = [];
        for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
            var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
            if ( shderObj == 0 )
                return 0;
            shaderObjs.push( shderObj );
        }
        var progObj = this.LinkProgram( shaderObjs )
        if ( progObj != 0 ) {
            progObj.attribIndex = {};
            var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
            for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
                var name = gl.getActiveAttrib( progObj, i_n ).name;
                progObj.attribIndex[name] = gl.getAttribLocation( progObj, name );
            }
            progObj.unifomLocation = {};
            var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
            for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
                var name = gl.getActiveUniform( progObj, i_n ).name;
                progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
            }
        }
        return progObj;
    }
    ShaderProgram.AttributeIndex = function( progObj, name ) { return progObj.attribIndex[name]; } 
    ShaderProgram.UniformLocation = function( progObj, name ) { return progObj.unifomLocation[name]; } 
    ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); } 
    ShaderProgram.SetUniformI1  = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1i( progObj.unifomLocation[name], val ); }
    ShaderProgram.SetUniformF1  = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1f( progObj.unifomLocation[name], val ); }
    ShaderProgram.SetUniformF2  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform2fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniformF3  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform3fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniformF4  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform4fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniformM33 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix3fv( progObj.unifomLocation[name], false, mat ); }
    ShaderProgram.SetUniformM44 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
    ShaderProgram.CompileShader = function( source, shaderStage ) {
        var shaderScript = document.getElementById(source);
        if (shaderScript)
        source = shaderScript.text;
        var shaderObj = gl.createShader( shaderStage );
        gl.shaderSource( shaderObj, source );
        gl.compileShader( shaderObj );
        var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
        if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
        return status ? shaderObj : null;
    } 
    ShaderProgram.LinkProgram = function( shaderObjs ) {
        var prog = gl.createProgram();
        for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
            gl.attachShader( prog, shaderObjs[i_sh] );
        gl.linkProgram( prog );
        status = gl.getProgramParameter( prog, gl.LINK_STATUS );
        if ( !status ) alert("Could not initialise shaders");
        gl.useProgram( null );
        return status ? prog : null;
    }
    
    var VertexBuffer = {};
    VertexBuffer.Create = function( attributes, indices ) {
        var buffer = {};
        buffer.buf = [];
        buffer.attr = []
        for ( var i = 0; i < attributes.length; ++ i ) {
            buffer.buf.push( gl.createBuffer() );
            buffer.attr.push( { size : attributes[i].attrSize, loc : attributes[i].attrLoc } );
            gl.bindBuffer( gl.ARRAY_BUFFER, buffer.buf[i] );
            gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( attributes[i].data ), gl.STATIC_DRAW );
        }
        buffer.inx = gl.createBuffer();
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffer.inx );
        gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
        buffer.inxLen = indices.length;
        gl.bindBuffer( gl.ARRAY_BUFFER, null );
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
        return buffer;
    }
    VertexBuffer.Draw = function( bufObj ) {
    for ( var i = 0; i < bufObj.buf.length; ++ i ) {
            gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.buf[i] );
            gl.vertexAttribPointer( bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0 );
            gl.enableVertexAttribArray( bufObj.attr[i].loc );
        }
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
        gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );
        for ( var i = 0; i < bufObj.buf.length; ++ i )
            gl.disableVertexAttribArray( bufObj.attr[i].loc );
        gl.bindBuffer( gl.ARRAY_BUFFER, null );
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
    }
    
    initScene();
    
    })();
    html,body { margin: 0; overflow: hidden; }
    #gui { position : absolute; top : 0; left : 0; }
    <script id="draw-shader-vs" type="x-shader/x-vertex">
    precision mediump float;
    
    attribute vec3 inPos;
    attribute vec3 inNV;
    attribute vec3 inCol;
    
    varying vec3 vertPos;
    varying vec3 vertNV;
    varying vec3 vertCol;
    
    uniform mat4 u_projectionMat44;
    uniform mat4 u_modelViewMat44;
    
    void main()
    {
        vertNV      = mat3( u_modelViewMat44 ) * normalize( inNV );
        vertCol     = inCol;
        vec4 pos    = u_modelViewMat44 * vec4( inPos, 1.0 );
        vertPos     = pos.xyz / pos.w;
        gl_Position = u_projectionMat44 * pos;
    }
    </script>
    
    <script id="draw-shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    
    varying vec3 vertPos;
    varying vec3 vertNV;
    varying vec3 vertCol;
    
    uniform float u_shininess;
    
    void main()
    {
        vec3 color = vertCol;
        vec3  normalV  = normalize( vertNV );
        vec3  eyeV     = normalize( -vertPos );
        vec3  halfV    = normalize( eyeV + normalV );
        float NdotH    = max( 0.0, dot( normalV, halfV ) );
        float shineFac = ( u_shininess + 2.0 ) * pow( NdotH, u_shininess ) / ( 2.0 * 3.14159265 );
        gl_FragColor   = vec4( color.rgb * (0.2 + NdotH), 1.0 );
    } 
    </script>
    
    <script id="post-shader-vs" type="x-shader/x-vertex">
    precision mediump float;
    
    attribute vec2 inPos;
    
    varying   vec2 pos;
    
    void main()
    {
        pos = inPos;
        gl_Position = vec4( inPos, 0.0, 1.0 );
    }
    </script>
    
    <script id="blur-shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    
    varying vec2 pos;
    
    uniform sampler2D u_texture;
    uniform vec2      u_textureSize;
    uniform float     u_sigma;
    uniform float     u_radius;
    uniform vec2      u_dir;
    
    float CalcGauss( float x, float sigma )
    {
        if ( sigma <= 0.0 )
            return 0.0;
      return exp( -(x*x) / (2.0 * sigma) ) / (2.0 * 3.14157 * sigma);
    }
    
    void main()
    {
        vec2 texC     = pos.st * 0.5 + 0.5;
        vec4 texCol   = texture2D( u_texture, texC );
        vec4 gaussCol = vec4( texCol.rgb, 1.0 );
        vec2 step     = u_dir / u_textureSize;
        for ( int i = 1; i <= 32; ++ i )
        {
            float weight = CalcGauss( float(i) / 32.0, u_sigma * 0.5 );
            if ( weight < 1.0/255.0 )
                break;
            texCol    = texture2D( u_texture, texC + u_radius * step * float(i) );
            gaussCol += vec4( texCol.rgb * weight, weight );
            texCol    = texture2D( u_texture, texC - u_radius * step * float(i) );
            gaussCol += vec4( texCol.rgb * weight, weight );
        }
        gaussCol.rgb = clamp( gaussCol.rgb / gaussCol.w, 0.0, 1.0 );
        gl_FragColor = vec4( gaussCol.rgb, 1.0 );
    }
    </script>
    
    <div>
    <form id="gui" name="inputs">
        <table>
            <tr> <td> <font color= #CCF>radius</font> </td> 
                    <td> <input type="range" id="radius" min="1" max="1000" value="1000" onchange="changeEventHandler(event);"/></td> </tr>
            <tr> <td> <font color= #CCF>blur</font> </td> 
                    <td> <input type="range" id="sigma" min="1" max="100" value="5" onchange="changeEventHandler(event);"/></td> </tr>
        </table>
    </form>
    </div>
    
    <canvas id="canvas" style="border: none;"></canvas>