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