diff --git a/README.md b/README.md index a903608..8b15344 100644 --- a/README.md +++ b/README.md @@ -3,29 +3,123 @@ WebGL Clustered Deferred and Forward+ Shading **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) **Google Chrome 222.2** on - Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Hanming Zhang +* Tested on: **Google Chrome 62.0** on + Windows 10 Education, i7-3630QM @ 2.40GHz, 16GB, GTX 670MX 3072MB (Personal laptop) ### Live Online -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +This project requires a WebGL-capable browser with support for several extensions(listed below). You can check for support on [WebGL Report](http://webglreport.com/): +- OES_texture_float +- OES_texture_float_linear +- OES_element_index_uint +- EXT_frag_depth +- WEBGL_depth_texture +- WEBGL_draw_buffer -### Demo Video/GIF +[Live Demo Link](https://HanmingZhang.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus/) -[![](img/video.png)](TODO) +[![](img/screenshot.jpg)](https://HanmingZhang.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus/) -### (TODO: Your README) +### Demo Video -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +[![](img/videoCover.jpg)](https://www.youtube.com/watch?v=JL3-XckKPKk) -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +Project Features +================ + +### Screenshots: +![](img/screenshot.jpg) | ![](img/screenshot_bloom.jpg) +------------ | ------------- +no post processing | bloom +![](img/screenshot_rampShading.jpg) | ![](img/screenshot_bloom+rampShading.jpg) +ramp shading | bloom + ramping shading + + +### Clustered Forward+ + - cluster data structure to keep track of how many lights are in each cluster and what their indices are + - Render the scene using only the lights that overlap a given cluster + +### Clustered Deferred + - cluster part are the same as Clustered Forward+ + - Store vertex attributes in g-buffer + - Read g-buffer in a shader to produce final output + - Two g-buffers are used(reconstruct norm using two-component normal and reconstruct position using screen space X, Y, and depth information) + +### Effects +- Bloom (additional pipeline stages include brightness filter, horizontal & vertical Gaussian blur and finally combine) +- Ramp shading + +### Controls : + - Left mouse button to rotate Camera + - Right mouse button to move Camera + - Middle mouse button to zoom in/out + + +### Project Analysis +#### All analysis happens on a 1024x768 canvas and 500 lights are in our sponza scene. + +- #### Clustered Deferred Optimization + +Before optimization, 4 g-buffers are used, which are albedo, normal, depth and position respectively and they are shown like below. + +![](img/color_albedo.jpg) | ![](img/normal.jpg) +------------ | ------------- +color / albedo | normal +![](img/depth.jpg) | ![](img/position.jpg) +depth | position + + After optimization, 2 g-buffers are used, which are albedo, view space depth(to determine fragment cluster depth index, because I slice clusters in view space), two component normal(X, Y) and NDC depth(to reconstruct position with screen space position). The structure is shown as below. + + ![](img/gbuffer.jpg) + + ##### Performance Comparison + + ![](img/gbuffer_chart.jpg) + + ##### Analysis: + As we can see, when the size of g-buffers reduces from 4 to 2, we have around 16% performance increase. During the process of rendering, we only need to write information to 2 frames buffers, and also only need to extract information from these 2 buffers. The bandwidth burden is reduced. + + + +- #### Forward, Clustered Forward and Clustered Deferred Comparison + + ![](img/comparsion_chart1.jpg) + + ##### Analysis: + Since forward will check every light in the scene in the fragment shader, so naturally, it takes the most time. Clusters works well, and we gain huge performance increase by using it, although on the CPU side, we need to loop through every light and assign their index to the influenced clusters. + + About cluster slice method I use (all slices happens on the view space): + 1. slice view space depth(between near and far clips) in a natural logarithm way, which means cluster size will smaller(in depth) when it's near and larger when it's far. + 2. evenly slice view frustum in X and Y directly (which means evenly slice our screen) + finally, our view frustum should looks like this, + ![](img/viewfrustum.jpg) + + As a result, we only shade limited amount of lights, which influences the cluster each fragment belongs to in fragment shader. This saves a lot of rendering time. + In terms of clustered deferred shading, since we only save visible(nearer to camera and not occluded) fragments' information(color, normal, depth) to g-buffers, we save the time to render those not visible fragments. + + + +- #### Bloom & ramp shading performance analysis + + ![](img/comparsion_chart2.jpg) + + ##### Analysis: + Basically, Bloom post processing effect take additional stages to do brightness filter, horizontal Gaussian blur, vertical Gaussian blur and finally combine it with origin no effect frame. But in our case, it does not take so long time to do all these things, since all these actions are very basic, and just reading and writing frame buffers again and again. Several results during these stages are shown below. + +![](img/bloom_brightnessFilter.jpg) | ![](img/bloom_brightnessFilter_horizontalBlur.jpg) | ![](img/bloom_brightnessFilter_horizontalBlur+verticalBlur.jpg) +------------ | ------------- | ------------- +after brightness filter | after horizontal Gaussian Blur | after vertical Gaussian Blur + + + Ramp shading is almost the same as no effect rendering in terms of performance, and we only do several extra steps to modulate the diffuse coefficient(lambert term) in fragment shader. ### Credits +* [OpenGL Bloom Tutorial](http://prideout.net/archive/bloom/index.php) by Philip Rideout +* [OpenGL Bloom Effects](https://www.youtube.com/watch?v=LyoSSoYyfVU) by ThinMatrix +* Clustered Deferred and Forward Shading, Olsson et.al. 2012 * [Three.js](https://github.com/mrdoob/three.js) by [@mrdoob](https://github.com/mrdoob) and contributors * [stats.js](https://github.com/mrdoob/stats.js) by [@mrdoob](https://github.com/mrdoob) and contributors * [webgl-debug](https://github.com/KhronosGroup/WebGLDeveloperTools) by Khronos Group Inc. diff --git a/img/bloom_brightnessFilter.jpg b/img/bloom_brightnessFilter.jpg new file mode 100644 index 0000000..4cec9c7 Binary files /dev/null and b/img/bloom_brightnessFilter.jpg differ diff --git a/img/bloom_brightnessFilter_horizontalBlur+verticalBlur.jpg b/img/bloom_brightnessFilter_horizontalBlur+verticalBlur.jpg new file mode 100644 index 0000000..a64ec7e Binary files /dev/null and b/img/bloom_brightnessFilter_horizontalBlur+verticalBlur.jpg differ diff --git a/img/bloom_brightnessFilter_horizontalBlur.jpg b/img/bloom_brightnessFilter_horizontalBlur.jpg new file mode 100644 index 0000000..d7e6b44 Binary files /dev/null and b/img/bloom_brightnessFilter_horizontalBlur.jpg differ diff --git a/img/color_albedo.jpg b/img/color_albedo.jpg new file mode 100644 index 0000000..59f0ae7 Binary files /dev/null and b/img/color_albedo.jpg differ diff --git a/img/comparsion_chart1.jpg b/img/comparsion_chart1.jpg new file mode 100644 index 0000000..dd01007 Binary files /dev/null and b/img/comparsion_chart1.jpg differ diff --git a/img/comparsion_chart2.jpg b/img/comparsion_chart2.jpg new file mode 100644 index 0000000..6db3a41 Binary files /dev/null and b/img/comparsion_chart2.jpg differ diff --git a/img/depth.jpg b/img/depth.jpg new file mode 100644 index 0000000..eaaf62b Binary files /dev/null and b/img/depth.jpg differ diff --git a/img/gbuffer.jpg b/img/gbuffer.jpg new file mode 100644 index 0000000..f8b538b Binary files /dev/null and b/img/gbuffer.jpg differ diff --git a/img/gbuffer_chart.jpg b/img/gbuffer_chart.jpg new file mode 100644 index 0000000..dfd20e5 Binary files /dev/null and b/img/gbuffer_chart.jpg differ diff --git a/img/normal.jpg b/img/normal.jpg new file mode 100644 index 0000000..5d8e3a3 Binary files /dev/null and b/img/normal.jpg differ diff --git a/img/position.jpg b/img/position.jpg new file mode 100644 index 0000000..ed41cdd Binary files /dev/null and b/img/position.jpg differ diff --git a/img/screenshot.jpg b/img/screenshot.jpg new file mode 100644 index 0000000..fbeabfe Binary files /dev/null and b/img/screenshot.jpg differ diff --git a/img/screenshot_bloom+rampShading.jpg b/img/screenshot_bloom+rampShading.jpg new file mode 100644 index 0000000..8e80aae Binary files /dev/null and b/img/screenshot_bloom+rampShading.jpg differ diff --git a/img/screenshot_bloom.jpg b/img/screenshot_bloom.jpg new file mode 100644 index 0000000..ccd5b33 Binary files /dev/null and b/img/screenshot_bloom.jpg differ diff --git a/img/screenshot_rampShading.jpg b/img/screenshot_rampShading.jpg new file mode 100644 index 0000000..f874ff3 Binary files /dev/null and b/img/screenshot_rampShading.jpg differ diff --git a/img/videoCover.jpg b/img/videoCover.jpg new file mode 100644 index 0000000..bf560cf Binary files /dev/null and b/img/videoCover.jpg differ diff --git a/img/viewfrustum.jpg b/img/viewfrustum.jpg new file mode 100644 index 0000000..7693a15 Binary files /dev/null and b/img/viewfrustum.jpg differ diff --git a/models/sponza/npm-debug.log b/models/sponza/npm-debug.log new file mode 100644 index 0000000..d74bcf8 --- /dev/null +++ b/models/sponza/npm-debug.log @@ -0,0 +1,47 @@ +0 info it worked if it ends with ok +1 verbose cli [ 'D:\\nodejs\\node.exe', +1 verbose cli 'D:\\nodejs\\node_modules\\npm\\bin\\npm-cli.js', +1 verbose cli 'start' ] +2 info using npm@3.10.10 +3 info using node@v6.10.0 +4 verbose run-script [ 'prestart', 'start', 'poststart' ] +5 info lifecycle @~prestart: @ +6 silly lifecycle @~prestart: no script for prestart, continuing +7 info lifecycle @~start: @ +8 verbose lifecycle @~start: unsafe-perm in lifecycle true +9 verbose lifecycle @~start: PATH: D:\nodejs\node_modules\npm\bin\node-gyp-bin;D:\GPU_Projects\Project5-WebGL-Clustered-Deferred-Forward-Plus\node_modules\.bin;C:\ProgramData\Oracle\Java\javapath;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\libnvvp;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\PharosSystems\Core;C:\Users\HM\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;D:\QuickTime\QTSystem\;D:\nodejs\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\GtkSharp\2.12\bin;D:\CMake\bin;C:\Program Files\Git\cmd;%USERPROFILE%\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\SourceGear\Common\DiffMerge\;C:\Users\HM\AppData\Local\Microsoft\WindowsApps;C:\Users\HM\AppData\Roaming\npm +10 verbose lifecycle @~start: CWD: D:\GPU_Projects\Project5-WebGL-Clustered-Deferred-Forward-Plus +11 silly lifecycle @~start: Args: [ '/d /s /c', 'webpack-dev-server' ] +12 silly lifecycle @~start: Returned: code: 3221225786 signal: null +13 info lifecycle @~start: Failed to exec start script +14 verbose stack Error: @ start: `webpack-dev-server` +14 verbose stack Exit status 3221225786 +14 verbose stack at EventEmitter. (D:\nodejs\node_modules\npm\lib\utils\lifecycle.js:255:16) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at EventEmitter.emit (events.js:191:7) +14 verbose stack at ChildProcess. (D:\nodejs\node_modules\npm\lib\utils\spawn.js:40:14) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at ChildProcess.emit (events.js:191:7) +14 verbose stack at maybeClose (internal/child_process.js:877:16) +14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) +15 verbose pkgid @ +16 verbose cwd D:\GPU_Projects\Project5-WebGL-Clustered-Deferred-Forward-Plus\models\sponza +17 error Windows_NT 10.0.14393 +18 error argv "D:\\nodejs\\node.exe" "D:\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "start" +19 error node v6.10.0 +20 error npm v3.10.10 +21 error code ELIFECYCLE +22 error @ start: `webpack-dev-server` +22 error Exit status 3221225786 +23 error Failed at the @ start script 'webpack-dev-server'. +23 error Make sure you have the latest version of node.js and npm installed. +23 error If you do, this is most likely a problem with the package, +23 error not with npm itself. +23 error Tell the author that this fails on your system: +23 error webpack-dev-server +23 error You can get information on how to open an issue for this project with: +23 error npm bugs +23 error Or if that isn't available, you can get their info via: +23 error npm owner ls +23 error There is likely additional logging output above. +24 verbose exit [ 1, true ] diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..20a5848 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,47 @@ +0 info it worked if it ends with ok +1 verbose cli [ 'D:\\nodejs\\node.exe', +1 verbose cli 'D:\\nodejs\\node_modules\\npm\\bin\\npm-cli.js', +1 verbose cli 'start' ] +2 info using npm@3.10.10 +3 info using node@v6.10.0 +4 verbose run-script [ 'prestart', 'start', 'poststart' ] +5 info lifecycle @~prestart: @ +6 silly lifecycle @~prestart: no script for prestart, continuing +7 info lifecycle @~start: @ +8 verbose lifecycle @~start: unsafe-perm in lifecycle true +9 verbose lifecycle @~start: PATH: D:\nodejs\node_modules\npm\bin\node-gyp-bin;D:\GPU_Projects\Project5-WebGL-Clustered-Deferred-Forward-Plus\node_modules\.bin;C:\ProgramData\Oracle\Java\javapath;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\libnvvp;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\PharosSystems\Core;C:\Users\HM\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;D:\QuickTime\QTSystem\;D:\nodejs\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\GtkSharp\2.12\bin;D:\CMake\bin;C:\Program Files\Git\cmd;C:\Windows\system32\config\systemprofile\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\SourceGear\Common\DiffMerge\;C:\Users\HM\AppData\Local\Microsoft\WindowsApps;C:\Users\HM\AppData\Roaming\npm;C:\Users\HM\AppData\Local\atom\bin +10 verbose lifecycle @~start: CWD: D:\GPU_Projects\Project5-WebGL-Clustered-Deferred-Forward-Plus +11 silly lifecycle @~start: Args: [ '/d /s /c', 'webpack-dev-server' ] +12 silly lifecycle @~start: Returned: code: 3221225786 signal: null +13 info lifecycle @~start: Failed to exec start script +14 verbose stack Error: @ start: `webpack-dev-server` +14 verbose stack Exit status 3221225786 +14 verbose stack at EventEmitter. (D:\nodejs\node_modules\npm\lib\utils\lifecycle.js:255:16) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at EventEmitter.emit (events.js:191:7) +14 verbose stack at ChildProcess. (D:\nodejs\node_modules\npm\lib\utils\spawn.js:40:14) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at ChildProcess.emit (events.js:191:7) +14 verbose stack at maybeClose (internal/child_process.js:877:16) +14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) +15 verbose pkgid @ +16 verbose cwd D:\GPU_Projects\Project5-WebGL-Clustered-Deferred-Forward-Plus +17 error Windows_NT 10.0.14393 +18 error argv "D:\\nodejs\\node.exe" "D:\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "start" +19 error node v6.10.0 +20 error npm v3.10.10 +21 error code ELIFECYCLE +22 error @ start: `webpack-dev-server` +22 error Exit status 3221225786 +23 error Failed at the @ start script 'webpack-dev-server'. +23 error Make sure you have the latest version of node.js and npm installed. +23 error If you do, this is most likely a problem with the package, +23 error not with npm itself. +23 error Tell the author that this fails on your system: +23 error webpack-dev-server +23 error You can get information on how to open an issue for this project with: +23 error npm bugs +23 error Or if that isn't available, you can get their info via: +23 error npm owner ls +23 error There is likely additional logging output above. +24 verbose exit [ 1, true ] diff --git a/src/init.js b/src/init.js index 1b09377..9de2fc2 100644 --- a/src/init.js +++ b/src/init.js @@ -1,5 +1,5 @@ // TODO: Change this to enable / disable debug mode -export const DEBUG = true && process.env.NODE_ENV === 'development'; +export const DEBUG = false && process.env.NODE_ENV === 'development'; import DAT from 'dat-gui'; import WebGLDebug from 'webgl-debug'; diff --git a/src/main.js b/src/main.js index 1cbbf9a..f69f3be 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,8 @@ const CLUSTERED_DEFFERED = 'Clustered Deferred'; const params = { renderer: CLUSTERED_FORWARD_PLUS, _renderer: null, + Bloom: false, + ToonShading: false, }; setRenderer(params.renderer); @@ -18,19 +20,50 @@ setRenderer(params.renderer); function setRenderer(renderer) { switch(renderer) { case FORWARD: - params._renderer = new ForwardRenderer(); + params._renderer = new ForwardRenderer(params.Bloom, params.ToonShading); break; case CLUSTERED_FORWARD_PLUS: - params._renderer = new ClusteredForwardPlusRenderer(15, 15, 15); + params._renderer = new ClusteredForwardPlusRenderer(15, 15, 15, params.Bloom, params.ToonShading); break; case CLUSTERED_DEFFERED: - params._renderer = new ClusteredDeferredRenderer(15, 15, 15); + params._renderer = new ClusteredDeferredRenderer(15, 15, 15, params.Bloom, params.ToonShading); + break; + } +} + +function setBloom(){ + switch(params.renderer) { + case FORWARD: + params._renderer = new ForwardRenderer(params.Bloom, params.ToonShading); + break; + case CLUSTERED_FORWARD_PLUS: + params._renderer = new ClusteredForwardPlusRenderer(15, 15, 15, params.Bloom, params.ToonShading); + break; + case CLUSTERED_DEFFERED: + params._renderer = new ClusteredDeferredRenderer(15, 15, 15, params.Bloom, params.ToonShading); + break; + } +} + +function setToonShading(){ + switch(params.renderer) { + case FORWARD: + params._renderer = new ForwardRenderer(params.Bloom, params.ToonShading); + break; + case CLUSTERED_FORWARD_PLUS: + params._renderer = new ClusteredForwardPlusRenderer(15, 15, 15, params.Bloom, params.ToonShading); + break; + case CLUSTERED_DEFFERED: + params._renderer = new ClusteredDeferredRenderer(15, 15, 15, params.Bloom, params.ToonShading); break; } } gui.add(params, 'renderer', [FORWARD, CLUSTERED_FORWARD_PLUS, CLUSTERED_DEFFERED]).onChange(setRenderer); +gui.add(params, 'Bloom').onChange(setBloom); +gui.add(params, 'ToonShading').onChange(setToonShading); + const scene = new Scene(); scene.loadGLTF('models/sponza/sponza.gltf'); @@ -43,4 +76,4 @@ function render() { params._renderer.render(camera, scene); } -makeRenderLoop(render)(); \ No newline at end of file +makeRenderLoop(render)(); diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js index 9521fbd..83a307d 100644 --- a/src/renderers/clustered.js +++ b/src/renderers/clustered.js @@ -2,18 +2,78 @@ import { mat4, vec4, vec3 } from 'gl-matrix'; import { NUM_LIGHTS } from '../scene'; import TextureBuffer from './textureBuffer'; +import { canvas } from '../init'; + +/* + Since so far, I just naviely evenly divide view space + when total light number incease, this number should also increase! + Say, change it to 1000 when NUM_LIGHTS in scene.js is 1000 +*/ export const MAX_LIGHTS_PER_CLUSTER = 100; +// Used in cluster update, 1 offset (in X, Y) is acceptable to eliminate some erros arised by accuracy +const cluster_adjustment_x_y = 2; +const cluster_adjustment_z = 0; + +// Adapted from paper Clusterd Defferred and Forwar Shading by Olsson +//function getCulsterIndexK(z_EyeSpace, nearClip, fovby2, ySlices) { + //let deg2rad = Math.PI/180.0; + + //let tmp1 = Math.log(-z_EyeSpace / nearClip) / Math.LN10; // convert from natural logarithm to a base 10 logarithm + //let tmp2 = Math.log(1.0 + 2.0 * Math.tan(fovby2 * deg2rad) / ySlices) / Math.LN10; // convert from natural logarithm to a base 10 logarithm + + //return Math.floor(0.12 * tmp1 / tmp2); // 0.2 is a cluster depth division scale + + //return 0.0; +//} + +// Slice view depth in a log way +function getCulsterDepthIndex(viewSpaceDepth, nearClip) { + if(viewSpaceDepth < nearClip){ + return -1.0; + } + else{ + //2.15 is calculated based on near and far clip + //near Clip : 0.1 + //far Clip : 1000.0 + return Math.floor(2.15 * Math.log(viewSpaceDepth - nearClip + 1.0)); + } +} + +function clamp(n, min, max){ + return Math.max(min, Math.min(n, max)); +} + +// math here, similar triangle is used +// to get the distance of centroid of the sphere(of point light) to plane +// function distanceToPlaneY(theta_radius_y, pointLightPos_ViewSpace){ +// var y1 = Math.tan(theta_radius_y) * (-pointLightPos_ViewSpace[2]); +// var tmp = Math.abs(y1 - pointLightPos_ViewSpace[1]); // delta Y +// +// return Math.cos(theta_radius_y) * tmp; +// } +// +// function distanceToPlaneX(theta_radius_x, pointLightPos_ViewSpace){ +// var x1 = Math.tan(theta_radius_x) * (-pointLightPos_ViewSpace[2]); +// var tmp = Math.abs(x1 - pointLightPos_ViewSpace[0]); // delta X +// +// return Math.cos(theta_radius_x) * tmp; +// } + + export default class ClusteredRenderer { + constructor(xSlices, ySlices, zSlices) { // Create a texture to store cluster data. Each cluster stores the number of lights followed by the light indices this._clusterTexture = new TextureBuffer(xSlices * ySlices * zSlices, MAX_LIGHTS_PER_CLUSTER + 1); this._xSlices = xSlices; this._ySlices = ySlices; this._zSlices = zSlices; + } - updateClusters(camera, viewMatrix, scene) { + + updateClusters(camera, viewMatrix, scene, viewProjectionMatrix) { // 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... @@ -27,6 +87,213 @@ export default class ClusteredRenderer { } } + // Just evenly divide cluster in view space(frustrum) here + //var cluster_depth_array_stride = (camera.far - camera.near) / this._zSlices; + + // Update the buffer used to populate the texture packed with light data + for (let i = 0; i < NUM_LIGHTS; ++i) { + + // Get light view space position + let lightPos = vec4.create(); + lightPos[0] = scene.lights[i].position[0]; + lightPos[1] = scene.lights[i].position[1]; + lightPos[2] = scene.lights[i].position[2]; + lightPos[3] = 1.0; + + // tranfomr light Pos from world to view space + vec4.transformMat4(lightPos, lightPos, viewMatrix); + + var pointLightRadius = scene.lights[i].radius; + + + // Clip sphere in z direction in view space + // Set clipping threshold values + var cluster_StartIdx_z_threshold = -lightPos[2] - pointLightRadius; + var cluster_EndIdx_z_threshold = -lightPos[2] + pointLightRadius; + + //var cluster_StartIdx_z = Math.floor((cluster_StartIdx_z_threshold - camera.near) / cluster_depth_array_stride); + //var cluster_EndIdx_z = Math.floor((cluster_EndIdx_z_threshold - camera.near) / cluster_depth_array_stride); + var cluster_StartIdx_z = getCulsterDepthIndex(cluster_StartIdx_z_threshold, camera.near); + var cluster_EndIdx_z = getCulsterDepthIndex(cluster_EndIdx_z_threshold, camera.near); + + + if(cluster_StartIdx_z > this._zSlices + cluster_adjustment_z || cluster_EndIdx_z < -cluster_adjustment_z){ continue; } // culling + cluster_StartIdx_z = clamp(cluster_StartIdx_z - cluster_adjustment_z, 0, this._zSlices - 1); + cluster_EndIdx_z = clamp(cluster_EndIdx_z + cluster_adjustment_z, 0, this._zSlices - 1); + + +/* + let cluster_StartIdx_z = 0; // 0 -> this._zSlices - 1 + let cluster_EndIdx_z = this._zSlices; // 0 -> this._zSlices - 1 + + let cluster_depth_distance = camera.near; + while(cluster_StartIdx_z <= this._zSlices){ + if(cluster_depth_distance >= cluster_StartIdx_z_threshold){ + break; + } + cluster_depth_distance += cluster_depth_array_stride; + cluster_StartIdx_z++; + } + if(cluster_StartIdx_z != 0){cluster_StartIdx_z--;} + + cluster_depth_distance= camera.far; + while(cluster_EndIdx_z >= cluster_StartIdx_z){ + if(cluster_depth_distance <= cluster_EndIdx_z_threshold){ + break; + } + cluster_depth_distance -= cluster_depth_array_stride; + cluster_EndIdx_z--; + } + if(cluster_EndIdx_z == this._zSlices){cluster_EndIdx_z--;} + else if(cluster_EndIdx_z != this._zSlices - 1){cluster_EndIdx_z++;} +*/ + + + // should be in the view space + let lightPos_vec3 = vec3.create(); + lightPos_vec3[0] = lightPos[0]; + lightPos_vec3[1] = lightPos[1]; + lightPos_vec3[2] = -lightPos[2]; // to positve + let lightCentroidDistance = vec3.length(lightPos_vec3); + + var cluster_StartIdx_x; + var cluster_EndIdx_x; + var cluster_StartIdx_y; + var cluster_EndIdx_y; + + // If eye is in the light (sphere) + if(lightCentroidDistance <= pointLightRadius){ + cluster_StartIdx_x = 0; + cluster_EndIdx_x = this._xSlices - 1; + + cluster_StartIdx_y = 0; + cluster_EndIdx_y = this._ySlices - 1; + } + // If eye is outside the light (sphere) + else{ + /* + This is one of the several cluster assignment methods I try. + It's based on pure angle, which have the best effect here. + Other methods like projecting sphere / Bounding box of light to screen, + or calculating distance to division plane (X, Y) all have some side effects, + and don't run very well in my case + */ + let rad2deg = 180.0 / Math.PI; + + let deltaAngle = rad2deg * Math.asin(pointLightRadius / lightCentroidDistance); // should always between 0 -> PI/2 (0->90) + let startAngle = -camera.fov / 2.0; + + //handle x direction, ignore y, on the x-z plane + let centroidAngle = rad2deg * Math.atan2(lightPos_vec3[0], lightPos_vec3[2]); + let minAngle = centroidAngle - deltaAngle; + let maxAngle = centroidAngle + deltaAngle; + + let x_angle_stride = camera.fov / this._xSlices; + cluster_StartIdx_x = Math.floor((minAngle - startAngle) / x_angle_stride); + cluster_EndIdx_x = Math.floor((maxAngle - startAngle) / x_angle_stride); + if(cluster_StartIdx_x > this._xSlices + cluster_adjustment_x_y || cluster_EndIdx_x < -cluster_adjustment_x_y){ continue; } // culling + cluster_StartIdx_x = clamp(cluster_StartIdx_x - cluster_adjustment_x_y, 0, this._xSlices - 1); + cluster_EndIdx_x = clamp(cluster_EndIdx_x + cluster_adjustment_x_y, 0, this._xSlices - 1); + + //handle y direction, ignore x, on the y-z plane + centroidAngle = rad2deg * Math.atan2(lightPos_vec3[1], lightPos_vec3[2]); + minAngle = centroidAngle - deltaAngle; + maxAngle = centroidAngle + deltaAngle; + + let y_angle_stride = camera.fov / this._ySlices; + cluster_StartIdx_y = Math.floor((minAngle - startAngle) / y_angle_stride); + cluster_EndIdx_y = Math.floor((maxAngle - startAngle) / y_angle_stride); + if(cluster_StartIdx_y > this._ySlices + cluster_adjustment_x_y || cluster_EndIdx_y < -cluster_adjustment_x_y){ continue; } // culling + cluster_StartIdx_y = clamp(cluster_StartIdx_y - cluster_adjustment_x_y, 0, this._ySlices - 1); + cluster_EndIdx_y = clamp(cluster_EndIdx_y + cluster_adjustment_x_y, 0, this._ySlices - 1); + } + + + // loop through clusters in clipped z direction + for (let z = cluster_StartIdx_z; z <= cluster_EndIdx_z; ++z) { +/* + // Clip based on dividing planes of view frustrum + // this is independent of depth + let deg2rad = Math.PI / 180.0; + let angle_stride_y = camera.fov / this._ySlices; + let angle_stride_x = camera.fov / this._xSlices; + + // x direction + let cluster_StartIdx_x = 0; // should between 0 -> this._xSlices - 1 finally + let cluster_EndIdx_x = this._xSlices; // 0 -> this._xSlices - 1 + + let view_angle_x = -camera.fov / 2.0; + while(cluster_StartIdx_x <= this._xSlices){ + if(distanceToPlaneX(view_angle_x * deg2rad, lightPos) <= pointLightRadius){ + break; + } + view_angle_x += angle_stride_x; + cluster_StartIdx_x ++; + } + if(cluster_StartIdx_x > this._xSlices){continue;} // no dividing plane is in the sphere + if(cluster_StartIdx_x != 0){cluster_StartIdx_x--;} // 0 -> this._xSlices - 1 + + view_angle_x = camera.fov / 2.0; + while(cluster_EndIdx_x >= cluster_StartIdx_x){ + if(distanceToPlaneX(view_angle_x * deg2rad, lightPos) <= pointLightRadius){ + break; + } + view_angle_x -= angle_stride_x; + cluster_EndIdx_x --; + } + if(cluster_EndIdx_x == this._xSlices){cluster_EndIdx_x--;} + else if(cluster_EndIdx_x != this._xSlices - 1){cluster_EndIdx_x++;} + + console.log("start idx x"); + console.log(cluster_StartIdx_x); + console.log("end idx x"); + console.log(cluster_EndIdx_x); + + // Y direction + let cluster_StartIdx_y = 0; // 0 -> this._ySlices - 1 + let cluster_EndIdx_y = this._ySlices; // 0 -> this._ySlices - 1 + + let view_angle_y = -camera.fov / 2.0; + while(cluster_StartIdx_y <= this._ySlices){ + if(distanceToPlaneY(view_angle_y * deg2rad, lightPos) <= pointLightRadius){ + break; + } + view_angle_y += angle_stride_y; + cluster_StartIdx_y ++; + } + if(cluster_StartIdx_y > this._ySlices){continue;} // no dividing plane is in the sphere + if(cluster_StartIdx_y != 0){cluster_StartIdx_y--;} + + view_angle_y = camera.fov / 2.0; + while(cluster_EndIdx_y >= cluster_StartIdx_y){ + if(distanceToPlaneY(view_angle_y * deg2rad, lightPos) <= pointLightRadius){ + break; + } + view_angle_y -= angle_stride_y; + cluster_EndIdx_y --; + } + if(cluster_EndIdx_y == this._ySlices){cluster_EndIdx_y--;} + else if(cluster_EndIdx_y != this._ySlices - 1){cluster_EndIdx_y++;} +*/ + + + // fill cluster buffer + for(let y = cluster_StartIdx_y; y <= cluster_EndIdx_y; ++y){ + for(let x = cluster_StartIdx_x; x <= cluster_EndIdx_x; ++x){ + let clusterIdx = x + y * this._xSlices + z * this._xSlices * this._ySlices; + + // assume account will always <= MAX_LIGHTS_PER_CLUSTER here + let count = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, 0)]; + count++; // add influenced count number + let pixelIdx = Math.floor(count / 4.0); + let offsetWithinPixel = count % 4; + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, pixelIdx) + offsetWithinPixel] = i; // add an light index + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, 0)] = count; // update influenced light count for this cluster + } + } + } + + } this._clusterTexture.update(); } -} \ No newline at end of file +} diff --git a/src/renderers/clusteredDeferred.js b/src/renderers/clusteredDeferred.js index 5e28e84..f080282 100644 --- a/src/renderers/clusteredDeferred.js +++ b/src/renderers/clusteredDeferred.js @@ -6,44 +6,415 @@ import toTextureVert from '../shaders/deferredToTexture.vert.glsl'; import toTextureFrag from '../shaders/deferredToTexture.frag.glsl'; import QuadVertSource from '../shaders/quad.vert.glsl'; import fsSource from '../shaders/deferred.frag.glsl.js'; + +import fsSourceRTT from '../shaders/deferredRTT.frag.glsl.js'; +import fsSourceBrightnessFilterRTT from '../shaders/brightnessFilterRTT.frag.glsl'; +import fsSourceBlurRTT from '../shaders/blurRTT.frag.glsl'; +import vsSourceHorizontalBlurRTT from '../shaders/horizontalBlurRTT.vert.glsl'; +import vsSourceVerticalBlurRTT from '../shaders/verticalBlurRTT.vert.glsl'; +import fsSourceCombine from '../shaders/combineFragment.frag.glsl'; + import TextureBuffer from './textureBuffer'; import ClusteredRenderer from './clustered'; +import { MAX_LIGHTS_PER_CLUSTER } from './clustered'; + + +//export const NUM_GBUFFERS = 4; +export const NUM_GBUFFERS = 2; -export const NUM_GBUFFERS = 4; export default class ClusteredDeferredRenderer extends ClusteredRenderer { - constructor(xSlices, ySlices, zSlices) { + + // *********************************************************************** + // ************************* Bloom Part starts *************************** + // *********************************************************************** + + // ------------- Bloom post prcessing shad program configure --------------- + bloomConfigration(){ + // Configure RTT shder program + this._progShadeRTT = loadShaderProgram(QuadVertSource, fsSourceRTT({ + numLights: NUM_LIGHTS, + numGBuffers: NUM_GBUFFERS, + maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER, + numXSlices: this._xSlices, + numYSlices: this._ySlices, + numZSlices: this._zSlices, + isToonShading: this.isToonShadingPostProcess, + }), { + uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_lightbuffer', 'u_nearClip', 'u_cluster_tile_size', 'u_cluster_depth_stride', 'u_viewMatrix', 'u_clusterbuffer', 'u_invViewProjMatrix', 'u_invViewMatrix'], + attribs: ['a_uv'], + }); + + this._progBirghtnessFilterRTT = loadShaderProgram(QuadVertSource, fsSourceBrightnessFilterRTT, { + uniforms: ['u_oriSceenBuffer'], + attribs: ['a_uv'], + }); + + this._progHorizontalBlurRTT = loadShaderProgram(vsSourceHorizontalBlurRTT, fsSourceBlurRTT, { + uniforms: ['u_targetWidth', 'u_texture'], + attribs: ['a_uv'], + }); + + this._progVerticalBlurRTT = loadShaderProgram(vsSourceVerticalBlurRTT, fsSourceBlurRTT, { + uniforms: ['u_targetHeight', 'u_texture'], + attribs: ['a_uv'], + }); + + this._progCombine = loadShaderProgram(QuadVertSource, fsSourceCombine, { + uniforms: ['u_colorTexture', 'u_hightlightTexture'], + attribs: ['a_uv'], + }); + } + + + // ---------------- add bloom post processing FBOs ---------------------- + setupBloomDrawBuffers(width, height){ + + let attachments_0 = new Array(1); + attachments_0[0] = WEBGL_draw_buffers[`COLOR_ATTACHMENT0_WEBGL`]; + + //*********** Origin Screnn frame buffer ************* + this._fbo_oriScreen = gl.createFramebuffer(); + + this._depthTex_postprocess_0 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_oriScreen); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_0, 0); + + + this._oriScreenBuffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._oriScreenBuffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //************** brightness filter frame buffer ******************** + this._fbo_brightnessFilter = gl.createFramebuffer(); + + this._depthTex_postprocess_1_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_brightnessFilter); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale, 0); + + this._birghtnessFilterBuffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._birghtnessFilterBuffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //**************** down scale frame buffer 1****************** + this._fbo_downScale1 = gl.createFramebuffer(); + + this._depthTex_postprocess_2_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale1); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale, 0); + + this._downScale1Buffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._downScale1Buffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //**************** down scale frame buffer 2****************** + this._fbo_downScale2 = gl.createFramebuffer(); + + this._depthTex_postprocess_3_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale2); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale, 0); + + this._downScale2Buffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._downScale2Buffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + + // ---------------- Resize bloom buffers ---------------------- + bloomBufferResize(width, height){ + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_0); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + } + + + // ---------------- Render bloom buffers ---------------------- + renderBloom(camera){ + // 1. Render origin sceen frame buffer using g-buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_oriScreen); + + // Clear the frame + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Use this shader program + gl.useProgram(this._progShadeRTT.glShaderProgram); + + // Set the light texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE0); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); + gl.uniform1i(this._progShadeRTT.u_lightbuffer, 0); + + // Set the cluster texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE1);//gl.activeTexture(gl.TEXTURE5); + gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture); + gl.uniform1i(this._progShadeRTT.u_clusterbuffer, 1); + + gl.uniform2f(this._progShadeRTT.u_cluster_tile_size, (canvas.width )/this._xSlices, (canvas.height)/this._ySlices); + gl.uniform1f(this._progShadeRTT.u_nearClip, camera.near); + gl.uniform1f(this._progShadeRTT.u_cluster_depth_stride, (camera.far - camera.near) / this._zSlices); + gl.uniformMatrix4fv(this._progShadeRTT.u_viewMatrix, false, this._viewMatrix); + gl.uniformMatrix4fv(this._progShadeRTT.u_invViewMatrix, false, this._invViewMatrix); + gl.uniformMatrix4fv(this._progShadeRTT.u_invViewProjMatrix, false, this._invViewProjMatrix); + + // Bind g-buffers + const firstGBufferBinding = 2; // 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]); + gl.uniform1i(this._progShadeRTT[`u_gbuffers[${i}]`], i + firstGBufferBinding); + } + + renderFullscreenQuad(this._progShadeRTT); + + + + // 2. Brightness filter + //since our Brightness frame buffer is downscale by 2 + gl.viewport(0, 0, canvas.width / this._brightnessFilterDownScale, canvas.height / this._brightnessFilterDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_brightnessFilter); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progBirghtnessFilterRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE4); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.uniform1i(this._progBirghtnessFilterRTT.u_oriSceenBuffer, 4); + + renderFullscreenQuad(this._progBirghtnessFilterRTT); + + + + // 3. Horizontal BLur + gl.viewport(0, 0, canvas.width / this._blurDownScale, canvas.height / this._blurDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale1); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progHorizontalBlurRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE5); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.uniform1i(this._progHorizontalBlurRTT.u_texture, 5); + + gl.uniform1f(this._progHorizontalBlurRTT.u_targetWidth, this._width / this._brightnessFilterDownScale); + + renderFullscreenQuad(this._progHorizontalBlurRTT); + + + // 4. Vertical BLur + gl.viewport(0, 0, canvas.width / this._blurDownScale, canvas.height / this._blurDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale2); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progVerticalBlurRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE6); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.uniform1i(this._progVerticalBlurRTT.u_texture, 6); + + gl.uniform1f(this._progVerticalBlurRTT.u_targetHeight, this._height / this._blurDownScale); + + renderFullscreenQuad(this._progVerticalBlurRTT); + + + // 5. Combine results + gl.viewport(0, 0, canvas.width, canvas.height); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progCombine.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.uniform1i(this._progCombine.u_colorTexture, 4); + gl.activeTexture(gl.TEXTURE7); + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.uniform1i(this._progCombine.u_hightlightTexture, 7); + + renderFullscreenQuad(this._progCombine); + } + // *********************************************************************** + // ************************* Bloom Part ends ***************************** + // *********************************************************************** + + + + constructor(xSlices, ySlices, zSlices, isBloom, isToonShading) { super(xSlices, ySlices, zSlices); - + + this.isBloomPostProcess = isBloom; + this.isToonShadingPostProcess = isToonShading; + + if(this.isBloomPostProcess){ + this._brightnessFilterDownScale = 2.0; + this._blurDownScale = 6.0; + } + + this.setupDrawBuffers(canvas.width, canvas.height); - + // Create a texture to store light data this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8); - + this._progCopy = loadShaderProgram(toTextureVert, toTextureFrag, { - uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap'], - attribs: ['a_position', 'a_normal', 'a_uv'], + uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_viewMatrix',], + attribs: ['a_position', 'a_normal', 'a_uv'], }); this._progShade = loadShaderProgram(QuadVertSource, fsSource({ numLights: NUM_LIGHTS, numGBuffers: NUM_GBUFFERS, + maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER, + numXSlices: xSlices, + numYSlices: ySlices, + numZSlices: zSlices, + isToonShading: this.isToonShadingPostProcess, }), { - uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'], - attribs: ['a_uv'], + uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_lightbuffer', 'u_nearClip', 'u_cluster_tile_size', 'u_cluster_depth_stride', 'u_viewMatrix', 'u_clusterbuffer', 'u_invViewProjMatrix', 'u_invViewMatrix'], + attribs: ['a_uv'], }); this._projectionMatrix = mat4.create(); this._viewMatrix = mat4.create(); this._viewProjectionMatrix = mat4.create(); + this._invViewMatrix = mat4.create(); + this._invViewProjMatrix = mat4.create(); + + + if(this.isBloomPostProcess){ + this.bloomConfigration(); + } + } + setupDrawBuffers(width, height) { this._width = width; this._height = height; - this._fbo = gl.createFramebuffer(); - + this._fbo_gbuffer = gl.createFramebuffer(); + //Create, bind, and store a depth target texture for the FBO this._depthTex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this._depthTex); @@ -54,12 +425,14 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); gl.bindTexture(gl.TEXTURE_2D, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo); + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_gbuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex, 0); + + let attachments = new Array(NUM_GBUFFERS); + // Create, bind, and store "color" target textures for the FBO this._gbuffers = new Array(NUM_GBUFFERS); - let attachments = new Array(NUM_GBUFFERS); for (let i = 0; i < NUM_GBUFFERS; i++) { attachments[i] = WEBGL_draw_buffers[`COLOR_ATTACHMENT${i}_WEBGL`]; this._gbuffers[i] = gl.createTexture(); @@ -71,7 +444,7 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); gl.bindTexture(gl.TEXTURE_2D, null); - gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[i], gl.TEXTURE_2D, this._gbuffers[i], 0); + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[i], gl.TEXTURE_2D, this._gbuffers[i], 0); } if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { @@ -83,8 +456,14 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { WEBGL_draw_buffers.drawBuffersWEBGL(attachments); gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if(this.isBloomPostProcess){ + this.setupBloomDrawBuffers(width, height); + } + } + resize(width, height) { this._width = width; this._height = height; @@ -95,9 +474,16 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { gl.bindTexture(gl.TEXTURE_2D, this._gbuffers[i]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); } + + // -------------------- resize bloom FBOs ---------------------------- + if(this.isBloomPostProcess){ + this.bloomBufferResize(width, height); + } + gl.bindTexture(gl.TEXTURE_2D, null); } + render(camera, scene) { if (canvas.width != this._width || canvas.height != this._height) { this.resize(canvas.width, canvas.height); @@ -108,12 +494,14 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { mat4.invert(this._viewMatrix, camera.matrixWorld.elements); mat4.copy(this._projectionMatrix, camera.projectionMatrix.elements); mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix); + mat4.invert(this._invViewMatrix, this._viewMatrix); + mat4.invert(this._invViewProjMatrix, this._viewProjectionMatrix); // Render to the whole screen gl.viewport(0, 0, canvas.width, canvas.height); // Bind the framebuffer - gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo); + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_gbuffer); // Clear the frame gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); @@ -123,10 +511,11 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { // Upload the camera matrix gl.uniformMatrix4fv(this._progCopy.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + gl.uniformMatrix4fv(this._progCopy.u_viewMatrix, false, this._viewMatrix); // 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._progCopy); - + // Update the buffer used to populate the texture packed with light data for (let i = 0; i < NUM_LIGHTS; ++i) { this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 0) + 0] = scene.lights[i].position[0]; @@ -144,25 +533,51 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { // Update the clusters for the frame this.updateClusters(camera, this._viewMatrix, scene); - // Bind the default null framebuffer which is the screen - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - // Clear the frame - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + if(!this.isBloomPostProcess){ + // ------------------ Use g-buffer render starts --------------------- + // Bind the default null framebuffer which is the screen + gl.bindFramebuffer(gl.FRAMEBUFFER, null); - // Use this shader program - gl.useProgram(this._progShade.glShaderProgram); + // Clear the frame + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - // TODO: Bind any other shader inputs + // Use this shader program + gl.useProgram(this._progShade.glShaderProgram); - // Bind g-buffers - const firstGBufferBinding = 0; // 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]); - gl.uniform1i(this._progShade[`u_gbuffers[${i}]`], i + firstGBufferBinding); + // TODO: Bind any other shader inputs + // Set the light texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); + gl.uniform1i(this._progShade.u_lightbuffer, 0); + + // Set the cluster texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture); + gl.uniform1i(this._progShade.u_clusterbuffer, 1); + + gl.uniform2f(this._progShade.u_cluster_tile_size, (canvas.width )/this._xSlices, (canvas.height)/this._ySlices); + gl.uniform1f(this._progShade.u_nearClip, camera.near); + gl.uniform1f(this._progShade.u_cluster_depth_stride, (camera.far - camera.near) / this._zSlices); + gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix); + gl.uniformMatrix4fv(this._progShade.u_invViewMatrix, false, this._invViewMatrix); + gl.uniformMatrix4fv(this._progShade.u_invViewProjMatrix, false, this._invViewProjMatrix); + + // Bind g-buffers + const firstGBufferBinding = 2; // 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]); + gl.uniform1i(this._progShade[`u_gbuffers[${i}]`], i + firstGBufferBinding); + } + + renderFullscreenQuad(this._progShade); } - renderFullscreenQuad(this._progShade); + else{ + // Bloom Post processing + this.renderBloom(camera); + } } + }; diff --git a/src/renderers/clusteredForwardPlus.js b/src/renderers/clusteredForwardPlus.js index 9e8afbe..e635592 100644 --- a/src/renderers/clusteredForwardPlus.js +++ b/src/renderers/clusteredForwardPlus.js @@ -1,32 +1,427 @@ -import { gl } from '../init'; -import { mat4, vec4, vec3 } from 'gl-matrix'; -import { loadShaderProgram } from '../utils'; +import { gl, WEBGL_draw_buffers, canvas } from '../init'; +import { mat4, vec4 } from 'gl-matrix'; +import { loadShaderProgram, renderFullscreenQuad } from '../utils'; import { NUM_LIGHTS } from '../scene'; import vsSource from '../shaders/clusteredForward.vert.glsl'; import fsSource from '../shaders/clusteredForward.frag.glsl.js'; + +import QuadVertSource from '../shaders/quad.vert.glsl'; +import fsSourceRTT from '../shaders/clusteredForwardRTT.frag.glsl.js'; +import fsSourceBrightnessFilterRTT from '../shaders/brightnessFilterRTT.frag.glsl'; +import fsSourceBlurRTT from '../shaders/blurRTT.frag.glsl'; +import vsSourceHorizontalBlurRTT from '../shaders/horizontalBlurRTT.vert.glsl'; +import vsSourceVerticalBlurRTT from '../shaders/verticalBlurRTT.vert.glsl'; +import fsSourceCombine from '../shaders/combineFragment.frag.glsl'; + import TextureBuffer from './textureBuffer'; import ClusteredRenderer from './clustered'; +import { MAX_LIGHTS_PER_CLUSTER } from './clustered'; + export default class ClusteredForwardPlusRenderer extends ClusteredRenderer { - constructor(xSlices, ySlices, zSlices) { + + // *********************************************************************** + // ************************* Bloom Part starts *************************** + // *********************************************************************** + + // ------------- Bloom post prcessing shad program configure --------------- + bloomConfigration(){ + // Configure RTT shder program + this._shaderProgramRTT = loadShaderProgram(vsSource, fsSourceRTT({ + numLights: NUM_LIGHTS, + maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER, + numXSlices: this._xSlices, + numYSlices: this._ySlices, + numZSlices: this._zSlices, + isToonShading: this.isToonShadingPostProcess, + }), { + uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer', 'u_viewMatrix', 'u_nearClip', 'u_cluster_tile_size', 'u_cluster_depth_stride'], + attribs: ['a_position', 'a_normal', 'a_uv'], + }); + + + this._progBirghtnessFilterRTT = loadShaderProgram(QuadVertSource, fsSourceBrightnessFilterRTT, { + uniforms: ['u_oriSceenBuffer'], + attribs: ['a_uv'], + }); + + + this._progHorizontalBlurRTT = loadShaderProgram(vsSourceHorizontalBlurRTT, fsSourceBlurRTT, { + uniforms: ['u_targetWidth', 'u_texture'], + attribs: ['a_uv'], + }); + + + this._progVerticalBlurRTT = loadShaderProgram(vsSourceVerticalBlurRTT, fsSourceBlurRTT, { + uniforms: ['u_targetHeight', 'u_texture'], + attribs: ['a_uv'], + }); + + + this._progCombine = loadShaderProgram(QuadVertSource, fsSourceCombine, { + uniforms: ['u_colorTexture', 'u_hightlightTexture'], + attribs: ['a_uv'], + }); + + } + + + // ---------------- add bloom post processing FBOs ---------------------- + setupBloomDrawBuffers(width, height){ + this._width = width; + this._height = height; + + let attachments_0 = new Array(1); + attachments_0[0] = WEBGL_draw_buffers[`COLOR_ATTACHMENT0_WEBGL`]; + + //*********** Origin Screnn frame buffer ************* + this._fbo_oriScreen = gl.createFramebuffer(); + + this._depthTex_postprocess_0 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_oriScreen); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_0, 0); + + + this._oriScreenBuffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._oriScreenBuffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //************** brightness filter frame buffer ******************** + this._fbo_brightnessFilter = gl.createFramebuffer(); + + this._depthTex_postprocess_1_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_brightnessFilter); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale, 0); + + this._birghtnessFilterBuffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._birghtnessFilterBuffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //**************** down scale frame buffer 1****************** + this._fbo_downScale1 = gl.createFramebuffer(); + + this._depthTex_postprocess_2_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale1); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale, 0); + + this._downScale1Buffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._downScale1Buffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //**************** down scale frame buffer 2****************** + this._fbo_downScale2 = gl.createFramebuffer(); + + this._depthTex_postprocess_3_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale2); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale, 0); + + this._downScale2Buffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._downScale2Buffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + + // ---------------- Resize bloom buffers ---------------------- + bloomBufferResize(width, height){ + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_0); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + } + + + // ---------------- Render bloom buffers ---------------------- + renderBloom(camera, scene){ + // 1. Render origin sceen frame buffer + // Bind the default null framebuffer which is the screen + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_oriScreen); + + // Render to the whole screen + gl.viewport(0, 0, canvas.width, canvas.height); + + // Clear the frame + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Use this shader program + gl.useProgram(this._shaderProgramRTT.glShaderProgram); + + // Upload the camera matrix + gl.uniformMatrix4fv(this._shaderProgramRTT.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + + // Set the light texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); + gl.uniform1i(this._shaderProgramRTT.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._shaderProgramRTT.u_clusterbuffer, 3); + + gl.uniform2f(this._shaderProgramRTT.u_cluster_tile_size, (canvas.width )/this._xSlices, (canvas.height)/this._ySlices); + gl.uniform1f(this._shaderProgramRTT.u_nearClip, camera.near); + gl.uniform1f(this._shaderProgramRTT.u_cluster_depth_stride, (camera.far - camera.near) / this._zSlices); + gl.uniformMatrix4fv(this._shaderProgramRTT.u_viewMatrix, false, this._viewMatrix); + + // 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._shaderProgramRTT); + + + + // 2. Brightness filter + //since our Brightness frame buffer is downscale by 2 + gl.viewport(0, 0, canvas.width / this._brightnessFilterDownScale, canvas.height / this._brightnessFilterDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_brightnessFilter); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progBirghtnessFilterRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE4); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.uniform1i(this._progBirghtnessFilterRTT.u_oriSceenBuffer, 4); + + renderFullscreenQuad(this._progBirghtnessFilterRTT); + + + + // 3. Horizontal BLur + gl.viewport(0, 0, canvas.width / this._blurDownScale, canvas.height / this._blurDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale1); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progHorizontalBlurRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE5); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.uniform1i(this._progHorizontalBlurRTT.u_texture, 5); + + gl.uniform1f(this._progHorizontalBlurRTT.u_targetWidth, this._width / this._brightnessFilterDownScale); + + renderFullscreenQuad(this._progHorizontalBlurRTT); + + + // 4. Vertical BLur + gl.viewport(0, 0, canvas.width / this._blurDownScale, canvas.height / this._blurDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale2); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progVerticalBlurRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE6); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.uniform1i(this._progVerticalBlurRTT.u_texture, 6); + + gl.uniform1f(this._progVerticalBlurRTT.u_targetHeight, this._height / this._blurDownScale); + + renderFullscreenQuad(this._progVerticalBlurRTT); + + + // 5. Combine results + gl.viewport(0, 0, canvas.width, canvas.height); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progCombine.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.uniform1i(this._progCombine.u_colorTexture, 4); + gl.activeTexture(gl.TEXTURE7); + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.uniform1i(this._progCombine.u_hightlightTexture, 7); + + renderFullscreenQuad(this._progCombine); + } + // *********************************************************************** + // ************************* Bloom Part ends ***************************** + // *********************************************************************** + + + + + + + constructor(xSlices, ySlices, zSlices, isBloom, isToonShading) { super(xSlices, ySlices, zSlices); + this.isBloomPostProcess = isBloom; + this.isToonShadingPostProcess = isToonShading; + + if(this.isBloomPostProcess){ + this._brightnessFilterDownScale = 2.0; + this._blurDownScale = 5.0; + + this.setupBloomDrawBuffers(canvas.width, canvas.height); + } + // Create a texture to store light data this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8); - + this._shaderProgram = loadShaderProgram(vsSource, fsSource({ numLights: NUM_LIGHTS, + maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER, + numXSlices: xSlices, + numYSlices: ySlices, + numZSlices: zSlices, + isToonShading: this.isToonShadingPostProcess, }), { - uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'], + uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer', 'u_viewMatrix', 'u_nearClip', 'u_cluster_tile_size', 'u_cluster_depth_stride'], attribs: ['a_position', 'a_normal', 'a_uv'], }); this._projectionMatrix = mat4.create(); this._viewMatrix = mat4.create(); this._viewProjectionMatrix = mat4.create(); + + if(this.isBloomPostProcess){ + this.bloomConfigration(); + } + } + resize(width, height) { + this._width = width; + this._height = height; + + // -------------------- resize bloom FBOs ---------------------------- + if(this.isBloomPostProcess){ + this.bloomBufferResize(width, height); + } + + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + render(camera, scene) { + + if(this.isBloomPostProcess){ + if (canvas.width != this._width || canvas.height != this._height) { + this.resize(canvas.width, canvas.height); + } + } + // Update the camera matrices camera.updateMatrixWorld(); mat4.invert(this._viewMatrix, camera.matrixWorld.elements); @@ -34,8 +429,8 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer { mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix); // Update cluster texture which maps from cluster index to light list - this.updateClusters(camera, this._viewMatrix, scene); - + this.updateClusters(camera, this._viewMatrix, scene, this._viewProjectionMatrix); + // Update the buffer used to populate the texture packed with light data for (let i = 0; i < NUM_LIGHTS; ++i) { this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 0) + 0] = scene.lights[i].position[0]; @@ -50,34 +445,55 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer { // Update the light texture this._lightTexture.update(); - // Bind the default null framebuffer which is the screen - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + if(!this.isBloomPostProcess){ + // Bind the default null framebuffer which is the screen + gl.bindFramebuffer(gl.FRAMEBUFFER, null); - // Render to the whole screen - gl.viewport(0, 0, canvas.width, canvas.height); + // Render to the whole screen + gl.viewport(0, 0, canvas.width, canvas.height); - // Clear the frame - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + // Clear the frame + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - // Use this shader program - gl.useProgram(this._shaderProgram.glShaderProgram); + // Use this shader program + gl.useProgram(this._shaderProgram.glShaderProgram); - // Upload the camera matrix - gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + // Upload the camera matrix + gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix); - // Set the light texture as a uniform input to the shader - gl.activeTexture(gl.TEXTURE2); - gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); - gl.uniform1i(this._shaderProgram.u_lightbuffer, 2); + // Set the light texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); + gl.uniform1i(this._shaderProgram.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._shaderProgram.u_clusterbuffer, 3); + // 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._shaderProgram.u_clusterbuffer, 3); - // TODO: Bind any other shader inputs + // TODO: Bind any other shader inputs - // 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); + + gl.uniform2f(this._shaderProgram.u_cluster_tile_size, (canvas.width )/this._xSlices, (canvas.height)/this._ySlices); + gl.uniform1f(this._shaderProgram.u_nearClip, camera.near); + gl.uniform1f(this._shaderProgram.u_cluster_depth_stride, (camera.far - camera.near) / this._zSlices); + gl.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix); + + //let deg2rad = Math.PI/180.0; + //gl.uniform1f(this._shaderProgram.u_fovby2_radius, deg2rad * camera.fov/2.0); + //gl.uniform1f(this._shaderProgram.u_ySlices, this._ySlices); + //gl.uniform1f(this._shaderProgram.u_cluster_tile_size_x, (canvas.width )/this._xSlices); + //gl.uniform1f(this._shaderProgram.u_cluster_tile_size_y, (canvas.height)/this._ySlices); + //gl.uniform1f(this._shaderProgram.u_zSlices, this._zSlices); + //gl.uniform1f(this._shaderProgram.u_xSlices, this._xSlices); + + + // 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); + } + else{ + // Bloom Post processing + this.renderBloom(camera, scene); + } } -}; \ No newline at end of file +}; diff --git a/src/renderers/forward.js b/src/renderers/forward.js index ac044f9..0e65c91 100644 --- a/src/renderers/forward.js +++ b/src/renderers/forward.js @@ -1,19 +1,362 @@ -import { gl } from '../init'; +import { gl, WEBGL_draw_buffers, canvas } from '../init'; import { mat4, vec4 } from 'gl-matrix'; -import { loadShaderProgram } from '../utils'; +import { loadShaderProgram, renderFullscreenQuad } from '../utils'; import { NUM_LIGHTS } from '../scene'; import vsSource from '../shaders/forward.vert.glsl'; import fsSource from '../shaders/forward.frag.glsl.js'; + +import QuadVertSource from '../shaders/quad.vert.glsl'; +import fsSourceRTT from '../shaders/forwardRTT.frag.glsl.js'; +import fsSourceBrightnessFilterRTT from '../shaders/brightnessFilterRTT.frag.glsl'; +import fsSourceBlurRTT from '../shaders/blurRTT.frag.glsl'; +import vsSourceHorizontalBlurRTT from '../shaders/horizontalBlurRTT.vert.glsl'; +import vsSourceVerticalBlurRTT from '../shaders/verticalBlurRTT.vert.glsl'; +import fsSourceCombine from '../shaders/combineFragment.frag.glsl'; + import TextureBuffer from './textureBuffer'; export default class ForwardRenderer { - constructor() { + + // *********************************************************************** + // ************************* Bloom Part starts *************************** + // *********************************************************************** + + // ------------- Bloom post prcessing shad program configure --------------- + bloomConfigration(){ + // Configure RTT shder program + this._shaderProgramRTT = loadShaderProgram(vsSource, fsSourceRTT({ + numLights: NUM_LIGHTS, + isToonShading: this.isToonShadingPostProcess, + }), { + uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer'], + attribs: ['a_position', 'a_normal', 'a_uv'], + }); + + + this._progBirghtnessFilterRTT = loadShaderProgram(QuadVertSource, fsSourceBrightnessFilterRTT, { + uniforms: ['u_oriSceenBuffer'], + attribs: ['a_uv'], + }); + + this._progHorizontalBlurRTT = loadShaderProgram(vsSourceHorizontalBlurRTT, fsSourceBlurRTT, { + uniforms: ['u_targetWidth', 'u_texture'], + attribs: ['a_uv'], + }); + + this._progVerticalBlurRTT = loadShaderProgram(vsSourceVerticalBlurRTT, fsSourceBlurRTT, { + uniforms: ['u_targetHeight', 'u_texture'], + attribs: ['a_uv'], + }); + + this._progCombine = loadShaderProgram(QuadVertSource, fsSourceCombine, { + uniforms: ['u_colorTexture', 'u_hightlightTexture'], + attribs: ['a_uv'], + }); + } + + + // ---------------- add bloom post processing FBOs ---------------------- + setupBloomDrawBuffers(width, height){ + this._width = width; + this._height = height; + + let attachments_0 = new Array(1); + attachments_0[0] = WEBGL_draw_buffers[`COLOR_ATTACHMENT0_WEBGL`]; + + //*********** Origin Screnn frame buffer ************* + this._fbo_oriScreen = gl.createFramebuffer(); + + this._depthTex_postprocess_0 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_oriScreen); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_0, 0); + + + this._oriScreenBuffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._oriScreenBuffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //************** brightness filter frame buffer ******************** + this._fbo_brightnessFilter = gl.createFramebuffer(); + + this._depthTex_postprocess_1_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_brightnessFilter); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale, 0); + + this._birghtnessFilterBuffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._birghtnessFilterBuffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //**************** down scale frame buffer 1****************** + this._fbo_downScale1 = gl.createFramebuffer(); + + this._depthTex_postprocess_2_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale1); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale, 0); + + this._downScale1Buffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._downScale1Buffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + //**************** down scale frame buffer 2****************** + this._fbo_downScale2 = gl.createFramebuffer(); + + this._depthTex_postprocess_3_downScale = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale2); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale, 0); + + this._downScale2Buffer = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + gl.bindTexture(gl.TEXTURE_2D, null); + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments_0[0], gl.TEXTURE_2D, this._downScale2Buffer, 0); + + if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { + throw "Framebuffer incomplete"; + } + + WEBGL_draw_buffers.drawBuffersWEBGL(attachments_0); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + + // ---------------- Resize bloom buffers ---------------------- + bloomBufferResize(width, height){ + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_0); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_1_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._brightnessFilterDownScale, height / this._brightnessFilterDownScale, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_2_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._depthTex_postprocess_3_downScale); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width / this._blurDownScale, height / this._blurDownScale, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null); + + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width / this._blurDownScale, height / this._blurDownScale, 0, gl.RGBA, gl.FLOAT, null); + } + + + // ---------------- Render bloom buffers ---------------------- + renderBloom(camera, scene){ + // 1. Render origin sceen frame buffer + // Bind the default null framebuffer which is the screen + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_oriScreen); + + // Render to the whole screen + gl.viewport(0, 0, canvas.width, canvas.height); + + // Clear the frame + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Use this shader program + gl.useProgram(this._shaderProgramRTT.glShaderProgram); + + // Upload the camera matrix + gl.uniformMatrix4fv(this._shaderProgramRTT.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + + // Set the light texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); + gl.uniform1i(this._shaderProgramRTT.u_lightbuffer, 2); + + // 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._shaderProgramRTT); + + + + // 2. Brightness filter + //since our Brightness frame buffer is downscale by 2 + gl.viewport(0, 0, canvas.width / this._brightnessFilterDownScale, canvas.height / this._brightnessFilterDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_brightnessFilter); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progBirghtnessFilterRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE4); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.uniform1i(this._progBirghtnessFilterRTT.u_oriSceenBuffer, 4); + + renderFullscreenQuad(this._progBirghtnessFilterRTT); + + + + // 3. Horizontal BLur + gl.viewport(0, 0, canvas.width / this._blurDownScale, canvas.height / this._blurDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale1); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progHorizontalBlurRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE5); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._birghtnessFilterBuffer); + gl.uniform1i(this._progHorizontalBlurRTT.u_texture, 5); + + gl.uniform1f(this._progHorizontalBlurRTT.u_targetWidth, this._width / this._brightnessFilterDownScale); + + renderFullscreenQuad(this._progHorizontalBlurRTT); + + + // 4. Vertical BLur + gl.viewport(0, 0, canvas.width / this._blurDownScale, canvas.height / this._blurDownScale); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo_downScale2); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progVerticalBlurRTT.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE6); //gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._downScale1Buffer); + gl.uniform1i(this._progVerticalBlurRTT.u_texture, 6); + + gl.uniform1f(this._progVerticalBlurRTT.u_targetHeight, this._height / this._blurDownScale); + + renderFullscreenQuad(this._progVerticalBlurRTT); + + + // 5. Combine results + gl.viewport(0, 0, canvas.width, canvas.height); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.useProgram(this._progCombine.glShaderProgram); + + // bind original screen frame buffer + gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this._oriScreenBuffer); + gl.uniform1i(this._progCombine.u_colorTexture, 4); + gl.activeTexture(gl.TEXTURE7); + gl.bindTexture(gl.TEXTURE_2D, this._downScale2Buffer); + gl.uniform1i(this._progCombine.u_hightlightTexture, 7); + + renderFullscreenQuad(this._progCombine); + } + // *********************************************************************** + // ************************* Bloom Part ends ***************************** + // *********************************************************************** + + + constructor(isBloom, isToonShading) { + + this.isBloomPostProcess = isBloom; + this.isToonShadingPostProcess = isToonShading; + + if(this.isBloomPostProcess){ + this._brightnessFilterDownScale = 2.0; + this._blurDownScale = 6.0; + + this.setupBloomDrawBuffers(canvas.width, canvas.height); + } + + // Create a texture to store light data this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8); // Initialize a shader program. The fragment shader source is compiled based on the number of lights this._shaderProgram = loadShaderProgram(vsSource, fsSource({ numLights: NUM_LIGHTS, + isToonShading: this.isToonShadingPostProcess, }), { uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer'], attribs: ['a_position', 'a_normal', 'a_uv'], @@ -22,9 +365,33 @@ export default class ForwardRenderer { this._projectionMatrix = mat4.create(); this._viewMatrix = mat4.create(); this._viewProjectionMatrix = mat4.create(); + + if(this.isBloomPostProcess){ + this.bloomConfigration(); + } + + } + + resize(width, height) { + this._width = width; + this._height = height; + + // -------------------- resize bloom FBOs ---------------------------- + if(this.isBloomPostProcess){ + this.bloomBufferResize(width, height); + } + + gl.bindTexture(gl.TEXTURE_2D, null); } render(camera, scene) { + + if(this.isBloomPostProcess){ + if (canvas.width != this._width || canvas.height != this._height) { + this.resize(canvas.width, canvas.height); + } + } + // Update the camera matrices camera.updateMatrixWorld(); mat4.invert(this._viewMatrix, camera.matrixWorld.elements); @@ -45,27 +412,33 @@ export default class ForwardRenderer { // Update the light texture this._lightTexture.update(); - // Bind the default null framebuffer which is the screen - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + if(!this.isBloomPostProcess){ + // Bind the default null framebuffer which is the screen + gl.bindFramebuffer(gl.FRAMEBUFFER, null); - // Render to the whole screen - gl.viewport(0, 0, canvas.width, canvas.height); + // Render to the whole screen + gl.viewport(0, 0, canvas.width, canvas.height); - // Clear the frame - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + // Clear the frame + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - // Use this shader program - gl.useProgram(this._shaderProgram.glShaderProgram); + // Use this shader program + gl.useProgram(this._shaderProgram.glShaderProgram); - // Upload the camera matrix - gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + // Upload the camera matrix + gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix); - // Set the light texture as a uniform input to the shader - gl.activeTexture(gl.TEXTURE2); - gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); - gl.uniform1i(this._shaderProgram.u_lightbuffer, 2); + // Set the light texture as a uniform input to the shader + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); + gl.uniform1i(this._shaderProgram.u_lightbuffer, 2); - // 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); + // 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); + } + else{ + // Bloom Post processing + this.renderBloom(camera, scene); + } } }; diff --git a/src/scene.js b/src/scene.js index 35f6700..7113fa5 100644 --- a/src/scene.js +++ b/src/scene.js @@ -1,11 +1,13 @@ const MinimalGLTFLoader = require('../lib/minimal-gltf-loader'); import { gl } from './init'; -// TODO: Edit if you want to change the light initial positions +// TODO: Edit if you want to change the light initial positions export const LIGHT_MIN = [-14, 0, -6]; export const LIGHT_MAX = [14, 20, 6]; export const LIGHT_RADIUS = 5.0; export const LIGHT_DT = -0.03; +//export const LIGHT_DT = 0.0; + // TODO: This controls the number of lights export const NUM_LIGHTS = 100; @@ -36,18 +38,18 @@ class Scene { var glTFLoader = new MinimalGLTFLoader.glTFLoader(gl); glTFLoader.loadGLTF(url, glTF => { var curScene = glTF.scenes[glTF.defaultScene]; - + var webGLTextures = {}; - + // temp var var i,len; var primitiveOrderID; - + var mesh; var primitive; var vertexBuffer; var indicesBuffer; - + // textures setting var textureID = 0; var textureInfo; @@ -56,30 +58,30 @@ class Scene { var magFilter, minFilter, wrapS, wrapT; var image; var texture; - + // temp for sponza var colorTextureName = 'texture_color'; var normalTextureName = 'texture_normal'; - + for (var tid in glTF.json.textures) { textureInfo = glTF.json.textures[tid]; target = textureInfo.target || gl.TEXTURE_2D; format = textureInfo.format || gl.RGBA; internalFormat = textureInfo.format || gl.RGBA; type = textureInfo.type || gl.UNSIGNED_BYTE; - + image = glTF.images[textureInfo.source]; - + texture = gl.createTexture(); gl.activeTexture(gl.TEXTURE0 + textureID); gl.bindTexture(target, texture); - + switch(target) { case 3553: // gl.TEXTURE_2D gl.texImage2D(target, 0, internalFormat, format, type, image); break; } - + // !! Sampler // raw WebGL 1, no sampler object, set magfilter, wrapS, etc samplerInfo = glTF.json.samplers[textureInfo.sampler]; @@ -91,22 +93,22 @@ class Scene { gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magFilter); gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS); gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT); - if (minFilter == gl.NEAREST_MIPMAP_NEAREST || - minFilter == gl.NEAREST_MIPMAP_LINEAR || + if (minFilter == gl.NEAREST_MIPMAP_NEAREST || + minFilter == gl.NEAREST_MIPMAP_LINEAR || minFilter == gl.LINEAR_MIPMAP_NEAREST || minFilter == gl.LINEAR_MIPMAP_LINEAR ) { gl.generateMipmap(target); } - - + + gl.bindTexture(target, null); - + webGLTextures[tid] = { texture: texture, target: target, id: textureID }; - + textureID++; } @@ -146,7 +148,7 @@ class Scene { uvInfo: {size: uvInfo.size, type: uvInfo.type, stride: uvInfo.stride, offset: uvInfo.offset}, // specific textures temp test - colmap: webGLTextures[colorTextureName].texture, + colmap: webGLTextures[colorTextureName].texture, normap: webGLTextures[normalTextureName].texture }); } @@ -180,16 +182,16 @@ class Scene { } gl.bindBuffer(gl.ARRAY_BUFFER, model.attributes); - + gl.enableVertexAttribArray(shaderProgram.a_position); gl.vertexAttribPointer(shaderProgram.a_position, model.posInfo.size, model.posInfo.type, false, model.posInfo.stride, model.posInfo.offset); - + gl.enableVertexAttribArray(shaderProgram.a_normal); gl.vertexAttribPointer(shaderProgram.a_normal, model.norInfo.size, model.norInfo.type, false, model.norInfo.stride, model.norInfo.offset); - + gl.enableVertexAttribArray(shaderProgram.a_uv); gl.vertexAttribPointer(shaderProgram.a_uv, model.uvInfo.size, model.uvInfo.type, false, model.uvInfo.stride, model.uvInfo.offset); - + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.idx); gl.drawElements(model.gltf.mode, model.gltf.indices.length, model.gltf.indicesComponentType, 0); @@ -198,4 +200,4 @@ class Scene { } -export default Scene; \ No newline at end of file +export default Scene; diff --git a/src/shaders/blurRTT.frag.glsl b/src/shaders/blurRTT.frag.glsl new file mode 100644 index 0000000..0c6eacf --- /dev/null +++ b/src/shaders/blurRTT.frag.glsl @@ -0,0 +1,31 @@ +#version 100 +#extension GL_EXT_draw_buffers: enable +precision highp float; + +uniform sampler2D u_texture; + +varying vec2 blurTextureCoords[11]; + + +void main() { + + vec3 fragColor = vec3(0.0); + + fragColor += texture2D(u_texture, blurTextureCoords[0]).rgb * 0.0093; + fragColor += texture2D(u_texture, blurTextureCoords[1]).rgb * 0.028002; + fragColor += texture2D(u_texture, blurTextureCoords[2]).rgb * 0.065984; + fragColor += texture2D(u_texture, blurTextureCoords[3]).rgb * 0.121703; + fragColor += texture2D(u_texture, blurTextureCoords[4]).rgb * 0.175713; + fragColor += texture2D(u_texture, blurTextureCoords[5]).rgb * 0.198596; + fragColor += texture2D(u_texture, blurTextureCoords[6]).rgb * 0.175713; + fragColor += texture2D(u_texture, blurTextureCoords[7]).rgb * 0.121703; + fragColor += texture2D(u_texture, blurTextureCoords[8]).rgb * 0.065984; + fragColor += texture2D(u_texture, blurTextureCoords[9]).rgb * 0.028002; + fragColor += texture2D(u_texture, blurTextureCoords[10]).rgb * 0.0093; + + //gl_FragColor = vec4(fragColor, 1.0); + + //Render to FBO + gl_FragData[0] = vec4(fragColor, 1.0); + +} diff --git a/src/shaders/brightnessFilterRTT.frag.glsl b/src/shaders/brightnessFilterRTT.frag.glsl new file mode 100644 index 0000000..f767d4e --- /dev/null +++ b/src/shaders/brightnessFilterRTT.frag.glsl @@ -0,0 +1,23 @@ +#version 100 +#extension GL_EXT_draw_buffers: enable +precision highp float; + +uniform sampler2D u_oriSceenBuffer; + +varying vec2 v_uv; + + +void main() { + + vec3 OriCol = vec3(texture2D(u_oriSceenBuffer, v_uv)); + + float brightness = OriCol.r * 0.2126 + OriCol.g * 0.7152 + OriCol.b * 0.0722; + + OriCol = brightness * OriCol; + + //gl_FragColor = vec4(OriCol, 1.0); + + //Render to FBO + gl_FragData[0] = vec4(OriCol, 1.0); + +} diff --git a/src/shaders/clusteredForward.frag.glsl.js b/src/shaders/clusteredForward.frag.glsl.js index 022fda7..b724284 100644 --- a/src/shaders/clusteredForward.frag.glsl.js +++ b/src/shaders/clusteredForward.frag.glsl.js @@ -9,6 +9,11 @@ export default function(params) { uniform sampler2D u_normap; uniform sampler2D u_lightbuffer; + uniform float u_nearClip; + uniform vec2 u_cluster_tile_size; + uniform float u_cluster_depth_stride; + uniform mat4 u_viewMatrix; + // TODO: Read this buffer to determine the lights influencing a cluster uniform sampler2D u_clusterbuffer; @@ -16,12 +21,29 @@ export default function(params) { varying vec3 v_normal; varying vec2 v_uv; + + //float getCulsterIndexK(float z_EyeSpace, float nearClip, float fovby2_radius, float ySlices) { + //float c = log(10.0); + //float tmp1 = log(-z_EyeSpace / nearClip) / c; + //float tmp2 = log(1.0 + 2.0 * tan(fovby2_radius) / ySlices) / c; + + //return floor(0.12 * tmp1 / tmp2); + //return 0.0; + //} + + int getCulsterDepthIndex(float viewSpaceDepth, float nearClip) { + //2.15 is calculated based on near and far clip + //near Clip : 0.1 + //far Clip : 1000.0 + return int(floor(2.15 * log(viewSpaceDepth - nearClip + 1.0))); + } + 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; + return normalize(normap.y * surftan + normap.x * surfbinor + normap.z * geomnor); } struct Light { @@ -81,21 +103,95 @@ export default function(params) { vec3 fragColor = vec3(0.0); - for (int i = 0; i < ${params.numLights}; ++i) { - Light light = UnpackLight(i); + vec3 pos_viewSpace = vec3(u_viewMatrix * vec4(v_position, 1.0)); + + // determine which cluster this fragment is in + int cluster_Idx_x = int(gl_FragCoord.x / u_cluster_tile_size.x); + int cluster_Idx_y = int(gl_FragCoord.y / u_cluster_tile_size.y); + //int cluster_Idx_z = int(getCulsterIndexK(v_eyeSpaceDepth, u_nearClip, u_fovby2_radius, u_ySlices)); + //int cluster_Idx_z = int((-pos_viewSpace.z - u_nearClip) / u_cluster_depth_stride); + int cluster_Idx_z = getCulsterDepthIndex(-pos_viewSpace.z, u_nearClip); + + // clusterTexture Size + const int clusterTexutreWidth = int(${params.numXSlices}) * int(${params.numYSlices}) * int(${params.numZSlices}); + const int clusterTextureHeight = int(ceil((float(${params.maxLightsPerCluster}) + 1.0) / 4.0)); + + // extract lights influencing this cluster from u_clusterbuffer + int clusterIdx = cluster_Idx_x + cluster_Idx_y * int(${params.numXSlices}) + cluster_Idx_z * int(${params.numXSlices}) * int(${params.numYSlices}); + + float cluster_u = float(clusterIdx + 1) / float(clusterTexutreWidth + 1); + + float cluster_v = 0.0; // because the texture space origin is at lower left, not upper left! + + float cluster_v_step = 1.0 / float(clusterTextureHeight + 1); + + cluster_v += cluster_v_step; + + vec4 cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + + int lightCountInCluster = int(cluster_texel[0]); + + int cluster_texel_fetch_Idx = 1; + + const int maxNumLights = int(min(float(${params.maxLightsPerCluster}),float(${params.numLights}))); + + // Shade lights store in the cluster instead of all + for (int i = 0; i < maxNumLights; ++i) { + if(i == lightCountInCluster) {break;} + + // Fetch light index + int lightIdx; + if(cluster_texel_fetch_Idx == 0){ + lightIdx = int(cluster_texel[0]); + } + else if(cluster_texel_fetch_Idx == 1){ + lightIdx = int(cluster_texel[1]); + } + else if(cluster_texel_fetch_Idx == 2){ + lightIdx = int(cluster_texel[2]); + } + else if(cluster_texel_fetch_Idx == 3){ + lightIdx = int(cluster_texel[3]); + } + + cluster_texel_fetch_Idx++; + + Light light = UnpackLight(lightIdx); float lightDistance = distance(light.position, v_position); vec3 L = (light.position - v_position) / lightDistance; float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); - float lambertTerm = max(dot(L, normal), 0.0); + + float lambertTerm; + + if(${params.isToonShading}){ + // ramp shading + float rampUnitLength = 0.25; + float rampUnitValue = 0.33; + // float rampCoord = dot(L, normal) * 0.5 + 0.5; // map from -1 -> 1 to 0 -> 1 + float rampCoord = max(dot(L, normal), 0.0); + int rampLevel = int(rampCoord / rampUnitLength); + lambertTerm = float(rampLevel) * rampUnitValue; + } + else{ + lambertTerm = max(dot(L, normal), 0.0); + } fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + + if(cluster_texel_fetch_Idx == 4){ + cluster_texel_fetch_Idx = 0; + cluster_v += cluster_v_step; + cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + } + } const vec3 ambientLight = vec3(0.025); fragColor += albedo * ambientLight; gl_FragColor = vec4(fragColor, 1.0); + } `; } diff --git a/src/shaders/clusteredForward.vert.glsl b/src/shaders/clusteredForward.vert.glsl index 9850c7f..d42560e 100644 --- a/src/shaders/clusteredForward.vert.glsl +++ b/src/shaders/clusteredForward.vert.glsl @@ -3,6 +3,7 @@ precision highp float; uniform mat4 u_viewProjectionMatrix; + attribute vec3 a_position; attribute vec3 a_normal; attribute vec2 a_uv; @@ -16,4 +17,5 @@ void main() { v_position = a_position; v_normal = a_normal; v_uv = a_uv; -} \ No newline at end of file + +} diff --git a/src/shaders/clusteredForwardRTT.frag.glsl.js b/src/shaders/clusteredForwardRTT.frag.glsl.js new file mode 100644 index 0000000..a3fb61f --- /dev/null +++ b/src/shaders/clusteredForwardRTT.frag.glsl.js @@ -0,0 +1,198 @@ +export default function(params) { + return ` + + #version 100 + #extension GL_EXT_draw_buffers: enable + precision highp float; + + uniform sampler2D u_colmap; + uniform sampler2D u_normap; + uniform sampler2D u_lightbuffer; + + uniform float u_nearClip; + uniform vec2 u_cluster_tile_size; + uniform float u_cluster_depth_stride; + uniform mat4 u_viewMatrix; + + // TODO: Read this buffer to determine the lights influencing a cluster + uniform sampler2D u_clusterbuffer; + + varying vec3 v_position; + varying vec3 v_normal; + varying vec2 v_uv; + + + //float getCulsterIndexK(float z_EyeSpace, float nearClip, float fovby2_radius, float ySlices) { + //float c = log(10.0); + //float tmp1 = log(-z_EyeSpace / nearClip) / c; + //float tmp2 = log(1.0 + 2.0 * tan(fovby2_radius) / ySlices) / c; + + //return floor(0.12 * tmp1 / tmp2); + //return 0.0; + //} + + int getCulsterDepthIndex(float viewSpaceDepth, float nearClip) { + //2.15 is calculated based on near and far clip + //near Clip : 0.1 + //far Clip : 1000.0 + return int(floor(2.15 * log(viewSpaceDepth - nearClip + 1.0))); + } + + 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 normalize(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() { + vec3 albedo = texture2D(u_colmap, v_uv).rgb; + vec3 normap = texture2D(u_normap, v_uv).xyz; + vec3 normal = applyNormalMap(v_normal, normap); + + vec3 fragColor = vec3(0.0); + + vec3 pos_viewSpace = vec3(u_viewMatrix * vec4(v_position, 1.0)); + + // determine which cluster this fragment is in + int cluster_Idx_x = int(gl_FragCoord.x / u_cluster_tile_size.x); + int cluster_Idx_y = int(gl_FragCoord.y / u_cluster_tile_size.y); + //int cluster_Idx_z = int(getCulsterIndexK(v_eyeSpaceDepth, u_nearClip, u_fovby2_radius, u_ySlices)); + //int cluster_Idx_z = int((-pos_viewSpace.z - u_nearClip) / u_cluster_depth_stride); + int cluster_Idx_z = getCulsterDepthIndex(-pos_viewSpace.z, u_nearClip); + + + // clusterTexture Size + const int clusterTexutreWidth = int(${params.numXSlices}) * int(${params.numYSlices}) * int(${params.numZSlices}); + const int clusterTextureHeight = int(ceil((float(${params.maxLightsPerCluster}) + 1.0) / 4.0)); + + // extract lights influencing this cluster from u_clusterbuffer + int clusterIdx = cluster_Idx_x + cluster_Idx_y * int(${params.numXSlices}) + cluster_Idx_z * int(${params.numXSlices}) * int(${params.numYSlices}); + + float cluster_u = float(clusterIdx + 1) / float(clusterTexutreWidth + 1); + + float cluster_v = 0.0; // because the texture space origin is at lower left, not upper left! + + float cluster_v_step = 1.0 / float(clusterTextureHeight + 1); + + cluster_v += cluster_v_step; + + vec4 cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + + int lightCountInCluster = int(cluster_texel[0]); + + int cluster_texel_fetch_Idx = 1; + + const int maxNumLights = int(min(float(${params.maxLightsPerCluster}),float(${params.numLights}))); + + // Shade lights store in the cluster instead of all + for (int i = 0; i < maxNumLights; ++i) { + if(i == lightCountInCluster) {break;} + + // Fetch light index + int lightIdx; + if(cluster_texel_fetch_Idx == 0){ + lightIdx = int(cluster_texel[0]); + } + else if(cluster_texel_fetch_Idx == 1){ + lightIdx = int(cluster_texel[1]); + } + else if(cluster_texel_fetch_Idx == 2){ + lightIdx = int(cluster_texel[2]); + } + else if(cluster_texel_fetch_Idx == 3){ + lightIdx = int(cluster_texel[3]); + } + + cluster_texel_fetch_Idx++; + + Light light = UnpackLight(lightIdx); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + //float lambertTerm = max(dot(L, normal), 0.0); + + float lambertTerm; + + if(${params.isToonShading}){ + // ramp shading + float rampUnitLength = 0.25; + float rampUnitValue = 0.33; + // float rampCoord = dot(L, normal) * 0.5 + 0.5; // map from -1 -> 1 to 0 -> 1 + float rampCoord = max(dot(L, normal), 0.0); + int rampLevel = int(rampCoord / rampUnitLength); + lambertTerm = float(rampLevel) * rampUnitValue; + } + else{ + lambertTerm = max(dot(L, normal), 0.0); + } + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + + if(cluster_texel_fetch_Idx == 4){ + cluster_texel_fetch_Idx = 0; + cluster_v += cluster_v_step; + cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + } + + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; + + gl_FragData[0] = vec4(fragColor, 1.0); + } + `; +} diff --git a/src/shaders/combineFragment.frag.glsl b/src/shaders/combineFragment.frag.glsl new file mode 100644 index 0000000..8505208 --- /dev/null +++ b/src/shaders/combineFragment.frag.glsl @@ -0,0 +1,17 @@ +#version 100 +precision highp float; + +uniform sampler2D u_colorTexture; +uniform sampler2D u_hightlightTexture; + +varying vec2 v_uv; + +void main() { + + vec3 fragColor = vec3(0.0); + + vec3 sceneColor = texture2D(u_colorTexture, v_uv).rgb; + vec3 highlightColor = texture2D(u_hightlightTexture, v_uv).rgb; + + gl_FragColor = vec4(0.8 * sceneColor + 2.5 * highlightColor, 1.0); +} diff --git a/src/shaders/deferred.frag.glsl.js b/src/shaders/deferred.frag.glsl.js index 50f1e75..79e1e3c 100644 --- a/src/shaders/deferred.frag.glsl.js +++ b/src/shaders/deferred.frag.glsl.js @@ -2,19 +2,207 @@ export default function(params) { return ` #version 100 precision highp float; - + + uniform float u_nearClip; + uniform vec2 u_cluster_tile_size; + uniform float u_cluster_depth_stride; + uniform mat4 u_viewMatrix; + uniform mat4 u_invViewMatrix; + uniform mat4 u_invViewProjMatrix; + uniform sampler2D u_clusterbuffer; + + + uniform sampler2D u_lightbuffer; + uniform sampler2D u_gbuffers[${params.numGBuffers}]; - + varying vec2 v_uv; - + + struct Light { + vec3 position; + float radius; + vec3 color; + }; + + int getCulsterDepthIndex(float viewSpaceDepth, float nearClip) { + //2.15 is calculated based on near and far clip + //near Clip : 0.1 + //far Clip : 1000.0 + return int(floor(2.15 * log(viewSpaceDepth - nearClip + 1.0))); + } + + 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.0)); + vec4 v2 = texture2D(u_lightbuffer, vec2(u, 0.5)); + 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); - gl_FragColor = vec4(v_uv, 0.0, 1.0); + // vec4 gb0 = texture2D(u_gbuffers[0], v_uv); // color / albedo + // vec4 gb1 = texture2D(u_gbuffers[1], v_uv); // normal + // vec4 gb2 = texture2D(u_gbuffers[2], v_uv); // depth + // vec4 gb3 = texture2D(u_gbuffers[3], v_uv); // v_position + + // optimized g-buffer + + // g-buffer[0] : color.x | color.y | color.z | viewSpaceDepth + // g-buffer[1] : normal.x | normal.y | 0.0 | NDC_Depth + + vec3 albedo = texture2D(u_gbuffers[0], v_uv).rgb; + //vec3 normal = texture2D(u_gbuffers[1], v_uv).xyz; + vec2 enc_nor = texture2D(u_gbuffers[1], v_uv).xy; + //vec3 v_position = texture2D(u_gbuffers[3], v_uv).xyz; + + float NDC_depth = texture2D(u_gbuffers[1], v_uv).w; + + // reconstruct v_position + vec4 screenPos = vec4(v_uv * 2.0 - vec2(1.0), NDC_depth, 1.0); + vec4 tmp_pos = u_invViewProjMatrix * screenPos; + tmp_pos = tmp_pos / tmp_pos.w; + vec3 v_position = tmp_pos.xyz; + + // reconstruct normal + // when reconstruct a normal, it should happen on view space, + // so that we can really handle normals of fragment we really see(deferred shading) + // But in our case, v_position and normal should on world space + // so we tranform normal back to world space + vec3 normal; + normal.xy = enc_nor; + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); + normal = vec3(u_invViewMatrix * vec4(normal, 0.0)); + + vec3 fragColor = vec3(0.0); + + //vec3 pos_viewSpace = vec3(u_viewMatrix * vec4(v_position, 1.0)); + + float viewSpaceDepth = texture2D(u_gbuffers[0], v_uv).w; + + // determine which cluster this fragment is in + int cluster_Idx_x = int(gl_FragCoord.x / u_cluster_tile_size.x); + int cluster_Idx_y = int(gl_FragCoord.y / u_cluster_tile_size.y); + //int cluster_Idx_z = int((-viewSpaceDepth - u_nearClip) / u_cluster_depth_stride); + int cluster_Idx_z = getCulsterDepthIndex(-viewSpaceDepth, u_nearClip); + + // clusterTexture Size + const int clusterTexutreWidth = int(${params.numXSlices}) * int(${params.numYSlices}) * int(${params.numZSlices}); + const int clusterTextureHeight = int(ceil((float(${params.maxLightsPerCluster}) + 1.0) / 4.0)); + + // extract lights influencing this cluster from u_clusterbuffer + int clusterIdx = cluster_Idx_x + cluster_Idx_y * int(${params.numXSlices}) + cluster_Idx_z * int(${params.numXSlices}) * int(${params.numYSlices}); + + float cluster_u = float(clusterIdx + 1) / float(clusterTexutreWidth + 1); + + float cluster_v = 0.0; // because the texture space origin is at lower left, not upper left! + + float cluster_v_step = 1.0 / float(clusterTextureHeight + 1); + + cluster_v += cluster_v_step; + + vec4 cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + + int lightCountInCluster = int(cluster_texel[0]); + + int cluster_texel_fetch_Idx = 1; + + const int maxNumLights = int(min(float(${params.maxLightsPerCluster}),float(${params.numLights}))); + + + + for (int i = 0; i < maxNumLights; ++i) { + if(i == lightCountInCluster) {break;} + + // Fetch light index + int lightIdx; + if(cluster_texel_fetch_Idx == 0){ + lightIdx = int(cluster_texel[0]); + } + else if(cluster_texel_fetch_Idx == 1){ + lightIdx = int(cluster_texel[1]); + } + else if(cluster_texel_fetch_Idx == 2){ + lightIdx = int(cluster_texel[2]); + } + else if(cluster_texel_fetch_Idx == 3){ + lightIdx = int(cluster_texel[3]); + } + + cluster_texel_fetch_Idx++; + + Light light = UnpackLight(lightIdx); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + //float lambertTerm = max(dot(L, normal), 0.0); + + float lambertTerm; + + if(${params.isToonShading}){ + // ramp shading + float rampUnitLength = 0.25; + float rampUnitValue = 0.33; + // float rampCoord = dot(L, normal) * 0.5 + 0.5; // map from -1 -> 1 to 0 -> 1 + float rampCoord = max(dot(L, normal), 0.0); + int rampLevel = int(rampCoord / rampUnitLength); + lambertTerm = float(rampLevel) * rampUnitValue; + } + else{ + lambertTerm = max(dot(L, normal), 0.0); + } + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + + if(cluster_texel_fetch_Idx == 4){ + cluster_texel_fetch_Idx = 0; + cluster_v += cluster_v_step; + cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + } + + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; + + gl_FragColor = vec4(fragColor, 1.0); } `; -} \ No newline at end of file +} diff --git a/src/shaders/deferredRTT.frag.glsl.js b/src/shaders/deferredRTT.frag.glsl.js new file mode 100644 index 0000000..fefc9cc --- /dev/null +++ b/src/shaders/deferredRTT.frag.glsl.js @@ -0,0 +1,212 @@ +export default function(params) { + return ` + #version 100 + #extension GL_EXT_draw_buffers: enable + + precision highp float; + + uniform float u_nearClip; + uniform vec2 u_cluster_tile_size; + uniform float u_cluster_depth_stride; + uniform mat4 u_viewMatrix; + uniform mat4 u_invViewMatrix; + uniform mat4 u_invViewProjMatrix; + uniform sampler2D u_clusterbuffer; + + + uniform sampler2D u_lightbuffer; + + uniform sampler2D u_gbuffers[${params.numGBuffers}]; + + varying vec2 v_uv; + + struct Light { + vec3 position; + float radius; + vec3 color; + }; + + int getCulsterDepthIndex(float viewSpaceDepth, float nearClip) { + //2.15 is calculated based on near and far clip + //near Clip : 0.1 + //far Clip : 1000.0 + return int(floor(2.15 * log(viewSpaceDepth - nearClip + 1.0))); + } + + 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.0)); + vec4 v2 = texture2D(u_lightbuffer, vec2(u, 0.5)); + 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() { + + // vec4 gb0 = texture2D(u_gbuffers[0], v_uv); // color / albedo + // vec4 gb1 = texture2D(u_gbuffers[1], v_uv); // normal + // vec4 gb2 = texture2D(u_gbuffers[2], v_uv); // depth + // vec4 gb3 = texture2D(u_gbuffers[3], v_uv); // v_position + + // optimized g-buffer + + // g-buffer[0] : color.x | color.y | color.z | viewSpaceDepth + // g-buffer[1] : normal.x | normal.y | 0.0 | NDC_Depth + + vec3 albedo = texture2D(u_gbuffers[0], v_uv).rgb; + // vec3 normal = texture2D(u_gbuffers[1], v_uv).xyz; + vec2 enc_nor = texture2D(u_gbuffers[1], v_uv).xy; + // vec3 v_position = texture2D(u_gbuffers[3], v_uv).xyz; + + float NDC_depth = texture2D(u_gbuffers[1], v_uv).w; + + // reconstruct v_position + vec4 screenPos = vec4(v_uv * 2.0 - vec2(1.0), NDC_depth, 1.0); + vec4 tmp_pos = u_invViewProjMatrix * screenPos; + tmp_pos = tmp_pos / tmp_pos.w; + vec3 v_position = tmp_pos.xyz; + + // reconstruct normal + // when reconstruct a normal, it should happen on view space, + // so that we can really handle normals of fragment we really see(deferred shading) + // But in our case, v_position and normal should on world space + // so we tranform normal back to world space + vec3 normal; + normal.xy = enc_nor; + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); + normal = vec3(u_invViewMatrix * vec4(normal, 0.0)); + + vec3 fragColor = vec3(0.0); + + //vec3 pos_viewSpace = vec3(u_viewMatrix * vec4(v_position, 1.0)); + + float viewSpaceDepth = texture2D(u_gbuffers[0], v_uv).w; + + // determine which cluster this fragment is in + int cluster_Idx_x = int(gl_FragCoord.x / u_cluster_tile_size.x); + int cluster_Idx_y = int(gl_FragCoord.y / u_cluster_tile_size.y); + //int cluster_Idx_z = int((-viewSpaceDepth - u_nearClip) / u_cluster_depth_stride); + int cluster_Idx_z = getCulsterDepthIndex(-viewSpaceDepth, u_nearClip); + + // clusterTexture Size + const int clusterTexutreWidth = int(${params.numXSlices}) * int(${params.numYSlices}) * int(${params.numZSlices}); + const int clusterTextureHeight = int(ceil((float(${params.maxLightsPerCluster}) + 1.0) / 4.0)); + + // extract lights influencing this cluster from u_clusterbuffer + int clusterIdx = cluster_Idx_x + cluster_Idx_y * int(${params.numXSlices}) + cluster_Idx_z * int(${params.numXSlices}) * int(${params.numYSlices}); + + float cluster_u = float(clusterIdx + 1) / float(clusterTexutreWidth + 1); + + float cluster_v = 0.0; // because the texture space origin is at lower left, not upper left! + + float cluster_v_step = 1.0 / float(clusterTextureHeight + 1); + + cluster_v += cluster_v_step; + + vec4 cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + + int lightCountInCluster = int(cluster_texel[0]); + + int cluster_texel_fetch_Idx = 1; + + const int maxNumLights = int(min(float(${params.maxLightsPerCluster}),float(${params.numLights}))); + + + + for (int i = 0; i < maxNumLights; ++i) { + if(i == lightCountInCluster) {break;} + + // Fetch light index + int lightIdx; + if(cluster_texel_fetch_Idx == 0){ + lightIdx = int(cluster_texel[0]); + } + else if(cluster_texel_fetch_Idx == 1){ + lightIdx = int(cluster_texel[1]); + } + else if(cluster_texel_fetch_Idx == 2){ + lightIdx = int(cluster_texel[2]); + } + else if(cluster_texel_fetch_Idx == 3){ + lightIdx = int(cluster_texel[3]); + } + + cluster_texel_fetch_Idx++; + + Light light = UnpackLight(lightIdx); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + //float lambertTerm = max(dot(L, normal), 0.0); + + float lambertTerm; + + if(${params.isToonShading}){ + // ramp shading + float rampUnitLength = 0.25; + float rampUnitValue = 0.33; + // float rampCoord = dot(L, normal) * 0.5 + 0.5; // map from -1 -> 1 to 0 -> 1 + float rampCoord = max(dot(L, normal), 0.0); + int rampLevel = int(rampCoord / rampUnitLength); + lambertTerm = float(rampLevel) * rampUnitValue; + } + else{ + lambertTerm = max(dot(L, normal), 0.0); + } + + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + + if(cluster_texel_fetch_Idx == 4){ + cluster_texel_fetch_Idx = 0; + cluster_v += cluster_v_step; + cluster_texel = texture2D(u_clusterbuffer, vec2(cluster_u, cluster_v)); + } + + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; + + // render to fbo + gl_FragData[0] = vec4(fragColor, 1.0); + } + `; +} diff --git a/src/shaders/deferredToTexture.frag.glsl b/src/shaders/deferredToTexture.frag.glsl index bafc086..1101db1 100644 --- a/src/shaders/deferredToTexture.frag.glsl +++ b/src/shaders/deferredToTexture.frag.glsl @@ -5,6 +5,9 @@ precision highp float; uniform sampler2D u_colmap; uniform sampler2D u_normap; +uniform mat4 u_viewMatrix; +uniform mat4 u_viewProjectionMatrix; + varying vec3 v_position; varying vec3 v_normal; varying vec2 v_uv; @@ -14,16 +17,30 @@ vec3 applyNormalMap(vec3 geomnor, vec3 normap) { 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; + return normalize(normap.y * surftan + normap.x * surfbinor + normap.z * geomnor); } void main() { - vec3 norm = applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv))); + // Since we need to reconstruct it later, it should on view space + vec3 norm = vec3(u_viewMatrix * vec4(applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv))), 0.0)); + //vec3 norm = 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] = ?? -} \ No newline at end of file + vec4 v_pos_NDC = u_viewProjectionMatrix * vec4(v_position, 1.0); + v_pos_NDC /= v_pos_NDC.w; + + vec3 depth = vec3(v_pos_NDC.z); + + vec4 v_pos_ViewSpace = u_viewMatrix * vec4(v_position, 1.0); + + // Populate g buffer + gl_FragData[0] = vec4(col, v_pos_ViewSpace.z); + gl_FragData[1] = vec4(norm.xy, 0.0, v_pos_NDC.z); + + //gl_FragData[1] = vec4(norm.xyz, v_pos_NDC.z); + + //gl_FragData[0] = vec4(col, v_pos_ViewSpace.z); + //gl_FragData[1] = vec4(norm, 0.0); + //gl_FragData[2] = vec4(depth, 0.0); + //gl_FragData[3] = vec4(v_position, 1.0); +} diff --git a/src/shaders/forward.frag.glsl.js b/src/shaders/forward.frag.glsl.js index 47f40a1..cc1e192 100644 --- a/src/shaders/forward.frag.glsl.js +++ b/src/shaders/forward.frag.glsl.js @@ -82,7 +82,23 @@ export default function(params) { vec3 L = (light.position - v_position) / lightDistance; float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); - float lambertTerm = max(dot(L, normal), 0.0); + //float lambertTerm = max(dot(L, normal), 0.0); + + float lambertTerm; + + if(${params.isToonShading}){ + // ramp shading + float rampUnitLength = 0.25; + float rampUnitValue = 0.33; + // float rampCoord = dot(L, normal) * 0.5 + 0.5; // map from -1 -> 1 to 0 -> 1 + float rampCoord = max(dot(L, normal), 0.0); + int rampLevel = int(rampCoord / rampUnitLength); + lambertTerm = float(rampLevel) * rampUnitValue; + } + else{ + lambertTerm = max(dot(L, normal), 0.0); + } + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); } diff --git a/src/shaders/forwardRTT.frag.glsl.js b/src/shaders/forwardRTT.frag.glsl.js new file mode 100644 index 0000000..609aeae --- /dev/null +++ b/src/shaders/forwardRTT.frag.glsl.js @@ -0,0 +1,112 @@ +export default function(params) { + return ` + #version 100 + #extension GL_EXT_draw_buffers: enable + precision highp float; + + uniform sampler2D u_colmap; + uniform sampler2D u_normap; + uniform sampler2D u_lightbuffer; + + varying vec3 v_position; + varying vec3 v_normal; + 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.0)); + vec4 v2 = texture2D(u_lightbuffer, vec2(u, 0.5)); + 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() { + vec3 albedo = texture2D(u_colmap, v_uv).rgb; + vec3 normap = texture2D(u_normap, v_uv).xyz; + vec3 normal = applyNormalMap(v_normal, normap); + + vec3 fragColor = vec3(0.0); + + for (int i = 0; i < ${params.numLights}; ++i) { + Light light = UnpackLight(i); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + //float lambertTerm = max(dot(L, normal), 0.0); + + float lambertTerm; + + if(${params.isToonShading}){ + // ramp shading + float rampUnitLength = 0.25; + float rampUnitValue = 0.33; + // float rampCoord = dot(L, normal) * 0.5 + 0.5; // map from -1 -> 1 to 0 -> 1 + float rampCoord = max(dot(L, normal), 0.0); + int rampLevel = int(rampCoord / rampUnitLength); + lambertTerm = float(rampLevel) * rampUnitValue; + } + else{ + lambertTerm = max(dot(L, normal), 0.0); + } + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; + + gl_FragData[0] = vec4(fragColor, 1.0); + } + `; +} diff --git a/src/shaders/horizontalBlurRTT.vert.glsl b/src/shaders/horizontalBlurRTT.vert.glsl new file mode 100644 index 0000000..6cc58ae --- /dev/null +++ b/src/shaders/horizontalBlurRTT.vert.glsl @@ -0,0 +1,21 @@ +#version 100 +precision highp float; + +uniform float u_targetWidth; + +attribute vec3 a_position; + +varying vec2 blurTextureCoords[11]; + +void main() { + gl_Position = vec4(a_position, 1.0); + + vec2 centerTexCoords = a_position.xy * 0.5 + 0.5; + + float pixelSize = 1.0 / u_targetWidth; + + for(int i = -5; i <= 5; i++){ + blurTextureCoords[i + 5] = centerTexCoords + vec2(pixelSize * float(i), 0.0); + } + +} diff --git a/src/shaders/verticalBlurRTT.vert.glsl b/src/shaders/verticalBlurRTT.vert.glsl new file mode 100644 index 0000000..f7a7e14 --- /dev/null +++ b/src/shaders/verticalBlurRTT.vert.glsl @@ -0,0 +1,20 @@ +#version 100 +precision highp float; + +uniform float u_targetHeight; + +attribute vec3 a_position; + +varying vec2 blurTextureCoords[11]; + +void main() { + gl_Position = vec4(a_position, 1.0); + + vec2 centerTexCoords = a_position.xy * 0.5 + 0.5; + + float pixelSize = 1.0 / u_targetHeight; + + for(int i = -5; i <= 5; i++){ + blurTextureCoords[i + 5] = centerTexCoords + vec2(0.0, pixelSize * float(i)); + } +}