├── .gitignore ├── README.md ├── deferred-rendering.jpg ├── fx ├── Contrast.glsl ├── Contrast.js ├── SSAO.glsl └── SSAO.js ├── index.html ├── lib └── watchscript.js ├── main.js ├── materials ├── DeferredPointLight.glsl └── DeferredPointLight.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | stuff 3 | .DS_Store 4 | main.web.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](deferred-rendering.jpg) 2 | 3 | Deferred rendering with sphere clipped lights in WebGL developed in [pex](http://github.com/vorg/pex/). -------------------------------------------------------------------------------- /deferred-rendering.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vorg/pex-exp-deferred-rendering/1f2ffd6a30199bff60691220851760a9bd5cf068/deferred-rendering.jpg -------------------------------------------------------------------------------- /fx/Contrast.glsl: -------------------------------------------------------------------------------- 1 | #ifdef VERT 2 | 3 | attribute vec2 position; 4 | attribute vec2 texCoord; 5 | varying vec2 vTexCoord; 6 | 7 | void main() { 8 | gl_Position = vec4(position, 0.0, 1.0); 9 | vTexCoord = texCoord; 10 | } 11 | 12 | #endif 13 | 14 | #ifdef FRAG 15 | 16 | varying vec2 vTexCoord; 17 | uniform sampler2D tex0; 18 | uniform float contrast; 19 | 20 | void main() { 21 | vec4 color = texture2D(tex0, vTexCoord).rgba; 22 | gl_FragColor.rgb = (color.rgb - 0.5) * contrast + 0.5; 23 | gl_FragColor.a = color.a; 24 | } 25 | 26 | #endif -------------------------------------------------------------------------------- /fx/Contrast.js: -------------------------------------------------------------------------------- 1 | var fx = require('pex-fx'); 2 | var FXStage = fx.FXStage; 3 | var fs = require('fs'); 4 | 5 | var ContrastGLSL = fs.readFileSync(__dirname + '/Contrast.glsl', 'utf8'); 6 | 7 | FXStage.prototype.contrast = function (options) { 8 | options = options || { contrast: 1 }; 9 | var outputSize = this.getOutputSize(options.width, options.height); 10 | var rt = this.getRenderTarget(outputSize.width, outputSize.height, options.depth, options.bpp); 11 | rt.bind(); 12 | this.getSourceTexture().bind(0); 13 | var program = this.getShader(ContrastGLSL); 14 | program.use(); 15 | program.uniforms.tex0(0); 16 | program.uniforms.contrast(options.contrast); 17 | this.drawFullScreenQuad(outputSize.width, outputSize.height, null, program); 18 | rt.unbind(); 19 | return this.asFXStage(rt, 'contrast'); 20 | }; 21 | 22 | module.exports = FXStage; -------------------------------------------------------------------------------- /fx/SSAO.glsl: -------------------------------------------------------------------------------- 1 | //based on http://blenderartists.org/forum/showthread.php?184102-nicer-and-faster-SSAO and http://www.pasteall.org/12299 2 | #ifdef VERT 3 | 4 | attribute vec2 position; 5 | attribute vec2 texCoord; 6 | 7 | varying vec2 vTexCoord; 8 | 9 | void main() { 10 | gl_Position = vec4(position, 0.0, 1.0); 11 | vTexCoord = texCoord; 12 | } 13 | 14 | #endif 15 | 16 | #ifdef FRAG 17 | 18 | #define PI 3.14159265 19 | 20 | varying vec2 vTexCoord; 21 | 22 | uniform sampler2D depthMap; 23 | uniform vec2 textureSize; 24 | uniform float near; 25 | uniform float far; 26 | 27 | const int samples = 3; 28 | const int rings = 5; 29 | 30 | uniform float strength; 31 | uniform float cutoutBg; 32 | 33 | vec2 rand(vec2 coord) { 34 | float noiseX = (fract(sin(dot(coord, vec2(12.9898,78.233))) * 43758.5453)); 35 | float noiseY = (fract(sin(dot(coord, vec2(12.9898,78.233) * 2.0)) * 43758.5453)); 36 | return vec2(noiseX,noiseY) * 0.004; 37 | } 38 | 39 | float compareDepths( in float depth1, in float depth2 ) 40 | { 41 | float aoCap = 1.0; 42 | float aoMultiplier = 100.0; 43 | float depthTolerance = 0.003; 44 | float aorange = 5.0;// units in space the AO effect extends to (this gets divided by the camera far range 45 | float diff = sqrt(clamp(1.0-(depth1-depth2) / (aorange/(far-near)),0.0,1.0)); 46 | float ao = min(aoCap, max(0.0, depth1-depth2-depthTolerance) * aoMultiplier) * diff; 47 | //if (diff > depthTolerance * 500) return 0; 48 | return ao * strength; 49 | } 50 | 51 | float readDepth(vec2 coord) { 52 | return texture2D(depthMap, coord).a/far; 53 | } 54 | 55 | void main() { 56 | vec2 texCoord = vec2(gl_FragCoord.x / textureSize.x, gl_FragCoord.y / textureSize.y); 57 | float depth = readDepth(texCoord); 58 | 59 | float d; 60 | 61 | float aspect = textureSize.x / textureSize.y; 62 | vec2 noise = rand(vTexCoord); 63 | 64 | float w = (1.0 / textureSize.x)/clamp(depth,0.05,1.0)+(noise.x*(1.0-noise.x)); 65 | float h = (1.0 / textureSize.y)/clamp(depth,0.05,1.0)+(noise.y*(1.0-noise.y)); 66 | 67 | float pw; 68 | float ph; 69 | 70 | float ao = 0.0; 71 | float s = 0.0; 72 | float fade = 4.0; 73 | 74 | for (int i = 0 ; i < rings; i += 1) 75 | { 76 | fade *= 0.5; 77 | for (int j = 0 ; j < samples*rings; j += 1) 78 | { 79 | if (j >= samples*i) break; 80 | float step = PI * 2.0 / (float(samples) * float(i)); 81 | pw = (cos(float(j)*step) * float(i) * 0.5); 82 | ph = (sin(float(j)*step) * float(i) * 0.5) * aspect; 83 | d = readDepth( vec2(texCoord.s + pw * w,texCoord.t + ph * h)); 84 | ao += compareDepths(depth, d) * fade; 85 | s += 1.0 * fade; 86 | } 87 | } 88 | 89 | ao /= s; 90 | ao *= 1.5; 91 | ao = 1.0 - ao; 92 | 93 | if (depth > 0.99) ao += 0.5; 94 | 95 | vec3 black = vec3(0.0, 0.0, 0.0); 96 | vec3 treshold = vec3(0.2, 0.2, 0.2); 97 | 98 | gl_FragColor = vec4(texCoord, 0.0, 1.0); 99 | //gl_FragColor = vec4(getDepth(texCoord), 0.0, 0.0, 1.0); 100 | gl_FragColor = vec4(ao, ao, ao, 1.0); 101 | 102 | if (depth*cutoutBg > 0.5) gl_FragColor = vec4(0.0); 103 | } 104 | 105 | #endif -------------------------------------------------------------------------------- /fx/SSAO.js: -------------------------------------------------------------------------------- 1 | var fx = require('pex-fx'); 2 | var FXStage = fx.FXStage; 3 | var geom = require('pex-geom') 4 | var Vec2 = geom.Vec2; 5 | var fs = require('fs'); 6 | 7 | var SSAOGLSL = fs.readFileSync(__dirname + '/SSAO.glsl', 'utf8'); 8 | 9 | FXStage.prototype.ssao = function (options) { 10 | options = options || {}; 11 | var outputSize = this.getOutputSize(options.width, options.height); 12 | var rt = this.getRenderTarget(outputSize.width, outputSize.height, options.depth, options.bpp); 13 | rt.bind(); 14 | var depthMap = this.getSourceTexture(options.depthMap); 15 | depthMap.bind(0); 16 | var program = this.getShader(SSAOGLSL); 17 | program.use(); 18 | program.uniforms.textureSize(Vec2.create(depthMap.width, depthMap.height)); 19 | program.uniforms.depthMap(0); 20 | program.uniforms.near(options.camera.getNear()); 21 | program.uniforms.far(options.camera.getFar()); 22 | program.uniforms.strength(options.strength === null ? 1 : options.strength); 23 | program.uniforms.cutoutBg(options.cutoutBg ? 1 : 0); 24 | this.drawFullScreenQuad(outputSize.width, outputSize.height, null, program); 25 | rt.unbind(); 26 | return this.asFXStage(rt, 'ssao'); 27 | }; 28 | 29 | module.exports = FXStage; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/watchscript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var browserify = require('browserify'); 5 | var watchify = require('watchify'); 6 | var browserSync = require('browser-sync'); 7 | var b = browserify(watchify.args); 8 | 9 | function watch() { 10 | b.add('./main.js'); 11 | 12 | b.transform({global:true}, 'brfs'); 13 | b.ignore('plask'); 14 | 15 | var bundler = watchify(b); 16 | bundler.on('update', rebundle); 17 | 18 | function rebundle () { 19 | return bundler.bundle() 20 | // log errors if they happen 21 | .on('error', function(e) { 22 | console.log('Browserify Error', e); 23 | }) 24 | .pipe(fs.createWriteStream('main.web.js')) 25 | browserSync.reload(); 26 | } 27 | 28 | return rebundle() 29 | 30 | } 31 | 32 | watch(); 33 | 34 | 35 | var files = [ 36 | './main.web.js' 37 | ]; 38 | 39 | browserSync.init(files, { 40 | server: { 41 | baseDir: './' 42 | } 43 | }); 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var sys = require('pex-sys'); 2 | var glu = require('pex-glu'); 3 | var materials = require('pex-materials'); 4 | var color = require('pex-color'); 5 | var gen = require('pex-gen'); 6 | var geom = require('pex-geom'); 7 | var fx = require('pex-fx'); 8 | var gui = require('pex-gui'); 9 | var random = require('pex-random'); 10 | 11 | var Box = gen.Box; 12 | var Sphere = gen.Sphere; 13 | var Dodecahedron = gen.Dodecahedron; 14 | var Tetrahedron = gen.Tetrahedron; 15 | var Mesh = glu.Mesh; 16 | var PerspectiveCamera = glu.PerspectiveCamera; 17 | var Arcball = glu.Arcball; 18 | var ShowNormals = materials.ShowNormals; 19 | var SolidColor = materials.SolidColor; 20 | var ShowDepth = materials.ShowDepth; 21 | var ShowPosition = materials.ShowPosition; 22 | var Color = color.Color; 23 | var Platform = sys.Platform; 24 | var Time = sys.Time; 25 | var Vec3 = geom.Vec3; 26 | var Quat = geom.Quat; 27 | var GUI = gui.GUI; 28 | var DeferredPointLight = require('./materials/DeferredPointLight'); 29 | var Contrast = require('./fx/Contrast'); 30 | 31 | var UP = new Vec3(0, 1, 0); 32 | 33 | var degToRad = 1/180.0 * Math.PI; 34 | 35 | function evalPos(theta, phi) { 36 | var pos = new Vec3(); 37 | pos.x = Math.sin(theta * degToRad) * Math.sin(phi * degToRad); 38 | pos.y = Math.cos(theta * degToRad); 39 | pos.z = Math.sin(theta * degToRad) * Math.cos(phi * degToRad); 40 | return pos; 41 | } 42 | 43 | sys.Window.create({ 44 | settings: { 45 | width: 1280, 46 | height: 720, 47 | type: '3d', 48 | fullscreen: true, 49 | highdpi: 1 50 | }, 51 | animate: true, 52 | exposure: 1, 53 | contrast: 1, 54 | ssaoStrength: 0.4, 55 | correctGamma: true, 56 | ssao: false, 57 | tonemapReinhard: true, 58 | roughness: 0.3, 59 | lightRadius: 0.95, 60 | numLights: Platform.isMobile ? 20 : 100, 61 | wrap: 0, 62 | init: function() { 63 | if (Platform.isBrowser) { 64 | console.log('OES_texture_float', this.gl.getExtension("OES_texture_float")); 65 | console.log('OES_texture_float_linear', this.gl.getExtension("OES_texture_float_linear")); 66 | console.log('OES_texture_half_float', this.gl.getExtension("OES_texture_half_float")); 67 | console.log('OES_texture_half_float_linear', this.gl.getExtension("OES_texture_half_float_linear")); 68 | //console.log('EXT_shader_texture_lod', this.gl.getExtension("EXT_shader_texture_lod")); 69 | //console.log('OES_standard_derivatives', this.gl.getExtension("OES_standard_derivatives")); 70 | } 71 | this.gui = new GUI(this); 72 | this.gui.addParam('Animate', this, 'animate'); 73 | this.gui.addParam('Wrap light', this, 'wrap'); 74 | this.gui.addParam('SSAO', this, 'ssao'); 75 | this.gui.addParam('SSAO Strength', this, 'ssaoStrength', { min: 0.0, max: 10 }); 76 | this.gui.addParam('Roughness', this, 'roughness', { min: 0.01, max: 1 }); 77 | this.gui.addParam('Tonemap Reinhard', this, 'tonemapReinhard'); 78 | this.gui.addParam('Exposure', this, 'exposure', { min: 0.5, max: 3 }); 79 | this.gui.addParam('Correct Gamma', this, 'correctGamma'); 80 | this.gui.addParam('Contrast', this, 'contrast', { min: 0.5, max: 3 }); 81 | 82 | this.scene = []; 83 | 84 | random.seed(0); 85 | 86 | var star = new Box().catmullClark().extrude(1).catmullClark().extrude().catmullClark(); 87 | star.computeNormals(); 88 | this.starMesh = new Mesh(star, null); 89 | this.scene.push(this.starMesh); 90 | 91 | var sphere = new Tetrahedron(0.6).dooSabin().triangulate().catmullClark(); 92 | sphere.computeNormals(); 93 | this.spheres = []; 94 | for(var i=0; i<50; i++) { 95 | var m = new Mesh(sphere, null); 96 | m.radius = random.float(2, 6); 97 | m.theta = random.float(0, 180); 98 | m.phi = random.float(0, 360); 99 | m.rotation = Quat.fromDirection(random.vec3().normalize()); 100 | this.scene.push(m); 101 | this.spheres.push(m); 102 | } 103 | 104 | this.camera = new PerspectiveCamera(60, 2/1, 1, 10); 105 | this.arcball = new Arcball(this, this.camera, 5); 106 | 107 | this.lightPos = new Vec3(3, 3, 3); 108 | this.lightBrightness = 5; 109 | this.solidColor = new SolidColor(); 110 | 111 | 112 | this.lights = []; 113 | for(var i=0; i