Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project 5: Beini Gu #7

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,533 changes: 4,533 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function render() {
// of objects based on relative depths in the scene, comment out /
//the gl.disable(gl.DEPTH_TEST) and gl.enable(gl.DEPTH_TEST) lines.
gl.disable(gl.DEPTH_TEST);
wireframe.render(camera);
//wireframe.render(camera);
gl.enable(gl.DEPTH_TEST);
}

Expand Down
80 changes: 76 additions & 4 deletions src/renderers/base.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import TextureBuffer from './textureBuffer';
import { vec4 } from 'gl-matrix';

export const MAX_LIGHTS_PER_CLUSTER = 100;

Expand All @@ -12,19 +13,90 @@ export default class BaseRenderer {
}

updateClusters(camera, viewMatrix, scene) {
// TODO: Update the cluster texture with the count and indices of the lights in each cluster
// This will take some time. The math is nontrivial...

// Reset the light count to 0 for every cluster
for (let z = 0; z < this._zSlices; ++z) {
for (let y = 0; y < this._ySlices; ++y) {
for (let x = 0; x < this._xSlices; ++x) {
let i = x + y * this._xSlices + z * this._xSlices * this._ySlices;
// Reset the light count to 0 for every cluster
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)] = 0;
}
}
}

// Calculate frustum values
let zDist = camera.far - camera.near;
let FovY = 2 * Math.tan(camera.fov * Math.PI / 360.0);
let FovX = camera.aspect * FovY;

//Function to clamp values
function clamp(value, max) {
return Math.min(Math.max(value, 0), max);
}

// The naive method is to loop through all the clusters and check if each light has influence on the cluster
// Instead of using the naive method, we use a AABB bounding box along all three dimensions to improve the performance

// Loop through all the lights
for (let lightIdx = 0; lightIdx < scene.lights.length; lightIdx++)
{
//Get the position of the current light and transform it into camera view space
let curLight = scene.lights[lightIdx];
let lightPosWorld = vec4.fromValues(curLight.position[0], curLight.position[1], curLight.position[2], 1.0);
let lightPosCamera = vec4.create();
vec4.transformMat4(lightPosCamera, lightPosWorld, viewMatrix);
lightPosCamera[2] *= -1.0; //reverse z direction

//We create the AABB bounding box in the camera view space
let lightX = lightPosCamera[0],
lightY = lightPosCamera[1],
lightZ = lightPosCamera[2];

let w = FovX * lightZ;
let h = FovY * lightZ;

let strideZ = zDist / this._zSlices,
strideX = w / this._xSlices,
strideY = h / this._ySlices;

let r = curLight.radius;

//Calculate the min and max slice indices, position divided by stride(step)
let maxSliceZ = Math.floor((lightZ - camera.near + r) / strideZ),
minSliceZ = Math.floor((lightZ - camera.near - r) / strideZ),
maxSliceX = Math.floor((w / 2 + lightX + r) / strideX),
minSliceX = Math.floor((w / 2 + lightX - r) / strideX),
maxSliceY = Math.floor((h / 2 + lightY + r) / strideY),
minSliceY = Math.floor((h / 2 + lightY - r) / strideY);

//clamp the values because adding the radius might cause out of bound
maxSliceZ = clamp(maxSliceZ, this._zSlices - 1);
minSliceZ = clamp(minSliceZ, this._zSlices - 1);
maxSliceX = clamp(maxSliceX, this._xSlices - 1);
minSliceX = clamp(minSliceX, this._xSlices - 1);
maxSliceY = clamp(maxSliceY, this._ySlices - 1);
minSliceY = clamp(minSliceY, this._ySlices - 1);

// Only loop through the bounding box
for (let z = minSliceZ; z <= maxSliceZ; z++) {
for (let y = minSliceY; y <= maxSliceY; y++) {
for (let x = minSliceX; x <= maxSliceX; x++) {
let i = x + y * this._xSlices + z * this._xSlices * this._ySlices;
let countCurCluster = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)];
// check if the count of lights in current cluster has reached maximum
if (countCurCluster >= MAX_LIGHTS_PER_CLUSTER) {
continue;
}
countCurCluster++;
//since the texture is stored with groups of 4 (rgba format)
let groupIdx = Math.floor(countCurCluster / 4);
let groupOffset = Math.floor(countCurCluster % 4);
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)] = countCurCluster;
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, groupIdx) + groupOffset] = lightIdx;
}
}
}
}

this._clusterTexture.update();
}
}
33 changes: 28 additions & 5 deletions src/renderers/clusteredDeferred.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import toTextureFrag from '../shaders/deferredToTexture.frag.glsl';
import QuadVertSource from '../shaders/quad.vert.glsl';
import fsSource from '../shaders/deferred.frag.glsl.js';
import TextureBuffer from './textureBuffer';
import BaseRenderer from './base';
import BaseRenderer, { MAX_LIGHTS_PER_CLUSTER } from './base';

export const NUM_GBUFFERS = 4;

Expand All @@ -28,8 +28,12 @@ export default class ClusteredDeferredRenderer extends BaseRenderer {
this._progShade = loadShaderProgram(QuadVertSource, fsSource({
numLights: NUM_LIGHTS,
numGBuffers: NUM_GBUFFERS,
xSlices: xSlices, ySlices: ySlices, zSlices: zSlices,
maxLights: MAX_LIGHTS_PER_CLUSTER,
}), {
uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'],
uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]',
'u_lightbuffer', 'u_clusterbuffer', 'u_near', 'u_far', 'u_viewMat',
'u_width', 'u_height', 'u_eye'],
attribs: ['a_uv'],
});

Expand Down Expand Up @@ -74,7 +78,7 @@ export default class ClusteredDeferredRenderer extends BaseRenderer {
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[i], gl.TEXTURE_2D, this._gbuffers[i], 0);
}

if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
throw "Framebuffer incomplete";
}

Expand Down Expand Up @@ -153,10 +157,29 @@ export default class ClusteredDeferredRenderer extends BaseRenderer {
// Use this shader program
gl.useProgram(this._progShade.glShaderProgram);

// TODO: Bind any other shader inputs
// Set the light texture as a uniform input to the shader (Same as forward plus)
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture);
gl.uniform1i(this._progShade.u_lightbuffer, 2);

// Set the cluster texture as a uniform input to the shader
gl.activeTexture(gl.TEXTURE3);
gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture);
gl.uniform1i(this._progShade.u_clusterbuffer, 3);

//Bind other uniforms
gl.uniformMatrix4fv(this._progShade.u_viewMat, false, this._viewMatrix);

gl.uniform1f(this._progShade.u_near, camera.near);
gl.uniform1f(this._progShade.u_far, camera.far);
gl.uniform1f(this._progShade.u_width, canvas.width);
gl.uniform1f(this._progShade.u_height, canvas.height);

// bind uniform u_eye for Blinn-Phong shading calculation
gl.uniform3f(this._progShade.u_eye, camera.position.x, camera.position.y, camera.position.z);

// Bind g-buffers
const firstGBufferBinding = 0; // You may have to change this if you use other texture slots
const firstGBufferBinding = NUM_GBUFFERS; // You may have to change this if you use other texture slots
for (let i = 0; i < NUM_GBUFFERS; i++) {
gl.activeTexture(gl[`TEXTURE${i + firstGBufferBinding}`]);
gl.bindTexture(gl.TEXTURE_2D, this._gbuffers[i]);
Expand Down
16 changes: 12 additions & 4 deletions src/renderers/forwardPlus.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NUM_LIGHTS } from '../scene';
import vsSource from '../shaders/forwardPlus.vert.glsl';
import fsSource from '../shaders/forwardPlus.frag.glsl.js';
import TextureBuffer from './textureBuffer';
import BaseRenderer from './base';
import BaseRenderer, { MAX_LIGHTS_PER_CLUSTER } from './base';

export default class ForwardPlusRenderer extends BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -15,9 +15,12 @@ export default class ForwardPlusRenderer extends BaseRenderer {
this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8);

this._shaderProgram = loadShaderProgram(vsSource, fsSource({
numLights: NUM_LIGHTS,
numLights: NUM_LIGHTS, xSlices: xSlices, ySlices: ySlices, zSlices: zSlices,
maxLights: MAX_LIGHTS_PER_CLUSTER,
}), {
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'],
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap',
'u_lightbuffer', 'u_clusterbuffer', 'u_near', 'u_far',
'u_width', 'u_height', 'u_viewMat'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});

Expand Down Expand Up @@ -75,8 +78,13 @@ export default class ForwardPlusRenderer extends BaseRenderer {
gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture);
gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3);

// TODO: Bind any other shader inputs
//Bind other uniforms
gl.uniformMatrix4fv(this._shaderProgram.u_viewMat, false, this._viewMatrix);

gl.uniform1f(this._shaderProgram.u_near, camera.near);
gl.uniform1f(this._shaderProgram.u_far, camera.far);
gl.uniform1f(this._shaderProgram.u_width, canvas.width);
gl.uniform1f(this._shaderProgram.u_height, canvas.height);
// Draw the scene. This function takes the shader program so that the model's textures can be bound to the right inputs
scene.draw(this._shaderProgram);
}
Expand Down
2 changes: 1 addition & 1 deletion src/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const LIGHT_RADIUS = 5.0;
export const LIGHT_DT = -0.03;

// TODO: This controls the number of lights
export const NUM_LIGHTS = 100;
export const NUM_LIGHTS = 200;

class Scene {
constructor() {
Expand Down
115 changes: 109 additions & 6 deletions src/shaders/deferred.frag.glsl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,120 @@ export default function(params) {
precision highp float;

uniform sampler2D u_gbuffers[${params.numGBuffers}];
uniform sampler2D u_lightbuffer;
uniform sampler2D u_clusterbuffer;

uniform float u_near;
uniform float u_far;
uniform float u_width;
uniform float u_height;
uniform mat4 u_viewMat;
uniform vec3 u_eye;

varying vec2 v_uv;

vec3 applyNormalMap(vec3 geomnor, vec3 normap) {
normap = normap * 2.0 - 1.0;
vec3 up = normalize(vec3(0.001, 1, 0.001));
vec3 surftan = normalize(cross(geomnor, up));
vec3 surfbinor = cross(geomnor, surftan);
return normap.y * surftan + normap.x * surfbinor + normap.z * geomnor;
}

struct Light {
vec3 position;
float radius;
vec3 color;
};

float ExtractFloat(sampler2D texture, int textureWidth, int textureHeight, int index, int component) {
float u = float(index + 1) / float(textureWidth + 1);
int pixel = component / 4;
float v = float(pixel + 1) / float(textureHeight + 1);
vec4 texel = texture2D(texture, vec2(u, v));
int pixelComponent = component - pixel * 4;
if (pixelComponent == 0) {
return texel[0];
} else if (pixelComponent == 1) {
return texel[1];
} else if (pixelComponent == 2) {
return texel[2];
} else if (pixelComponent == 3) {
return texel[3];
}
}

Light UnpackLight(int index) {
Light light;
float u = float(index + 1) / float(${params.numLights + 1});
vec4 v1 = texture2D(u_lightbuffer, vec2(u, 0.3));
vec4 v2 = texture2D(u_lightbuffer, vec2(u, 0.6));
light.position = v1.xyz;

// LOOK: This extracts the 4th float (radius) of the (index)th light in the buffer
// Note that this is just an example implementation to extract one float.
// There are more efficient ways if you need adjacent values
light.radius = ExtractFloat(u_lightbuffer, ${params.numLights}, 2, index, 3);

light.color = v2.rgb;
return light;
}

// Cubic approximation of gaussian curve so we falloff to exactly 0 at the light radius
float cubicGaussian(float h) {
if (h < 1.0) {
return 0.25 * pow(2.0 - h, 3.0) - pow(1.0 - h, 3.0);
} else if (h < 2.0) {
return 0.25 * pow(2.0 - h, 3.0);
} else {
return 0.0;
}
}

void main() {
// TODO: extract data from g buffers and do lighting
// vec4 gb0 = texture2D(u_gbuffers[0], v_uv);
// vec4 gb1 = texture2D(u_gbuffers[1], v_uv);
// vec4 gb2 = texture2D(u_gbuffers[2], v_uv);
// vec4 gb3 = texture2D(u_gbuffers[3], v_uv);
vec3 fragColor = vec3(0.0);
vec4 albedo = texture2D(u_gbuffers[0], v_uv);
vec4 normal = texture2D(u_gbuffers[1], v_uv);
vec4 position = texture2D(u_gbuffers[2], v_uv);

vec4 viewPos = u_viewMat * vec4(position.x, position.y, position.z, 1.0);

//Getting the frustum index
int x = int(gl_FragCoord.x / (u_width / float(${params.xSlices})));
int y = int(gl_FragCoord.y / (u_height / float(${params.ySlices})));
int z = int((-viewPos.z - u_near) / ((u_far - u_near) / float(${params.zSlices})));

gl_FragColor = vec4(v_uv, 0.0, 1.0);
int clusterIdx = x + y * ${params.xSlices} + z * ${params.xSlices} * ${params.ySlices};

const int clusterTextureWidth = ${params.xSlices} * ${params.ySlices} * ${params.zSlices};
const int clusterTextureHeight = int(ceil(float(${params.maxLights} + 1) / 4.0));

int lightsCount = int(ExtractFloat(u_clusterbuffer, clusterTextureWidth, clusterTextureHeight, clusterIdx, 0));
for (int i = 0; i < ${params.numLights}; ++i) {
if (i >= lightsCount) break;

int lightIdx = int(ExtractFloat(u_clusterbuffer, clusterTextureWidth, clusterTextureHeight, clusterIdx, i + 1));
Light light = UnpackLight(lightIdx);
float lightDist = distance(light.position, position.xyz);

vec3 L = (light.position - position.xyz) / lightDist;
vec3 V = normalize(light.position - u_eye);
vec3 H = normalize(V + L);

float lightIntensity = cubicGaussian(2.0 * lightDist / light.radius);
float lambertTerm = max(dot(L, normal.xyz), 0.0);

//Calculate Blinn-Phong shading
float exponent = 200.0;
float specularTerm = pow(max(dot(H, normal.xyz), 0.0), exponent);

fragColor += (albedo.xyz * lambertTerm + specularTerm) * light.color * vec3(lightIntensity);
}

const vec3 ambientLight = vec3(0.025);
fragColor += albedo.xyz * ambientLight;

gl_FragColor = vec4(fragColor, 1.0);
}
`;
}
12 changes: 5 additions & 7 deletions src/shaders/deferredToTexture.frag.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ vec3 applyNormalMap(vec3 geomnor, vec3 normap) {
}

void main() {
vec3 norm = applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv)));
vec3 norm = normalize(applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv))));
vec3 col = vec3(texture2D(u_colmap, v_uv));

// TODO: populate your g buffer
// gl_FragData[0] = ??
// gl_FragData[1] = ??
// gl_FragData[2] = ??
// gl_FragData[3] = ??
}
gl_FragData[0] = vec4(col.xyz, 0.0);
gl_FragData[1] = vec4(norm.xyz, 0.0);
gl_FragData[2] = vec4(v_position.xyz, 0.0);
}
Loading