123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
- import {player} from './player.js';
- import {world} from './world.js';
- import {background} from './background.js';
- const _VS = `
- varying vec3 vWorldPosition;
- void main() {
- vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
- vWorldPosition = worldPosition.xyz;
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
- }`;
- const _FS = `
- uniform vec3 topColor;
- uniform vec3 bottomColor;
- uniform float offset;
- uniform float exponent;
- varying vec3 vWorldPosition;
- void main() {
- float h = normalize( vWorldPosition + offset ).y;
- gl_FragColor = vec4( mix( bottomColor, topColor, max( pow( max( h , 0.0), exponent ), 0.0 ) ), 1.0 );
- }`;
- const _PCSS = `
- #define LIGHT_WORLD_SIZE 0.05
- #define LIGHT_FRUSTUM_WIDTH 3.75
- #define LIGHT_SIZE_UV (LIGHT_WORLD_SIZE / LIGHT_FRUSTUM_WIDTH)
- #define NEAR_PLANE 1.0
- #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 );
- }
- `;
- const _PCSSGetShadow = `
- return PCSS( shadowMap, shadowCoord );
- `;
- class BasicWorldDemo {
- constructor() {
- this._Initialize();
- this._gameStarted = false;
- document.getElementById('game-menu').onclick = (msg) => this._OnStart(msg);
- }
- _OnStart(msg) {
- document.getElementById('game-menu').style.display = 'none';
- this._gameStarted = true;
- }
- _Initialize() {
- // overwrite shadowmap code
- let shadowCode = THREE.ShaderChunk.shadowmap_pars_fragment;
- shadowCode = shadowCode.replace(
- '#ifdef USE_SHADOWMAP',
- '#ifdef USE_SHADOWMAP' +
- _PCSS
- );
- shadowCode = shadowCode.replace(
- '#if defined( SHADOWMAP_TYPE_PCF )',
- _PCSSGetShadow +
- '#if defined( SHADOWMAP_TYPE_PCF )'
- );
- THREE.ShaderChunk.shadowmap_pars_fragment = shadowCode;
- // renderer
- this.threejs_ = new THREE.WebGLRenderer({
- antialias: true,
- });
- this.threejs_.outputEncoding = THREE.sRGBEncoding;
- this.threejs_.gammaFactor = 2.2;
- // this.threejs_.toneMapping = THREE.ReinhardToneMapping;
- this.threejs_.shadowMap.enabled = true;
- // this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
- this.threejs_.setPixelRatio(window.devicePixelRatio);
- this.threejs_.setSize(window.innerWidth, window.innerHeight);
- document.getElementById('container').appendChild(this.threejs_.domElement);
- window.addEventListener('resize', () => {
- this.OnWindowResize_();
- }, false);
- const fov = 60;
- const aspect = 1920 / 1080;
- const near = 1.0;
- const far = 20000.0;
- this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
- this.camera_.position.set(-5, 5, 10);
- this.camera_.lookAt(8, 3, 0);
- this.scene_ = new THREE.Scene();
- let light = new THREE.DirectionalLight(0xFFFFFF, 1.0);
- light.position.set(60, 100, 10);
- light.target.position.set(40, 0, 0);
- light.castShadow = true;
- light.shadow.bias = -0.001;
- light.shadow.mapSize.width = 4096;
- light.shadow.mapSize.height = 4096;
- light.shadow.camera.far = 200.0;
- light.shadow.camera.near = 1.0;
- light.shadow.camera.left = 50;
- light.shadow.camera.right = -50;
- light.shadow.camera.top = 50;
- light.shadow.camera.bottom = -50;
- this.scene_.add(light);
- light = new THREE.HemisphereLight(0x202020, 0x004080, 0.6);
- this.scene_.add(light);
- this.scene_.background = new THREE.Color(0x808080);
- this.scene_.fog = new THREE.FogExp2(0x89b2eb, 0.00125);
- const ground = new THREE.Mesh(
- new THREE.PlaneGeometry(20000, 20000, 10, 10),
- new THREE.MeshStandardMaterial({
- color: 0xf6f47f,
- }));
- ground.castShadow = false;
- ground.receiveShadow = true;
- ground.rotation.x = -Math.PI / 2;
- this.scene_.add(ground);
- const uniforms = {
- topColor: { value: new THREE.Color(0x0077FF) },
- bottomColor: { value: new THREE.Color(0x89b2eb) },
- offset: { value: 33 },
- exponent: { value: 0.6 }
- };
- const skyGeo = new THREE.SphereBufferGeometry(1000, 32, 15);
- const skyMat = new THREE.ShaderMaterial({
- uniforms: uniforms,
- vertexShader: _VS,
- fragmentShader: _FS,
- side: THREE.BackSide,
- });
- this.scene_.add(new THREE.Mesh(skyGeo, skyMat));
- this.world_ = new world.WorldManager({scene: this.scene_});
- this.player_ = new player.Player({scene: this.scene_, world: this.world_});
- this.background_ = new background.Background({scene: this.scene_});
- this.gameOver_ = false;
- this.previousRAF_ = null;
- this.RAF_();
- this.OnWindowResize_();
- }
- OnWindowResize_() {
- this.camera_.aspect = window.innerWidth / window.innerHeight;
- this.camera_.updateProjectionMatrix();
- this.threejs_.setSize(window.innerWidth, window.innerHeight);
- }
- RAF_() {
- requestAnimationFrame((t) => {
- if (this.previousRAF_ === null) {
- this.previousRAF_ = t;
- }
- this.RAF_();
- this.Step_((t - this.previousRAF_) / 1000.0);
- this.threejs_.render(this.scene_, this.camera_);
- this.previousRAF_ = t;
- });
- }
- Step_(timeElapsed) {
- if (this.gameOver_ || !this._gameStarted) {
- return;
- }
- this.player_.Update(timeElapsed);
- this.world_.Update(timeElapsed);
- this.background_.Update(timeElapsed);
- if (this.player_.gameOver && !this.gameOver_) {
- this.gameOver_ = true;
- document.getElementById('game-over').classList.toggle('active');
- }
- }
- }
- let _APP = null;
- window.addEventListener('DOMContentLoaded', () => {
- _APP = new BasicWorldDemo();
- });
|