rayzee 8.3.0 → 8.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -3
- package/dist/DLSSNeural-BNSJ8V3z.js +202 -0
- package/dist/DLSSSuperRes-BoEW89Cl.js +266 -0
- package/dist/ToneMapGPU-DzqSyzTx.js +341 -0
- package/dist/rayzee.es.js +4603 -4606
- package/dist/rayzee.umd.js +264 -68
- package/dist/rolldown-runtime-CAFD8bLK.js +11 -0
- package/package.json +1 -1
- package/src/AssetConfig.js +7 -0
- package/src/EngineDefaults.js +10 -6
- package/src/EngineIssues.js +1 -0
- package/src/LightUnits.js +122 -0
- package/src/PathTracerApp.js +10 -6
- package/src/Processor/AssetLoader.js +61 -43
- package/src/Processor/LightSerializer.js +17 -9
- package/src/Processor/ToneMapGPU.js +317 -0
- package/src/Stages/PathTracer.js +23 -3
- package/src/TSL/LightsCore.js +8 -3
- package/src/TSL/LightsSampling.js +1 -4
- package/src/dlss/DLSSNeural.js +530 -0
- package/src/dlss/DLSSSuperRes.js +584 -0
- package/src/index.js +7 -0
- package/src/managers/DenoisingManager.js +288 -7
- package/src/managers/LightManager.js +15 -17
|
@@ -2,6 +2,11 @@ import { EventDispatcher } from 'three';
|
|
|
2
2
|
import { OIDNDenoiser } from '../Passes/OIDNDenoiser.js';
|
|
3
3
|
import { AIUpscaler } from '../Passes/AIUpscaler.js';
|
|
4
4
|
import { EngineEvents } from '../EngineEvents.js';
|
|
5
|
+
import { createLogger } from '../utils/Logger.js';
|
|
6
|
+
|
|
7
|
+
// The neural passes live in `../dlss/` but report through the manager that drives them, so they
|
|
8
|
+
// share one namespace: `rayzee.log.only( 'dlss' )` shows the whole chain.
|
|
9
|
+
const dlssLog = createLogger( 'dlss' );
|
|
5
10
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS } from '../EngineDefaults.js';
|
|
6
11
|
|
|
7
12
|
// A refresh slower than this is a slideshow, not a live view, so the cadence swaps to a cheaper
|
|
@@ -107,6 +112,17 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
107
112
|
|
|
108
113
|
this.denoiser = null;
|
|
109
114
|
this.upscaler = null;
|
|
115
|
+
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'dlss' is the neural
|
|
116
|
+
// super-resolution pass, which is fixed at 2x and needs a denoised source.
|
|
117
|
+
this.upscalerBackend = 'esrgan';
|
|
118
|
+
this._dlssUpscaler = null;
|
|
119
|
+
// The neural-rendering (detail) pass is independent of the upscaler: it changes appearance,
|
|
120
|
+
// not resolution, and runs last because its result cannot be read back.
|
|
121
|
+
this.neuralRendering = false;
|
|
122
|
+
this.neuralRenderingSettings = {};
|
|
123
|
+
this._dlssNeural = null;
|
|
124
|
+
this._dlssModule = null;
|
|
125
|
+
this._neuralPostBusy = false;
|
|
110
126
|
|
|
111
127
|
// The tier the finished image uses. The loaded tier is not always this one: while the
|
|
112
128
|
// image is still accumulating we run a cheaper model (see previewQuality).
|
|
@@ -133,6 +149,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
133
149
|
this._holdWhileMoving = null;
|
|
134
150
|
this._onReset = null;
|
|
135
151
|
this._onPostProcessRefresh = null;
|
|
152
|
+
this._onDisplayRefresh = null;
|
|
136
153
|
|
|
137
154
|
// Resolution tracking — used for canvas restoration on reset
|
|
138
155
|
this._lastRenderWidth = 0;
|
|
@@ -786,6 +803,242 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
786
803
|
|
|
787
804
|
}
|
|
788
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Selects the AI upscaler's model. 'dlss' is fixed at 2x and reads the denoised picture, so it
|
|
808
|
+
* is a no-op without a denoiser — feeding it raw Monte-Carlo noise measurably loses to bilinear.
|
|
809
|
+
*
|
|
810
|
+
* @param {'esrgan'|'dlss'} backend
|
|
811
|
+
*/
|
|
812
|
+
setUpscalerBackend( backend ) {
|
|
813
|
+
|
|
814
|
+
const next = backend === 'dlss' ? 'dlss' : 'esrgan';
|
|
815
|
+
if ( next === this.upscalerBackend ) return;
|
|
816
|
+
|
|
817
|
+
this.upscalerBackend = next;
|
|
818
|
+
// Whatever is on screen came from the model being switched away from.
|
|
819
|
+
this._restoreRenderDisplay();
|
|
820
|
+
if ( next !== 'dlss' ) this._releaseDLSSUpscaler();
|
|
821
|
+
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Puts the render canvas back in front and tells the host the output is the render size again.
|
|
826
|
+
*
|
|
827
|
+
* Guarded on the overlay actually being visible: `abort()` runs on every reset, which is every
|
|
828
|
+
* frame of a camera drag, and an unguarded event there would be a per-frame storm.
|
|
829
|
+
*
|
|
830
|
+
* @returns {boolean} whether anything was actually restored
|
|
831
|
+
*/
|
|
832
|
+
_restoreRenderDisplay() {
|
|
833
|
+
|
|
834
|
+
const wasShowing = this.upscalerCanvas && this.upscalerCanvas.style.display !== 'none';
|
|
835
|
+
|
|
836
|
+
if ( this.upscalerCanvas ) this.upscalerCanvas.style.display = 'none';
|
|
837
|
+
if ( this.mainCanvas ) this.mainCanvas.style.opacity = '1';
|
|
838
|
+
|
|
839
|
+
if ( wasShowing ) {
|
|
840
|
+
|
|
841
|
+
const stage = this._stages?.pathTracer;
|
|
842
|
+
if ( stage?.width && stage?.height ) {
|
|
843
|
+
|
|
844
|
+
this.dispatchEvent( { type: 'resolution_changed', width: stage.width, height: stage.height } );
|
|
845
|
+
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return !! wasShowing;
|
|
851
|
+
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
_releaseDLSSUpscaler() {
|
|
855
|
+
|
|
856
|
+
this._dlssUpscaler?.dispose();
|
|
857
|
+
this._dlssUpscaler = null;
|
|
858
|
+
|
|
859
|
+
// The frame packer lives on the module, not on the upscaler, and holds two buffers the size
|
|
860
|
+
// of the render. Nothing else reaches it, so this is its only release point. Guarded on the
|
|
861
|
+
// module having been loaded, so a teardown never pulls in 1 MB of runtime to call a no-op.
|
|
862
|
+
this._dlssModule?.releaseDenoisedReader();
|
|
863
|
+
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/** Turns the neural-rendering (detail) pass on or off, and sets its appearance controls. */
|
|
867
|
+
setNeuralRendering( enabled, settings = null ) {
|
|
868
|
+
|
|
869
|
+
this.neuralRendering = !! enabled;
|
|
870
|
+
if ( settings ) this.neuralRenderingSettings = { ...this.neuralRenderingSettings, ...settings };
|
|
871
|
+
if ( ! this.neuralRendering ) {
|
|
872
|
+
|
|
873
|
+
this._releaseDLSSNeural();
|
|
874
|
+
this._restoreRenderDisplay();
|
|
875
|
+
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
_releaseDLSSNeural() {
|
|
881
|
+
|
|
882
|
+
this._dlssNeural?.dispose();
|
|
883
|
+
this._dlssNeural = null;
|
|
884
|
+
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* The neural post-chain over the picture the closing denoise just produced: super resolution
|
|
889
|
+
* first, then the detail pass, either or both.
|
|
890
|
+
*
|
|
891
|
+
* Order is forced by the models. Super resolution hands back light that can be fed onward; the
|
|
892
|
+
* detail pass writes an `rgba8unorm` texture with no `COPY_SRC`, so nothing can read its result
|
|
893
|
+
* and it has to be last — and it presents in its own display space rather than the engine's tone
|
|
894
|
+
* curve. Which shape super resolution returns therefore depends on what follows it: packed
|
|
895
|
+
* halves when the detail pass is next, display bytes when it is the end of the chain.
|
|
896
|
+
*
|
|
897
|
+
* Deliberately fire-and-forget: the render is already finished and a failure must not break it.
|
|
898
|
+
*/
|
|
899
|
+
async _runNeuralPost( isStillComplete ) {
|
|
900
|
+
|
|
901
|
+
const wantSR = this.upscaler?.enabled && this.upscalerBackend === 'dlss';
|
|
902
|
+
const wantNR = this.neuralRendering;
|
|
903
|
+
if ( ! wantSR && ! wantNR ) return;
|
|
904
|
+
|
|
905
|
+
// A second completion can land while the first pass is still in flight — the models are
|
|
906
|
+
// created lazily and reused through fields, so two runs would race over the same handles.
|
|
907
|
+
if ( this._neuralPostBusy ) return;
|
|
908
|
+
|
|
909
|
+
if ( ! this.upscalerCanvas ) {
|
|
910
|
+
|
|
911
|
+
dlssLog.warn( 'neural pass skipped: the engine has no overlay canvas to present on' );
|
|
912
|
+
return;
|
|
913
|
+
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if ( ! this.pipeline?.context?.getTexture( 'oidn:output' ) ) {
|
|
917
|
+
|
|
918
|
+
dlssLog.warn( 'neural pass skipped: no denoised picture — it needs OIDN' );
|
|
919
|
+
return;
|
|
920
|
+
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
this._neuralPostBusy = true;
|
|
924
|
+
this.dispatchEvent( { type: EngineEvents.UPSCALING_START } );
|
|
925
|
+
|
|
926
|
+
try {
|
|
927
|
+
|
|
928
|
+
const sr = await import( '../dlss/DLSSSuperRes.js' );
|
|
929
|
+
this._dlssModule = sr;
|
|
930
|
+
|
|
931
|
+
// The detail pass has a size ceiling — above it a run takes minutes and then loses every GPU
|
|
932
|
+
// device in the page. Running it FIRST is what keeps it under: at render size it sees a
|
|
933
|
+
// quarter of the pixels it would after a 2x upscale, and the render reserve already caps
|
|
934
|
+
// that at 2048. The check stays as a floor under a raised reserve.
|
|
935
|
+
const nrModule = wantNR ? await import( '../dlss/DLSSNeural.js' ) : null;
|
|
936
|
+
const srcSize = this.denoiser?._outTexSize;
|
|
937
|
+
const nrPixels = ( srcSize?.width ?? 0 ) * ( srcSize?.height ?? 0 );
|
|
938
|
+
const runNR = wantNR && nrPixels <= nrModule.DLSS_NR_MAX_PIXELS;
|
|
939
|
+
|
|
940
|
+
if ( wantNR && ! runNR ) {
|
|
941
|
+
|
|
942
|
+
dlssLog.warn(
|
|
943
|
+
`Neural rendering skipped: the render is ${( nrPixels / 1e6 ).toFixed( 1 )} MP, above the ` +
|
|
944
|
+
`${( nrModule.DLSS_NR_MAX_PIXELS / 1e6 ).toFixed( 1 )} MP the pass survives.` +
|
|
945
|
+
( wantSR ? ' The upscale still ran.' : '' )
|
|
946
|
+
);
|
|
947
|
+
this._releaseDLSSNeural();
|
|
948
|
+
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if ( ! wantSR && ! runNR ) return;
|
|
952
|
+
|
|
953
|
+
// Must be the effective value, not the settings one: AutoExposure overwrites
|
|
954
|
+
// `renderer.toneMappingExposure` every frame and never touches settings, so reading
|
|
955
|
+
// settings — as this did — silently ignores auto-exposure. Same helper OIDN uses.
|
|
956
|
+
const tone = {
|
|
957
|
+
exposure: this._getEffectiveExposure(),
|
|
958
|
+
toneMapping: this._getToneMapping(),
|
|
959
|
+
saturation: this._getSaturation?.() ?? 1,
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
// Scene-referred throughout. Exposure reaches the detail pass through its own `paper_white`
|
|
963
|
+
// and comes back out again, so the engine's tone curve is still the only one applied.
|
|
964
|
+
const source = await sr.readDenoisedHalf( this.denoiser );
|
|
965
|
+
|
|
966
|
+
if ( wantSR ) {
|
|
967
|
+
|
|
968
|
+
if ( this._dlssUpscaler
|
|
969
|
+
&& ( this._dlssUpscaler.inputWidth !== source.width || this._dlssUpscaler.inputHeight !== source.height ) ) {
|
|
970
|
+
|
|
971
|
+
this._releaseDLSSUpscaler();
|
|
972
|
+
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if ( ! this._dlssUpscaler ) {
|
|
976
|
+
|
|
977
|
+
this._dlssUpscaler = await sr.DLSSSuperRes.create( { width: source.width, height: source.height } );
|
|
978
|
+
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
let image = source;
|
|
984
|
+
|
|
985
|
+
// Detail first, upscale second. The detail pass hands back scene-referred light (see
|
|
986
|
+
// `app/public/dlss/PATCHES.md`), so the upscaler still gets the linear HDR it expects —
|
|
987
|
+
// which is what makes this order possible at all.
|
|
988
|
+
if ( runNR ) {
|
|
989
|
+
|
|
990
|
+
const result = await nrModule.enhanceFrame( {
|
|
991
|
+
source: image,
|
|
992
|
+
settings: this.neuralRenderingSettings,
|
|
993
|
+
instance: this._dlssNeural,
|
|
994
|
+
exposure: tone.exposure,
|
|
995
|
+
tone: wantSR ? null : tone,
|
|
996
|
+
} );
|
|
997
|
+
this._dlssNeural = result.instance;
|
|
998
|
+
image = result;
|
|
999
|
+
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if ( wantSR ) image = await this._dlssUpscaler.upscaleToRGBA8( image, tone );
|
|
1003
|
+
|
|
1004
|
+
if ( ! isStillComplete() ) {
|
|
1005
|
+
|
|
1006
|
+
this.dropDisplay();
|
|
1007
|
+
return;
|
|
1008
|
+
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
sr.presentRGBA8( this.upscalerCanvas, image.rgba8, image.width, image.height );
|
|
1012
|
+
|
|
1013
|
+
if ( ! isStillComplete() ) {
|
|
1014
|
+
|
|
1015
|
+
this.dropDisplay();
|
|
1016
|
+
return;
|
|
1017
|
+
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// The 2D canvas sits BEHIND the render canvas in the DOM (`insertBefore`), so making it
|
|
1021
|
+
// `display:block` is not enough — the render canvas has to get out of the way. This is
|
|
1022
|
+
// what `AIUpscaler._revealOutput` does for the other backend, and `abort()` already
|
|
1023
|
+
// restores both sides.
|
|
1024
|
+
if ( this.mainCanvas ) this.mainCanvas.style.opacity = '0';
|
|
1025
|
+
|
|
1026
|
+
// The host shows the output dimensions; without this they keep reading the render size.
|
|
1027
|
+
this.dispatchEvent( { type: 'resolution_changed', width: image.width, height: image.height } );
|
|
1028
|
+
|
|
1029
|
+
} catch ( error ) {
|
|
1030
|
+
|
|
1031
|
+
dlssLog.warn( 'neural pass failed', error );
|
|
1032
|
+
|
|
1033
|
+
} finally {
|
|
1034
|
+
|
|
1035
|
+
this._neuralPostBusy = false;
|
|
1036
|
+
this.dispatchEvent( { type: EngineEvents.UPSCALING_END } );
|
|
1037
|
+
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
}
|
|
1041
|
+
|
|
789
1042
|
onRenderComplete( { isStillComplete, context } ) {
|
|
790
1043
|
|
|
791
1044
|
// Remove any stale completion-chain listener from a previous render cycle
|
|
@@ -805,12 +1058,21 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
805
1058
|
|
|
806
1059
|
if ( ! isStillComplete() ) return;
|
|
807
1060
|
|
|
808
|
-
|
|
1061
|
+
// The loop stops one tick after a render completes, so nothing is left to draw the closing
|
|
1062
|
+
// denoise. Ordered after `_denoiserEndHandler`, which publishes the picture.
|
|
1063
|
+
if ( closing ) this._onDisplayRefresh?.();
|
|
1064
|
+
|
|
1065
|
+
// One owner of the overlay canvas, so the neural chain and the ONNX upscaler are
|
|
1066
|
+
// exclusive. The neural chain also runs on its own when only its detail pass is on.
|
|
1067
|
+
if ( this.neuralRendering || ( this.upscaler?.enabled && this.upscalerBackend === 'dlss' ) ) {
|
|
809
1068
|
|
|
810
|
-
this.
|
|
1069
|
+
this._runNeuralPost( isStillComplete );
|
|
1070
|
+
return;
|
|
811
1071
|
|
|
812
1072
|
}
|
|
813
1073
|
|
|
1074
|
+
if ( this.upscaler?.enabled ) this.upscaler.start();
|
|
1075
|
+
|
|
814
1076
|
};
|
|
815
1077
|
|
|
816
1078
|
if ( ! closing ) {
|
|
@@ -887,8 +1149,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
887
1149
|
|
|
888
1150
|
// The 2D canvas is the upscaler's alone: a reset means its enlarged result no longer
|
|
889
1151
|
// describes anything, so the render canvas comes back.
|
|
890
|
-
|
|
891
|
-
if ( this.
|
|
1152
|
+
this._restoreRenderDisplay();
|
|
1153
|
+
if ( mainCanvas && mainCanvas !== this.mainCanvas ) mainCanvas.style.opacity = '1';
|
|
892
1154
|
if ( this.upscaler ) this.upscaler.abort();
|
|
893
1155
|
|
|
894
1156
|
const moving = !! this._stages.pathTracer?.viewIsChanging;
|
|
@@ -917,9 +1179,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
917
1179
|
dropDisplay() {
|
|
918
1180
|
|
|
919
1181
|
this._unpublishOutput();
|
|
920
|
-
|
|
921
|
-
if ( this.upscalerCanvas ) this.upscalerCanvas.style.display = 'none';
|
|
922
|
-
if ( this.mainCanvas ) this.mainCanvas.style.opacity = '1';
|
|
1182
|
+
this._restoreRenderDisplay();
|
|
923
1183
|
|
|
924
1184
|
}
|
|
925
1185
|
|
|
@@ -927,6 +1187,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
927
1187
|
|
|
928
1188
|
// Remove pending completion-chain listener
|
|
929
1189
|
this._cleanupCompletionListener();
|
|
1190
|
+
// Owns a second GPUDevice plus 3 MB of weights, so it must not outlive the manager.
|
|
1191
|
+
this._releaseDLSSUpscaler();
|
|
1192
|
+
this._releaseDLSSNeural();
|
|
1193
|
+
|
|
930
1194
|
// Before the denoiser destroys the picture the Compositor would otherwise still sample.
|
|
931
1195
|
this._unpublishOutput();
|
|
932
1196
|
|
|
@@ -947,6 +1211,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
947
1211
|
if ( this._upscalerEndHandler ) this.upscaler.removeEventListener( 'end', this._upscalerEndHandler );
|
|
948
1212
|
this.upscaler.dispose();
|
|
949
1213
|
this.upscaler = null;
|
|
1214
|
+
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'dlss' is the neural
|
|
1215
|
+
// super-resolution pass, which is fixed at 2x and needs a denoised source.
|
|
1216
|
+
this.upscalerBackend = 'esrgan';
|
|
1217
|
+
this._dlssUpscaler = null;
|
|
950
1218
|
|
|
951
1219
|
}
|
|
952
1220
|
|
|
@@ -957,6 +1225,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
957
1225
|
this._upscalerProgressHandler = null;
|
|
958
1226
|
this._upscalerEndHandler = null;
|
|
959
1227
|
|
|
1228
|
+
this._onReset = null;
|
|
1229
|
+
this._onPostProcessRefresh = null;
|
|
1230
|
+
this._onDisplayRefresh = null;
|
|
1231
|
+
|
|
960
1232
|
if ( this.upscalerCanvas?.parentNode ) {
|
|
961
1233
|
|
|
962
1234
|
this.upscalerCanvas.parentNode.removeChild( this.upscalerCanvas );
|
|
@@ -989,6 +1261,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
989
1261
|
|
|
990
1262
|
}
|
|
991
1263
|
|
|
1264
|
+
/** @param {Function} fn - () => void, redraws the finished frame without re-running the completion chain */
|
|
1265
|
+
setDisplayRefreshCallback( fn ) {
|
|
1266
|
+
|
|
1267
|
+
this._onDisplayRefresh = fn;
|
|
1268
|
+
|
|
1269
|
+
}
|
|
1270
|
+
|
|
992
1271
|
/** @param {import('../RenderSettings.js').RenderSettings} settings */
|
|
993
1272
|
setSettings( settings ) {
|
|
994
1273
|
|
|
@@ -1128,6 +1407,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1128
1407
|
setUpscalerEnabled( enabled ) {
|
|
1129
1408
|
|
|
1130
1409
|
if ( this.upscaler ) this.upscaler.enabled = enabled;
|
|
1410
|
+
// The enlarged picture describes a setting that is no longer on.
|
|
1411
|
+
if ( ! enabled ) this._restoreRenderDisplay();
|
|
1131
1412
|
|
|
1132
1413
|
}
|
|
1133
1414
|
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
EventDispatcher, DirectionalLight, PointLight, SpotLight, RectAreaLight,
|
|
3
3
|
Object3D, MathUtils
|
|
4
4
|
} from 'three';
|
|
5
|
+
import { bakeAreaLightScale, lightPower, setLightPower } from '../LightUnits.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Manages scene lights: add, remove, transfer from mesh scene to WebGPU
|
|
@@ -37,12 +38,13 @@ export class LightManager extends EventDispatcher {
|
|
|
37
38
|
*/
|
|
38
39
|
addLight( type ) {
|
|
39
40
|
|
|
41
|
+
// Authored as Blender Power in Watts (the Sun as W/m²), converted to the stored
|
|
42
|
+
// quantity by setLightPower() once the light exists — see LightUnits.js.
|
|
40
43
|
const defaults = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
RectAreaLight: { position: [ 0, 2, 0 ], intensity: 100, color: '#ffffff', width: 2, height: 2 }
|
|
44
|
+
DirectionalLight: { position: [ 1, 1, 1 ], power: 1.0, color: '#ffffff' },
|
|
45
|
+
PointLight: { position: [ 0, 2, 0 ], power: 1000, color: '#ffffff' },
|
|
46
|
+
SpotLight: { position: [ 0, 1, 0 ], power: 1000, color: '#ffffff', angle: 15 },
|
|
47
|
+
RectAreaLight: { position: [ 0, 2, 0 ], power: 100, color: '#ffffff', width: 2, height: 2 }
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
const props = defaults[ type ];
|
|
@@ -52,17 +54,17 @@ export class LightManager extends EventDispatcher {
|
|
|
52
54
|
|
|
53
55
|
if ( type === 'DirectionalLight' ) {
|
|
54
56
|
|
|
55
|
-
light = new DirectionalLight( props.color,
|
|
57
|
+
light = new DirectionalLight( props.color, 1 );
|
|
56
58
|
light.position.fromArray( props.position );
|
|
57
59
|
|
|
58
60
|
} else if ( type === 'PointLight' ) {
|
|
59
61
|
|
|
60
|
-
light = new PointLight( props.color,
|
|
62
|
+
light = new PointLight( props.color, 1 );
|
|
61
63
|
light.position.fromArray( props.position );
|
|
62
64
|
|
|
63
65
|
} else if ( type === 'SpotLight' ) {
|
|
64
66
|
|
|
65
|
-
light = new SpotLight( props.color,
|
|
67
|
+
light = new SpotLight( props.color, 1 );
|
|
66
68
|
light.position.fromArray( props.position );
|
|
67
69
|
light.angle = MathUtils.degToRad( props.angle );
|
|
68
70
|
const target = new Object3D();
|
|
@@ -71,7 +73,7 @@ export class LightManager extends EventDispatcher {
|
|
|
71
73
|
|
|
72
74
|
} else if ( type === 'RectAreaLight' ) {
|
|
73
75
|
|
|
74
|
-
light = new RectAreaLight( props.color,
|
|
76
|
+
light = new RectAreaLight( props.color, 1, props.width, props.height );
|
|
75
77
|
light.position.fromArray( props.position );
|
|
76
78
|
light.lookAt( 0, 0, 0 );
|
|
77
79
|
// Blender-style emission defaults: power-normalized, full Lambertian
|
|
@@ -82,6 +84,8 @@ export class LightManager extends EventDispatcher {
|
|
|
82
84
|
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
setLightPower( light, props.power );
|
|
88
|
+
|
|
85
89
|
// Blender-style emission controls common to every light type.
|
|
86
90
|
light.userData.temperature = 6500;
|
|
87
91
|
light.userData.useTemperature = false;
|
|
@@ -178,13 +182,7 @@ export class LightManager extends EventDispatcher {
|
|
|
178
182
|
light.getWorldQuaternion( cloned.quaternion );
|
|
179
183
|
light.getWorldScale( cloned.scale );
|
|
180
184
|
|
|
181
|
-
if ( cloned.isRectAreaLight )
|
|
182
|
-
|
|
183
|
-
cloned.width *= cloned.scale.x;
|
|
184
|
-
cloned.height *= cloned.scale.y;
|
|
185
|
-
cloned.scale.set( 1, 1, 1 );
|
|
186
|
-
|
|
187
|
-
}
|
|
185
|
+
if ( cloned.isRectAreaLight ) bakeAreaLightScale( cloned );
|
|
188
186
|
|
|
189
187
|
if ( ( light.isSpotLight || light.isDirectionalLight ) && light.target ) {
|
|
190
188
|
|
|
@@ -338,7 +336,7 @@ export class LightManager extends EventDispatcher {
|
|
|
338
336
|
name: light.name,
|
|
339
337
|
type: light.type,
|
|
340
338
|
visible: light.visible,
|
|
341
|
-
intensity: light.
|
|
339
|
+
intensity: lightPower( light ), // Blender Power (W); the light stores the three.js quantity
|
|
342
340
|
color: `#${light.color.getHexString()}`,
|
|
343
341
|
position: [ light.position.x, light.position.y, light.position.z ],
|
|
344
342
|
angle,
|