rayzee 8.5.0 → 8.6.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 +60 -56
- package/dist/{DLSSNeural-BNSJ8V3z.js → NeuralRetouch-CYOJ21Wn.js} +33 -33
- package/dist/{DLSSSuperRes-BoEW89Cl.js → NeuralSuperRes-Dc6jRYke.js} +25 -25
- package/dist/ToneMapGPU-BVE852iN.js +533 -0
- package/dist/rayzee.es.js +27235 -9074
- package/dist/rayzee.umd.js +255 -187
- package/package.json +1 -1
- package/src/AssetConfig.js +21 -7
- package/src/Color/BakedViews.js +134 -0
- package/src/Color/BuiltinViews.js +261 -0
- package/src/Color/ColorManagement.js +1130 -0
- package/src/Color/ColorSpaces.js +432 -0
- package/src/Color/Displays.js +19 -0
- package/src/Color/InputColorSpaces.js +243 -0
- package/src/Color/LutBake.js +371 -0
- package/src/Color/OcioRuntime.js +338 -0
- package/src/Color/OcioViews.js +419 -0
- package/src/Color/ViewTransforms.js +323 -0
- package/src/Color/WorkingMatrix.js +86 -0
- package/src/EngineDefaults.js +55 -3
- package/src/EngineIssues.js +3 -0
- package/src/Passes/AIUpscaler.js +16 -7
- package/src/Passes/OIDNDenoiser.js +13 -5
- package/src/PathTracerApp.js +227 -7
- package/src/Processor/AssetLoader.js +1 -2
- package/src/Processor/EmissiveTriangleBuilder.js +34 -2
- package/src/Processor/GeometryExtractor.js +151 -138
- package/src/Processor/LightSerializer.js +8 -3
- package/src/Processor/MaterialPacking.js +168 -0
- package/src/Processor/PackedRayBuffer.js +6 -2
- package/src/Processor/ProceduralSky.js +4 -0
- package/src/Processor/SceneProcessor.js +25 -1
- package/src/Processor/SimpleSky.js +8 -0
- package/src/Processor/TextureCreator.js +137 -91
- package/src/Processor/TextureReadback.js +92 -0
- package/src/Processor/ToneMapCPU.js +80 -165
- package/src/Processor/ToneMapGPU.js +122 -151
- package/src/RenderSettings.js +1 -0
- package/src/Stages/Compositor.js +11 -2
- package/src/Stages/PathTracer.js +2 -0
- package/src/Stages/PathTracerStage.js +1 -1
- package/src/TSL/Common.js +15 -0
- package/src/TSL/Debugger.js +2 -3
- package/src/TSL/EmissiveSampling.js +6 -8
- package/src/TSL/ExtendKernel.js +9 -0
- package/src/TSL/Fresnel.js +23 -1
- package/src/TSL/HitFacet.js +102 -0
- package/src/TSL/LightsDirect.js +12 -33
- package/src/TSL/LightsSampling.js +15 -7
- package/src/TSL/MaterialEvaluation.js +26 -19
- package/src/TSL/MaterialProperties.js +74 -37
- package/src/TSL/MaterialTransmission.js +2 -3
- package/src/TSL/ShadeKernel.js +34 -21
- package/src/TSL/ShadowTerminator.js +125 -0
- package/src/TSL/Struct.js +9 -0
- package/src/TSL/Subsurface.js +2 -3
- package/src/index.js +32 -4
- package/src/managers/DenoisingManager.js +60 -44
- package/src/managers/EnvironmentManager.js +20 -0
- package/src/managers/MaterialDataManager.js +118 -208
- package/src/managers/UniformManager.js +1 -0
- package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
- package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
- package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
package/package.json
CHANGED
package/src/AssetConfig.js
CHANGED
|
@@ -26,18 +26,32 @@ const config = {
|
|
|
26
26
|
dracoDecoderPath: 'https://www.gstatic.com/draco/v1/decoders/',
|
|
27
27
|
ktx2TranscoderPath: 'https://cdn.jsdelivr.net/npm/three@0.183.2/examples/jsm/libs/basis/',
|
|
28
28
|
|
|
29
|
-
// OIDN denoiser model weights (oidn-web tza files)
|
|
30
|
-
|
|
29
|
+
// OIDN denoiser model weights (oidn-web tza files): the four tiers' models, mirrored from the
|
|
30
|
+
// `denoiser` npm package 0.0.11 with their Apache-2.0 licence. Versioned so they cannot change underneath.
|
|
31
|
+
oidnWeightsBaseUrl: 'https://assets.rayzee.atulmourya.com/oidn/denoiser-0.0.11/',
|
|
32
|
+
|
|
33
|
+
// OpenColorIO WebAssembly runtime (~6 MB), needed only once a colour-managed config is
|
|
34
|
+
// loaded. The engine never names the package: a bare specifier in engine source would make it
|
|
35
|
+
// a hard dependency of every host, and @vite-ignore leaves the browser unable to resolve it.
|
|
36
|
+
//
|
|
37
|
+
// configureAssets( { ocioRuntimeFactory: () => import( '@bb-studio/ocio' ) } ) // bundled
|
|
38
|
+
// configureAssets( { ocioRuntimeUrl: '/vendor/ocio/index.js' } ) // served
|
|
39
|
+
//
|
|
40
|
+
ocioRuntimeFactory: null,
|
|
41
|
+
ocioRuntimeUrl: null,
|
|
42
|
+
// Only needed when a served runtime cannot find its own .wasm.
|
|
43
|
+
ocioWasmUrl: null,
|
|
31
44
|
|
|
32
45
|
// AI upscaler ONNX model base URL. Quality presets resolve relative paths against this.
|
|
33
46
|
upscalerModelBaseUrl: 'https://huggingface.co/notaneimu/onnx-image-models/resolve/main/',
|
|
34
47
|
|
|
35
|
-
//
|
|
36
|
-
// `globalThis.
|
|
37
|
-
|
|
48
|
+
// Neural post-pass runtime. A plain script the host serves, not a module: it installs
|
|
49
|
+
// `globalThis.NeuralRuntime`, so it cannot be imported and the host must publish it somewhere.
|
|
50
|
+
neuralRuntimeUrl: '/neural/neural-runtime.js',
|
|
38
51
|
|
|
39
|
-
// Base for the
|
|
40
|
-
|
|
52
|
+
// Base for the neural weight manifests the runtime fetches (`generated/...` beneath this). No
|
|
53
|
+
// default: the weights are not redistributable, so a host supplies its own copy.
|
|
54
|
+
neuralAssetBaseUrl: null,
|
|
41
55
|
|
|
42
56
|
// Prefix used when the engine writes to client-side stores (IndexedDB, etc).
|
|
43
57
|
// Set to a unique value to avoid collisions when multiple apps embed the engine on the same origin.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A baked view in a file, so a host can show a config's view before the OCIO runtime or the config
|
|
3
|
+
* has loaded — the app's first frame uses one.
|
|
4
|
+
*
|
|
5
|
+
* gzip of: 'RZVT', a u32 header length, the JSON header, then the table's red, green and blue as
|
|
6
|
+
* half floats, each delta-coded along red and stored as a high-byte plane then a low-byte plane
|
|
7
|
+
* (156 KB for a 65³ table; 2.2 MB raw). Alpha is always 1 and not stored.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const MAGIC = [ 0x52, 0x5a, 0x56, 0x54 ];
|
|
11
|
+
const FORMAT = 'rayzee-baked-view';
|
|
12
|
+
export const BAKED_VIEW_VERSION = 1;
|
|
13
|
+
const HALF_ONE = 0x3c00;
|
|
14
|
+
|
|
15
|
+
async function pipe( bytes, transform ) {
|
|
16
|
+
|
|
17
|
+
return new Uint8Array( await new Response( new Blob( [ bytes ] ).stream().pipeThrough( transform ) ).arrayBuffer() );
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Object} entry - an OCIO registry entry
|
|
23
|
+
* @param {Object} [extra] - header fields to add, e.g. the config fingerprint
|
|
24
|
+
* @returns {Promise<Uint8Array>}
|
|
25
|
+
*/
|
|
26
|
+
export async function encodeBakedView( entry, extra = {} ) {
|
|
27
|
+
|
|
28
|
+
const data = entry?.table?.data;
|
|
29
|
+
if ( entry?.source !== 'ocio' || ! ( data instanceof Uint16Array ) ) throw new Error( 'only a baked OCIO view can be saved' );
|
|
30
|
+
|
|
31
|
+
const { size, minEv, maxEv } = entry.ocio;
|
|
32
|
+
const n = size * size * size;
|
|
33
|
+
if ( data.length !== n * 4 ) throw new Error( `table holds ${data.length} values, expected ${n * 4}` );
|
|
34
|
+
|
|
35
|
+
const planes = new Uint8Array( n * 6 );
|
|
36
|
+
for ( let ch = 0; ch < 3; ch ++ ) {
|
|
37
|
+
|
|
38
|
+
for ( let i = 0; i < n; i ++ ) {
|
|
39
|
+
|
|
40
|
+
if ( ch === 0 && data[ i * 4 + 3 ] !== HALF_ONE ) throw new Error( 'table alpha is not 1' );
|
|
41
|
+
const v = data[ i * 4 + ch ];
|
|
42
|
+
const d = ( v - ( i % size ? data[ ( i - 1 ) * 4 + ch ] : 0 ) ) & 0xffff;
|
|
43
|
+
planes[ ch * n + i ] = d >> 8;
|
|
44
|
+
planes[ 3 * n + ch * n + i ] = d & 0xff;
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { display, view, look, context, source, configId } = entry.ocio;
|
|
51
|
+
const header = new TextEncoder().encode( JSON.stringify( {
|
|
52
|
+
format: FORMAT, version: BAKED_VIEW_VERSION,
|
|
53
|
+
name: entry.name, configId, display, view, look: look ?? null, context: context ?? null, source,
|
|
54
|
+
size, minEv, maxEv, error: entry.error ?? null,
|
|
55
|
+
...extra,
|
|
56
|
+
} ) );
|
|
57
|
+
|
|
58
|
+
const out = new Uint8Array( 8 + header.length + planes.length );
|
|
59
|
+
out.set( MAGIC, 0 );
|
|
60
|
+
new DataView( out.buffer ).setUint32( 4, header.length, true );
|
|
61
|
+
out.set( header, 8 );
|
|
62
|
+
out.set( planes, 8 + header.length );
|
|
63
|
+
return await pipe( out, new CompressionStream( 'gzip' ) );
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {ArrayBuffer|Uint8Array} bytes - from {@link encodeBakedView}
|
|
69
|
+
* @returns {Promise<Object>} the header's fields, plus `data`: the RGBA half table
|
|
70
|
+
*/
|
|
71
|
+
export async function decodeBakedView( bytes ) {
|
|
72
|
+
|
|
73
|
+
const raw = await pipe( bytes, new DecompressionStream( 'gzip' ) );
|
|
74
|
+
if ( raw.length < 8 || MAGIC.some( ( b, i ) => raw[ i ] !== b ) ) throw new Error( 'not a baked view file' );
|
|
75
|
+
|
|
76
|
+
const length = new DataView( raw.buffer, raw.byteOffset ).getUint32( 4, true );
|
|
77
|
+
const header = JSON.parse( new TextDecoder().decode( raw.subarray( 8, 8 + length ) ) );
|
|
78
|
+
if ( header.format !== FORMAT || header.version !== BAKED_VIEW_VERSION ) {
|
|
79
|
+
|
|
80
|
+
throw new Error( `unsupported baked view ${header.format} v${header.version}` );
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { size, minEv, maxEv } = header;
|
|
85
|
+
if ( ! Number.isInteger( size ) || size < 2 || size > 129 || ! ( minEv < maxEv ) ) throw new Error( 'baked view has a bad table shape' );
|
|
86
|
+
|
|
87
|
+
const n = size * size * size;
|
|
88
|
+
const planes = raw.subarray( 8 + length );
|
|
89
|
+
if ( planes.length !== n * 6 ) throw new Error( `baked view holds ${planes.length} bytes of table, expected ${n * 6}` );
|
|
90
|
+
|
|
91
|
+
const data = new Uint16Array( n * 4 );
|
|
92
|
+
for ( let ch = 0; ch < 3; ch ++ ) {
|
|
93
|
+
|
|
94
|
+
let prev = 0;
|
|
95
|
+
for ( let i = 0; i < n; i ++ ) {
|
|
96
|
+
|
|
97
|
+
if ( i % size === 0 ) prev = 0;
|
|
98
|
+
prev = ( prev + ( ( planes[ ch * n + i ] << 8 ) | planes[ 3 * n + ch * n + i ] ) ) & 0xffff;
|
|
99
|
+
data[ i * 4 + ch ] = prev;
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for ( let i = 0; i < n; i ++ ) data[ i * 4 + 3 ] = HALF_ONE;
|
|
106
|
+
|
|
107
|
+
return { ...header, data };
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* What a config package is, byte for byte: a baked view is only trusted against the files it was
|
|
113
|
+
* baked from. Null when this context has no WebCrypto (plain http).
|
|
114
|
+
*
|
|
115
|
+
* @returns {Promise<?string>}
|
|
116
|
+
*/
|
|
117
|
+
export async function configFingerprint( { builtin, text, files = [], configPath = 'config.ocio' } = {} ) {
|
|
118
|
+
|
|
119
|
+
if ( builtin ) return `builtin:${builtin}`;
|
|
120
|
+
|
|
121
|
+
const subtle = globalThis.crypto?.subtle;
|
|
122
|
+
if ( ! subtle ) return null;
|
|
123
|
+
|
|
124
|
+
const hex = buffer => Array.from( new Uint8Array( buffer ), b => b.toString( 16 ).padStart( 2, '0' ) ).join( '' );
|
|
125
|
+
const digest = async data => hex( await subtle.digest( 'SHA-256', data ) );
|
|
126
|
+
|
|
127
|
+
const lines = await Promise.all( files.map( async f => `${f.relativePath ?? f.path ?? f.name}:${await digest( f.data )}` ) );
|
|
128
|
+
if ( text !== undefined ) lines.push( `text:${await digest( new TextEncoder().encode( text ) )}` );
|
|
129
|
+
lines.sort();
|
|
130
|
+
lines.unshift( `config:${configPath}` );
|
|
131
|
+
|
|
132
|
+
return `sha256:${await digest( new TextEncoder().encode( lines.join( '\n' ) ) )}`;
|
|
133
|
+
|
|
134
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seven view transforms three.js ships, as registry entries.
|
|
3
|
+
*
|
|
4
|
+
* Moved here verbatim from `ToneMapCPU.js` and `ToneMapGPU.js` so that one list feeds the CPU
|
|
5
|
+
* readback, the WGSL readback, the live canvas and the host's menu. The maths is unchanged —
|
|
6
|
+
* `tests/unit/color/builtinViews.test.js` hashes the output against the values these produced
|
|
7
|
+
* before the move.
|
|
8
|
+
*
|
|
9
|
+
* All seven are scene-referred in, **linear** out: the renderer's output pass and the readback
|
|
10
|
+
* both apply the sRGB transfer afterwards. An OCIO view is the other kind — see `outputEncoded`
|
|
11
|
+
* in `ViewTransforms.js`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
NoToneMapping, LinearToneMapping, ReinhardToneMapping,
|
|
16
|
+
CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping, NeutralToneMapping
|
|
17
|
+
} from 'three';
|
|
18
|
+
|
|
19
|
+
const clamp01 = x => Math.min( Math.max( x, 0 ), 1 );
|
|
20
|
+
|
|
21
|
+
// Distinct from linearToneMap on purpose: three.js returns early for NoToneMapping without
|
|
22
|
+
// applying exposure, so this one must ignore it.
|
|
23
|
+
function noToneMap( r, g, b, _exposure, out ) {
|
|
24
|
+
|
|
25
|
+
out[ 0 ] = clamp01( r );
|
|
26
|
+
out[ 1 ] = clamp01( g );
|
|
27
|
+
out[ 2 ] = clamp01( b );
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function linearToneMap( r, g, b, exposure, out ) {
|
|
32
|
+
|
|
33
|
+
out[ 0 ] = clamp01( r * exposure );
|
|
34
|
+
out[ 1 ] = clamp01( g * exposure );
|
|
35
|
+
out[ 2 ] = clamp01( b * exposure );
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function reinhardToneMap( r, g, b, exposure, out ) {
|
|
40
|
+
|
|
41
|
+
r *= exposure; g *= exposure; b *= exposure;
|
|
42
|
+
out[ 0 ] = clamp01( r / ( r + 1 ) );
|
|
43
|
+
out[ 1 ] = clamp01( g / ( g + 1 ) );
|
|
44
|
+
out[ 2 ] = clamp01( b / ( b + 1 ) );
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cineonToneMap( r, g, b, exposure, out ) {
|
|
49
|
+
|
|
50
|
+
r = Math.max( r * exposure - 0.004, 0 );
|
|
51
|
+
g = Math.max( g * exposure - 0.004, 0 );
|
|
52
|
+
b = Math.max( b * exposure - 0.004, 0 );
|
|
53
|
+
const f = c => Math.pow( ( c * ( 6.2 * c + 0.5 ) ) / ( c * ( 6.2 * c + 1.7 ) + 0.06 ), 2.2 );
|
|
54
|
+
out[ 0 ] = f( r );
|
|
55
|
+
out[ 1 ] = f( g );
|
|
56
|
+
out[ 2 ] = f( b );
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function acesFilmicToneMap( r, g, b, exposure, out ) {
|
|
61
|
+
|
|
62
|
+
r = r * exposure / 0.6;
|
|
63
|
+
g = g * exposure / 0.6;
|
|
64
|
+
b = b * exposure / 0.6;
|
|
65
|
+
|
|
66
|
+
let ir = 0.59719 * r + 0.35458 * g + 0.04823 * b;
|
|
67
|
+
let ig = 0.07600 * r + 0.90834 * g + 0.01566 * b;
|
|
68
|
+
let ib = 0.02840 * r + 0.13383 * g + 0.83777 * b;
|
|
69
|
+
|
|
70
|
+
const f = v => {
|
|
71
|
+
|
|
72
|
+
const a = v * ( v + 0.0245786 ) - 0.000090537;
|
|
73
|
+
const d = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;
|
|
74
|
+
return a / d;
|
|
75
|
+
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
ir = f( ir ); ig = f( ig ); ib = f( ib );
|
|
79
|
+
|
|
80
|
+
out[ 0 ] = clamp01( 1.60475 * ir - 0.53108 * ig - 0.07367 * ib );
|
|
81
|
+
out[ 1 ] = clamp01( - 0.10208 * ir + 1.10813 * ig - 0.00605 * ib );
|
|
82
|
+
out[ 2 ] = clamp01( - 0.00327 * ir - 0.07276 * ig + 1.07602 * ib );
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function agxToneMap( r, g, b, exposure, out ) {
|
|
87
|
+
|
|
88
|
+
r *= exposure; g *= exposure; b *= exposure;
|
|
89
|
+
|
|
90
|
+
let cr = 0.6274 * r + 0.3293 * g + 0.0433 * b;
|
|
91
|
+
let cg = 0.0691 * r + 0.9195 * g + 0.0113 * b;
|
|
92
|
+
let cb = 0.0164 * r + 0.0880 * g + 0.8956 * b;
|
|
93
|
+
|
|
94
|
+
let ar = 0.856627153315983 * cr + 0.0951212405381588 * cg + 0.0482516061458583 * cb;
|
|
95
|
+
let ag = 0.137318972929847 * cr + 0.761241990602591 * cg + 0.101439036467562 * cb;
|
|
96
|
+
let ab = 0.11189821299995 * cr + 0.0767994186031903 * cg + 0.811302368396859 * cb;
|
|
97
|
+
|
|
98
|
+
const AgxMinEv = - 12.47393, AgxMaxEv = 4.026069, range = AgxMaxEv - AgxMinEv;
|
|
99
|
+
ar = clamp01( ( Math.log2( Math.max( ar, 1e-10 ) ) - AgxMinEv ) / range );
|
|
100
|
+
ag = clamp01( ( Math.log2( Math.max( ag, 1e-10 ) ) - AgxMinEv ) / range );
|
|
101
|
+
ab = clamp01( ( Math.log2( Math.max( ab, 1e-10 ) ) - AgxMinEv ) / range );
|
|
102
|
+
|
|
103
|
+
const approx = x => {
|
|
104
|
+
|
|
105
|
+
const x2 = x * x, x4 = x2 * x2;
|
|
106
|
+
return 15.5 * x4 * x2 - 40.14 * x4 * x + 31.96 * x4 - 6.868 * x2 * x + 0.4298 * x2 + 0.1191 * x - 0.00232;
|
|
107
|
+
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
ar = approx( ar ); ag = approx( ag ); ab = approx( ab );
|
|
111
|
+
|
|
112
|
+
let or = 1.1271005818144368 * ar - 0.11060664309660323 * ag - 0.016493938717834573 * ab;
|
|
113
|
+
let og = - 0.1413297634984383 * ar + 1.157823702216272 * ag - 0.016493938717834257 * ab;
|
|
114
|
+
let ob = - 0.14132976349843826 * ar - 0.11060664309660294 * ag + 1.2519364065950405 * ab;
|
|
115
|
+
|
|
116
|
+
or = Math.pow( Math.max( 0, or ), 2.2 );
|
|
117
|
+
og = Math.pow( Math.max( 0, og ), 2.2 );
|
|
118
|
+
ob = Math.pow( Math.max( 0, ob ), 2.2 );
|
|
119
|
+
|
|
120
|
+
out[ 0 ] = clamp01( 1.6605 * or - 0.5876 * og - 0.0728 * ob );
|
|
121
|
+
out[ 1 ] = clamp01( - 0.1246 * or + 1.1329 * og - 0.0083 * ob );
|
|
122
|
+
out[ 2 ] = clamp01( - 0.0182 * or - 0.1006 * og + 1.1187 * ob );
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function neutralToneMap( r, g, b, exposure, out ) {
|
|
127
|
+
|
|
128
|
+
const StartCompression = 0.8 - 0.04;
|
|
129
|
+
const Desaturation = 0.15;
|
|
130
|
+
|
|
131
|
+
r *= exposure; g *= exposure; b *= exposure;
|
|
132
|
+
|
|
133
|
+
const x = Math.min( r, Math.min( g, b ) );
|
|
134
|
+
const offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
|
|
135
|
+
|
|
136
|
+
r -= offset; g -= offset; b -= offset;
|
|
137
|
+
|
|
138
|
+
const peak = Math.max( r, Math.max( g, b ) );
|
|
139
|
+
|
|
140
|
+
if ( peak < StartCompression ) {
|
|
141
|
+
|
|
142
|
+
out[ 0 ] = r; out[ 1 ] = g; out[ 2 ] = b;
|
|
143
|
+
return;
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const d = 1 - StartCompression;
|
|
148
|
+
const newPeak = 1 - d * d / ( peak + d - StartCompression );
|
|
149
|
+
const scale = newPeak / peak;
|
|
150
|
+
r *= scale; g *= scale; b *= scale;
|
|
151
|
+
const gFactor = 1 - 1 / ( Desaturation * ( peak - newPeak ) + 1 );
|
|
152
|
+
|
|
153
|
+
out[ 0 ] = r + ( newPeak - r ) * gFactor;
|
|
154
|
+
out[ 1 ] = g + ( newPeak - g ) * gFactor;
|
|
155
|
+
out[ 2 ] = b + ( newPeak - b ) * gFactor;
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const REINHARD_WGSL = /* wgsl */ `
|
|
160
|
+
fn tm_reinhard( c: vec3<f32> ) -> vec3<f32> {
|
|
161
|
+
return clamp( c / ( c + vec3<f32>( 1.0 ) ), vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
const CINEON_WGSL = /* wgsl */ `
|
|
166
|
+
fn tm_cineon( c0: vec3<f32> ) -> vec3<f32> {
|
|
167
|
+
let c = max( c0 - vec3<f32>( 0.004 ), vec3<f32>( 0.0 ) );
|
|
168
|
+
let v = ( c * ( 6.2 * c + vec3<f32>( 0.5 ) ) ) / ( c * ( 6.2 * c + vec3<f32>( 1.7 ) ) + vec3<f32>( 0.06 ) );
|
|
169
|
+
return pow( max( v, vec3<f32>( 0.0 ) ), vec3<f32>( 2.2 ) );
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
|
|
173
|
+
const ACES_WGSL = /* wgsl */ `
|
|
174
|
+
fn tm_aces( c0: vec3<f32> ) -> vec3<f32> {
|
|
175
|
+
let c = c0 / 0.6;
|
|
176
|
+
let m_in = mat3x3<f32>(
|
|
177
|
+
vec3<f32>( 0.59719, 0.07600, 0.02840 ),
|
|
178
|
+
vec3<f32>( 0.35458, 0.90834, 0.13383 ),
|
|
179
|
+
vec3<f32>( 0.04823, 0.01566, 0.83777 ) );
|
|
180
|
+
let v = m_in * c;
|
|
181
|
+
let a = v * ( v + vec3<f32>( 0.0245786 ) ) - vec3<f32>( 0.000090537 );
|
|
182
|
+
let b = v * ( 0.983729 * v + vec3<f32>( 0.4329510 ) ) + vec3<f32>( 0.238081 );
|
|
183
|
+
let m_out = mat3x3<f32>(
|
|
184
|
+
vec3<f32>( 1.60475, -0.10208, -0.00327 ),
|
|
185
|
+
vec3<f32>( -0.53108, 1.10813, -0.07276 ),
|
|
186
|
+
vec3<f32>( -0.07367, -0.00605, 1.07602 ) );
|
|
187
|
+
return clamp( m_out * ( a / b ), vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
188
|
+
}
|
|
189
|
+
`;
|
|
190
|
+
|
|
191
|
+
const AGX_WGSL = /* wgsl */ `
|
|
192
|
+
fn tm_agx( c0: vec3<f32> ) -> vec3<f32> {
|
|
193
|
+
let m_in = mat3x3<f32>(
|
|
194
|
+
vec3<f32>( 0.6274, 0.0691, 0.0164 ),
|
|
195
|
+
vec3<f32>( 0.3293, 0.9195, 0.0880 ),
|
|
196
|
+
vec3<f32>( 0.0433, 0.0113, 0.8956 ) );
|
|
197
|
+
let m_agx = mat3x3<f32>(
|
|
198
|
+
vec3<f32>( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),
|
|
199
|
+
vec3<f32>( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),
|
|
200
|
+
vec3<f32>( 0.0482516061458583, 0.101439036467562, 0.811302368396859 ) );
|
|
201
|
+
|
|
202
|
+
var v = m_agx * ( m_in * c0 );
|
|
203
|
+
|
|
204
|
+
let minEv = -12.47393;
|
|
205
|
+
let maxEv = 4.026069;
|
|
206
|
+
v = clamp( ( log2( max( v, vec3<f32>( 1e-10 ) ) ) - vec3<f32>( minEv ) ) / ( maxEv - minEv ),
|
|
207
|
+
vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
208
|
+
|
|
209
|
+
let x2 = v * v;
|
|
210
|
+
let x4 = x2 * x2;
|
|
211
|
+
v = 15.5 * x4 * x2 - 40.14 * x4 * v + 31.96 * x4 - 6.868 * x2 * v + 0.4298 * x2 + 0.1191 * v
|
|
212
|
+
- vec3<f32>( 0.00232 );
|
|
213
|
+
|
|
214
|
+
let m_out = mat3x3<f32>(
|
|
215
|
+
vec3<f32>( 1.1271005818144368, -0.1413297634984383, -0.14132976349843826 ),
|
|
216
|
+
vec3<f32>( -0.11060664309660323, 1.157823702216272, -0.11060664309660294 ),
|
|
217
|
+
vec3<f32>( -0.016493938717834573, -0.016493938717834257, 1.2519364065950405 ) );
|
|
218
|
+
let o = pow( max( m_out * v, vec3<f32>( 0.0 ) ), vec3<f32>( 2.2 ) );
|
|
219
|
+
|
|
220
|
+
let m_srgb = mat3x3<f32>(
|
|
221
|
+
vec3<f32>( 1.6605, -0.1246, -0.0182 ),
|
|
222
|
+
vec3<f32>( -0.5876, 1.1329, -0.1006 ),
|
|
223
|
+
vec3<f32>( -0.0728, -0.0083, 1.1187 ) );
|
|
224
|
+
return clamp( m_srgb * o, vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
|
|
228
|
+
const NEUTRAL_WGSL = /* wgsl */ `
|
|
229
|
+
fn tm_neutral( c0: vec3<f32> ) -> vec3<f32> {
|
|
230
|
+
let startCompression = 0.8 - 0.04;
|
|
231
|
+
let desaturation = 0.15;
|
|
232
|
+
|
|
233
|
+
let x = min( c0.r, min( c0.g, c0.b ) );
|
|
234
|
+
var offset = 0.04;
|
|
235
|
+
if ( x < 0.08 ) { offset = x - 6.25 * x * x; }
|
|
236
|
+
var c = c0 - vec3<f32>( offset );
|
|
237
|
+
|
|
238
|
+
let peak = max( c.r, max( c.g, c.b ) );
|
|
239
|
+
if ( peak < startCompression ) { return c; }
|
|
240
|
+
|
|
241
|
+
let d = 1.0 - startCompression;
|
|
242
|
+
let newPeak = 1.0 - d * d / ( peak + d - startCompression );
|
|
243
|
+
c = c * ( newPeak / peak );
|
|
244
|
+
let gFactor = 1.0 - 1.0 / ( desaturation * ( peak - newPeak ) + 1.0 );
|
|
245
|
+
return mix( c, vec3<f32>( newPeak ), gFactor );
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
|
|
249
|
+
/** The seven, in the order a host menu should show them. */
|
|
250
|
+
export const BUILTIN_VIEWS = [
|
|
251
|
+
// `appliesExposure: false` is three.js's own behaviour, not an oversight: ToneMappingNode
|
|
252
|
+
// returns early for NoToneMapping without applying `toneMappingExposure`, so a readback that
|
|
253
|
+
// applied it would paint brighter than the viewport it replaces.
|
|
254
|
+
{ id: NoToneMapping, name: 'None', wgslConst: 'TM_NONE', cpu: noToneMap, wgsl: null, call: null, appliesExposure: false },
|
|
255
|
+
{ id: LinearToneMapping, name: 'Linear', wgslConst: 'TM_LINEAR', cpu: linearToneMap, wgsl: null, call: null },
|
|
256
|
+
{ id: ReinhardToneMapping, name: 'Reinhard', wgslConst: 'TM_REINHARD', cpu: reinhardToneMap, wgsl: REINHARD_WGSL, call: 'tm_reinhard( c )' },
|
|
257
|
+
{ id: CineonToneMapping, name: 'Cineon', wgslConst: 'TM_CINEON', cpu: cineonToneMap, wgsl: CINEON_WGSL, call: 'tm_cineon( c )' },
|
|
258
|
+
{ id: ACESFilmicToneMapping, name: 'ACES Filmic', wgslConst: 'TM_ACES', cpu: acesFilmicToneMap, wgsl: ACES_WGSL, call: 'tm_aces( c )' },
|
|
259
|
+
{ id: AgXToneMapping, name: 'AgX', wgslConst: 'TM_AGX', cpu: agxToneMap, wgsl: AGX_WGSL, call: 'tm_agx( c )' },
|
|
260
|
+
{ id: NeutralToneMapping, name: 'Neutral', wgslConst: 'TM_NEUTRAL', cpu: neutralToneMap, wgsl: NEUTRAL_WGSL, call: 'tm_neutral( c )' },
|
|
261
|
+
].map( v => ( { source: 'builtin', outputEncoded: false, appliesExposure: true, ...v } ) );
|