All Lessons
Lesson 7

Post-Processing Effects

Code

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

const sceneVS = `
  attribute vec4 aPosition;
  attribute vec4 aColor;
  uniform float uTime;
  varying lowp vec4 vColor;
  
  void main() {
    float angle = uTime;
    float c = cos(angle);
    float s = sin(angle);
    mat2 rot = mat2(c, s, -s, c);
    vec2 rotated = rot * aPosition.xy;
    gl_Position = vec4(rotated, 0.0, 1.0);
    vColor = aColor;
  }
`;

const sceneFS = `
  varying lowp vec4 vColor;
  void main() {
    gl_FragColor = vColor;
  }
`;

const postVS = `
  attribute vec2 aPosition;
  varying highp vec2 vTexCoord;
  
  void main() {
    gl_Position = vec4(aPosition, 0.0, 1.0);
    vTexCoord = aPosition * 0.5 + 0.5;
  }
`;

const postFS = `
  varying highp vec2 vTexCoord;
  uniform sampler2D uTexture;
  
  void main() {
    highp vec2 offset = (vTexCoord - 0.5) * 0.01;
    highp float r = texture2D(uTexture, vTexCoord + offset).r;
    highp float g = texture2D(uTexture, vTexCoord).g;
    highp float b = texture2D(uTexture, vTexCoord - offset).b;
    gl_FragColor = vec4(r, g, b, 1.0);
  }
`;

function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  return shaderProgram;
}

function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  return shader;
}

// Create framebuffer and texture
const fb = gl.createFramebuffer();
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 512, 512, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

// Scene geometry
const trianglePos = [0.0, 0.5, -0.5, -0.5, 0.5, -0.5];
const triangleCol = [1,0,0,1, 0,1,0,1, 0,0,1,1];
const quadPos = [-1,-1, 1,-1, -1,1, 1,1];

const trianglePosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, trianglePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(trianglePos), gl.STATIC_DRAW);

const triangleColBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleColBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleCol), gl.STATIC_DRAW);

const quadPosBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, quadPosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quadPos), gl.STATIC_DRAW);

const sceneProgram = initShaderProgram(gl, sceneVS, sceneFS);
const postProgram = initShaderProgram(gl, postVS, postFS);

const scenePos = gl.getAttribLocation(sceneProgram, 'aPosition');
const sceneCol = gl.getAttribLocation(sceneProgram, 'aColor');
const sceneTime = gl.getUniformLocation(sceneProgram, 'uTime');
const postPos = gl.getAttribLocation(postProgram, 'aPosition');

let time = 0;

function render() {
  time += 0.01;
  
  // Render to texture
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.viewport(0, 0, 512, 512);
  gl.clearColor(0.1, 0.1, 0.15, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.useProgram(sceneProgram);
  gl.uniform1f(sceneTime, time);
  
  gl.bindBuffer(gl.ARRAY_BUFFER, trianglePosBuffer);
  gl.vertexAttribPointer(scenePos, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(scenePos);
  
  gl.bindBuffer(gl.ARRAY_BUFFER, triangleColBuffer);
  gl.vertexAttribPointer(sceneCol, 4, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(sceneCol);
  
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  
  // Render to screen with post-processing
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.useProgram(postProgram);
  gl.bindTexture(gl.TEXTURE_2D, texture);
  
  gl.bindBuffer(gl.ARRAY_BUFFER, quadPosBuffer);
  gl.vertexAttribPointer(postPos, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(postPos);
  
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  
  requestAnimationFrame(render);
}

render();

Preview