diff --git a/.github/workflows/read-size.yml b/.github/workflows/read-size.yml index fdfaf26dbe4382..80e7d4b08ca165 100644 --- a/.github/workflows/read-size.yml +++ b/.github/workflows/read-size.yml @@ -38,10 +38,15 @@ jobs: id: read-size run: | # minify the builds to measure their size + npx terser build/three.core.js --module --compress --mangle --output build/three.core.min.js npx terser build/three.module.js --module --compress --mangle --output build/three.module.min.js npx terser build/three.webgpu.js --module --compress --mangle --output build/three.webgpu.min.js npx terser build/three.webgpu.nodes.js --module --compress --mangle --output build/three.webgpu.nodes.min.js + CORE_FILESIZE=$(stat --format=%s build/three.core.min.js) + gzip -k build/three.core.min.js + CORE_FILESIZE_GZIP=$(stat --format=%s build/three.core.min.js.gz) + WEBGL_FILESIZE=$(stat --format=%s build/three.module.min.js) gzip -k build/three.module.min.js WEBGL_FILESIZE_GZIP=$(stat --format=%s build/three.module.min.js.gz) @@ -66,7 +71,7 @@ jobs: PR=${{ github.event.pull_request.number }} # write the output in a json file to upload it as artifact - node -pe "JSON.stringify({ filesize: $WEBGL_FILESIZE, gzip: $WEBGL_FILESIZE_GZIP, treeshaken: $WEBGL_TREESHAKEN, treeshakenGzip: $WEBGL_TREESHAKEN_GZIP, filesize2: $WEBGPU_FILESIZE, gzip2: $WEBGPU_FILESIZE_GZIP, treeshaken2: $WEBGPU_TREESHAKEN, treeshakenGzip2: $WEBGPU_TREESHAKEN_GZIP, filesize3: $WEBGPU_NODES_FILESIZE, gzip3: $WEBGPU_NODES_FILESIZE_GZIP, treeshaken3: $WEBGPU_NODES_TREESHAKEN, treeshakenGzip3: $WEBGPU_NODES_TREESHAKEN_GZIP, pr: $PR })" > sizes.json + node -pe "JSON.stringify({ coreFilesize: $CORE_FILESIZE, coreGzip: $CORE_FILESIZE_GZIP, filesize: $WEBGL_FILESIZE, gzip: $WEBGL_FILESIZE_GZIP, treeshaken: $WEBGL_TREESHAKEN, treeshakenGzip: $WEBGL_TREESHAKEN_GZIP, filesize2: $WEBGPU_FILESIZE, gzip2: $WEBGPU_FILESIZE_GZIP, treeshaken2: $WEBGPU_TREESHAKEN, treeshakenGzip2: $WEBGPU_TREESHAKEN_GZIP, filesize3: $WEBGPU_NODES_FILESIZE, gzip3: $WEBGPU_NODES_FILESIZE_GZIP, treeshaken3: $WEBGPU_NODES_TREESHAKEN, treeshakenGzip3: $WEBGPU_NODES_TREESHAKEN_GZIP, pr: $PR })" > sizes.json - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: diff --git a/.github/workflows/report-size.yml b/.github/workflows/report-size.yml index 84aec2a467596d..56ad6d59cc1030 100644 --- a/.github/workflows/report-size.yml +++ b/.github/workflows/report-size.yml @@ -74,10 +74,15 @@ jobs: id: read-size run: | # minify the builds to measure their size + npx terser build/three.core.js --module --compress --mangle --output build/three.core.min.js npx terser build/three.module.js --module --compress --mangle --output build/three.module.min.js npx terser build/three.webgpu.js --module --compress --mangle --output build/three.webgpu.min.js npx terser build/three.webgpu.nodes.js --module --compress --mangle --output build/three.webgpu.nodes.min.js + CORE_FILESIZE_BASE=$(stat --format=%s build/three.core.min.js) + gzip -k build/three.core.min.js + CORE_FILESIZE_BASE_GZIP=$(stat --format=%s build/three.core.min.js.gz) + WEBGL_FILESIZE_BASE=$(stat --format=%s build/three.module.min.js) gzip -k build/three.module.min.js WEBGL_FILESIZE_BASE_GZIP=$(stat --format=%s build/three.module.min.js.gz) @@ -100,6 +105,12 @@ jobs: WEBGPU_NODES_TREESHAKEN_BASE_GZIP=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js.gz) # log to console + echo "CORE_FILESIZE_BASE=$CORE_FILESIZE_BASE" + echo "CORE_FILESIZE_BASE_GZIP=$CORE_FILESIZE_BASE_GZIP" + + echo "CORE_FILESIZE_BASE=$CORE_FILESIZE_BASE" >> $GITHUB_OUTPUT + echo "CORE_FILESIZE_BASE_GZIP=$CORE_FILESIZE_BASE_GZIP" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE_BASE=$WEBGL_FILESIZE_BASE" echo "WEBGL_FILESIZE_BASE_GZIP=$WEBGL_FILESIZE_BASE_GZIP" echo "WEBGL_TREESHAKEN_BASE=$WEBGL_TREESHAKEN_BASE" @@ -135,6 +146,10 @@ jobs: # It's important these are passed as env variables. # https://securitylab.github.com/research/github-actions-untrusted-input/ env: + CORE_FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).coreFilesize }} + CORE_FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).coreGzip }} + CORE_FILESIZE_BASE: ${{ steps.read-size.outputs.CORE_FILESIZE_BASE }} + CORE_FILESIZE_BASE_GZIP: ${{ steps.read-size.outputs.CORE_FILESIZE_BASE_GZIP }} WEBGL_FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize }} WEBGL_FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip }} WEBGL_FILESIZE_BASE: ${{ steps.read-size.outputs.WEBGL_FILESIZE_BASE }} @@ -160,6 +175,13 @@ jobs: WEBGPU_NODES_TREESHAKEN_BASE: ${{ steps.read-size.outputs.WEBGPU_NODES_TREESHAKEN_BASE }} WEBGPU_NODES_TREESHAKEN_BASE_GZIP: ${{ steps.read-size.outputs.WEBGPU_NODES_TREESHAKEN_BASE_GZIP }} run: | + CORE_FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$CORE_FILESIZE") + CORE_FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$CORE_FILESIZE_GZIP") + CORE_FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$CORE_FILESIZE_BASE") + CORE_FILESIZE_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$CORE_FILESIZE_BASE_GZIP") + CORE_FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$CORE_FILESIZE" "$CORE_FILESIZE_BASE") + CORE_FILESIZE_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$CORE_FILESIZE_GZIP" "$CORE_FILESIZE_BASE_GZIP") + WEBGL_FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_FILESIZE") WEBGL_FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_FILESIZE_GZIP") WEBGL_FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_FILESIZE_BASE") @@ -202,6 +224,13 @@ jobs: WEBGPU_NODES_TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_NODES_TREESHAKEN" "$WEBGPU_NODES_TREESHAKEN_BASE") WEBGPU_NODES_TREESHAKEN_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_NODES_TREESHAKEN_GZIP" "$WEBGPU_NODES_TREESHAKEN_BASE_GZIP") + echo "CORE_FILESIZE=$CORE_FILESIZE_FORM" >> $GITHUB_OUTPUT + echo "CORE_FILESIZE_GZIP=$CORE_FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "CORE_FILESIZE_BASE=$CORE_FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT + echo "CORE_FILESIZE_BASE_GZIP=$CORE_FILESIZE_BASE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "CORE_FILESIZE_DIFF=$CORE_FILESIZE_DIFF" >> $GITHUB_OUTPUT + echo "CORE_FILESIZE_DIFF_GZIP=$CORE_FILESIZE_DIFF_GZIP" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE=$WEBGL_FILESIZE_FORM" >> $GITHUB_OUTPUT echo "WEBGL_FILESIZE_GZIP=$WEBGL_FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT echo "WEBGL_FILESIZE_BASE=$WEBGL_FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT @@ -264,6 +293,7 @@ jobs: || Before | After | Diff | |:-:|:-:|:-:|:-:| + | Core | ${{ steps.format.outputs.CORE_FILESIZE_BASE }}
**${{ steps.format.outputs.CORE_FILESIZE_BASE_GZIP }}** | ${{ steps.format.outputs.CORE_FILESIZE }}
**${{ steps.format.outputs.CORE_FILESIZE_GZIP }}** | ${{ steps.format.outputs.CORE_FILESIZE_DIFF }}
**${{ steps.format.outputs.CORE_FILESIZE_DIFF_GZIP }}** | | WebGL | ${{ steps.format.outputs.WEBGL_FILESIZE_BASE }}
**${{ steps.format.outputs.WEBGL_FILESIZE_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGL_FILESIZE }}
**${{ steps.format.outputs.WEBGL_FILESIZE_GZIP }}** | ${{ steps.format.outputs.WEBGL_FILESIZE_DIFF }}
**${{ steps.format.outputs.WEBGL_FILESIZE_DIFF_GZIP }}** | | WebGPU | ${{ steps.format.outputs.WEBGPU_FILESIZE_BASE }}
**${{ steps.format.outputs.WEBGPU_FILESIZE_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_FILESIZE }}
**${{ steps.format.outputs.WEBGPU_FILESIZE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_FILESIZE_DIFF }}
**${{ steps.format.outputs.WEBGPU_FILESIZE_DIFF_GZIP }}** | | WebGPU Nodes | ${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_BASE }}
**${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_NODES_FILESIZE }}
**${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_DIFF }}
**${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_DIFF_GZIP }}** | diff --git a/src/lights/SunLight.js b/examples/jsm/lights/SunLight.js similarity index 93% rename from src/lights/SunLight.js rename to examples/jsm/lights/SunLight.js index 189a50d4ced37b..30ad955a158ee4 100644 --- a/src/lights/SunLight.js +++ b/examples/jsm/lights/SunLight.js @@ -1,6 +1,5 @@ -import { Light } from './Light.js'; +import { Light, Object3D } from 'three'; import { SunLightShadow } from './SunLightShadow.js'; -import { Object3D } from '../core/Object3D.js'; /** * A sun-like light that gets emitted in a specific direction, with rays that @@ -23,6 +22,7 @@ import { Object3D } from '../core/Object3D.js'; * use {@link DirectionalLight} with `CSMShadowNode` instead. * * @augments Light + * @three_import import { SunLight } from 'three/addons/lights/SunLight.js'; */ class SunLight extends Light { diff --git a/src/lights/SunLightShadow.js b/examples/jsm/lights/SunLightShadow.js similarity index 94% rename from src/lights/SunLightShadow.js rename to examples/jsm/lights/SunLightShadow.js index bd4f791ceed6f9..a09094ec065544 100644 --- a/src/lights/SunLightShadow.js +++ b/examples/jsm/lights/SunLightShadow.js @@ -1,9 +1,11 @@ -import { LightShadow } from './LightShadow.js'; -import { OrthographicCamera } from '../cameras/OrthographicCamera.js'; -import { Frustum } from '../math/Frustum.js'; -import { Matrix4 } from '../math/Matrix4.js'; -import { Vector3 } from '../math/Vector3.js'; -import { Vector4 } from '../math/Vector4.js'; +import { + Frustum, + LightShadow, + Matrix4, + OrthographicCamera, + Vector3, + Vector4 +} from 'three'; const _lightOrientationMatrix = /*@__PURE__*/ new Matrix4(); const _viewToLightMatrix = /*@__PURE__*/ new Matrix4(); @@ -38,14 +40,14 @@ const _cascadeCorners = [ // must match the cascade count in the sun shadow shader chunks -const _cascadeCount = 4; +const _cascadeCount = 2; // fraction of each cascade's depth range that blends into the next cascade const _cascadeFade = 0.1; /** - * Represents the shadow configuration of {@link SunLight}, using four + * Represents the shadow configuration of {@link SunLight}, using two * cascaded shadow maps (CSM). * * The shadow camera projection is fitted automatically to slices of the view @@ -56,6 +58,7 @@ const _cascadeFade = 0.1; * The default `mapSize` is `1024x1024` per cascade. * * @augments LightShadow + * @three_import import { SunLightShadow } from 'three/addons/lights/SunLightShadow.js'; */ class SunLightShadow extends LightShadow { @@ -87,7 +90,7 @@ class SunLightShadow extends LightShadow { this._cascadeData = []; this._viewportCount = _cascadeCount; - this._frameExtents.set( 2, 2 ); + this._frameExtents.set( 2, 1 ); for ( let i = 0; i < _cascadeCount; i ++ ) { @@ -157,7 +160,7 @@ class SunLightShadow extends LightShadow { for ( let i = 0; i < _cascadeCount; i ++ ) { - this._viewports[ i ].set( i % 2 + insetX, Math.floor( i / 2 ) + insetY, 1 - 2 * insetX, 1 - 2 * insetY ); + this._viewports[ i ].set( i + insetX, insetY, 1 - 2 * insetX, 1 - 2 * insetY ); } diff --git a/examples/jsm/loaders/FBXLoader.js b/examples/jsm/loaders/FBXLoader.js index 411d99b8cbd632..23adc69e9faf63 100644 --- a/examples/jsm/loaders/FBXLoader.js +++ b/examples/jsm/loaders/FBXLoader.js @@ -378,7 +378,7 @@ class FBXTreeParser { } else { // Binary Format const array = new Uint8Array( content ); - return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) ); + return URL.createObjectURL( new Blob( [ array ], { type: type } ) ); } diff --git a/examples/screenshots/webgl_lightprobes_sponza.jpg b/examples/screenshots/webgl_lightprobes_sponza.jpg index eb5006834c14c6..b92ef93804a4f3 100644 Binary files a/examples/screenshots/webgl_lightprobes_sponza.jpg and b/examples/screenshots/webgl_lightprobes_sponza.jpg differ diff --git a/examples/webgl_animation_multiple.html b/examples/webgl_animation_multiple.html index 87233925bee5b7..f970e632df42b9 100644 --- a/examples/webgl_animation_multiple.html +++ b/examples/webgl_animation_multiple.html @@ -29,6 +29,7 @@ import * as THREE from 'three'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; @@ -61,7 +62,7 @@ hemiLight.position.set( 0, 20, 0 ); scene.add( hemiLight ); - const sunLight = new THREE.SunLight( 0xffffff, 3 ); + const sunLight = new SunLight( 0xffffff, 3 ); sunLight.position.set( - 3, 10, - 10 ); sunLight.castShadow = true; sunLight.shadow.camera.far = 20; diff --git a/examples/webgl_animation_skinning_additive_blending.html b/examples/webgl_animation_skinning_additive_blending.html index 36f3725be13f22..3ec59e04ef25dd 100644 --- a/examples/webgl_animation_skinning_additive_blending.html +++ b/examples/webgl_animation_skinning_additive_blending.html @@ -40,6 +40,7 @@ import Stats from 'three/addons/libs/stats.module.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; @@ -79,7 +80,7 @@ hemiLight.position.set( 0, 20, 0 ); scene.add( hemiLight ); - const sunLight = new THREE.SunLight( 0xffffff, 3 ); + const sunLight = new SunLight( 0xffffff, 3 ); sunLight.position.set( 3, 10, 10 ); sunLight.castShadow = true; sunLight.shadow.camera.far = 20; diff --git a/examples/webgl_animation_skinning_blending.html b/examples/webgl_animation_skinning_blending.html index db4df6bec73913..02a201e7691644 100644 --- a/examples/webgl_animation_skinning_blending.html +++ b/examples/webgl_animation_skinning_blending.html @@ -39,6 +39,7 @@ import Stats from 'three/addons/libs/stats.module.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; let scene, renderer, camera, stats; @@ -74,7 +75,7 @@ hemiLight.position.set( 0, 20, 0 ); scene.add( hemiLight ); - const sunLight = new THREE.SunLight( 0xffffff, 3 ); + const sunLight = new SunLight( 0xffffff, 3 ); sunLight.position.set( - 3, 10, - 10 ); sunLight.castShadow = true; sunLight.shadow.camera.far = 20; diff --git a/examples/webgl_animation_walk.html b/examples/webgl_animation_walk.html index a3e1c271b6232f..0787f0cbf104e1 100644 --- a/examples/webgl_animation_walk.html +++ b/examples/webgl_animation_walk.html @@ -36,6 +36,7 @@ import * as THREE from 'three'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; @@ -91,7 +92,7 @@ group = new THREE.Group(); scene.add( group ); - const sunLight = new THREE.SunLight( 0xffffff, 5 ); + const sunLight = new SunLight( 0xffffff, 5 ); sunLight.position.set( - 2, 5, - 3 ); sunLight.castShadow = true; sunLight.shadow.camera.far = 20; diff --git a/examples/webgl_gpgpu_water.html b/examples/webgl_gpgpu_water.html index 71aa9f85d72d8a..ed20ac11d6d14b 100644 --- a/examples/webgl_gpgpu_water.html +++ b/examples/webgl_gpgpu_water.html @@ -145,6 +145,7 @@ import Stats from 'three/addons/libs/stats.module.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js'; @@ -216,7 +217,7 @@ scene = new THREE.Scene(); - sun = new THREE.SunLight( 0xFFFFFF, 4.0 ); + sun = new SunLight( 0xFFFFFF, 4.0 ); sun.position.set( - 1, 2.6, 1.4 ); scene.add( sun ); diff --git a/examples/webgl_instancing_morph.html b/examples/webgl_instancing_morph.html index ba3532303faa6a..237be093fc6dba 100644 --- a/examples/webgl_instancing_morph.html +++ b/examples/webgl_instancing_morph.html @@ -24,6 +24,7 @@ import * as THREE from 'three'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import Stats from 'three/addons/libs/stats.module.js'; @@ -55,7 +56,7 @@ scene.fog = new THREE.Fog( 0x99DDFF, 5000, 10000 ); - const light = new THREE.SunLight( 0xffffff, 2 ); + const light = new SunLight( 0xffffff, 2 ); light.position.set( 200, 1000, 50 ); diff --git a/examples/webgl_lightprobes_sponza.html b/examples/webgl_lightprobes_sponza.html index 922de361952266..ad2d205e78b0fd 100644 --- a/examples/webgl_lightprobes_sponza.html +++ b/examples/webgl_lightprobes_sponza.html @@ -32,6 +32,7 @@ import * as THREE from 'three'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; @@ -137,7 +138,7 @@ let isBaking = false; let bakeQueued = false; - sunLight = new THREE.SunLight( 0xfff2dc, 100.0 ); + sunLight = new SunLight( 0xfff2dc, 100.0 ); sunLight.castShadow = true; sunLight.shadow.camera.far = 50; sunLight.shadow.mapSize.setScalar( 2048 ); diff --git a/examples/webgl_lights_sunlight.html b/examples/webgl_lights_sunlight.html index 0c855796ae566c..5be6907e4a7e3a 100644 --- a/examples/webgl_lights_sunlight.html +++ b/examples/webgl_lights_sunlight.html @@ -31,6 +31,7 @@ import * as THREE from 'three'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js'; import { Sky } from 'three/addons/objects/Sky.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; @@ -58,7 +59,7 @@ #if defined( USE_SHADOWMAP ) && NUM_SUN_LIGHT_SHADOWS > 0 float cascade = 0.0; - for ( int i = 0; i < 4; i ++ ) cascade += step( sunShadowCascade[ i ].y, vSunShadowWorldPosition.w ); + for ( int i = 0; i < 2; i ++ ) cascade += step( sunShadowCascade[ i ].y, vSunShadowWorldPosition.w ); gl_FragColor.rgb *= 0.7 + 0.3 * cos( cascade * 1.2 + vec3( 0.0, 4.2, 2.1 ) ); #endif @@ -132,7 +133,7 @@ sky.material.uniforms.rayleigh.value = 2; sky.material.uniforms.showSunDisc.value = false; // the sun is represented by the light - sunLight = new THREE.SunLight(); + sunLight = new SunLight(); sunLight.castShadow = true; sunLight.shadow.camera.far = params.far; // maximum shadow distance sunLight.shadow.mapSize.setScalar( params.resolution ); diff --git a/examples/webgl_loader_3mf_materials.html b/examples/webgl_loader_3mf_materials.html index a0dac5fc4d70ea..fd9fd6d7bfa8ee 100644 --- a/examples/webgl_loader_3mf_materials.html +++ b/examples/webgl_loader_3mf_materials.html @@ -34,6 +34,7 @@ import * as THREE from 'three'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js'; @@ -57,7 +58,7 @@ hemiLight.position.set( 0, 100, 0 ); scene.add( hemiLight ); - const sunLight = new THREE.SunLight( 0xffffff, 3 ); + const sunLight = new SunLight( 0xffffff, 3 ); sunLight.position.set( 0, 40, 50 ); sunLight.castShadow = true; sunLight.shadow.camera.far = 100; diff --git a/examples/webgl_loader_fbx.html b/examples/webgl_loader_fbx.html index f1e613f92241a5..2641eb196ece72 100644 --- a/examples/webgl_loader_fbx.html +++ b/examples/webgl_loader_fbx.html @@ -32,6 +32,7 @@ import Stats from 'three/addons/libs/stats.module.js'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { FBXLoader } from 'three/addons/loaders/FBXLoader.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; @@ -90,7 +91,7 @@ hemiLight.position.set( 0, 200, 0 ); scene.add( hemiLight ); - const sunLight = new THREE.SunLight( 0xffffff, 5 ); + const sunLight = new SunLight( 0xffffff, 5 ); sunLight.position.set( 0, 200, 100 ); sunLight.castShadow = true; scene.add( sunLight ); diff --git a/examples/webgl_loader_md2_control.html b/examples/webgl_loader_md2_control.html index edff9fec2cbb0b..8de18e5f5899bc 100644 --- a/examples/webgl_loader_md2_control.html +++ b/examples/webgl_loader_md2_control.html @@ -41,6 +41,7 @@ import Stats from 'three/addons/libs/stats.module.js'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { MD2CharacterComplex } from 'three/addons/misc/MD2CharacterComplex.js'; import { Gyroscope } from 'three/addons/misc/Gyroscope.js'; @@ -92,7 +93,7 @@ scene.add( new THREE.AmbientLight( 0x666666, 3 ) ); - const sunLight = new THREE.SunLight( 0xffffff, 7 ); + const sunLight = new SunLight( 0xffffff, 7 ); sunLight.position.set( 200, 450, 500 ); sunLight.castShadow = true; sunLight.shadow.camera.far = 2000; diff --git a/examples/webgl_postprocessing_outline.html b/examples/webgl_postprocessing_outline.html index 54b9973b6634bb..e192e5885eea07 100644 --- a/examples/webgl_postprocessing_outline.html +++ b/examples/webgl_postprocessing_outline.html @@ -31,6 +31,7 @@ import Stats from 'three/addons/libs/stats.module.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { OBJLoader } from 'three/addons/loaders/OBJLoader.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; @@ -151,7 +152,7 @@ scene.add( new THREE.AmbientLight( 0xaaaaaa, 0.6 ) ); - const light = new THREE.SunLight( 0xddffdd, 2 ); + const light = new SunLight( 0xddffdd, 2 ); light.position.set( 5, 5, 5 ); light.castShadow = true; light.shadow.camera.far = 40; diff --git a/examples/webgl_shadowmap_performance.html b/examples/webgl_shadowmap_performance.html index 6b96e126d2969a..3763ea2762b050 100644 --- a/examples/webgl_shadowmap_performance.html +++ b/examples/webgl_shadowmap_performance.html @@ -33,6 +33,7 @@ import Stats from 'three/addons/libs/stats.module.js'; + import { SunLight } from 'three/addons/lights/SunLight.js'; import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { FontLoader } from 'three/addons/loaders/FontLoader.js'; @@ -79,7 +80,7 @@ const ambient = new THREE.AmbientLight( 0xffffff ); scene.add( ambient ); - const sunLight = new THREE.SunLight( 0xffffff, 3 ); + const sunLight = new SunLight( 0xffffff, 3 ); sunLight.position.set( 0, 1500, 1000 ); sunLight.castShadow = true; sunLight.shadow.camera.far = FAR; diff --git a/src/Three.Core.js b/src/Three.Core.js index 211c3d49581798..458a9a84c763ab 100644 --- a/src/Three.Core.js +++ b/src/Three.Core.js @@ -60,10 +60,10 @@ export { SpotLight } from './lights/SpotLight.js'; export { PointLight } from './lights/PointLight.js'; export { RectAreaLight } from './lights/RectAreaLight.js'; export { HemisphereLight } from './lights/HemisphereLight.js'; -export { SunLight } from './lights/SunLight.js'; export { DirectionalLight } from './lights/DirectionalLight.js'; export { AmbientLight } from './lights/AmbientLight.js'; export { Light } from './lights/Light.js'; +export { LightShadow } from './lights/LightShadow.js'; export { LightProbe } from './lights/LightProbe.js'; export { StereoCamera } from './cameras/StereoCamera.js'; export { PerspectiveCamera } from './cameras/PerspectiveCamera.js'; diff --git a/src/loaders/ObjectLoader.js b/src/loaders/ObjectLoader.js index 56a9acf5cea24f..9be1220b963620 100644 --- a/src/loaders/ObjectLoader.js +++ b/src/loaders/ObjectLoader.js @@ -18,7 +18,6 @@ import { LinearMipmapLinearFilter } from '../constants.js'; import { InstancedBufferAttribute } from '../core/InstancedBufferAttribute.js'; -import { SunLight } from '../lights/SunLight.js'; import { Color } from '../math/Color.js'; import { Vector3 } from '../math/Vector3.js'; import { Object3D } from '../core/Object3D.js'; @@ -922,12 +921,6 @@ class ObjectLoader extends Loader { break; - case 'SunLight': - - object = new SunLight( data.color, data.intensity ); - - break; - case 'DirectionalLight': object = new DirectionalLight( data.color, data.intensity ); diff --git a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js index 748b3beaa7b001..a32bc3a44d948e 100644 --- a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js @@ -25,8 +25,8 @@ export default /* glsl */` #endif - uniform mat4 sunShadowMatrix[ NUM_SUN_LIGHT_SHADOWS * 4 ]; - uniform vec4 sunShadowCascade[ NUM_SUN_LIGHT_SHADOWS * 4 ]; + uniform mat4 sunShadowMatrix[ NUM_SUN_LIGHT_SHADOWS * 2 ]; + uniform vec4 sunShadowCascade[ NUM_SUN_LIGHT_SHADOWS * 2 ]; varying vec4 vSunShadowWorldPosition; varying vec3 vSunShadowWorldNormal; @@ -301,13 +301,13 @@ export default /* glsl */` vec4 shadowWorldPosition = vec4( vSunShadowWorldPosition.xyz + vSunShadowWorldNormal * sunLightShadow.shadowNormalBias, 1.0 ); float viewDepth = vSunShadowWorldPosition.w; - int cascadeOffset = shadowIndex * 4; + int cascadeOffset = shadowIndex * 2; float shadow = 1.0; // walk the cascades back to front so each fade band can blend with the shadow behind it - for ( int i = 3; i >= 0; i -- ) { + for ( int i = 1; i >= 0; i -- ) { // ( begin, end, fade start ) view depths of the cascade diff --git a/src/renderers/webgl/WebGLLights.js b/src/renderers/webgl/WebGLLights.js index 5c0855221f1974..2b5887745a48e8 100644 --- a/src/renderers/webgl/WebGLLights.js +++ b/src/renderers/webgl/WebGLLights.js @@ -306,12 +306,12 @@ function WebGLLights( extensions ) { state.sunShadow[ numSunShadows ] = shadowUniforms; state.sunShadowMap[ numSunShadows ] = shadowMap; - // four cascades per sun light, matching the sun shadow shader chunks + // two cascades per sun light, matching the sun shadow shader chunks - for ( let j = 0; j < 4; j ++ ) { + for ( let j = 0; j < 2; j ++ ) { - state.sunShadowMatrix[ numSunShadows * 4 + j ] = shadow.getMatrix( j ); - state.sunShadowCascade[ numSunShadows * 4 + j ] = shadow._cascadeData[ j ]; + state.sunShadowMatrix[ numSunShadows * 2 + j ] = shadow.getMatrix( j ); + state.sunShadowCascade[ numSunShadows * 2 + j ] = shadow._cascadeData[ j ]; } @@ -510,8 +510,8 @@ function WebGLLights( extensions ) { state.sunShadow.length = numSunShadows; state.sunShadowMap.length = numSunShadows; - state.sunShadowMatrix.length = numSunShadows * 4; - state.sunShadowCascade.length = numSunShadows * 4; + state.sunShadowMatrix.length = numSunShadows * 2; + state.sunShadowCascade.length = numSunShadows * 2; state.directionalShadow.length = numDirectionalShadows; state.directionalShadowMap.length = numDirectionalShadows; state.directionalShadowMatrix.length = numDirectionalShadows; diff --git a/test/unit/src/lights/SunLight.tests.js b/test/unit/src/lights/SunLight.tests.js deleted file mode 100644 index 4aa1c0eb023dad..00000000000000 --- a/test/unit/src/lights/SunLight.tests.js +++ /dev/null @@ -1,80 +0,0 @@ -import { SunLight } from '../../../../src/lights/SunLight.js'; - -import { Light } from '../../../../src/lights/Light.js'; -import { ObjectLoader } from '../../../../src/loaders/ObjectLoader.js'; -import { Vector3 } from '../../../../src/math/Vector3.js'; - -export default QUnit.module( 'Lights', () => { - - QUnit.module( 'SunLight', () => { - - // INHERITANCE - QUnit.test( 'Extending', ( assert ) => { - - const object = new SunLight(); - assert.strictEqual( - object instanceof Light, true, - 'SunLight extends from Light' - ); - - } ); - - // INSTANCING - QUnit.test( 'Instancing', ( assert ) => { - - const object = new SunLight(); - assert.ok( object, 'Can instantiate a SunLight.' ); - - } ); - - // PROPERTIES - QUnit.test( 'type', ( assert ) => { - - const object = new SunLight(); - assert.ok( object.type === 'SunLight', 'SunLight.type should be SunLight' ); - - } ); - - QUnit.test( 'shadow', ( assert ) => { - - const object = new SunLight(); - assert.ok( object.shadow.isSunLightShadow, 'SunLight.shadow is a SunLightShadow' ); - - } ); - - // PUBLIC - QUnit.test( 'isSunLight', ( assert ) => { - - const object = new SunLight(); - assert.ok( object.isSunLight, 'SunLight.isSunLight should be true' ); - - } ); - - QUnit.test( 'position', ( assert ) => { - - const object = new SunLight(); - assert.ok( object.position.distanceTo( new Vector3( 0, 1, 0 ) ) < 1e-12, 'Shines straight down by default' ); - - } ); - - // OTHERS - QUnit.test( 'toJSON', ( assert ) => { - - const light = new SunLight( 0xffaa88, 2 ); - light.shadow.bias = 10; - light.position.setFromSphericalCoords( 1, 0.5, 1.2 ); - light.updateMatrix(); - - const json = light.toJSON(); - const newLight = new ObjectLoader().parse( json ); - - assert.ok( newLight.isSunLight, 'Reloaded light is a SunLight' ); - assert.ok( newLight.shadow.isSunLightShadow, 'Reloaded shadow is a SunLightShadow' ); - assert.ok( newLight.position.distanceTo( light.position ) < 1e-6, 'Reloaded light keeps its direction' ); - assert.smartEqual( newLight.shadow, light.shadow, 'Reloaded shadow is identical to the original one' ); - - } ); - - } ); - -} ); diff --git a/test/unit/src/lights/SunLightShadow.tests.js b/test/unit/src/lights/SunLightShadow.tests.js deleted file mode 100644 index 44cc1b616d6325..00000000000000 --- a/test/unit/src/lights/SunLightShadow.tests.js +++ /dev/null @@ -1,271 +0,0 @@ -import { SunLightShadow } from '../../../../src/lights/SunLightShadow.js'; - -import { LightShadow } from '../../../../src/lights/LightShadow.js'; -import { ObjectLoader } from '../../../../src/loaders/ObjectLoader.js'; -import { SunLight } from '../../../../src/lights/SunLight.js'; -import { PerspectiveCamera } from '../../../../src/cameras/PerspectiveCamera.js'; -import { OrthographicCamera } from '../../../../src/cameras/OrthographicCamera.js'; -import { Vector3 } from '../../../../src/math/Vector3.js'; - -export default QUnit.module( 'Lights', () => { - - QUnit.module( 'SunLightShadow', () => { - - // INHERITANCE - QUnit.test( 'Extending', ( assert ) => { - - const object = new SunLightShadow(); - assert.strictEqual( - object instanceof LightShadow, true, - 'SunLightShadow extends from LightShadow' - ); - - } ); - - // INSTANCING - QUnit.test( 'Instancing', ( assert ) => { - - const object = new SunLightShadow(); - assert.ok( object, 'Can instantiate a SunLightShadow.' ); - - } ); - - // PUBLIC - QUnit.test( 'isSunLightShadow', ( assert ) => { - - const object = new SunLightShadow(); - assert.ok( - object.isSunLightShadow, - 'SunLightShadow.isSunLightShadow should be true' - ); - - } ); - - QUnit.test( 'cascades', ( assert ) => { - - const light = new SunLight(); - const camera = new PerspectiveCamera( 60, 1, 0.1, 100 ); - const shadow = light.shadow; - light.position.setFromSphericalCoords( 1, Math.PI / 2 - 0.9, 0.7 ); - light.updateMatrixWorld(); - camera.updateMatrixWorld(); - - shadow.camera.far = 80; - shadow.updateMatrices( light, camera ); - - assert.strictEqual( shadow.getViewportCount(), 4, 'Creates four cascade viewports' ); - assert.deepEqual( shadow.getFrameExtents().toArray(), [ 2, 2 ], 'Uses a compact atlas layout' ); - assert.strictEqual( shadow._cascadeSplits.length, 5, 'Creates all cascade split boundaries' ); - assert.strictEqual( shadow._cascadeSplits[ 0 ], camera.near, 'Starts at the view camera near plane' ); - assert.strictEqual( shadow._cascadeSplits[ 4 ], shadow.camera.far, 'Ends at the shadow camera far plane' ); - - const orthographicCamera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 100 ); - orthographicCamera.updateMatrixWorld(); - shadow.updateMatrices( light, orthographicCamera ); - assert.ok( shadow._cascadeSplits.every( Number.isFinite ), 'Supports an orthographic view camera with a zero near plane' ); - - const infiniteCamera = new PerspectiveCamera( 60, 1, 0.1, 100 ); - infiniteCamera.projectionMatrix.elements[ 10 ] = - 1; - infiniteCamera.projectionMatrix.elements[ 14 ] = - 2 * infiniteCamera.near; - infiniteCamera.projectionMatrixInverse.copy( infiniteCamera.projectionMatrix ).invert(); - infiniteCamera.far = Infinity; - infiniteCamera.updateMatrixWorld(); - shadow.camera.far = 80; - shadow.updateMatrices( light, infiniteCamera ); - assert.ok( shadow._matrices.every( matrix => matrix.elements.every( Number.isFinite ) ), 'Supports an infinite-far projection matrix' ); - assert.strictEqual( shadow._cascadeSplits[ 4 ], shadow.camera.far, 'Falls back to the shadow camera for a non-finite view far plane' ); - - } ); - - QUnit.test( 'cascade fade', ( assert ) => { - - const light = new SunLight(); - const camera = new PerspectiveCamera( 60, 1, 0.1, 100 ); - const shadow = light.shadow; - light.position.setFromSphericalCoords( 1, Math.PI / 2 - 0.9, 0.7 ); - light.updateMatrixWorld(); - camera.updateMatrixWorld(); - - shadow.camera.far = 80; - shadow.updateMatrices( light, camera ); - - for ( let i = 0; i < 4; i ++ ) { - - const cascade = shadow._cascadeData[ i ]; - - assert.strictEqual( cascade.y, shadow._cascadeSplits[ i + 1 ], `Cascade ${i} ends at its split` ); - assert.ok( cascade.z < cascade.y && cascade.z >= shadow._cascadeSplits[ i ], `Cascade ${i} fades out within its own range` ); - - if ( i === 0 ) assert.ok( cascade.x < 0, 'First cascade is open below the near plane' ); - else assert.strictEqual( cascade.x, shadow._cascadeData[ i - 1 ].z, `Cascade ${i} begins where the previous fade starts` ); - - } - - } ); - - QUnit.test( 'cascade caster margin', ( assert ) => { - - const light = new SunLight(); - const camera = new PerspectiveCamera( 60, 1, 0.1, 100 ); - const shadow = light.shadow; - light.position.setFromSphericalCoords( 1, Math.PI / 2 - 0.9, 0.7 ); - light.updateMatrixWorld(); - camera.updateMatrixWorld(); - shadow.camera.far = 100; - shadow.updateMatrices( light, camera ); - - const lightDirection = light.position.clone().normalize().negate(); - - for ( let i = 0; i < 4; i ++ ) { - - const depth = ( shadow._cascadeSplits[ i ] + shadow._cascadeSplits[ i + 1 ] ) * 0.5; - const caster = new Vector3( 0, 0, - depth ).addScaledVector( lightDirection, - 90 ); - assert.ok( shadow.getFrustum( i ).containsPoint( caster ), `Cascade ${i} covers casters above the view frustum` ); - - } - - } ); - - QUnit.test( 'cascade containment', ( assert ) => { - - const light = new SunLight(); - const camera = new PerspectiveCamera( 60, 1, 0.01, 0.1 ); - const shadow = light.shadow; - light.position.setFromSphericalCoords( 1, Math.PI / 2 - 0.9, 0.7 ); - light.updateMatrixWorld(); - camera.updateMatrixWorld(); - shadow.camera.far = 1; - shadow.updateMatrices( light, camera ); - - for ( let i = 0; i < 4; i ++ ) { - - let containsSlice = true; - - for ( const depth of shadow._cascadeSplits.slice( i, i + 2 ) ) { - - const halfHeight = Math.tan( camera.fov * Math.PI / 360 ) * depth; - - for ( const x of [ - 1, 1 ] ) { - - for ( const y of [ - 1, 1 ] ) { - - containsSlice = containsSlice && shadow.getFrustum( i ).containsPoint( new Vector3( x * halfHeight, y * halfHeight, - depth ) ); - - } - - } - - } - - assert.ok( containsSlice, `Cascade ${i} contains its view slice` ); - - } - - } ); - - QUnit.test( 'cascade stabilization containment', ( assert ) => { - - const light = new SunLight(); - const camera = new PerspectiveCamera( 60, 1, 1, 10 ); - const shadow = light.shadow; - camera.updateMatrixWorld(); - light.position.setFromSphericalCoords( 1, Math.PI / 2 - 0.6, Math.PI / 4 ); - light.updateMatrixWorld(); - - shadow.mapSize.set( 16, 16 ); - shadow.updateMatrices( light, camera ); - - for ( const depth of shadow._cascadeSplits.slice( 0, 2 ) ) { - - const halfHeight = Math.tan( camera.fov * Math.PI / 360 ) * depth; - - for ( const x of [ - 1, 1 ] ) { - - for ( const y of [ - 1, 1 ] ) { - - const corner = new Vector3( x * halfHeight * camera.aspect, y * halfHeight, - depth ).applyMatrix4( camera.matrixWorld ); - assert.ok( shadow.getFrustum( 0 ).containsPoint( corner ), 'Texel stabilization keeps the slice corner inside its cascade' ); - - } - - } - - } - - } ); - - QUnit.test( 'reversed depth cascade containment', ( assert ) => { - - const light = new SunLight(); - const camera = new PerspectiveCamera( 60, 1, 0.1, 100 ); - const shadow = light.shadow; - light.position.setFromSphericalCoords( 1, Math.PI / 2 - 0.9, 0.7 ); - light.updateMatrixWorld(); - camera.updateMatrixWorld(); - - for ( const [ viewReversedDepth, shadowReversedDepth ] of [[ false, false ], [ false, true ], [ true, true ]] ) { - - camera._reversedDepth = viewReversedDepth; - camera.updateProjectionMatrix(); - shadow.camera._reversedDepth = shadowReversedDepth; - shadow.updateMatrices( light, camera ); - - for ( let i = 0; i < 4; i ++ ) { - - const cascadeCamera = shadow.getCamera( i ); - const depth = ( shadow._cascadeSplits[ i ] + shadow._cascadeSplits[ i + 1 ] ) * 0.5; - assert.strictEqual( cascadeCamera.reversedDepth, shadowReversedDepth, `Cascade ${i} uses the shadow map depth convention` ); - assert.ok( shadow.getMatrix( i ).elements.every( Number.isFinite ), `Cascade ${i} has a finite shadow matrix` ); - assert.ok( shadow.getFrustum( i ).containsPoint( new Vector3( 0, 0, - depth ) ), `Cascade ${i} contains its view slice` ); - - } - - } - - } ); - - // OTHERS - QUnit.test( 'clone/copy', ( assert ) => { - - const a = new SunLightShadow(); - const b = new SunLightShadow(); - - assert.notDeepEqual( a, b, 'Newly instanced shadows are not equal' ); - - const c = a.clone(); - assert.smartEqual( a, c, 'Shadows are identical after clone()' ); - - c.mapSize.set( 1024, 1024 ); - assert.notDeepEqual( a, c, 'Shadows are different again after change' ); - - b.copy( a ); - assert.smartEqual( a, b, 'Shadows are identical after copy()' ); - - b.mapSize.set( 512, 512 ); - assert.notDeepEqual( a, b, 'Shadows are different again after change' ); - - } ); - - QUnit.test( 'toJSON', ( assert ) => { - - const light = new SunLight(); - const shadow = light.shadow; - - shadow.bias = 10; - shadow.radius = 5; - shadow.mapSize.set( 1024, 1024 ); - - const json = light.toJSON(); - const newLight = new ObjectLoader().parse( json ); - - assert.ok( newLight.shadow.isSunLightShadow, 'Reloaded shadow is a SunLightShadow' ); - assert.smartEqual( - newLight.shadow, light.shadow, - 'Reloaded shadow is identical to the original one' - ); - - } ); - - } ); - -} ); diff --git a/test/unit/three.source.unit.js b/test/unit/three.source.unit.js index e4bb31ec642a1f..d545b5d81c438c 100644 --- a/test/unit/three.source.unit.js +++ b/test/unit/three.source.unit.js @@ -131,8 +131,6 @@ import './src/helpers/SpotLightHelper.tests.js'; import './src/lights/AmbientLight.tests.js'; import './src/lights/DirectionalLight.tests.js'; import './src/lights/DirectionalLightShadow.tests.js'; -import './src/lights/SunLight.tests.js'; -import './src/lights/SunLightShadow.tests.js'; import './src/lights/HemisphereLight.tests.js'; import './src/lights/Light.tests.js'; import './src/lights/LightProbe.tests.js';