Simon 5 жил өмнө
parent
commit
88c4e6f07c

+ 14 - 0
base.css

@@ -0,0 +1,14 @@
+canvas {
+  width: 100%;
+  height: 100%
+}
+
+body {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  background: #000000;
+  margin: 0;
+  padding: 0;
+  overscroll-behavior: none;
+}

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Half Assed Light Pre-pass</title>
+  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+  <link rel="stylesheet" type="text/css" href="base.css">
+  <script src="https://cdn.jsdelivr.net/gh/toji/gl-matrix/dist/gl-matrix-min.js"></script>
+</head>
+<body>
+  <script src="./main.js" type="module">
+  </script>
+</body>
+</html>

+ 1465 - 0
main.js

@@ -0,0 +1,1465 @@
+const { mat4, mat3, vec2, vec3, vec4, quat } = glMatrix;
+
+let GL = null;
+
+const _OPAQUE_VS = `#version 300 es
+precision highp float;
+
+
+uniform mat3 normalMatrix;
+uniform mat4 modelMatrix;
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+in vec3 position;
+in vec3 normal;
+in vec3 tangent;
+in vec4 colour;
+in vec2 uv0;
+
+out vec2 vUV0;
+out vec4 vColour;
+
+void main(void) {
+  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+  vColour = colour;
+  vUV0 = uv0;
+}
+`;
+
+
+const _OPAQUE_FS = `#version 300 es
+precision highp float;
+
+
+uniform sampler2D diffuseTexture;
+uniform sampler2D normalTexture;
+uniform sampler2D gBuffer_Light;
+uniform vec4 resolution;
+
+in vec4 vColour;
+in vec2 vUV0;
+
+
+layout(location = 0) out vec4 out_FragColour;
+
+
+void main(void) {
+  vec2 uv = gl_FragCoord.xy / resolution.xy;
+  vec4 lightSample = texture(gBuffer_Light, uv);
+  vec4 albedo = texture(diffuseTexture, vUV0);
+
+  out_FragColour = (albedo * vec4(lightSample.xyz, 1.0) +
+      lightSample.w * vec4(0.3, 0.6, 0.1, 0.0));
+}
+`;
+
+
+const _QUAD_VS = `#version 300 es
+precision highp float;
+
+
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+in vec3 position;
+in vec2 uv0;
+
+
+void main(void) {
+  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+
+const _QUAD_FS = `#version 300 es
+precision highp float;
+
+
+uniform sampler2D gBuffer_Normal;
+uniform sampler2D gBuffer_Position;
+
+uniform vec3 lightColour;
+
+#define _LIGHT_TYPE_POINT
+
+#ifdef _LIGHT_TYPE_DIRECTIONAL
+uniform vec3 lightDirection;
+#endif
+
+uniform vec3 lightPosition;
+uniform vec3 lightAttenuation;
+
+uniform vec3 cameraPosition;
+uniform vec4 resolution;
+
+
+out vec4 out_FragColour;
+
+#define saturate(a) clamp(a, 0.0, 1.0)
+
+float _SmootherStep(float x, float a, float b) {
+  x = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
+  return x * (b - a) + a;
+}
+
+
+vec2 _CalculatePhong(vec3 lightDirection, vec3 cameraPosition, vec3 position, vec3 normal) {
+  vec3 viewDirection = normalize(cameraPosition - position);
+  vec3 H = normalize(lightDirection.xyz + viewDirection);
+  float NdotH = dot(normal.xyz, H);
+  float specular = saturate(pow(NdotH, 32.0));
+  float diffuse = saturate(dot(lightDirection.xyz, normal.xyz));
+
+  return vec2(diffuse, diffuse * specular);
+}
+
+vec4 _CalculateLight_Directional(
+    vec3 lightDirection, vec3 lightColour, vec3 position, vec3 normal) {
+
+  vec2 lightSample = _CalculatePhong(-lightDirection, cameraPosition, position, normal);
+
+  return vec4(lightSample.x * lightColour, lightSample.y);
+}
+
+vec4 _CalculateLight_Point(
+    vec3 lightPosition, vec3 lightAttenuation, vec3 lightColour, vec3 position, vec3 normal) {
+
+  vec3 dirToLight = lightPosition - position;
+  float lightDistance = length(dirToLight);
+  dirToLight = normalize(dirToLight);
+
+  vec2 lightSample = _CalculatePhong(dirToLight, cameraPosition, position, normal);
+  float falloff = saturate((lightDistance - lightAttenuation.x) / lightAttenuation.y);
+
+  lightSample *= _SmootherStep(falloff, 1.0, 0.0);
+
+  return vec4(lightSample.x * lightColour, lightSample.y);
+}
+
+
+void main(void) {
+  vec2 uv = gl_FragCoord.xy / resolution.xy;
+
+  vec4 normal = texture(gBuffer_Normal, uv);
+  vec4 position = texture(gBuffer_Position, uv);
+
+#ifdef _LIGHT_TYPE_DIRECTIONAL
+  vec4 lightSample = _CalculateLight_Directional(
+      lightDirection, lightColour, position.xyz, normal.xyz);
+#elif defined(_LIGHT_TYPE_POINT)
+  vec4 lightSample = _CalculateLight_Point(
+      lightPosition, lightAttenuation, lightColour, position.xyz, normal.xyz);
+#endif
+
+  out_FragColour = lightSample;
+}
+`;
+
+const _QUAD_COLOUR_VS = `#version 300 es
+precision highp float;
+
+
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+in vec3 position;
+in vec2 uv0;
+
+
+void main(void) {
+  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+
+const _QUAD_COLOUR_FS = `#version 300 es
+precision highp float;
+
+
+uniform sampler2D gQuadTexture;
+uniform vec4 resolution;
+
+out vec4 out_FragColour;
+
+
+void main(void) {
+  vec2 uv = gl_FragCoord.xy / resolution.xy;
+
+  out_FragColour = texture(gQuadTexture, uv);
+}
+`;
+
+const _SIMPLE_VS = `#version 300 es
+precision highp float;
+
+
+uniform mat3 normalMatrix;
+uniform mat4 modelMatrix;
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+in vec3 position;
+in vec3 normal;
+in vec3 tangent;
+in vec2 uv0;
+
+out vec4 vWSPosition;
+out vec3 vNormal;
+out vec3 vTangent;
+out vec2 vUV0;
+
+void main(void) {
+  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+  vNormal = normalize(normalMatrix * normal);
+  vTangent = normalize(normalMatrix * tangent);
+  vWSPosition = modelMatrix * vec4(position, 1.0);
+  vUV0 = uv0;
+}
+`;
+
+
+const _SIMPLE_FS = `#version 300 es
+precision highp float;
+
+
+uniform sampler2D normalTexture;
+
+in vec4 vWSPosition;
+in vec3 vNormal;
+in vec3 vTangent;
+in vec2 vUV0;
+
+layout(location = 0) out vec4 out_Normals;
+layout(location = 1) out vec4 out_Position;
+
+
+void main(void) {
+  vec3 bitangent = normalize(cross(vTangent, vNormal));
+  mat3 tbn = mat3(vTangent, bitangent, vNormal);
+  vec3 normalSample = normalize(texture(normalTexture, vUV0).xyz * 2.0 - 1.0);
+  vec3 vsNormal = normalize(tbn * normalSample);
+
+  out_Normals = vec4(vsNormal, 1.0);
+  out_Position = vWSPosition;
+}
+`;
+
+class Shader {
+  constructor(vsrc, fsrc, defines) {
+    defines = defines || [];
+
+    this._Init(vsrc, fsrc, defines);
+  }
+
+  _Init(vsrc, fsrc, defines) {
+    this._defines = defines;
+
+    vsrc = this._ModifySourceWithDefines(vsrc, defines);
+    fsrc = this._ModifySourceWithDefines(fsrc, defines);
+
+    this._vsSource = vsrc;
+    this._fsSource = fsrc;
+
+    this._vsProgram = this._Load(GL.VERTEX_SHADER, vsrc);
+    this._fsProgram = this._Load(GL.FRAGMENT_SHADER, fsrc);
+
+    this._shader = GL.createProgram();
+    GL.attachShader(this._shader, this._vsProgram);
+    GL.attachShader(this._shader, this._fsProgram);
+    GL.linkProgram(this._shader);
+  
+    if (!GL.getProgramParameter(this._shader, GL.LINK_STATUS)) {
+      return null;
+    }
+  
+    this.attribs = {
+      positions: GL.getAttribLocation(this._shader, 'position'),
+      normals: GL.getAttribLocation(this._shader, 'normal'),
+      tangents: GL.getAttribLocation(this._shader, 'tangent'),
+      uvs: GL.getAttribLocation(this._shader, 'uv0'),
+      colours: GL.getAttribLocation(this._shader, 'colour'),
+    };
+    this.uniforms = {
+      projectionMatrix: {
+        type: 'mat4',
+        location: GL.getUniformLocation(this._shader, 'projectionMatrix')
+      },
+      modelViewMatrix: {
+        type: 'mat4',
+        location: GL.getUniformLocation(this._shader, 'modelViewMatrix'),
+      },
+      modelMatrix: {
+        type: 'mat4',
+        location: GL.getUniformLocation(this._shader, 'modelMatrix'),
+      },
+      normalMatrix: {
+        type: 'mat3',
+        location: GL.getUniformLocation(this._shader, 'normalMatrix'),
+      },
+      resolution: {
+        type: 'vec4',
+        location: GL.getUniformLocation(this._shader, 'resolution'),
+      },
+      lightColour: {
+        type: 'vec3',
+        location: GL.getUniformLocation(this._shader, 'lightColour'),
+      },
+      lightDirection: {
+        type: 'vec3',
+        location: GL.getUniformLocation(this._shader, 'lightDirection'),
+      },
+      lightPosition: {
+        type: 'vec3',
+        location: GL.getUniformLocation(this._shader, 'lightPosition'),
+      },
+      lightAttenuation: {
+        type: 'vec3',
+        location: GL.getUniformLocation(this._shader, 'lightAttenuation'),
+      },
+      cameraPosition: {
+        type: 'vec3',
+        location: GL.getUniformLocation(this._shader, 'cameraPosition'),
+      },
+      diffuseTexture: {
+        type: 'texture',
+        location: GL.getUniformLocation(this._shader, 'diffuseTexture'),
+      },
+      normalTexture: {
+        type: 'texture',
+        location: GL.getUniformLocation(this._shader, 'normalTexture'),
+      },
+      gBuffer_Light: {
+        type: 'texture',
+        location: GL.getUniformLocation(this._shader, 'gBuffer_Light'),
+      },
+      gBuffer_Colour: {
+        type: 'texture',
+        location: GL.getUniformLocation(this._shader, 'gBuffer_Colour'),
+      },
+      gBuffer_Normal: {
+        type: 'texture',
+        location: GL.getUniformLocation(this._shader, 'gBuffer_Normal'),
+      },
+      gBuffer_Position: {
+        type: 'texture',
+        location: GL.getUniformLocation(this._shader, 'gBuffer_Position'),
+      },
+      gQuadTexture: {
+        type: 'texture',
+        location: GL.getUniformLocation(this._shader, 'gQuadTexture'),
+      }
+    };
+  }
+
+  _ModifySourceWithDefines(src, defines) {
+    const lines = src.split('\n');
+
+    const defineStrings = defines.map(d => '#define ' + d);
+
+    lines.splice(3, 0, defineStrings);
+
+    return lines.join('\n');
+  }
+
+  _Load(type, source) {
+    const shader = GL.createShader(type);
+  
+    GL.shaderSource(shader, source);
+    GL.compileShader(shader);
+  
+    if (!GL.getShaderParameter(shader, GL.COMPILE_STATUS)) {
+      console.log(GL.getShaderInfoLog(shader));
+      console.log(source);
+      GL.deleteShader(shader);
+      return null;
+    }
+  
+    return shader;
+  }
+
+  Bind() {
+    GL.useProgram(this._shader);
+  }
+}
+
+
+class ShaderInstance {
+  constructor(shader) {
+    this._shaderData = shader;
+    this._uniforms = {};
+    for (let k in shader.uniforms) {
+      this._uniforms[k] = {
+        location: shader.uniforms[k].location,
+        type: shader.uniforms[k].type,
+        value: null
+      };
+    }
+    this._attribs = {...shader.attribs};
+  }
+
+  SetMat4(name, m) {
+    this._uniforms[name].value = m;
+  }
+
+  SetMat3(name, m) {
+    this._uniforms[name].value = m;
+  }
+
+  SetVec4(name, v) {
+    this._uniforms[name].value = v;
+  }
+
+  SetVec3(name, v) {
+    this._uniforms[name].value = v;
+  }
+
+  SetTexture(name, t) {
+    this._uniforms[name].value = t;
+  }
+
+  Bind(constants) {
+    this._shaderData.Bind();
+
+    let textureIndex = 0;
+
+    for (let k in this._uniforms) {
+      const v = this._uniforms[k];
+
+      let value = constants[k];
+      if (v.value) {
+        value = v.value;
+      }
+
+      if (value && v.location) {
+        const t = v.type;
+
+        if (t == 'mat4') {
+          GL.uniformMatrix4fv(v.location, false, value);
+        } else if (t == 'mat3') {
+          GL.uniformMatrix3fv(v.location, false, value);
+        } else if (t == 'vec4') {
+          GL.uniform4fv(v.location, value);
+        } else if (t == 'vec3') {
+          GL.uniform3fv(v.location, value);
+        } else if (t == 'texture') {
+          value.Bind(textureIndex);
+          GL.uniform1i(v.location, textureIndex);
+          textureIndex++;
+        }
+      }
+    }
+  }
+}
+
+
+class Texture {
+  constructor() {
+  }
+
+  Load(src) {
+    this._name = src;
+    this._Load(src);
+    return this;
+  }
+
+  _Load(src) {
+    this._texture = GL.createTexture();
+    GL.bindTexture(GL.TEXTURE_2D, this._texture);
+    GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA,
+                  1, 1, 0, GL.RGBA, GL.UNSIGNED_BYTE,
+                  new Uint8Array([0, 0, 255, 255]));
+
+    const img = new Image();
+    img.src = src;
+    img.onload = () => {
+      GL.bindTexture(GL.TEXTURE_2D, this._texture);
+      GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, img);  
+      GL.generateMipmap(GL.TEXTURE_2D);
+      GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_LINEAR);
+      GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
+      GL.bindTexture(GL.TEXTURE_2D, null);
+    };
+  }
+
+  Bind(index) {
+    if (!this._texture) {
+      return;
+    }
+    GL.activeTexture(GL.TEXTURE0 + index);
+    GL.bindTexture(GL.TEXTURE_2D, this._texture);
+  }
+
+  Unbind() {
+    GL.bindTexture(GL.TEXTURE_2D, null);
+  }
+}
+
+
+class Mesh {
+  constructor() {
+    this._buffers = {};
+
+    this._OnInit();
+  }
+
+  _BufferData(info, name) {
+    if (name == 'index') {
+      info.buffer = GL.createBuffer();
+      GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, info.buffer);
+      GL.bufferData(GL.ELEMENT_ARRAY_BUFFER, new Uint16Array(info.data), GL.STATIC_DRAW);
+    } else {
+      info.buffer = GL.createBuffer();
+      GL.bindBuffer(GL.ARRAY_BUFFER, info.buffer);
+      GL.bufferData(GL.ARRAY_BUFFER, new Float32Array(info.data), GL.STATIC_DRAW);
+    }
+
+    this._buffers[name] = info;
+  }
+
+  Bind(shader) {
+    for (let k in this._buffers) {
+      if (shader._attribs[k] == -1) {
+        continue;
+      }
+
+      const b = this._buffers[k];
+
+      if (k == 'index') {
+        GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, b.buffer);
+      } else {
+        GL.bindBuffer(GL.ARRAY_BUFFER, b.buffer);
+        GL.vertexAttribPointer(shader._attribs[k], b.size, GL.FLOAT, false, 0, 0);
+        GL.enableVertexAttribArray(shader._attribs[k]);
+      }
+    }
+  }
+
+  Draw() {
+    const vertexCount = this._buffers.index.data.length;
+    GL.drawElements(GL.TRIANGLES, vertexCount, GL.UNSIGNED_SHORT, 0);
+  }
+}
+
+
+class MeshInstance {
+  constructor(mesh, shaders, shaderParams) {
+    this._mesh = mesh;
+    this._shaders = shaders;
+
+    shaderParams = shaderParams || {};
+    for (let sk in shaders) {
+      const s = shaders[sk];
+      for (let k in shaderParams) {
+        s.SetTexture(k, shaderParams[k]);
+      }  
+    }
+
+    this._position = vec3.create();
+    this._scale = vec3.fromValues(1, 1, 1);
+    this._rotation = quat.create();
+  }
+
+  SetPosition(x, y, z) {
+    vec3.set(this._position, x, y, z);
+  }
+
+  RotateX(rad) {
+    quat.rotateX(this._rotation, this._rotation, rad);
+  }
+
+  RotateY(rad) {
+    quat.rotateY(this._rotation, this._rotation, rad);
+  }
+
+  Scale(x, y, z) {
+    vec3.set(this._scale, x, y, z);
+  }
+
+  Bind(constants, pass) {
+    const modelMatrix = mat4.create();
+    mat4.fromRotationTranslationScale(
+        modelMatrix, this._rotation, this._position, this._scale);
+
+    // TODO View matrix
+    const viewMatrix = constants['viewMatrix'];
+    const modelViewMatrix = mat4.create();
+    mat4.multiply(modelViewMatrix, viewMatrix, modelMatrix);
+
+    const normalMatrix = mat3.create();
+    mat3.fromMat4(normalMatrix, modelMatrix);
+    mat3.invert(normalMatrix, normalMatrix);
+    mat3.transpose(normalMatrix, normalMatrix);
+
+    const s = this._shaders[pass];
+
+    s.SetMat4('modelViewMatrix', modelViewMatrix);
+    s.SetMat4('modelMatrix', modelMatrix);
+    s.SetMat3('normalMatrix', normalMatrix);
+    s.Bind(constants);
+
+    this._mesh.Bind(s);
+  }
+
+  Draw() {
+    this._mesh.Draw();
+  }
+}
+
+
+class Box extends Mesh {
+  constructor() {
+    super();
+  }
+
+  _OnInit() {
+    const positions = [
+      // Front face
+      -1.0, -1.0,  1.0,
+      1.0, -1.0,  1.0,
+      1.0,  1.0,  1.0,
+      -1.0,  1.0,  1.0,
+
+      // Back face
+      -1.0, -1.0, -1.0,
+      -1.0,  1.0, -1.0,
+      1.0,  1.0, -1.0,
+      1.0, -1.0, -1.0,
+
+      // Top face
+      -1.0,  1.0, -1.0,
+      -1.0,  1.0,  1.0,
+      1.0,  1.0,  1.0,
+      1.0,  1.0, -1.0,
+
+      // Bottom face
+      -1.0, -1.0, -1.0,
+      1.0, -1.0, -1.0,
+      1.0, -1.0,  1.0,
+      -1.0, -1.0,  1.0,
+
+      // Right face
+      1.0, -1.0, -1.0,
+      1.0,  1.0, -1.0,
+      1.0,  1.0,  1.0,
+      1.0, -1.0,  1.0,
+
+      // Left face
+      -1.0, -1.0, -1.0,
+      -1.0, -1.0,  1.0,
+      -1.0,  1.0,  1.0,
+      -1.0,  1.0, -1.0,
+    ];
+
+    const uvs = [
+      // Front face
+      0.0, 0.0,
+      1.0, 0.0,
+      1.0, 1.0,
+      0.0, 1.0,
+
+      // Back face
+      0.0, 0.0,
+      1.0, 0.0,
+      1.0, 1.0,
+      0.0, 1.0,
+
+      // Top face
+      0.0, 0.0,
+      1.0, 0.0,
+      1.0, 1.0,
+      0.0, 1.0,
+
+      // Bottom face
+      0.0, 0.0,
+      1.0, 0.0,
+      1.0, 1.0,
+      0.0, 1.0,
+
+      // Right face
+      0.0, 0.0,
+      1.0, 0.0,
+      1.0, 1.0,
+      0.0, 1.0,
+
+      // Left face
+      0.0, 0.0,
+      1.0, 0.0,
+      1.0, 1.0,
+      0.0, 1.0,
+    ];
+
+    const normals = [
+      // Front face
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+
+      // Back face
+      0.0, 0.0, -1.0,
+      0.0, 0.0, -1.0,
+      0.0, 0.0, -1.0,
+      0.0, 0.0, -1.0,
+
+      // Top face
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+
+      // Bottom face
+      0.0, -1.0, 0.0,
+      0.0, -1.0, 0.0,
+      0.0, -1.0, 0.0,
+      0.0, -1.0, 0.0,
+
+      // Right face
+      1.0, 0.0, 0.0,
+      1.0, 0.0, 0.0,
+      1.0, 0.0, 0.0,
+      1.0, 0.0, 0.0,
+
+      // Left face
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+    ];
+
+    const tangents = [
+      // Front face
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+
+      // Back face
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+
+      // Top face
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+
+      // Bottom face
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+
+      // Right face
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+      0.0, 1.0, 0.0,
+
+      // Left face
+      0.0, 0.0, -1.0,
+      0.0, 0.0, -1.0,
+      0.0, 0.0, -1.0,
+      0.0, 0.0, -1.0,
+    ];
+
+    const faceColors = [
+      [1.0,  1.0,  1.0,  1.0],    // Front face: white
+      [1.0,  0.0,  0.0,  1.0],    // Back face: red
+      [0.0,  1.0,  0.0,  1.0],    // Top face: green
+      [0.0,  0.0,  1.0,  1.0],    // Bottom face: blue
+      [1.0,  1.0,  0.0,  1.0],    // Right face: yellow
+      [1.0,  0.0,  1.0,  1.0],    // Left face: purple
+    ];
+
+    // Convert the array of colors into a table for all the vertices.
+
+    let colours = [];
+
+    for (var j = 0; j < faceColors.length; ++j) {
+      const c = faceColors[j];
+
+      // Repeat each color four times for the four vertices of the face
+      colours = colours.concat(c, c, c, c);
+    }
+
+    const indices = [
+      0,  1,  2,      0,  2,  3,    // front
+      4,  5,  6,      4,  6,  7,    // back
+      8,  9,  10,     8,  10, 11,   // top
+      12, 13, 14,     12, 14, 15,   // bottom
+      16, 17, 18,     16, 18, 19,   // right
+      20, 21, 22,     20, 22, 23,   // left
+    ];
+
+    this._BufferData({size: 3, data: positions}, 'positions');
+    this._BufferData({size: 3, data: normals}, 'normals');
+    this._BufferData({size: 3, data: tangents}, 'tangents');
+    this._BufferData({size: 4, data: colours}, 'colours');
+    this._BufferData({size: 2, data: uvs}, 'uvs');
+    this._BufferData({data: indices}, 'index');
+  }
+}
+
+class Quad extends Mesh {
+  constructor() {
+    super();
+  }
+
+  _OnInit() {
+    const positions = [
+      -0.5, -0.5, 1.0,
+      0.5, -0.5, 1.0,
+      0.5, 0.5, 1.0,
+      -0.5, 0.5, 1.0,
+    ];
+
+    const normals = [
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+      0.0, 0.0, 1.0,
+    ];
+
+    const tangents = [
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+      -1.0, 0.0, 0.0,
+    ];
+
+    const uvs = [
+      0.0, 0.0,
+      1.0, 0.0,
+      1.0, 1.0,
+      0.0, 1.0,
+    ];
+
+    const indices = [
+      0, 1, 2,
+      0, 2, 3,
+    ];
+
+    this._BufferData({size: 3, data: positions}, 'positions');
+    this._BufferData({size: 3, data: normals}, 'normals');
+    this._BufferData({size: 3, data: tangents}, 'tangents');
+    this._BufferData({size: 2, data: uvs}, 'uvs');
+    this._BufferData({data: indices}, 'index');
+  }
+}
+
+
+class Camera {
+  constructor() {
+    this._position = vec3.create();
+    this._target = vec3.create();
+    this._viewMatrix = mat4.create();
+    this._cameraMatrix = mat4.create();
+  }
+
+  SetPosition(x, y, z) {
+    vec3.set(this._position, x, y, z);
+  }
+
+  SetTarget(x, y, z) {
+    vec3.set(this._target, x, y, z);
+  }
+
+  UpdateConstants(constants) {
+    mat4.lookAt(this._viewMatrix, this._position, this._target, vec3.fromValues(0, 1, 0));
+    mat4.invert(this._cameraMatrix, this._viewMatrix);
+
+    constants['projectionMatrix'] = this._projectionMatrix;
+    constants['viewMatrix'] = this._viewMatrix;
+    constants['cameraMatrix'] = this._cameraMatrix;
+    constants['cameraPosition'] = this._position;
+  }
+}
+
+
+class PerspectiveCamera extends Camera {
+  constructor(fov, aspect, zNear, zFar) {
+    super();
+
+    this._projectionMatrix = mat4.create();
+    this._fov = fov;
+    this._aspect = aspect;
+    this._zNear = zNear;
+    this._zFar = zFar;
+  
+    mat4.perspective(this._projectionMatrix, fov * Math.PI / 180.0, aspect, zNear, zFar);
+  }
+
+  GetUp() {
+    const v = vec4.fromValues(0, 0, 1, 0);
+
+    vec4.transformMat4(v, v, this._cameraMatrix);
+
+    return v;
+  }
+
+  GetRight() {
+    const v = vec4.fromValues(1, 0, 0, 0);
+
+    vec4.transformMat4(v, v, this._cameraMatrix);
+
+    return v;
+  }
+}
+
+
+class OrthoCamera extends Camera {
+  constructor(l, r, b, t, n, f) {
+    super();
+
+    this._projectionMatrix = mat4.create();
+  
+    mat4.ortho(this._projectionMatrix, l, r, b, t, n, f);
+  }
+}
+
+
+class Light {
+  constructor() {
+  }
+
+  UpdateConstants() {
+  }
+}
+
+
+class DirectionalLight extends Light {
+  constructor() {
+    super();
+
+    this._colour = vec3.fromValues(1, 1, 1);
+    this._direction = vec3.fromValues(1, 0, 0);
+  }
+
+  get Type() {
+    return 'Directional';
+  }
+
+  SetColour(r, g, b) {
+    vec3.set(this._colour, r, g, b);
+  }
+
+  SetDirection(x, y, z) {
+    vec3.set(this._direction, x, y, z);
+    vec3.normalize(this._direction, this._direction);
+  }
+
+  UpdateConstants(constants) {
+    constants['lightDirection'] = this._direction;
+    constants['lightColour'] = this._colour;
+  }
+}
+
+class PointLight extends Light {
+  constructor() {
+    super();
+
+    this._colour = vec3.fromValues(1, 1, 1);
+    this._position = vec3.create();
+    this._attenuation = vec3.create();
+  }
+
+  get Type() {
+    return 'Point';
+  }
+
+  SetColour(r, g, b) {
+    vec3.set(this._colour, r, g, b);
+  }
+
+  SetPosition(x, y, z) {
+    vec3.set(this._position, x, y, z);
+  }
+
+  SetRadius(r1, r2) {
+    vec3.set(this._attenuation, r1, r2, 0);
+  }
+
+  UpdateConstants(constants) {
+    constants['lightPosition'] = this._position;
+    constants['lightColour'] = this._colour;
+    constants['lightAttenuation'] = this._attenuation;
+  }
+}
+
+
+class Renderer {
+  constructor() {
+    this._Init();
+  }
+
+  _Init() {
+    this._canvas = document.createElement('canvas');
+
+    document.body.appendChild(this._canvas);
+
+    GL = this._canvas.getContext('webgl2');
+
+    if (GL === null) {
+      alert("Unable to initialize WebGL. Your browser or machine may not support it.");
+      return;
+    }
+
+    this._constants = {};
+
+    this._textures = {};
+    this._textures['test-diffuse'] = new Texture().Load('./resources/rough-wet-cobble-albedo-1024.png');
+    this._textures['test-normal'] = new Texture().Load('./resources/rough-wet-cobble-normal-1024.jpg');
+    this._textures['worn-bumpy-rock-albedo'] = new Texture().Load(
+        './resources/worn-bumpy-rock-albedo-1024.png');
+    this._textures['worn-bumpy-rock-normal'] = new Texture().Load(
+        './resources/worn-bumpy-rock-normal-1024.jpg');
+
+    this._shaders = {};
+    this._shaders['z'] = new Shader(_SIMPLE_VS, _SIMPLE_FS);
+    this._shaders['default'] = new Shader(_OPAQUE_VS, _OPAQUE_FS);
+
+    this._shaders['post-quad-colour'] = new Shader(
+        _QUAD_COLOUR_VS, _QUAD_COLOUR_FS);
+    this._shaders['post-quad-directional'] = new Shader(
+        _QUAD_VS, _QUAD_FS, ['_LIGHT_TYPE_DIRECTIONAL']);
+    this._shaders['post-quad-point'] = new Shader(
+        _QUAD_VS, _QUAD_FS, ['_LIGHT_TYPE_POINT']);
+
+    this._camera = new PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1.0, 1000.0);
+    this._camera.SetPosition(0, 20, 10);
+    this._camera.SetTarget(0, 0, -20);
+
+    this._postCamera = new OrthoCamera(0.0, 1.0, 0.0, 1.0, 1.0, 1000.0);
+
+    this._meshes = [];
+    this._lights = [];
+
+    this._quadDirectional = new MeshInstance(
+        new Quad(),
+        {light: new ShaderInstance(this._shaders['post-quad-directional'])});
+    this._quadDirectional.SetPosition(0.5, 0.5, -10.0);
+
+    this._quadPoint = new MeshInstance(
+        new Quad(),
+        {light: new ShaderInstance(this._shaders['post-quad-point'])});
+    this._quadPoint.SetPosition(0.5, 0.5, -10.0);
+
+    this._quadColour = new MeshInstance(
+        new Quad(),
+        {colour: new ShaderInstance(this._shaders['post-quad-colour'])});
+    this._quadColour.SetPosition(0.5, 0.5, -10.0);
+
+    this._InitGBuffer();
+    this.Resize(window.innerWidth, window.innerHeight);
+  }
+
+  _InitGBuffer() {
+    // Float textures have only been around for like 15 years.
+    // So of course make them an extension.
+    GL.getExtension('EXT_color_buffer_float');
+
+    this._depthBuffer = GL.createRenderbuffer();
+    GL.bindRenderbuffer(GL.RENDERBUFFER, this._depthBuffer);
+    GL.renderbufferStorage(
+        GL.RENDERBUFFER,
+        GL.DEPTH_COMPONENT24,
+        window.innerWidth, window.innerHeight);
+    GL.bindRenderbuffer(GL.RENDERBUFFER, null);
+
+    this._normalBuffer = GL.createTexture();
+    GL.bindTexture(GL.TEXTURE_2D, this._normalBuffer);
+    GL.texImage2D(
+        GL.TEXTURE_2D, 0, GL.RGBA32F, window.innerWidth, window.innerHeight,
+        0, GL.RGBA, GL.FLOAT, null);  
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
+      GL.bindTexture(GL.TEXTURE_2D, null);
+
+    this._positionBuffer = GL.createTexture();
+    GL.bindTexture(GL.TEXTURE_2D, this._positionBuffer);
+    GL.texImage2D(
+        GL.TEXTURE_2D, 0, GL.RGBA32F, window.innerWidth, window.innerHeight,
+        0, GL.RGBA, GL.FLOAT, null);  
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
+    GL.bindTexture(GL.TEXTURE_2D, null);
+
+    this._lightBuffer = GL.createTexture();
+    GL.bindTexture(GL.TEXTURE_2D, this._lightBuffer);
+    GL.texImage2D(
+        GL.TEXTURE_2D, 0, GL.RGBA32F, window.innerWidth, window.innerHeight,
+        0, GL.RGBA, GL.FLOAT, null);  
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
+    GL.bindTexture(GL.TEXTURE_2D, null);
+
+    this._colourBuffer = GL.createTexture();
+    GL.bindTexture(GL.TEXTURE_2D, this._colourBuffer);
+    GL.texImage2D(
+        GL.TEXTURE_2D, 0, GL.RGBA32F, window.innerWidth, window.innerHeight,
+        0, GL.RGBA, GL.FLOAT, null);  
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
+    GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
+    GL.bindTexture(GL.TEXTURE_2D, null);
+
+    // Create the FBO's for each pass
+    this._zFBO = GL.createFramebuffer();
+    GL.bindFramebuffer(GL.FRAMEBUFFER, this._zFBO);
+    GL.framebufferRenderbuffer(
+        GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.RENDERBUFFER, this._depthBuffer);
+    GL.framebufferTexture2D(
+        GL.DRAW_FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, this._normalBuffer, 0);
+    GL.framebufferTexture2D(
+        GL.DRAW_FRAMEBUFFER, GL.COLOR_ATTACHMENT1, GL.TEXTURE_2D, this._positionBuffer, 0);
+    GL.bindFramebuffer(GL.FRAMEBUFFER, null);
+
+    this._lightFBO = GL.createFramebuffer();
+    GL.bindFramebuffer(GL.FRAMEBUFFER, this._lightFBO);
+    GL.framebufferRenderbuffer(
+        GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.RENDERBUFFER, this._depthBuffer);
+    GL.framebufferTexture2D(
+        GL.DRAW_FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, this._lightBuffer, 0);
+    GL.bindFramebuffer(GL.FRAMEBUFFER, null);
+
+    this._colourFBO = GL.createFramebuffer();
+    GL.bindFramebuffer(GL.FRAMEBUFFER, this._colourFBO);
+    GL.framebufferRenderbuffer(
+        GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.RENDERBUFFER, this._depthBuffer);
+    GL.framebufferTexture2D(
+        GL.DRAW_FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, this._colourBuffer, 0);
+    GL.bindFramebuffer(GL.FRAMEBUFFER, null);
+
+    // GROSS
+    this._normalTexture = new Texture();
+    this._normalTexture._texture = this._normalBuffer;
+
+    this._positionTexture = new Texture();
+    this._positionTexture._texture = this._positionBuffer;
+
+    this._lightTexture = new Texture();
+    this._lightTexture._texture = this._lightBuffer;
+
+    this._colourTexture = new Texture();
+    this._colourTexture._texture = this._colourBuffer;
+  }
+
+  CreateMeshInstance(mesh, shaderParams) {
+    const params = {};
+    for (let k in shaderParams.params) {
+      params[k] = this._textures[shaderParams.params[k]];
+    }
+
+    const m = new MeshInstance(
+        mesh,
+        {
+          z: new ShaderInstance(this._shaders['z']),
+          colour: new ShaderInstance(this._shaders[shaderParams.shader])
+        }, params);
+
+    this._meshes.push(m);
+
+    return m;
+  }
+
+  CreateLight(type) {
+    let l = null;
+
+    if (type == 'directional') {
+      l = new DirectionalLight();
+    } else if (type == 'point') {
+      l = new PointLight();
+    }
+
+    if (!l) {
+      return null;
+    }
+
+    this._lights.push(l);
+
+    return l;
+  }
+
+  Resize(w, h) {
+    this._canvas.width = w;
+    this._canvas.height = h;
+    GL.viewport(0, 0, w, h);
+  }
+
+  _SetQuadSizeForLight(quad, light) {
+    const wvp = mat4.create();
+    const w = mat4.create();
+    mat4.fromTranslation(w, light._position);
+
+    const viewMatrix = this._camera._viewMatrix;
+    const projectionMatrix = this._camera._projectionMatrix;
+
+    const _TransformToScreenSpace = (p) => {
+      const screenPos = vec4.fromValues(
+          p[0], p[1], p[2], 1.0);
+
+      vec4.transformMat4(screenPos, screenPos, projectionMatrix);
+
+      screenPos[0] = (screenPos[0] / screenPos[3]) * 0.5 + 0.5;
+      screenPos[1] = (screenPos[1] / screenPos[3]) * 0.5 + 0.5;
+
+      return screenPos;
+    };
+
+    const lightRadius = (light._attenuation[0] + light._attenuation[1]);
+    const lightDistance = vec3.distance(this._camera._position, light._position);
+
+    if (lightDistance < lightRadius) {
+      quad.SetPosition(0.5, 0.5, -10);
+      quad.Scale(1, 1, 1);
+    } else {
+      const viewSpaceCenter = vec3.clone(light._position);
+      vec3.transformMat4(viewSpaceCenter, viewSpaceCenter, viewMatrix);
+
+      const rightPos = vec3.clone(viewSpaceCenter);
+      const upPos = vec3.clone(viewSpaceCenter);
+      vec3.add(rightPos, rightPos, vec3.fromValues(lightRadius, 0, 0));
+      vec3.add(upPos, upPos, vec3.fromValues(0, -lightRadius, 0));
+
+      const center = _TransformToScreenSpace(light._position);    
+      const up = _TransformToScreenSpace(upPos);
+      const right = _TransformToScreenSpace(rightPos);
+
+      const radius = 2 * Math.max(
+          vec2.distance(center, up), vec2.distance(center, right));
+
+      quad.SetPosition(center[0], center[1], -10);
+      quad.Scale(radius, radius, 1);
+    }
+  }
+
+  Render(timeElapsed) {
+    this._constants['resolution'] = vec4.fromValues(
+        window.innerWidth, window.innerHeight, 0, 0);
+    this._camera.UpdateConstants(this._constants);
+
+    this._constants['gBuffer_Normal'] = null;
+    this._constants['gBuffer_Position'] = null;
+    this._constants['gBuffer_Colour'] = null;
+    this._constants['gBuffer_Light'] = null;
+
+    // Z-Prepass + normals
+    GL.bindFramebuffer(GL.FRAMEBUFFER, this._zFBO);
+    GL.drawBuffers([GL.COLOR_ATTACHMENT0, GL.COLOR_ATTACHMENT1]);
+
+    GL.clearColor(0.0, 0.0, 0.0, 0.0);
+    GL.clearDepth(1.0);
+    GL.enable(GL.DEPTH_TEST);
+    GL.depthMask(true);
+    GL.depthFunc(GL.LEQUAL);
+    GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
+
+    this._camera.UpdateConstants(this._constants);
+
+    for (let m of this._meshes) {
+      m.Bind(this._constants, 'z');
+      m.Draw();
+    }
+
+    GL.useProgram(null);
+    GL.bindTexture(GL.TEXTURE_2D, null);
+
+    // Light buffer generation
+    GL.bindFramebuffer(GL.FRAMEBUFFER, this._lightFBO);
+    GL.drawBuffers([GL.COLOR_ATTACHMENT0]);
+
+    GL.clear(GL.COLOR_BUFFER_BIT);
+    GL.disable(GL.DEPTH_TEST);
+    GL.enable(GL.BLEND);
+    GL.blendFunc(GL.ONE, GL.ONE);
+
+    this._postCamera.UpdateConstants(this._constants);
+
+    this._constants['gBuffer_Normal'] = this._normalTexture;
+    this._constants['gBuffer_Position'] = this._positionTexture;
+
+    for (let l of this._lights) {
+      l.UpdateConstants(this._constants);
+
+      let quad = null;
+      if (l.Type == 'Directional') {
+        quad = this._quadDirectional;
+      } else if (l.Type == 'Point') {
+        quad = this._quadPoint;
+
+        // Calculate screenspace size
+        this._SetQuadSizeForLight(quad, l);
+      }
+      quad.Bind(this._constants, 'light');
+      quad.Draw();
+    }
+
+    GL.useProgram(null);
+    GL.bindTexture(GL.TEXTURE_2D, null);
+
+    // Colour pass
+    GL.bindFramebuffer(GL.FRAMEBUFFER, this._colourFBO);
+    GL.drawBuffers([GL.COLOR_ATTACHMENT0]);
+    GL.disable(GL.BLEND);
+    GL.depthMask(false);
+    GL.enable(GL.DEPTH_TEST);
+
+    this._camera.UpdateConstants(this._constants);
+
+    this._constants['gBuffer_Colour'] = null;
+    this._constants['gBuffer_Light'] = this._lightTexture;
+    this._constants['gBuffer_Normal'] = null;
+    this._constants['gBuffer_Position'] = null;
+
+    for (let m of this._meshes) {
+      m.Bind(this._constants, 'colour');
+      m.Draw();
+    }
+
+    GL.useProgram(null);
+    GL.bindTexture(GL.TEXTURE_2D, null);
+    GL.disable(GL.BLEND);
+
+    // Now just draw directly to screen
+    GL.bindFramebuffer(GL.FRAMEBUFFER, null);
+    GL.disable(GL.DEPTH_TEST);
+    GL.disable(GL.BLEND);
+
+    this._postCamera.UpdateConstants(this._constants);
+
+    // Really fucking hate JavaScript sometimes.
+    this._constants['gQuadTexture'] = this._colourTexture;
+
+    this._quadColour.Bind(this._constants, 'colour');
+    this._quadColour.Draw();
+  }
+}
+
+class LightPrepassDemo {
+  constructor() {
+    this._Initialize();
+  }
+
+  _Initialize() {
+    this._renderer = new Renderer();
+
+    window.addEventListener('resize', () => {
+      this._OnWindowResize();
+    }, false);
+
+    this._Init();
+
+    this._previousRAF = null;
+    this._RAF();
+  }
+
+  _OnWindowResize() {
+    this._renderer.Resize(window.innerWidth, window.innerHeight);
+  }
+
+  _Init() {
+    this._CreateLights();
+    this._CreateMeshes();
+  }
+
+  _CreateLights() {
+    this._lights = [];
+
+    for (let i = -9; i <= 9; i++) {
+      let l = this._renderer.CreateLight('point');
+
+      const v = vec3.fromValues(Math.random(), Math.random(), Math.random());
+      vec3.normalize(v, v);
+
+      const p = vec3.fromValues(
+        (Math.random() * 2 - 1) * 10,
+        3,
+        -Math.random() * 10 - 10);
+
+      l.SetColour(v[0], v[1], v[2]);
+      l.SetPosition(p[0], p[1], p[2]);
+      l.SetRadius(4, 1);
+
+      this._lights.push({
+          light: l,
+          position: p,
+          acc: Math.random() * 10.0,
+          accSpeed: Math.random() * 0.5 + 0.5,
+      });
+    }
+  }
+
+  _CreateMeshes() {
+    this._meshes = [];
+
+    let m = this._renderer.CreateMeshInstance(
+        new Quad(),
+        {
+          shader: 'default',
+          params: {
+            diffuseTexture: 'worn-bumpy-rock-albedo',
+            normalTexture: 'worn-bumpy-rock-normal',
+          }
+        });
+    m.SetPosition(0, -2, -10);
+    m.RotateX(-Math.PI * 0.5);
+    m.Scale(50, 50, 1);
+
+    for (let x = -5; x < 5; x++) {
+      for (let y = 0; y < 20; y++) {
+        let m = this._renderer.CreateMeshInstance(
+            new Box(),
+            {
+              shader: 'default',
+              params: {
+                diffuseTexture: 'test-diffuse',
+                normalTexture: 'test-normal',
+              }
+            });
+        m.SetPosition(x * 4, 0, -y * 4);
+    
+        this._meshes.push(m);
+      }
+    }
+  }
+
+  _RAF() {
+    requestAnimationFrame((t) => {
+      if (this._previousRAF === null) {
+        this._previousRAF = t;
+      }
+
+      this._RAF();
+      this._Step(t - this._previousRAF);
+      this._previousRAF = t;
+    });
+  }
+
+  _Step(timeElapsed) {
+    const timeElapsedS = timeElapsed * 0.001;
+
+    for (let m of this._meshes) {
+      m.RotateY(timeElapsedS);
+    }
+
+    for (let l of this._lights) {
+      l.acc += timeElapsed * 0.001 * l.accSpeed;
+
+      l.light.SetPosition(
+          l.position[0] + 10 * Math.cos(l.acc),
+          l.position[1],
+          l.position[2] + 10 * Math.sin(l.acc));
+    }
+
+    this._renderer.Render(timeElapsedS);
+  }
+}
+
+
+let _APP = null;
+
+window.addEventListener('DOMContentLoaded', () => {
+  _APP = new LightPrepassDemo();
+});

BIN
resources/rough-wet-cobble-albedo-1024.png


BIN
resources/rough-wet-cobble-normal-1024.jpg


BIN
resources/worn-bumpy-rock-albedo-1024.png


BIN
resources/worn-bumpy-rock-normal-1024.jpg