@volter/blender-engine 0.1.148 → 0.1.150
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/LICENSE +22 -5
- package/README.md +4 -11
- package/browser/addons/vrm/LICENSE_(OPTION1)_MIT.txt +22 -0
- package/browser/addons/vrm/LICENSE_(OPTION2)_GPL-3_0.txt +674 -0
- package/browser/addons/vrm/LICENSE_MAIN.txt +1 -0
- package/browser/addons/vrm/README.txt +3 -0
- package/browser/addons/vrm/__init__.py +41 -0
- package/browser/addons/vrm/blender_manifest.toml +16 -0
- package/browser/addons/vrm/common/__init__.py +1 -0
- package/browser/addons/vrm/common/animation.py +58 -0
- package/browser/addons/vrm/common/blender_manifest.py +82 -0
- package/browser/addons/vrm/common/char.py +16 -0
- package/browser/addons/vrm/common/convert.py +287 -0
- package/browser/addons/vrm/common/convert_any.py +48 -0
- package/browser/addons/vrm/common/debug.py +40 -0
- package/browser/addons/vrm/common/deep.py +162 -0
- package/browser/addons/vrm/common/error_dialog.py +274 -0
- package/browser/addons/vrm/common/fs.py +71 -0
- package/browser/addons/vrm/common/gl.py +20 -0
- package/browser/addons/vrm/common/gltf.py +1149 -0
- package/browser/addons/vrm/common/human_bone_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/human_bone_mapper/biped_mapping.py +84 -0
- package/browser/addons/vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
- package/browser/addons/vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
- package/browser/addons/vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
- package/browser/addons/vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
- package/browser/addons/vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
- package/browser/addons/vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
- package/browser/addons/vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
- package/browser/addons/vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
- package/browser/addons/vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
- package/browser/addons/vrm/common/legacy_gltf.py +16 -0
- package/browser/addons/vrm/common/logger.py +84 -0
- package/browser/addons/vrm/common/mtoon0.blend +0 -0
- package/browser/addons/vrm/common/mtoon1.blend +0 -0
- package/browser/addons/vrm/common/mtoon1_outline.blend +0 -0
- package/browser/addons/vrm/common/mtoon_auto_setup.blend +0 -0
- package/browser/addons/vrm/common/mtoon_unversioned.py +62 -0
- package/browser/addons/vrm/common/ops/__init__.py +10 -0
- package/browser/addons/vrm/common/ops/export_scene.py +67 -0
- package/browser/addons/vrm/common/ops/icyp.py +53 -0
- package/browser/addons/vrm/common/ops/import_scene.py +57 -0
- package/browser/addons/vrm/common/ops/vrm.py +2380 -0
- package/browser/addons/vrm/common/ops/wm.py +167 -0
- package/browser/addons/vrm/common/preferences.py +362 -0
- package/browser/addons/vrm/common/progress.py +87 -0
- package/browser/addons/vrm/common/rotation.py +89 -0
- package/browser/addons/vrm/common/safe_removal.py +46 -0
- package/browser/addons/vrm/common/scene_watcher.py +230 -0
- package/browser/addons/vrm/common/shader.py +2243 -0
- package/browser/addons/vrm/common/shape_key_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
- package/browser/addons/vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
- package/browser/addons/vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
- package/browser/addons/vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
- package/browser/addons/vrm/common/third_party_user_extension.py +214 -0
- package/browser/addons/vrm/common/transparent_z_write.blend +0 -0
- package/browser/addons/vrm/common/version.py +161 -0
- package/browser/addons/vrm/common/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm0/human_bone.py +662 -0
- package/browser/addons/vrm/common/vrm0/material_property.py +346 -0
- package/browser/addons/vrm/common/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm1/expression_preset.py +207 -0
- package/browser/addons/vrm/common/vrm1/human_bone.py +822 -0
- package/browser/addons/vrm/common/workspace.py +227 -0
- package/browser/addons/vrm/common/writable_context.py +77 -0
- package/browser/addons/vrm/documentation/en-us/animation/index.md +63 -0
- package/browser/addons/vrm/documentation/en-us/create-humanoid-vrm-from-scratch/index.md +131 -0
- package/browser/addons/vrm/documentation/en-us/create-simple-vrm-from-scratch/index.md +30 -0
- package/browser/addons/vrm/documentation/en-us/development/index.md +136 -0
- package/browser/addons/vrm/documentation/en-us/index.md +41 -0
- package/browser/addons/vrm/documentation/en-us/installation/index.md +152 -0
- package/browser/addons/vrm/documentation/en-us/material-mtoon/index.md +122 -0
- package/browser/addons/vrm/documentation/en-us/material-pbr/index.md +94 -0
- package/browser/addons/vrm/documentation/en-us/scripting-api/index.md +391 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrm/index.md +57 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrma/index.md +17 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrm/index.md +61 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrma/index.md +13 -0
- package/browser/addons/vrm/documentation/ja-jp/animation/index.md +57 -0
- package/browser/addons/vrm/documentation/ja-jp/create-humanoid-vrm-from-scratch/index.md +129 -0
- package/browser/addons/vrm/documentation/ja-jp/create-simple-vrm-from-scratch/index.md +29 -0
- package/browser/addons/vrm/documentation/ja-jp/development/index.md +137 -0
- package/browser/addons/vrm/documentation/ja-jp/index.md +37 -0
- package/browser/addons/vrm/documentation/ja-jp/installation/index.md +150 -0
- package/browser/addons/vrm/documentation/ja-jp/material-mtoon/index.md +113 -0
- package/browser/addons/vrm/documentation/ja-jp/material-pbr/index.md +90 -0
- package/browser/addons/vrm/documentation/ja-jp/scripting-api/index.md +389 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrm/index.md +55 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrma/index.md +19 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrm/index.md +56 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrma/index.md +14 -0
- package/browser/addons/vrm/editor/__init__.py +1 -0
- package/browser/addons/vrm/editor/extension.py +425 -0
- package/browser/addons/vrm/editor/extension_accessor.py +75 -0
- package/browser/addons/vrm/editor/handler.py +84 -0
- package/browser/addons/vrm/editor/khr_character/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_character/panel.py +69 -0
- package/browser/addons/vrm/editor/khr_character/property_group.py +27 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ops.py +464 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/panel.py +136 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
- package/browser/addons/vrm/editor/make_armature.py +709 -0
- package/browser/addons/vrm/editor/menu.py +147 -0
- package/browser/addons/vrm/editor/migration.py +297 -0
- package/browser/addons/vrm/editor/mtoon1/__init__.py +1 -0
- package/browser/addons/vrm/editor/mtoon1/handler.py +20 -0
- package/browser/addons/vrm/editor/mtoon1/migration.py +536 -0
- package/browser/addons/vrm/editor/mtoon1/ops.py +304 -0
- package/browser/addons/vrm/editor/mtoon1/panel.py +442 -0
- package/browser/addons/vrm/editor/mtoon1/property_group.py +4977 -0
- package/browser/addons/vrm/editor/mtoon1/scene_watcher.py +381 -0
- package/browser/addons/vrm/editor/node_constraint1/__init__.py +1 -0
- package/browser/addons/vrm/editor/node_constraint1/panel.py +317 -0
- package/browser/addons/vrm/editor/node_constraint1/property_group.py +25 -0
- package/browser/addons/vrm/editor/ops.py +471 -0
- package/browser/addons/vrm/editor/panel.py +315 -0
- package/browser/addons/vrm/editor/property_group.py +888 -0
- package/browser/addons/vrm/editor/search.py +682 -0
- package/browser/addons/vrm/editor/spring_bone1/__init__.py +1 -0
- package/browser/addons/vrm/editor/spring_bone1/handler.py +994 -0
- package/browser/addons/vrm/editor/spring_bone1/menu.py +246 -0
- package/browser/addons/vrm/editor/spring_bone1/migration.py +86 -0
- package/browser/addons/vrm/editor/spring_bone1/ops.py +1679 -0
- package/browser/addons/vrm/editor/spring_bone1/panel.py +605 -0
- package/browser/addons/vrm/editor/spring_bone1/property_group.py +1457 -0
- package/browser/addons/vrm/editor/spring_bone1/ui_list.py +408 -0
- package/browser/addons/vrm/editor/subscription.py +123 -0
- package/browser/addons/vrm/editor/t_pose.py +713 -0
- package/browser/addons/vrm/editor/validation.py +1271 -0
- package/browser/addons/vrm/editor/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm0/gizmo_group.py +63 -0
- package/browser/addons/vrm/editor/vrm0/handler.py +23 -0
- package/browser/addons/vrm/editor/vrm0/menu.py +145 -0
- package/browser/addons/vrm/editor/vrm0/migration.py +761 -0
- package/browser/addons/vrm/editor/vrm0/ops.py +2145 -0
- package/browser/addons/vrm/editor/vrm0/panel.py +1333 -0
- package/browser/addons/vrm/editor/vrm0/property_group.py +1818 -0
- package/browser/addons/vrm/editor/vrm0/ui_list.py +688 -0
- package/browser/addons/vrm/editor/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm1/handler.py +28 -0
- package/browser/addons/vrm/editor/vrm1/menu.py +76 -0
- package/browser/addons/vrm/editor/vrm1/migration.py +321 -0
- package/browser/addons/vrm/editor/vrm1/ops.py +2679 -0
- package/browser/addons/vrm/editor/vrm1/panel.py +1243 -0
- package/browser/addons/vrm/editor/vrm1/property_group.py +2087 -0
- package/browser/addons/vrm/editor/vrm1/scene_watcher.py +90 -0
- package/browser/addons/vrm/editor/vrm1/ui_list.py +533 -0
- package/browser/addons/vrm/exporter/__init__.py +1 -0
- package/browser/addons/vrm/exporter/abstract_base_vrm_exporter.py +628 -0
- package/browser/addons/vrm/exporter/export_scene.py +930 -0
- package/browser/addons/vrm/exporter/gltf2_export_user_extension.py +45 -0
- package/browser/addons/vrm/exporter/khr_character_exporter.py +34 -0
- package/browser/addons/vrm/exporter/vrm0_exporter.py +4020 -0
- package/browser/addons/vrm/exporter/vrm1_exporter.py +3682 -0
- package/browser/addons/vrm/exporter/vrm_animation_exporter.py +1035 -0
- package/browser/addons/vrm/external/__init__.py +1 -0
- package/browser/addons/vrm/external/auto_rig_pro_support.py +3 -0
- package/browser/addons/vrm/external/io_scene_gltf2_support.py +418 -0
- package/browser/addons/vrm/external/rigify_support.py +1 -0
- package/browser/addons/vrm/importer/__init__.py +1 -0
- package/browser/addons/vrm/importer/abstract_base_vrm_importer.py +1548 -0
- package/browser/addons/vrm/importer/file_handler.py +34 -0
- package/browser/addons/vrm/importer/gltf2_import_user_extension.py +268 -0
- package/browser/addons/vrm/importer/import_scene.py +646 -0
- package/browser/addons/vrm/importer/license_validation.py +198 -0
- package/browser/addons/vrm/importer/vrm0_importer.py +1723 -0
- package/browser/addons/vrm/importer/vrm1_importer.py +2014 -0
- package/browser/addons/vrm/importer/vrm_animation_importer.py +763 -0
- package/browser/addons/vrm/importer/vrm_diff.py +316 -0
- package/browser/addons/vrm/locale/__init__.py +1 -0
- package/browser/addons/vrm/locale/ja_jp.py +826 -0
- package/browser/addons/vrm/locale/ruff.toml +8 -0
- package/browser/addons/vrm/locale/translation_dictionary.py +10 -0
- package/browser/addons/vrm/locale/zh_hans.py +928 -0
- package/browser/addons/vrm/registration.py +670 -0
- package/browser/addons/vrm.json +18 -0
- package/browser/blender-emscripten-engine.mts +66 -129
- package/browser/blender-engine.mts +20 -186
- package/browser/blender-wali-engine.mts +37 -81
- package/browser/describe-thrown.mts +3 -6
- package/browser/document-chunks.mts +4 -16
- package/browser/gltf_export.py +3161 -0
- package/browser/heap-growth.mts +23 -0
- package/browser/index.ts +3 -0
- package/browser/protocol.ts +130 -43
- package/browser/pull-job.mts +2 -10
- package/browser/rna.ts +190 -73
- package/browser/runtime.ts +271 -106
- package/browser/session-addons.mts +33 -0
- package/browser/session-frame.mts +5 -24
- package/browser/session.py +1389 -1446
- package/browser/three/attach-presenter.ts +11 -39
- package/browser/three/blender-base64.ts +1 -36
- package/browser/three/blender-corner-normals.ts +1 -23
- package/browser/three/blender-display-shaders.json +1 -1
- package/browser/three/blender-display-shaders.lut +0 -0
- package/browser/three/blender-display-transform.ts +2 -1
- package/browser/three/blender-frame-columns.ts +5 -13
- package/browser/three/blender-graph-material.ts +18 -3
- package/browser/three/blender-node-graph.ts +10 -34
- package/browser/three/blender-physical-material.ts +2 -31
- package/browser/three/blender-runtime-armature.ts +7 -60
- package/browser/three/blender-runtime-frame.ts +9 -23
- package/browser/three/blender-runtime-geometry.ts +22 -26
- package/browser/three/blender-runtime-lighting.ts +48 -178
- package/browser/three/blender-runtime-shadows.ts +187 -1
- package/browser/three/blender-runtime-skeleton.ts +53 -1
- package/browser/three/blender-runtime-view.ts +492 -306
- package/browser/three/blender-runtime-weights.ts +9 -72
- package/browser/three/blender-sky.ts +13 -33
- package/browser/three/blender-triangulate.ts +7 -35
- package/browser/three/blender-viewport-sampling.ts +235 -0
- package/browser/three/blender-workbench-material.ts +25 -29
- package/browser/three/blender-workbench-outline.ts +199 -0
- package/browser/three/blender-world-volume.ts +62 -10
- package/browser/three/presenter.ts +10 -26
- package/browser/three/release.ts +5 -21
- package/browser/three/sky-precompute-worker.ts +1 -18
- package/browser/three/sky-worker.ts +5 -23
- package/browser/three/world-field-sampler.ts +12 -26
- package/browser/volter_three.py +9 -105
- package/browser/worker.ts +195 -180
- package/package.json +5 -2
- package/wasm/BUNDLE.json +17 -17
- package/wasm/DEPENDENCY-LICENSES.txt +14 -0
- package/wasm/blender_browser.data.br +0 -0
- package/wasm/blender_browser.js +1 -1
- package/wasm/blender_browser.wasm.br +0 -0
- package/browser/frame-transfer.mts +0 -19
- package/browser/three/agx-base-srgb.lut +0 -0
- package/browser/three/agx-look-medium-high-contrast.lut +0 -0
- package/browser/three/agx-look-punchy.lut +0 -0
- package/browser/three/blender-agx.ts +0 -235
- package/browser/three/blender-display-lut.ts +0 -145
- package/browser/three/blender-filmic.ts +0 -49
- package/browser/three/blender-standard.ts +0 -68
- package/browser/three/blender-world-visibility.ts +0 -278
- package/browser/three/filmic-srgb.lut +0 -0
|
Binary file
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/** Buffers at every depth of a frame move to the presenter exactly once. */
|
|
2
|
-
export function frameTransferBuffers(frame: unknown): ArrayBuffer[] {
|
|
3
|
-
const buffers = new Set<ArrayBuffer>();
|
|
4
|
-
const visited = new Set<object>();
|
|
5
|
-
const walk = (value: unknown): void => {
|
|
6
|
-
if (value === null || typeof value !== 'object' || visited.has(value)) return;
|
|
7
|
-
visited.add(value);
|
|
8
|
-
if (value instanceof ArrayBuffer) {
|
|
9
|
-
buffers.add(value);
|
|
10
|
-
} else if (ArrayBuffer.isView(value)) {
|
|
11
|
-
// Shared Wasm memory cannot be transferred; arena readers make owned copies.
|
|
12
|
-
if (value.buffer instanceof ArrayBuffer) buffers.add(value.buffer);
|
|
13
|
-
} else {
|
|
14
|
-
for (const child of Object.values(value)) walk(child);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
walk(frame);
|
|
18
|
-
return [...buffers];
|
|
19
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { encodeDisplayFrame, sampleDisplayLut } from './blender-display-lut';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Blender's AgX view transform, transcribed from the pinned OCIO config.
|
|
5
|
-
*
|
|
6
|
-
* WHY THIS EXISTS AND `THREE.AgXToneMapping` DOES NOT DO. three's AgX is
|
|
7
|
-
* Filament's approximation: it allocates log2 to `AgxMaxEv = 4.026069` where
|
|
8
|
-
* Blender's config allocates to `+12.5260688117`, and it substitutes a
|
|
9
|
-
* 6th-order polynomial for the config's curve. MEASURED on flat grey, it is
|
|
10
|
-
* 23 to 48 levels of 255 away from Blender -- 42 of them at middle grey, where
|
|
11
|
-
* Blender answers 0.4456 and three answers 0.6060. Every render this lane
|
|
12
|
-
* produced carried that.
|
|
13
|
-
*
|
|
14
|
-
* THE CHAIN IS THE CONFIG'S, step for step (`AgX Base Rec.1886` on display
|
|
15
|
-
* `sRGB`):
|
|
16
|
-
*
|
|
17
|
-
* scene linear Rec.709 -> CIE XYZ D65 -> Linear FilmLight E-Gamut
|
|
18
|
-
* -> lg2 allocation over [-12.47393, +12.5260688117]
|
|
19
|
-
* -> AgX_Base_sRGB.cube, 57^3, TETRAHEDRAL
|
|
20
|
-
* -> Rec.1886 -> sRGB (the Rec.709 matrices cancel: `srgb(x^2.4)`)
|
|
21
|
-
*
|
|
22
|
-
* VERIFIED THREE WAYS, all agreeing to the digit: this code, Blender's own
|
|
23
|
-
* `image.save_render()` on a known float buffer, and the `PyOpenColorIO` 2.5.0
|
|
24
|
-
* that ships inside the oracle.
|
|
25
|
-
*
|
|
26
|
-
* scene linear here save_render OCIO
|
|
27
|
-
* 0.01337859 0.008866 0.00886492 --
|
|
28
|
-
* 0.15904408 0.158059 0.15805990 --
|
|
29
|
-
* 1.01823460 0.559880 0.55986845 --
|
|
30
|
-
* 0.18 0.461326 -- 0.46132022 (sRGB encoded)
|
|
31
|
-
*
|
|
32
|
-
* A render of a flat emission plane is NOT the instrument -- Cycles' film does
|
|
33
|
-
* not hand the view transform exactly the colour the emission node was set to,
|
|
34
|
-
* and chasing that cost a wrong "unexplained residual" once.
|
|
35
|
-
*
|
|
36
|
-
* THE LUT IS DATA, not a curve: `agx-base-srgb.lut` is the config's own
|
|
37
|
-
* `AgX_Base_sRGB.cube` as little-endian uint16 over [0,1], red varying fastest,
|
|
38
|
-
* 185,193 RGB triples. One part in 65,535 is 0.004 of a 255-level step, far
|
|
39
|
-
* under anything measurable here, and it halves what a project carries.
|
|
40
|
-
* Source sha256 e707a36f3e90ee79bc342332febf91334c02ce3974cac700ece00ca9d4507491.
|
|
41
|
-
*
|
|
42
|
-
* NOT WIRED INTO THE VIEW YET, and the reason is named rather than left to be
|
|
43
|
-
* discovered: `renderer.toneMapping` applies per fragment inside every
|
|
44
|
-
* material's shader, and that seam takes an enum, not a 3D texture. Reaching
|
|
45
|
-
* it means the photograph rendering to a float target and this chain running
|
|
46
|
-
* as a full-screen pass. Until then the capture still uses three's curve and
|
|
47
|
-
* the AgX LOOKS are still refused by name in `bpy/_render_three.py` -- which
|
|
48
|
-
* is the honest state, not a degrade. The LOOKS also need a SECOND pinned LUT
|
|
49
|
-
* (`luminance_compensation_bt2020.cube`, 37^3): their process space is `AgX
|
|
50
|
-
* Log`, which is not this chain's E-Gamut path.
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
|
-
/** The LUT's edge length; `AgX_Base_sRGB.cube` says `LUT_3D_SIZE 57`. */
|
|
54
|
-
export const AGX_LUT_SIZE = 57;
|
|
55
|
-
|
|
56
|
-
/** `AllocationTransform {allocation: lg2, vars: [-12.47393, 12.5260688117]}`.
|
|
57
|
-
* The ends are `log2(2^-10 * 0.18)` and `log2(2^15 * 0.18)`: ten stops under
|
|
58
|
-
* middle grey to fifteen over, which is why middle grey lands on exactly
|
|
59
|
-
* 0.4 of the domain. */
|
|
60
|
-
const LOG2_MIN = -12.47393;
|
|
61
|
-
const LOG2_MAX = 12.5260688117;
|
|
62
|
-
|
|
63
|
-
/** Linear Rec.709 -> CIE XYZ D65, the inverse of the config's own
|
|
64
|
-
* `Linear Rec.709` matrix. */
|
|
65
|
-
const RGB_TO_XYZ = [
|
|
66
|
-
[0.4123908, 0.3575843, 0.1804808],
|
|
67
|
-
[0.212639, 0.7151687, 0.0721923],
|
|
68
|
-
[0.0193308, 0.1191948, 0.9505322],
|
|
69
|
-
] as const;
|
|
70
|
-
|
|
71
|
-
/** CIE XYZ D65 -> Linear FilmLight E-Gamut, verbatim from the config. */
|
|
72
|
-
const XYZ_TO_EGAMUT = [
|
|
73
|
-
[1.5250528, -0.3159135, -0.1226583],
|
|
74
|
-
[-0.5091526, 1.3333274, 0.1382844],
|
|
75
|
-
[0.0957153, 0.0508974, 0.7879558],
|
|
76
|
-
] as const;
|
|
77
|
-
|
|
78
|
-
/** In place, into `out`. A frame is 1.3M pixels, so nothing on this path
|
|
79
|
-
* allocates -- see `blender-display-lut.ts`'s header. */
|
|
80
|
-
function transform(
|
|
81
|
-
m: readonly (readonly number[])[],
|
|
82
|
-
v: ArrayLike<number>,
|
|
83
|
-
out: Float64Array,
|
|
84
|
-
): void {
|
|
85
|
-
const a = v[0]!;
|
|
86
|
-
const b = v[1]!;
|
|
87
|
-
const c = v[2]!;
|
|
88
|
-
out[0] = m[0]![0]! * a + m[0]![1]! * b + m[0]![2]! * c;
|
|
89
|
-
out[1] = m[1]![0]! * a + m[1]![1]! * b + m[1]![2]! * c;
|
|
90
|
-
out[2] = m[2]![0]! * a + m[2]![1]! * b + m[2]![2]! * c;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Scratch for the two matrix stages and the display answer. */
|
|
94
|
-
const stageA = new Float64Array(3);
|
|
95
|
-
const stageB = new Float64Array(3);
|
|
96
|
-
const display: [number, number, number] = [0, 0, 0];
|
|
97
|
-
|
|
98
|
-
/** The sRGB display colourspace's own encode -- piecewise, not a pure 2.2. */
|
|
99
|
-
function srgbEncode(value: number): number {
|
|
100
|
-
const v = Math.min(1, Math.max(0, value));
|
|
101
|
-
return v <= 0.0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* The 57^3 LUT, sampled TETRAHEDRALLY -- which is what the config asks for by
|
|
106
|
-
* name (`interpolation: tetrahedral`).
|
|
107
|
-
*
|
|
108
|
-
* On the neutral diagonal every barycentric weight collapses to the same `t`,
|
|
109
|
-
* so a grey input is a straight lerp between the two diagonal corners; that is
|
|
110
|
-
* the case the verification above exercises, and rendering at inputs that land
|
|
111
|
-
* exactly ON a grid point exercises the table itself with no interpolation at
|
|
112
|
-
* all.
|
|
113
|
-
*/
|
|
114
|
-
/** The returned triple is SCRATCH -- the next call overwrites it. */
|
|
115
|
-
export function sampleAgxLut(
|
|
116
|
-
lut: Uint16Array,
|
|
117
|
-
r: number,
|
|
118
|
-
g: number,
|
|
119
|
-
b: number,
|
|
120
|
-
): [number, number, number] {
|
|
121
|
-
return sampleDisplayLut(lut, AGX_LUT_SIZE, r, g, b);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** One scene-linear Rec.709 colour through Blender's AgX, to sRGB display.
|
|
125
|
-
* THE RETURNED TRIPLE IS SCRATCH -- the next call overwrites it. */
|
|
126
|
-
export function agxDisplay(lut: Uint16Array, rgb: ArrayLike<number>): [number, number, number] {
|
|
127
|
-
transform(RGB_TO_XYZ, rgb, stageB);
|
|
128
|
-
transform(XYZ_TO_EGAMUT, stageB, stageA);
|
|
129
|
-
const span = LOG2_MAX - LOG2_MIN;
|
|
130
|
-
const formed = sampleAgxLut(
|
|
131
|
-
lut,
|
|
132
|
-
(Math.log2(Math.max(stageA[0]!, 1e-10)) - LOG2_MIN) / span,
|
|
133
|
-
(Math.log2(Math.max(stageA[1]!, 1e-10)) - LOG2_MIN) / span,
|
|
134
|
-
(Math.log2(Math.max(stageA[2]!, 1e-10)) - LOG2_MIN) / span,
|
|
135
|
-
);
|
|
136
|
-
// The view ends at Rec.1886 and the display is sRGB; both are Rec.709
|
|
137
|
-
// primaries, so the matrices cancel and only the two curves remain.
|
|
138
|
-
display[0] = srgbEncode(Math.max(0, formed[0]) ** 2.4);
|
|
139
|
-
display[1] = srgbEncode(Math.max(0, formed[1]) ** 2.4);
|
|
140
|
-
display[2] = srgbEncode(Math.max(0, formed[2]) ** 2.4);
|
|
141
|
-
return display;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* THE LOOKS, as the config's own composed transform.
|
|
146
|
-
*
|
|
147
|
-
* A look is not a curve that can be applied after the fact. OCIO converts to
|
|
148
|
-
* the look's process space (`AgX Log` -- luminance-compensated Rec.2020 through
|
|
149
|
-
* a SECOND 37^3 LUT, an inset matrix and the lg2 allocation), grades there,
|
|
150
|
-
* converts back, and only then runs the display view. Two of those steps invert
|
|
151
|
-
* a 3D LUT, which is not something to do per pixel in a browser.
|
|
152
|
-
*
|
|
153
|
-
* SO THE COMPOSED look+view IS SAMPLED, by OCIO itself, on the display LUT's
|
|
154
|
-
* own lg2 grid -- `2^(x*(MAX-MIN)+MIN)` at every 57^3 corner, through the
|
|
155
|
-
* `sRGB`/`AgX` display view with the look overridden. The table that comes out
|
|
156
|
-
* is a drop-in for the base one: allocate, sample, done -- its values ARE the
|
|
157
|
-
* sRGB display answer, so no `Rec.1886` step follows it.
|
|
158
|
-
*
|
|
159
|
-
* MEASURED against OCIO's direct answer for ten colours off the grid: WORST 3
|
|
160
|
-
* LEVELS of 255 (`AgX - Medium High Contrast`) and 1 (`AgX - Punchy`). That
|
|
161
|
-
* residual is resampling a curve that already contains an interpolated LUT,
|
|
162
|
-
* and it lands on an artifact that is a DECLARED difference -- a three.js
|
|
163
|
-
* raster is never compared to a Cycles path trace for equality.
|
|
164
|
-
*
|
|
165
|
-
* TWO OTHER REPRESENTATIONS WERE TRIED AND MEASURED WORSE. Baking the look
|
|
166
|
-
* alone as a scene-to-scene map and re-log-encoding it scored 19 levels,
|
|
167
|
-
* because a saturation grade produces NEGATIVE linear values and a log
|
|
168
|
-
* encoding collapses every one of them onto its floor -- which is exactly why
|
|
169
|
-
* the config's own `AgX Log` carries a luminance-compensation LUT to remove
|
|
170
|
-
* the negatives before the grade. Carrying the log span alongside the table
|
|
171
|
-
* did not help, because the loss is the floor, not the clamp.
|
|
172
|
-
*
|
|
173
|
-
* A LOOK WITH NO TABLE HERE IS REFUSED BY NAME in `bpy/_render_three.py`.
|
|
174
|
-
* There is no generic path: `AgX - Base Contrast` is the identity and already
|
|
175
|
-
* accepted, these two are baked, and the rest say so.
|
|
176
|
-
*/
|
|
177
|
-
export const AGX_LOOK_TABLES = {
|
|
178
|
-
'AgX - Medium High Contrast': 'agx-look-medium-high-contrast.lut',
|
|
179
|
-
'AgX - Punchy': 'agx-look-punchy.lut',
|
|
180
|
-
} as const;
|
|
181
|
-
|
|
182
|
-
/** One scene-linear Rec.709 colour through a composed look+view table.
|
|
183
|
-
* The returned triple is SCRATCH, as in `agxDisplay`. */
|
|
184
|
-
export function agxDisplayWithLook(
|
|
185
|
-
lookLut: Uint16Array,
|
|
186
|
-
rgb: ArrayLike<number>,
|
|
187
|
-
): [number, number, number] {
|
|
188
|
-
// The composed table is sampled in the SCENE's own channels: OCIO was handed
|
|
189
|
-
// `2^(x*(MAX-MIN)+MIN)` per axis, so the grid axis IS the allocated scene
|
|
190
|
-
// value and no matrix runs before the lookup.
|
|
191
|
-
const span = LOG2_MAX - LOG2_MIN;
|
|
192
|
-
return sampleAgxLut(
|
|
193
|
-
lookLut,
|
|
194
|
-
(Math.log2(Math.max(rgb[0]!, 1e-10)) - LOG2_MIN) / span,
|
|
195
|
-
(Math.log2(Math.max(rgb[1]!, 1e-10)) - LOG2_MIN) / span,
|
|
196
|
-
(Math.log2(Math.max(rgb[2]!, 1e-10)) - LOG2_MIN) / span,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** One IEEE half (uint16 bits) as a JS number. `readRenderTargetPixels` on a
|
|
201
|
-
* `HalfFloatType` target hands back exactly these. */
|
|
202
|
-
export { decodeHalf } from './blender-display-lut';
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* A captured HDR frame through Blender's AgX, as the RGBA bytes a PNG wants.
|
|
206
|
-
*
|
|
207
|
-
* THE FRAME IS ALREADY THERE. `Object3DDocumentSession.captureImage` renders
|
|
208
|
-
* into a `HalfFloatType` target to keep lit surfaces off the byte clip, and
|
|
209
|
-
* only then resolves through the viewport's output pass into the bytes it
|
|
210
|
-
* reads back. That half-float target is scene-referred linear -- which is the
|
|
211
|
-
* one thing an AgX LOOK, an EXR write and this transform all need and an
|
|
212
|
-
* 8-bit canvas cannot give. `readRenderTargetPixels` accepts it: three's
|
|
213
|
-
* `textureTypeReadable` passes `HalfFloatType` whenever
|
|
214
|
-
* `EXT_color_buffer_half_float` or `EXT_color_buffer_float` is present, and
|
|
215
|
-
* WebGL2 has both.
|
|
216
|
-
*
|
|
217
|
-
* ALPHA IS CARRIED, NOT TRANSFORMED. Blender treats alpha as data, not colour,
|
|
218
|
-
* everywhere else in this lane (`Image.save`'s encode does the same), so it is
|
|
219
|
-
* scaled to a byte and nothing more.
|
|
220
|
-
*/
|
|
221
|
-
export function agxEncodeFrame(
|
|
222
|
-
lut: Uint16Array,
|
|
223
|
-
halfPixels: Uint16Array,
|
|
224
|
-
pixelCount: number,
|
|
225
|
-
options: { readonly exposure?: number; readonly composedLook?: boolean } = {},
|
|
226
|
-
): Uint8ClampedArray {
|
|
227
|
-
// BLENDER'S EXPOSURE IS A LINEAR MULTIPLIER APPLIED BEFORE THE VIEW
|
|
228
|
-
// TRANSFORM (`view_settings.exposure` in stops, which the session already turns
|
|
229
|
-
// into `2 ** stops`), which is the same place `toneMappingExposure` sat.
|
|
230
|
-
const exposure = options.exposure ?? 1;
|
|
231
|
-
// A composed look table IS the display answer; the base table still has the
|
|
232
|
-
// matrices and the `Rec.1886 -> sRGB` tail after it.
|
|
233
|
-
const through = options.composedLook === true ? agxDisplayWithLook : agxDisplay;
|
|
234
|
-
return encodeDisplayFrame(halfPixels, pixelCount, exposure, (rgb) => through(lut, rgb));
|
|
235
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tetrahedral sampling of normalized, red-fastest RGB display tables.
|
|
3
|
-
*
|
|
4
|
-
* THIS FILE IS THE FRAME LOOP, so it allocates NOTHING per pixel. Measured at
|
|
5
|
-
* 1440x900: the previous shape returned a fresh array for each of the four
|
|
6
|
-
* tetrahedron corners and built its result with `.map`, and `encodeDisplayFrame`
|
|
7
|
-
* built a fresh `[r, g, b]` per pixel -- roughly ten allocations per pixel, 13M
|
|
8
|
-
* per frame, and about half the wall clock. Every buffer below is module-level
|
|
9
|
-
* scratch instead, which is why the returned triples are documented as SCRATCH:
|
|
10
|
-
* a caller reads or copies the result before the next call, and the frame loop
|
|
11
|
-
* does exactly that.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/** Scratch: c000, second, third and c111 as four RGB triples. */
|
|
15
|
-
const corner = new Float64Array(12);
|
|
16
|
-
const sampled: [number, number, number] = [0, 0, 0];
|
|
17
|
-
const frac = new Float64Array(3);
|
|
18
|
-
const frameInput = new Float64Array(3);
|
|
19
|
-
|
|
20
|
-
/** THE SIX TETRAHEDRA of a cube cell, one row each: the second corner's
|
|
21
|
-
* offsets, the third corner's offsets, then which of `[fr, fg, fb]` supplies
|
|
22
|
-
* w1, w2 and w3. The same walk the branch chain did -- a flat table keeps the
|
|
23
|
-
* choice out of the sampler's own nesting. */
|
|
24
|
-
// biome-ignore format: one tetrahedron per row is the whole point of the table
|
|
25
|
-
const TETRA = new Int8Array([
|
|
26
|
-
1, 0, 0, 1, 1, 0, 0, 1, 2,
|
|
27
|
-
1, 0, 0, 1, 0, 1, 0, 2, 1,
|
|
28
|
-
0, 0, 1, 1, 0, 1, 2, 0, 1,
|
|
29
|
-
0, 0, 1, 0, 1, 1, 2, 1, 0,
|
|
30
|
-
0, 1, 0, 0, 1, 1, 1, 2, 0,
|
|
31
|
-
0, 1, 0, 1, 1, 0, 1, 0, 2,
|
|
32
|
-
]);
|
|
33
|
-
|
|
34
|
-
/** Which of the six the fractional position falls in -- the three fractions
|
|
35
|
-
* ordered largest first, as tetrahedral interpolation is defined. */
|
|
36
|
-
function tetraCase(fr: number, fg: number, fb: number): number {
|
|
37
|
-
if (fr > fg) {
|
|
38
|
-
if (fg > fb) return 0;
|
|
39
|
-
return fr > fb ? 1 : 2;
|
|
40
|
-
}
|
|
41
|
-
if (fb > fg) return 3;
|
|
42
|
-
if (fb > fr) return 4;
|
|
43
|
-
return 5;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Tetrahedral sampling of normalized, red-fastest RGB display tables.
|
|
47
|
-
* THE RETURNED TRIPLE IS SCRATCH -- the next call overwrites it. */
|
|
48
|
-
export function sampleDisplayLut(
|
|
49
|
-
lut: Uint16Array,
|
|
50
|
-
n: number,
|
|
51
|
-
r: number,
|
|
52
|
-
g: number,
|
|
53
|
-
b: number,
|
|
54
|
-
): [number, number, number] {
|
|
55
|
-
const plane = n * n;
|
|
56
|
-
const x = Math.min(1, Math.max(0, r)) * (n - 1);
|
|
57
|
-
const y = Math.min(1, Math.max(0, g)) * (n - 1);
|
|
58
|
-
const z = Math.min(1, Math.max(0, b)) * (n - 1);
|
|
59
|
-
const ir = Math.min(Math.floor(x), n - 2);
|
|
60
|
-
const ig = Math.min(Math.floor(y), n - 2);
|
|
61
|
-
const ib = Math.min(Math.floor(z), n - 2);
|
|
62
|
-
const fr = x - ir;
|
|
63
|
-
const fg = y - ig;
|
|
64
|
-
const fb = z - ib;
|
|
65
|
-
const read = (slot: number, dr: number, dg: number, db: number): void => {
|
|
66
|
-
const at = (ir + dr + (ig + dg) * n + (ib + db) * plane) * 3;
|
|
67
|
-
corner[slot] = lut[at]! / 65535;
|
|
68
|
-
corner[slot + 1] = lut[at + 1]! / 65535;
|
|
69
|
-
corner[slot + 2] = lut[at + 2]! / 65535;
|
|
70
|
-
};
|
|
71
|
-
read(0, 0, 0, 0);
|
|
72
|
-
read(9, 1, 1, 1);
|
|
73
|
-
frac[0] = fr;
|
|
74
|
-
frac[1] = fg;
|
|
75
|
-
frac[2] = fb;
|
|
76
|
-
const t = tetraCase(fr, fg, fb) * 9;
|
|
77
|
-
read(3, TETRA[t]!, TETRA[t + 1]!, TETRA[t + 2]!);
|
|
78
|
-
read(6, TETRA[t + 3]!, TETRA[t + 4]!, TETRA[t + 5]!);
|
|
79
|
-
const w1 = frac[TETRA[t + 6]!]!;
|
|
80
|
-
const w2 = frac[TETRA[t + 7]!]!;
|
|
81
|
-
const w3 = frac[TETRA[t + 8]!]!;
|
|
82
|
-
for (let k = 0; k < 3; k++) {
|
|
83
|
-
const c000 = corner[k]!;
|
|
84
|
-
sampled[k] =
|
|
85
|
-
c000 +
|
|
86
|
-
w1 * (corner[3 + k]! - c000) +
|
|
87
|
-
w2 * (corner[6 + k]! - corner[3 + k]!) +
|
|
88
|
-
w3 * (corner[9 + k]! - corner[6 + k]!);
|
|
89
|
-
}
|
|
90
|
-
return sampled;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function decodeHalf(bits: number): number {
|
|
94
|
-
const sign = bits & 0x8000 ? -1 : 1;
|
|
95
|
-
const exponent = (bits >> 10) & 0x1f;
|
|
96
|
-
const fraction = bits & 0x3ff;
|
|
97
|
-
if (exponent === 0) return sign * 2 ** -24 * fraction;
|
|
98
|
-
if (exponent === 31) return fraction ? Number.NaN : sign * Number.POSITIVE_INFINITY;
|
|
99
|
-
return sign * 2 ** (exponent - 15) * (1 + fraction / 1024);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Transform associated scene-linear half-float RGBA to straight display RGBA.
|
|
103
|
-
* Unassociate before the nonlinear view transform; keep the input frame intact
|
|
104
|
-
* for the compositor and EXR. Alpha remains ungraded data.
|
|
105
|
-
* `through` is handed a REUSED triple -- it must read its three values before
|
|
106
|
-
* returning, which every display chain here does. */
|
|
107
|
-
export function encodeDisplayFrame(
|
|
108
|
-
halfPixels: Uint16Array,
|
|
109
|
-
pixelCount: number,
|
|
110
|
-
exposure: number,
|
|
111
|
-
through: (rgb: ArrayLike<number>) => readonly [number, number, number],
|
|
112
|
-
): Uint8ClampedArray {
|
|
113
|
-
const out = new Uint8ClampedArray(pixelCount * 4);
|
|
114
|
-
for (let i = 0; i < pixelCount; i++) {
|
|
115
|
-
const at = i * 4;
|
|
116
|
-
const alpha = decodeHalf(halfPixels[at + 3]!);
|
|
117
|
-
const scale = alpha === 0 ? exposure : exposure / alpha;
|
|
118
|
-
frameInput[0] = decodeHalf(halfPixels[at]!) * scale;
|
|
119
|
-
frameInput[1] = decodeHalf(halfPixels[at + 1]!) * scale;
|
|
120
|
-
frameInput[2] = decodeHalf(halfPixels[at + 2]!) * scale;
|
|
121
|
-
const display = through(frameInput);
|
|
122
|
-
out[at] = Math.round(display[0] * 255);
|
|
123
|
-
out[at + 1] = Math.round(display[1] * 255);
|
|
124
|
-
out[at + 2] = Math.round(display[2] * 255);
|
|
125
|
-
out[at + 3] = Math.round(Math.min(1, Math.max(0, alpha)) * 255);
|
|
126
|
-
}
|
|
127
|
-
return out;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Where a display table lives: beside this module, wherever this module is
|
|
132
|
-
* served from.
|
|
133
|
-
*
|
|
134
|
-
* THE `.lut` SUFFIX IS SPELLED IN THE TEMPLATE, NOT IN THE ARGUMENT, and that
|
|
135
|
-
* is the difference between a four-file glob and a whole-directory one. A
|
|
136
|
-
* bundler cannot see the value of `file`, so it resolves
|
|
137
|
-
* `new URL(`./${x}`, import.meta.url)` by globbing EVERY sibling -- measured
|
|
138
|
-
* 2026-09-20 on the release build: every `.ts` in this directory came out
|
|
139
|
-
* inlined as a base64 data URL beside the tables, 6.4 MB of module. With the
|
|
140
|
-
* suffix in the template the glob is `./*.lut` and only the four tables are
|
|
141
|
-
* reachable, which is the whole truth of what this function can return.
|
|
142
|
-
*/
|
|
143
|
-
export function displayTableUrl(file: string): URL {
|
|
144
|
-
return new URL(`./${file.replace(/\.lut$/, '')}.lut`, import.meta.url);
|
|
145
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { encodeDisplayFrame, sampleDisplayLut } from './blender-display-lut';
|
|
2
|
-
|
|
3
|
-
/** Blender 5.2 fbe6228777e7 OCIO Filmic/sRGB: log allocation, highlight
|
|
4
|
-
* desaturation, then the display curve. filmic-srgb.lut contains the pinned
|
|
5
|
-
* filmic_desat_33.cube followed by its 4096-entry display curve, normalized
|
|
6
|
-
* to little-endian uint16. The table output is already display-encoded sRGB.
|
|
7
|
-
*/
|
|
8
|
-
const SIZE = 33;
|
|
9
|
-
const CUBE_VALUES = SIZE ** 3 * 3;
|
|
10
|
-
const CURVE_SIZE = 4096;
|
|
11
|
-
const LOG_MIN = -12.473931188;
|
|
12
|
-
const LOG_SPAN = 25;
|
|
13
|
-
|
|
14
|
-
function curve(lut: Uint16Array, value: number): number {
|
|
15
|
-
const position = Math.min(1, Math.max(0, value)) * (CURVE_SIZE - 1);
|
|
16
|
-
const index = Math.min(Math.floor(position), CURVE_SIZE - 2);
|
|
17
|
-
const t = position - index;
|
|
18
|
-
return ((1 - t) * lut[CUBE_VALUES + index]! + t * lut[CUBE_VALUES + index + 1]!) / 65535;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Scratch, as everything on the frame path is -- see `blender-display-lut.ts`. */
|
|
22
|
-
const display: [number, number, number] = [0, 0, 0];
|
|
23
|
-
|
|
24
|
-
/** One scene-linear colour through Filmic, to sRGB display.
|
|
25
|
-
* THE RETURNED TRIPLE IS SCRATCH -- the next call overwrites it. */
|
|
26
|
-
export function filmicDisplay(lut: Uint16Array, rgb: ArrayLike<number>): [number, number, number] {
|
|
27
|
-
const desaturated = sampleDisplayLut(
|
|
28
|
-
lut,
|
|
29
|
-
SIZE,
|
|
30
|
-
(Math.log2(Math.max(rgb[0]!, 1e-10)) - LOG_MIN) / LOG_SPAN,
|
|
31
|
-
(Math.log2(Math.max(rgb[1]!, 1e-10)) - LOG_MIN) / LOG_SPAN,
|
|
32
|
-
(Math.log2(Math.max(rgb[2]!, 1e-10)) - LOG_MIN) / LOG_SPAN,
|
|
33
|
-
);
|
|
34
|
-
display[0] = curve(lut, desaturated[0] / 0.66);
|
|
35
|
-
display[1] = curve(lut, desaturated[1] / 0.66);
|
|
36
|
-
display[2] = curve(lut, desaturated[2] / 0.66);
|
|
37
|
-
return display;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function filmicEncodeFrame(
|
|
41
|
-
lut: Uint16Array,
|
|
42
|
-
pixels: Uint16Array,
|
|
43
|
-
count: number,
|
|
44
|
-
exposure: number,
|
|
45
|
-
): Uint8ClampedArray {
|
|
46
|
-
if (lut.length !== CUBE_VALUES + CURVE_SIZE)
|
|
47
|
-
throw new Error('Invalid Blender Filmic display table');
|
|
48
|
-
return encodeDisplayFrame(pixels, count, exposure, (rgb) => filmicDisplay(lut, rgb));
|
|
49
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Blender's `Standard` view transform, off a scene-linear frame.
|
|
3
|
-
*
|
|
4
|
-
* THE CONFIG SAYS WHAT THIS IS. In the pinned colour configuration
|
|
5
|
-
* (`release/datafiles/colormanagement/config.ocio`) the `Standard` view
|
|
6
|
-
* resolves to the `sRGB` display colour space, whose `from_display_reference`
|
|
7
|
-
* is a `ColorSpaceTransform` to `Linear Rec.709` followed by
|
|
8
|
-
* `ExponentWithLinearTransform {gamma: 2.4, offset: 0.055, direction: inverse}`.
|
|
9
|
-
* Blender's scene reference IS Linear Rec.709, so the colour-space leg is the
|
|
10
|
-
* identity and the whole transform is that one curve — the sRGB compound
|
|
11
|
-
* (piece-wise) encoding, with no LUT and nothing to load.
|
|
12
|
-
*
|
|
13
|
-
* THE CONSTANTS ARE DERIVED, NOT QUOTED. OCIO computes the break point and the
|
|
14
|
-
* linear slope from `gamma` and `offset` by requiring the two pieces to meet
|
|
15
|
-
* with a continuous derivative, which lands at 0.0030402 and 12.9232 — close
|
|
16
|
-
* to, and deliberately not, IEC 61966-2-1's rounded 0.0031308 and 12.92.
|
|
17
|
-
* Deriving them here keeps the two numbers honest against the two the config
|
|
18
|
-
* actually states.
|
|
19
|
-
*
|
|
20
|
-
* WHERE IT IS USED, and where it is not: a COMPOSITED frame, whose pixels are
|
|
21
|
-
* the compositor's scene-referred output and can no longer be photographed
|
|
22
|
-
* through three's own output encoding. An ordinary Standard render is still
|
|
23
|
-
* three's capture, unchanged — it is the same curve applied by the same
|
|
24
|
-
* renderer that drew the frame, and moving it here would change bytes for no
|
|
25
|
-
* reason.
|
|
26
|
-
*
|
|
27
|
-
* NEGATIVE INPUT: clamped. OCIO's default negative handling for this transform
|
|
28
|
-
* mirrors the curve about zero, which lands below zero and clips to byte 0 in
|
|
29
|
-
* `encodeDisplayFrame` — the same byte clamping to zero produces, so the two
|
|
30
|
-
* readings are indistinguishable in the PNG this writes.
|
|
31
|
-
*/
|
|
32
|
-
import { encodeDisplayFrame } from './blender-display-lut';
|
|
33
|
-
|
|
34
|
-
const GAMMA = 2.4;
|
|
35
|
-
const OFFSET = 0.055;
|
|
36
|
-
/** The display-side break, where the power piece meets the linear one. */
|
|
37
|
-
const BREAK = OFFSET / (GAMMA - 1);
|
|
38
|
-
/** The linear piece's slope, from matching the power piece's derivative there. */
|
|
39
|
-
const SLOPE = 1 / ((GAMMA / (1 + OFFSET)) * ((BREAK + OFFSET) / (1 + OFFSET)) ** (GAMMA - 1));
|
|
40
|
-
/** The same break expressed on the scene-linear side. */
|
|
41
|
-
const LINEAR_BREAK = ((BREAK + OFFSET) / (1 + OFFSET)) ** GAMMA;
|
|
42
|
-
|
|
43
|
-
/** One scene-linear channel through the sRGB compound encoding. */
|
|
44
|
-
export function standardChannel(value: number): number {
|
|
45
|
-
if (!(value > 0)) return 0;
|
|
46
|
-
if (value < LINEAR_BREAK) return value * SLOPE;
|
|
47
|
-
return (1 + OFFSET) * value ** (1 / GAMMA) - OFFSET;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Scratch, as everything on the frame path is — see `blender-display-lut.ts`. */
|
|
51
|
-
const display: [number, number, number] = [0, 0, 0];
|
|
52
|
-
|
|
53
|
-
/** One scene-linear colour through Standard, to sRGB display.
|
|
54
|
-
* THE RETURNED TRIPLE IS SCRATCH — the next call overwrites it. */
|
|
55
|
-
export function standardDisplay(rgb: ArrayLike<number>): [number, number, number] {
|
|
56
|
-
display[0] = standardChannel(rgb[0]!);
|
|
57
|
-
display[1] = standardChannel(rgb[1]!);
|
|
58
|
-
display[2] = standardChannel(rgb[2]!);
|
|
59
|
-
return display;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function standardEncodeFrame(
|
|
63
|
-
pixels: Uint16Array,
|
|
64
|
-
count: number,
|
|
65
|
-
exposure: number,
|
|
66
|
-
): Uint8ClampedArray {
|
|
67
|
-
return encodeDisplayFrame(pixels, count, exposure, standardDisplay);
|
|
68
|
-
}
|