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(); });