diff --git a/script.js b/script.js
index 85a21c88cb75d0e08d60d3b4e7add1f48f6009f2..5deb9e4e75263d4aa94030f516f252a9c5cb5644 100644
--- a/script.js
+++ b/script.js
@@ -1,9 +1,280 @@
+const glsl_1st_vert = `#version 300 es
+layout(location = 0) in vec4 iPosition;
+layout(location = 1) in vec4 iNormal;
+uniform vec2 resolution;
+out vec4 vPosition;
+out vec4 vNormal;
+
+// Refer from gl-matrix
+mat4 persp(in float fovy, in float aspect, in float near, in float far) {
+    float f = 1. / tan(fovy / 2.), nf = 1. / (near - far);
+    return mat4(
+        f / aspect, 0, 0, 0,
+        0, f, 0, 0,
+        0, 0, (far + near) * nf, -1,
+        0, 0, (far * near) * nf * 2., 0);
+}
+mat4 lookAt(in vec3 eye, in vec3 center, in vec3 up) {
+    vec3 z = eye - center;
+    z /= length(z);
+
+    vec3 x = cross(up, z);
+    x /= length(x);
+
+    vec3 y = cross(z, x);
+    y /= length(y);
+
+    return transpose(mat4(
+        x, -dot(x, eye),
+        y, -dot(y, eye),
+        z, -dot(z, eye),
+        0,0,0,1
+    ));
+}
+
+void main() {
+    mat4 projection = persp(radians(30.0), resolution.x / resolution.y, .1, 1e3);
+    mat4 view = lookAt(
+        vec3(1, 1, 1) * 5.,
+        vec3(0, 0, 0),
+        vec3(0, 1, 0)
+    );
+    mat4 model = mat4(
+         1,0,0,0
+        ,0,1,0,0
+        ,0,0,1,0
+        ,0,0,0,1
+    );
+
+    gl_Position = vPosition = projection * view * model * iPosition;
+    vNormal= transpose(inverse(view * model)) * iNormal;
+}
+`;
+const glsl_1st_frag = `#version 300 es
+precision mediump float;
+layout(location = 0) out vec4 fPosition;
+layout(location = 1) out vec4 fNormal;
+in highp vec4 vPosition;
+in highp vec4 vNormal;
+void main() {
+    fPosition = vec4((vPosition.xyz / vPosition.w + 1.) / 2., 1);
+    fNormal = vec4(vNormal.xyz, 1);
+}
+`;
+
+const glsl_2nd_vert = `#version 300 es
+out highp vec2 vUV;
+void main() {
+    vUV = vec2((gl_VertexID / 2) ^ (gl_VertexID % 2), gl_VertexID / 2);
+    gl_Position = vec4(vec2(-1, -1) + 2. * vUV, 0, 1);
+}
+`;
+const glsl_2nd_frag = `#version 300 es
+precision mediump float;
+precision highp sampler2DArray;
+layout(location = 0) out vec4 fColor;
+uniform vec2 resolution;
+uniform sampler2DArray gBuffers;
+in highp vec2 vUV;
+
+// Kernel refer from https://en.wikipedia.org/wiki/Sobel_operator#Formulation
+float sobel(mat3 r) {
+    mat3 t = transpose(r);
+    vec3 v = (r[2] - r[0] + t[2] - t[0]) * vec3(1,2,1);
+    return v.x+v.y+v.z;
+}
+void main() {
+    vec2 texel = 1. / resolution;
+    mat3 normalx = mat3(
+         texture(gBuffers, vec3(vUV + texel * vec2(-1,-1), 1)).x
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0,-1), 1)).x
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1,-1), 1)).x
+
+        ,texture(gBuffers, vec3(vUV + texel * vec2(-1, 0), 1)).x
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0, 0), 1)).x
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1, 0), 1)).x
+
+        ,texture(gBuffers, vec3(vUV + texel * vec2(-1, 1), 1)).x
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0, 1), 1)).x
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1, 1), 1)).x
+    ), normaly = mat3(
+         texture(gBuffers, vec3(vUV + texel * vec2(-1,-1), 1)).y
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0,-1), 1)).y
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1,-1), 1)).y
+
+        ,texture(gBuffers, vec3(vUV + texel * vec2(-1, 0), 1)).y
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0, 0), 1)).y
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1, 0), 1)).y
+
+        ,texture(gBuffers, vec3(vUV + texel * vec2(-1, 1), 1)).y
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0, 1), 1)).y
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1, 1), 1)).y
+    ), normalz = mat3(
+         texture(gBuffers, vec3(vUV + texel * vec2(-1,-1), 1)).z
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0,-1), 1)).z
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1,-1), 1)).z
+
+        ,texture(gBuffers, vec3(vUV + texel * vec2(-1, 0), 1)).z
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0, 0), 1)).z
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1, 0), 1)).z
+
+        ,texture(gBuffers, vec3(vUV + texel * vec2(-1, 1), 1)).z
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 0, 1), 1)).z
+        ,texture(gBuffers, vec3(vUV + texel * vec2( 1, 1), 1)).z
+    );
+    float G = sobel(normalx) + sobel(normaly) + sobel(normalz);
+
+    fColor = G>5e-2 ? vec4(0,0,0,1) : texture(gBuffers, vec3(vUV, 0));
+}
+`;
+
+const vertices = [
+    /* x,y,z, */
+    -1.,-1.,-1.,-1, 0, 0,
+    -1.,-1., 1.,-1, 0, 0,
+    -1., 1., 1.,-1, 0, 0,
+    -1.,-1.,-1.,-1, 0, 0,
+    -1., 1., 1.,-1, 0, 0,
+    -1., 1.,-1.,-1, 0, 0,
+     1.,-1., 1., 0,-1, 0,
+    -1.,-1.,-1., 0,-1, 0,
+     1.,-1.,-1., 0,-1, 0,
+     1.,-1., 1., 0,-1, 0,
+    -1.,-1., 1., 0,-1, 0,
+    -1.,-1.,-1., 0,-1, 0,
+     1., 1.,-1., 0, 0,-1,
+    -1.,-1.,-1., 0, 0,-1,
+    -1., 1.,-1., 0, 0,-1,
+     1., 1.,-1., 0, 0,-1,
+     1.,-1.,-1., 0, 0,-1,
+    -1.,-1.,-1., 0, 0,-1,
+     1., 1., 1., 1, 0, 0,
+     1.,-1.,-1., 1, 0, 0,
+     1., 1.,-1., 1, 0, 0,
+     1.,-1.,-1., 1, 0, 0,
+     1., 1., 1., 1, 0, 0,
+     1.,-1., 1., 1, 0, 0,
+     1., 1., 1., 0, 1, 0,
+     1., 1.,-1., 0, 1, 0,
+    -1., 1.,-1., 0, 1, 0,
+     1., 1., 1., 0, 1, 0,
+    -1., 1.,-1., 0, 1, 0,
+    -1., 1., 1., 0, 1, 0,
+    -1., 1., 1., 0, 0, 1,
+    -1.,-1., 1., 0, 0, 1,
+     1.,-1., 1., 0, 0, 1,
+     1., 1., 1., 0, 0, 1,
+    -1., 1., 1., 0, 0, 1,
+     1.,-1., 1., 0, 0, 1,
+];
+
+const shaderFromCode = (gl, type, source) => {
+    const s = gl.createShader(type);
+    gl.shaderSource(s, source);
+    gl.compileShader(s);
+    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
+        throw new Error("Failed: compile shader.\n" + gl.getShaderInfoLog(s));
+
+    return s;
+}
+const shaderProgramLoader = (gl, vert, frag) => {
+    const p = gl.createProgram();
+    gl.attachShader(p, shaderFromCode(gl, gl.VERTEX_SHADER, vert));
+    gl.attachShader(p, shaderFromCode(gl, gl.FRAGMENT_SHADER, frag));
+    gl.linkProgram(p);
+    if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
+        throw new Error("Failed: link program.\n" + gl.getProgramInfoLog(p));
+    }
+    return p;
+};
+
 document.addEventListener('DOMContentLoaded', () => {
-    if ('undefined' === typeof WebGL2RenderingContext) {
+    if ('undefined' === typeof WebGL2RenderingContext)
         throw new Error('This tutorial required WebGL version 2.');
-    }
-    const glCtx = document.querySelector('canvas#view').getContext('webgl2');
-    if (!glCtx) {
-        throw new Error('Failed WebGL Initialization.')
-    }
+    const dom = document.querySelector('canvas#view');
+    const gl = dom.getContext('webgl2');
+    if (!gl)
+        throw new Error('Failed WebGL Initialization.');
+
+    const program1st = shaderProgramLoader(gl, glsl_1st_vert, glsl_1st_frag);
+    const program2nd = shaderProgramLoader(gl, glsl_2nd_vert, glsl_2nd_frag);
+
+    const GFramebuffer = gl.createFramebuffer();
+    let GBuffers = null;
+    window.addEventListener('resize', () => {
+        dom.width  = dom.clientWidth;
+        dom.height = dom.clientHeight;
+        gl.viewport(0, 0, dom.width, dom.height);
+        if (null !== GBuffers) {
+            gl.deleteTexture(GBuffers);
+            GBuffers = null;
+        }
+        gl.activeTexture(gl.TEXTURE0);
+        GBuffers = gl.createTexture();
+        gl.bindTexture(gl.TEXTURE_2D_ARRAY, GBuffers);
+        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+        gl.texImage3D(
+            gl.TEXTURE_2D_ARRAY,
+            0,
+            gl.RGBA,
+            dom.clientWidth, dom.clientHeight,
+            2,
+            0,
+            gl.RGBA,
+            gl.UNSIGNED_BYTE,
+            null
+        );
+        gl.useProgram(program1st);
+        gl.bindFramebuffer(gl.FRAMEBUFFER, GFramebuffer);
+
+        var drawBuffers = [
+            gl.COLOR_ATTACHMENT0,
+            gl.COLOR_ATTACHMENT1,
+        ];
+        for (let i = 0; i < 2; ++i)
+            gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, drawBuffers[i], GBuffers, 0, i);
+        gl.drawBuffers(drawBuffers);
+    });
+    window.dispatchEvent(new Event('resize'));
+
+    ((program) => {
+        const vbo = gl.createBuffer();
+        gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
+        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+
+        gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 24, 0);
+        gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 24, 12);
+    })(program1st);
+
+    gl.enable(gl.CULL_FACE);
+    gl.cullFace(gl.BACK);
+
+    const loop = () => {
+        requestAnimationFrame(loop);
+
+        ((gl) => {
+            gl.bindFramebuffer(gl.FRAMEBUFFER, GFramebuffer);
+            gl.useProgram(program1st);
+            gl.uniform2f(gl.getUniformLocation(program1st, "resolution"), dom.clientWidth, dom.clientHeight);
+
+            gl.clearColor(0, 0, 0, 0);
+            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+            gl.enable(gl.DEPTH_TEST);
+            gl.enableVertexAttribArray(0);
+            gl.enableVertexAttribArray(1);
+            gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3);
+            gl.disableVertexAttribArray(0);
+            gl.disableVertexAttribArray(1);
+
+            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+            gl.useProgram(program2nd);
+            gl.uniform2f(gl.getUniformLocation(program2nd, "resolution"), dom.clientWidth, dom.clientHeight);
+
+            gl.disable(gl.DEPTH_TEST);
+            gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
+        })(gl);
+    };
+    loop();
 });