diff --git a/app/assets/textures/clouds.jpg b/app/assets/textures/clouds.jpg new file mode 100644 index 0000000..f3698e8 Binary files /dev/null and b/app/assets/textures/clouds.jpg differ diff --git a/app/assets/textures/clouds.png b/app/assets/textures/clouds.png new file mode 100644 index 0000000..d1202e9 Binary files /dev/null and b/app/assets/textures/clouds.png differ diff --git a/app/js/components/Bean.js b/app/js/components/Bean.js index ad6a1b4..5397dec 100644 --- a/app/js/components/Bean.js +++ b/app/js/components/Bean.js @@ -12,6 +12,7 @@ class Bean extends THREE.Object3D { this.mesh = new THREE.Mesh(geometry, material); this.mesh.castShadow = true; + this.mesh.receiveShadow = true; this.add(this.mesh); this.position.z += this.radius / 2 + 0.01; @@ -19,9 +20,10 @@ class Bean extends THREE.Object3D { this.onUpdate = this.onUpdate.bind(this); } - onUpdate() { - // this.rotation.x += props.rotation; - // this.rotation.y += props.rotation; + onUpdate(time) { + this.position.y = 0.01 * Math.sin(Math.PI * 2 * time / 20) + 0.01 * Math.sin(Math.PI * 2 * time / 70); + this.position.x = 0.1 * Math.sin(Math.PI * 2 * time / 5300); + this.position.z = 0.1 * Math.cos(Math.PI * 2 * time / 5300); } } diff --git a/app/js/components/Clouds.js b/app/js/components/Clouds.js new file mode 100644 index 0000000..c7eda7a --- /dev/null +++ b/app/js/components/Clouds.js @@ -0,0 +1,43 @@ +import props from 'js/core/props'; +// import depthShaderFrag from 'js/shaders/depthShader-frag'; +// import depthShaderVert from 'js/shaders/depthShader-vert'; + +class Clouds extends THREE.Object3D { + constructor() { + super(); + + let loader = new THREE.TextureLoader(); + this.cloudTexture = loader.load('textures/clouds.png'); + this.cloudTexture.wrapS = this.cloudTexture.wrapT = THREE.RepeatWrapping; + this.cloudTexture.repeat.set(3, 3); + + this.material = new THREE.MeshBasicMaterial({ + map: this.cloudTexture, + transparent: true, + side: THREE.DoubleSide, + }); + let geometry = new THREE.PlaneBufferGeometry(200, 200); + let mesh = new THREE.Mesh(geometry, this.material); + + mesh.customDepthMaterial = new THREE.MeshDepthMaterial({ + depthPacking: THREE.RGBADepthPacking, + map: this.cloudTexture, + alphaTest: 0.3 + }); + + mesh.rotation.x = -Math.PI/2; + mesh.position.y = 10; + mesh.castShadow = true; + + this.add(mesh); + + this.onUpdate = this.onUpdate.bind(this); + } + + onUpdate(time) { + this.cloudTexture.offset.x = (time / 1000) % 200; + this.cloudTexture.offset.y = (time / 5000) % 400; + } +} + +module.exports = Clouds; diff --git a/app/js/components/Light.js b/app/js/components/Light.js index b50ce5d..791592d 100644 --- a/app/js/components/Light.js +++ b/app/js/components/Light.js @@ -7,14 +7,14 @@ class Lighting extends THREE.Object3D { let dirLight = new THREE.DirectionalLight( 0xffffff, 1 ); dirLight.color.setHSL( 0.095, 1, 0.95 ); dirLight.position.set( -1, 1.75, 1 ); - dirLight.position.multiplyScalar( 2 ); + dirLight.position.multiplyScalar( 20 ); dirLight.castShadow = true; dirLight.shadow.mapSize.width = 1024; dirLight.shadow.mapSize.height = 1024; - dirLight.shadow.radius = 3; + dirLight.shadow.radius = 2; - let d = 10; + let d = 100; dirLight.shadow.camera.left = -d; dirLight.shadow.camera.right = d; dirLight.shadow.camera.top = d; diff --git a/app/js/core/Loop.js b/app/js/core/Loop.js index 63e30e7..a5e8c9b 100644 --- a/app/js/core/Loop.js +++ b/app/js/core/Loop.js @@ -3,6 +3,7 @@ class Loop { constructor() { this._idRAF = -1; this._count = 0; + this._time = (new Date()).getTime(); this._listeners = []; @@ -13,10 +14,11 @@ class Loop { _update() { let listener = null; let i = this._count; + this._time = (new Date()).getTime() while (--i >= 0) { listener = this._listeners[i]; if (listener) { - listener.apply(this, null); + listener.apply(this, [this._time]); } } this._idRAF = requestAnimationFrame(this._binds.update); diff --git a/app/js/core/Webgl.js b/app/js/core/Webgl.js index be664ca..4ddc4ac 100644 --- a/app/js/core/Webgl.js +++ b/app/js/core/Webgl.js @@ -10,30 +10,15 @@ export default class Webgl { this.camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 1000); this.camera.position.z = 10; - // // overwrite shadowmap code - // let shader = THREE.ShaderChunk.shadowmap_pars_fragment; - // shader = shader.replace( - // '#ifdef USE_SHADOWMAP', - // '#ifdef USE_SHADOWMAP\n' + PCSS - // ); - // shader = shader.replace( - // '#if defined( SHADOWMAP_TYPE_PCF )', - // ` - // return PCSS( shadowMap, shadowCoord ); - - // #if defined( SHADOWMAP_TYPE_PCF )` - // ); - // THREE.ShaderChunk.shadowmap_pars_fragment = shader; - this._renderer = new THREE.WebGLRenderer({ antialias: true, }); - this._renderer.setPixelRatio(window.devicePixelRatio); this._renderer.gammaInput = true; this._renderer.gammaOutput = true; this._renderer.shadowMap.enabled = true; - this._renderer.shadowMap.type = THREE.BasicShadowMap; + // this._renderer.shadowMap.type = THREE.BasicShadowMap; + this._renderer.shadowMap.type = THREE.PCFSoftShadowMap; this.dom = this._renderer.domElement; @@ -91,9 +76,12 @@ export default class Webgl { this.camera.aspect = w / h; this.camera.updateProjectionMatrix(); - this._renderer.setSize(w, h); if (this.usePostprocessing) { - this._composer.setSize(w, h); + this._renderer.setSize(w, h); + this._renderer.setPixelRatio(window.devicePixelRatio / 2); + this._composer.setSize(w/2, h/2); + } else { + this._renderer.setSize(w, h); } } } diff --git a/app/js/shaders/PCSS-frag.glsl b/app/js/shaders/PCSS-frag.glsl deleted file mode 100644 index aa8e0bb..0000000 --- a/app/js/shaders/PCSS-frag.glsl +++ /dev/null @@ -1,84 +0,0 @@ -#define LIGHT_WORLD_SIZE 0.005 -#define LIGHT_FRUSTUM_WIDTH 3.75 -#define LIGHT_SIZE_UV (LIGHT_WORLD_SIZE / LIGHT_FRUSTUM_WIDTH) -#define NEAR_PLANE 9.5 - -#define NUM_SAMPLES 17 -#define NUM_RINGS 11 -#define BLOCKER_SEARCH_NUM_SAMPLES NUM_SAMPLES -#define PCF_NUM_SAMPLES NUM_SAMPLES - -vec2 poissonDisk[NUM_SAMPLES]; - -void initPoissonSamples( const in vec2 randomSeed ) { - float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES ); - float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES ); - - // jsfiddle that shows sample pattern: https://jsfiddle.net/a16ff1p7/ - float angle = rand( randomSeed ) * PI2; - float radius = INV_NUM_SAMPLES; - float radiusStep = radius; - - for( int i = 0; i < NUM_SAMPLES; i ++ ) { - poissonDisk[i] = vec2( cos( angle ), sin( angle ) ) * pow( radius, 0.75 ); - radius += radiusStep; - angle += ANGLE_STEP; - } -} - -float penumbraSize( const in float zReceiver, const in float zBlocker ) { // Parallel plane estimation - return (zReceiver - zBlocker) / zBlocker; -} - -float findBlocker( sampler2D shadowMap, const in vec2 uv, const in float zReceiver ) { - // This uses similar triangles to compute what - // area of the shadow map we should search - float searchRadius = LIGHT_SIZE_UV * ( zReceiver - NEAR_PLANE ) / zReceiver; - float blockerDepthSum = 0.0; - int numBlockers = 0; - - for( int i = 0; i < BLOCKER_SEARCH_NUM_SAMPLES; i++ ) { - float shadowMapDepth = unpackRGBAToDepth(texture2D(shadowMap, uv + poissonDisk[i] * searchRadius)); - if ( shadowMapDepth < zReceiver ) { - blockerDepthSum += shadowMapDepth; - numBlockers ++; - } - } - - if( numBlockers == 0 ) return -1.0; - - return blockerDepthSum / float( numBlockers ); -} - -float PCF_Filter(sampler2D shadowMap, vec2 uv, float zReceiver, float filterRadius ) { - float sum = 0.0; - for( int i = 0; i < PCF_NUM_SAMPLES; i ++ ) { - float depth = unpackRGBAToDepth( texture2D( shadowMap, uv + poissonDisk[ i ] * filterRadius ) ); - if( zReceiver <= depth ) sum += 1.0; - } - for( int i = 0; i < PCF_NUM_SAMPLES; i ++ ) { - float depth = unpackRGBAToDepth( texture2D( shadowMap, uv + -poissonDisk[ i ].yx * filterRadius ) ); - if( zReceiver <= depth ) sum += 1.0; - } - return sum / ( 2.0 * float( PCF_NUM_SAMPLES ) ); -} - -float PCSS ( sampler2D shadowMap, vec4 coords ) { - vec2 uv = coords.xy; - float zReceiver = coords.z; // Assumed to be eye-space z in this code - - initPoissonSamples( uv ); - // STEP 1: blocker search - float avgBlockerDepth = findBlocker( shadowMap, uv, zReceiver ); - - //There are no occluders so early out (this saves filtering) - if( avgBlockerDepth == -1.0 ) return 1.0; - - // STEP 2: penumbra size - float penumbraRatio = penumbraSize( zReceiver, avgBlockerDepth ); - float filterRadius = penumbraRatio * LIGHT_SIZE_UV * NEAR_PLANE / zReceiver; - - // STEP 3: filtering - //return avgBlockerDepth; - return PCF_Filter( shadowMap, uv, zReceiver, filterRadius ); -} diff --git a/app/js/shaders/depthShader-frag.glsl b/app/js/shaders/depthShader-frag.glsl new file mode 100644 index 0000000..6313683 --- /dev/null +++ b/app/js/shaders/depthShader-frag.glsl @@ -0,0 +1,16 @@ +uniform sampler2D texture; +varying vec2 vUV; + +vec4 pack_depth(const in float depth) { + const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); + const vec4 bit_mask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0); + vec4 res = fract(depth * bit_shift); + res -= res.xxyz * bit_mask; + return res; +} + +void main() { + vec4 pixel = texture2D(texture, vUV); + if (pixel.a < 0.5) discard; + gl_FragData[0] = pack_depth(gl_FragCoord.z); +} diff --git a/app/js/shaders/depthShader-vert.glsl b/app/js/shaders/depthShader-vert.glsl new file mode 100644 index 0000000..c9e3c75 --- /dev/null +++ b/app/js/shaders/depthShader-vert.glsl @@ -0,0 +1,6 @@ +varying vec2 vUV; + +void main() { + vUV = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +} diff --git a/app/main.js b/app/main.js index 1fa0f13..eed92c3 100644 --- a/app/main.js +++ b/app/main.js @@ -3,8 +3,8 @@ import loop from 'js/core/Loop'; import props from 'js/core/props'; import Bean from 'js/components/Bean'; import Ground from 'js/components/Ground'; +import Clouds from 'js/components/Clouds'; import Light from 'js/components/Light'; -import { createLight, createHemisphereLight } from 'js/components/Light'; // ## // INIT @@ -21,6 +21,10 @@ gui.close(); webgl.add(new Ground()); +const clouds = new Clouds(); +webgl.add(clouds); +loop.add(clouds.onUpdate); + const light = new Light(); webgl.add(light); loop.add(light.onUpdate);