@volter/blender-engine 0.1.148 → 0.1.149
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 +16 -0
- 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 +60 -128
- package/browser/blender-engine.mts +20 -186
- package/browser/blender-wali-engine.mts +35 -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 +2 -0
- package/browser/protocol.ts +119 -35
- package/browser/pull-job.mts +2 -10
- package/browser/rna.ts +190 -73
- package/browser/runtime.ts +197 -82
- package/browser/session-addons.mts +33 -0
- package/browser/session-frame.mts +5 -24
- package/browser/session.py +1338 -1427
- 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 +505 -283
- 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 +156 -151
- 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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLENDER'S SOLID OBJECT OUTLINE (`View3DShading.show_object_outline`), as Workbench draws it:
|
|
3
|
+
* every object is drawn into an object-id buffer (the background is id 0), and each pixel is
|
|
4
|
+
* darkened toward `object_outline_color` by the share of its four neighbours, one pixel away
|
|
5
|
+
* (`ui_scale` pixels), whose id differs from its own:
|
|
6
|
+
*
|
|
7
|
+
* opacity = 1 - 0.25 * (number of the four neighbours with the pixel's own id)
|
|
8
|
+
* colour = outline_colour * opacity + colour * (1 - opacity) (premultiplied alpha over)
|
|
9
|
+
*
|
|
10
|
+
* (`workbench_effect_outline_frag.glsl`). So a silhouette against the background and the seam
|
|
11
|
+
* between two touching objects both carry a band on each side. It is drawn over the shaded frame
|
|
12
|
+
* in scene-linear, before the view transform, as Blender composites it.
|
|
13
|
+
*
|
|
14
|
+
* One object, one id: a mesh the frame presents is one Blender object or one instance of a
|
|
15
|
+
* collection, each its own draw in Blender's id buffer.
|
|
16
|
+
*/
|
|
17
|
+
import * as THREE from 'three';
|
|
18
|
+
import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js';
|
|
19
|
+
|
|
20
|
+
const ID_VERTEX = /* glsl */ `
|
|
21
|
+
#include <common>
|
|
22
|
+
#include <batching_pars_vertex>
|
|
23
|
+
#include <morphtarget_pars_vertex>
|
|
24
|
+
#include <skinning_pars_vertex>
|
|
25
|
+
#include <logdepthbuf_pars_vertex>
|
|
26
|
+
#include <clipping_planes_pars_vertex>
|
|
27
|
+
void main() {
|
|
28
|
+
#include <batching_vertex>
|
|
29
|
+
#include <skinbase_vertex>
|
|
30
|
+
#include <morphinstance_vertex>
|
|
31
|
+
#include <begin_vertex>
|
|
32
|
+
#include <morphtarget_vertex>
|
|
33
|
+
#include <skinning_vertex>
|
|
34
|
+
#include <project_vertex>
|
|
35
|
+
#include <logdepthbuf_vertex>
|
|
36
|
+
#include <clipping_planes_vertex>
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const ID_FRAGMENT = /* glsl */ `
|
|
41
|
+
uniform vec3 objectId;
|
|
42
|
+
#include <logdepthbuf_pars_fragment>
|
|
43
|
+
#include <clipping_planes_pars_fragment>
|
|
44
|
+
void main() {
|
|
45
|
+
#include <clipping_planes_fragment>
|
|
46
|
+
#include <logdepthbuf_fragment>
|
|
47
|
+
gl_FragColor = vec4(objectId, 1.0);
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
/** The outline itself, over the frame: Blender's four-neighbour test on the id buffer. */
|
|
52
|
+
const OUTLINE_FRAGMENT = /* glsl */ `
|
|
53
|
+
precision highp float;
|
|
54
|
+
uniform sampler2D ids;
|
|
55
|
+
uniform int reach;
|
|
56
|
+
uniform vec3 outlineColor;
|
|
57
|
+
in vec2 vUv;
|
|
58
|
+
out vec4 outputColor;
|
|
59
|
+
vec4 idAt(ivec2 p, ivec2 size) { return texelFetch(ids, clamp(p, ivec2(0), size - 1), 0); }
|
|
60
|
+
void main() {
|
|
61
|
+
ivec2 size = textureSize(ids, 0);
|
|
62
|
+
ivec2 p = ivec2(vUv * vec2(size));
|
|
63
|
+
vec4 centre = idAt(p, size);
|
|
64
|
+
float same = 0.0;
|
|
65
|
+
same += all(equal(idAt(p + ivec2(0, reach), size), centre)) ? 1.0 : 0.0;
|
|
66
|
+
same += all(equal(idAt(p - ivec2(0, reach), size), centre)) ? 1.0 : 0.0;
|
|
67
|
+
same += all(equal(idAt(p + ivec2(reach, 0), size), centre)) ? 1.0 : 0.0;
|
|
68
|
+
same += all(equal(idAt(p - ivec2(reach, 0), size), centre)) ? 1.0 : 0.0;
|
|
69
|
+
float opacity = 1.0 - 0.25 * same;
|
|
70
|
+
outputColor = vec4(outlineColor * opacity, opacity);
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
export class WorkbenchOutline {
|
|
75
|
+
private target: THREE.WebGLRenderTarget | null = null;
|
|
76
|
+
private readonly ids = new THREE.ShaderMaterial({
|
|
77
|
+
name: 'Blender object id',
|
|
78
|
+
uniforms: { objectId: { value: new THREE.Color() } },
|
|
79
|
+
vertexShader: ID_VERTEX,
|
|
80
|
+
fragmentShader: ID_FRAGMENT,
|
|
81
|
+
});
|
|
82
|
+
private readonly composite = new THREE.RawShaderMaterial({
|
|
83
|
+
name: 'Blender object outline',
|
|
84
|
+
glslVersion: THREE.GLSL3,
|
|
85
|
+
uniforms: { ids: { value: null }, reach: { value: 1 }, outlineColor: { value: new THREE.Color() } },
|
|
86
|
+
vertexShader: `precision highp float; in vec3 position; in vec2 uv; out vec2 vUv;
|
|
87
|
+
void main(){vUv=uv;gl_Position=vec4(position,1.0);}`,
|
|
88
|
+
fragmentShader: OUTLINE_FRAGMENT,
|
|
89
|
+
depthTest: false,
|
|
90
|
+
depthWrite: false,
|
|
91
|
+
transparent: true,
|
|
92
|
+
blending: THREE.CustomBlending,
|
|
93
|
+
blendSrc: THREE.OneFactor,
|
|
94
|
+
blendDst: THREE.OneMinusSrcAlphaFactor,
|
|
95
|
+
blendSrcAlpha: THREE.OneFactor,
|
|
96
|
+
blendDstAlpha: THREE.OneMinusSrcAlphaFactor,
|
|
97
|
+
});
|
|
98
|
+
private readonly quad = new FullScreenQuad(this.composite);
|
|
99
|
+
private readonly idOf = new Map<THREE.Object3D, number>();
|
|
100
|
+
|
|
101
|
+
constructor() {
|
|
102
|
+
// Each draw carries its own object's id; the material is shared by every draw of the pass.
|
|
103
|
+
this.ids.onBeforeRender = (_renderer, _scene, _camera, _geometry, object) => {
|
|
104
|
+
const id = this.idOf.get(object) ?? 0;
|
|
105
|
+
(this.ids.uniforms['objectId']!.value as THREE.Color).setRGB(
|
|
106
|
+
(id & 0xff) / 255, ((id >> 8) & 0xff) / 255, ((id >> 16) & 0xff) / 255, THREE.LinearSRGBColorSpace);
|
|
107
|
+
this.ids.uniformsNeedUpdate = true;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Draw the outline over what the renderer's CURRENT target holds (the frame just drawn through
|
|
113
|
+
* `camera`), `reach` target pixels out. `objects` are the frame's presented objects in its
|
|
114
|
+
* order, each drawn with its own id where `drawn(object)` says it is drawn; `internal` are the
|
|
115
|
+
* presenter's own draws standing in for some of them (batches), hidden for the id pass.
|
|
116
|
+
*/
|
|
117
|
+
draw(
|
|
118
|
+
renderer: THREE.WebGLRenderer,
|
|
119
|
+
scene: THREE.Scene,
|
|
120
|
+
camera: THREE.Camera,
|
|
121
|
+
objects: Iterable<THREE.Object3D>,
|
|
122
|
+
drawn: (object: THREE.Object3D) => boolean,
|
|
123
|
+
internal: readonly THREE.Object3D[],
|
|
124
|
+
color: readonly [number, number, number],
|
|
125
|
+
reach: number,
|
|
126
|
+
): void {
|
|
127
|
+
const output = renderer.getRenderTarget();
|
|
128
|
+
const size = output ? new THREE.Vector2(output.width, output.height) : renderer.getDrawingBufferSize(new THREE.Vector2());
|
|
129
|
+
if (size.x < 1 || size.y < 1) return;
|
|
130
|
+
this.target ??= new THREE.WebGLRenderTarget(size.x, size.y, {
|
|
131
|
+
minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, generateMipmaps: false, depthBuffer: true,
|
|
132
|
+
});
|
|
133
|
+
if (this.target.width !== size.x || this.target.height !== size.y) this.target.setSize(size.x, size.y);
|
|
134
|
+
this.idOf.clear();
|
|
135
|
+
const layers = new Map<THREE.Object3D, number>();
|
|
136
|
+
const shown = new Map<THREE.Object3D, boolean>();
|
|
137
|
+
let next = 1;
|
|
138
|
+
for (const object of objects) {
|
|
139
|
+
if (!(object as THREE.Mesh).isMesh) continue;
|
|
140
|
+
this.idOf.set(object, next++);
|
|
141
|
+
// A batched member is drawn by its batch, with its own layer cleared; the id pass draws
|
|
142
|
+
// the member itself, so it carries its own id.
|
|
143
|
+
layers.set(object, object.layers.mask);
|
|
144
|
+
object.layers.mask = drawn(object) ? 1 : 0;
|
|
145
|
+
}
|
|
146
|
+
for (const object of internal) {
|
|
147
|
+
shown.set(object, object.visible);
|
|
148
|
+
object.visible = false;
|
|
149
|
+
}
|
|
150
|
+
const background = scene.background;
|
|
151
|
+
const override = scene.overrideMaterial;
|
|
152
|
+
const autoClear = renderer.autoClear;
|
|
153
|
+
const shadows = renderer.shadowMap.enabled;
|
|
154
|
+
const clearColor = renderer.getClearColor(new THREE.Color());
|
|
155
|
+
const clearAlpha = renderer.getClearAlpha();
|
|
156
|
+
const cameraLayers = camera.layers.mask;
|
|
157
|
+
try {
|
|
158
|
+
scene.background = null;
|
|
159
|
+
scene.overrideMaterial = this.ids;
|
|
160
|
+
renderer.autoClear = false;
|
|
161
|
+
renderer.shadowMap.enabled = false;
|
|
162
|
+
camera.layers.mask = 1;
|
|
163
|
+
renderer.setRenderTarget(this.target);
|
|
164
|
+
renderer.setClearColor(0x000000, 0);
|
|
165
|
+
renderer.clear(true, true, false);
|
|
166
|
+
renderer.render(scene, camera);
|
|
167
|
+
} finally {
|
|
168
|
+
scene.background = background;
|
|
169
|
+
scene.overrideMaterial = override;
|
|
170
|
+
renderer.autoClear = autoClear;
|
|
171
|
+
renderer.shadowMap.enabled = shadows;
|
|
172
|
+
renderer.setClearColor(clearColor, clearAlpha);
|
|
173
|
+
camera.layers.mask = cameraLayers;
|
|
174
|
+
for (const [object, mask] of layers) object.layers.mask = mask;
|
|
175
|
+
for (const [object, visible] of shown) object.visible = visible;
|
|
176
|
+
renderer.setRenderTarget(output);
|
|
177
|
+
}
|
|
178
|
+
this.composite.uniforms['ids']!.value = this.target.texture;
|
|
179
|
+
this.composite.uniforms['reach']!.value = Math.max(1, Math.round(reach));
|
|
180
|
+
(this.composite.uniforms['outlineColor']!.value as THREE.Color).setRGB(color[0], color[1], color[2], THREE.LinearSRGBColorSpace);
|
|
181
|
+
// Laid OVER the frame: a renderer that clears before every draw (`autoClear`, on wherever no
|
|
182
|
+
// composer has turned it off) would wipe the frame first and leave the outline alone.
|
|
183
|
+
renderer.autoClear = false;
|
|
184
|
+
try {
|
|
185
|
+
this.quad.render(renderer);
|
|
186
|
+
} finally {
|
|
187
|
+
renderer.autoClear = autoClear;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
dispose(): void {
|
|
192
|
+
this.target?.dispose();
|
|
193
|
+
this.target = null;
|
|
194
|
+
this.ids.dispose();
|
|
195
|
+
this.composite.dispose();
|
|
196
|
+
this.quad.dispose();
|
|
197
|
+
this.idOf.clear();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -131,6 +131,7 @@ export class WorldVolumePass {
|
|
|
131
131
|
};
|
|
132
132
|
let declarations = '', lighting = '';
|
|
133
133
|
let lightIndex = 0, samplers = 2;
|
|
134
|
+
const shadowKinds = new Set<string>();
|
|
134
135
|
scene.traverseVisible(object => {
|
|
135
136
|
if (!(object instanceof THREE.PointLight || object instanceof THREE.SpotLight || object instanceof THREE.RectAreaLight)) return;
|
|
136
137
|
const light = object, id = lightIndex++;
|
|
@@ -145,18 +146,26 @@ export class WorldVolumePass {
|
|
|
145
146
|
declarations += `uniform vec3 ld${id}; uniform vec2 cone${id};\n`;
|
|
146
147
|
spot = `smoothstep(cone${id}.x, max(cone${id}.x+1e-6,cone${id}.y),dot(-L,ld${id}))`;
|
|
147
148
|
}
|
|
148
|
-
|
|
149
|
+
const depthMap = light instanceof THREE.RectAreaLight || !light.castShadow ? null : light.shadow.map?.depthTexture ?? null;
|
|
150
|
+
if (!(light instanceof THREE.RectAreaLight) && depthMap) {
|
|
151
|
+
// THE SHADOW MAP AS THREE.JS (r182+) KEEPS IT: the light's depth, in `shadow.map.depthTexture` (a cube of
|
|
152
|
+
// depths for a point light). With PCF it is a comparison texture (hardware depth compare, sampled through
|
|
153
|
+
// a shadow sampler), otherwise plain depths. The pass samples it with functions of its own: three's
|
|
154
|
+
// `getShadow`/`getPointShadow` exist only for the lights its own programs count, and their signatures
|
|
155
|
+
// follow its internals.
|
|
149
156
|
if (++samplers > renderer.capabilities.maxTextures) throw new Error('World volume shadow samplers exceed this GPU texture-unit limit');
|
|
150
|
-
|
|
157
|
+
const point = light instanceof THREE.PointLight, compare = depthMap.compareFunction !== null;
|
|
158
|
+
uniforms[`sm${id}`] = {value: depthMap};
|
|
151
159
|
uniforms[`sx${id}`] = {value: light.shadow.matrix};
|
|
152
|
-
uniforms[`ss${id}`] = {value: light.shadow.mapSize};
|
|
153
160
|
uniforms[`sb${id}`] = {value: light.shadow.bias};
|
|
154
161
|
uniforms[`sn${id}`] = {value: light.shadow.camera.near};
|
|
155
162
|
uniforms[`sf${id}`] = {value: light.shadow.camera.far};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
const sampler = point ? (compare ? 'samplerCubeShadow' : 'samplerCube') : (compare ? 'sampler2DShadow' : 'sampler2D');
|
|
164
|
+
declarations += `uniform ${sampler} sm${id}; uniform mat4 sx${id}; uniform float sb${id},sn${id},sf${id};\n`;
|
|
165
|
+
visibility = point
|
|
166
|
+
? `cubeShadow(sm${id},sb${id},sn${id},sf${id},(sx${id}*vec4(p,1.0)).xyz)`
|
|
167
|
+
: `planeShadow(sm${id},sb${id},sx${id}*vec4(p,1.0))`;
|
|
168
|
+
shadowKinds.add(sampler);
|
|
160
169
|
}
|
|
161
170
|
if (light instanceof THREE.RectAreaLight) {
|
|
162
171
|
const rotation = new THREE.Matrix4().extractRotation(light.matrixWorld);
|
|
@@ -189,17 +198,60 @@ export class WorldVolumePass {
|
|
|
189
198
|
const positive = this.medium.extinction.toArray().filter(v => v > 0);
|
|
190
199
|
uniforms['range'] = {value: positive.length ? 20/Math.min(...positive) : 1};
|
|
191
200
|
this.material?.dispose();
|
|
201
|
+
// Each sampler kind used gets its function; a reversed depth buffer (three's `reversedDepthBuffer`) writes
|
|
202
|
+
// and compares depths the other way round, as three's own shadow functions handle it.
|
|
203
|
+
const reversed = renderer.state.buffers.depth.getReversed();
|
|
204
|
+
let shadowFunctions = reversed ? '#define VOLUME_REVERSED_DEPTH\n' : '';
|
|
205
|
+
const plane = (sampler: string, test: string) => `float planeShadow(${sampler} map,float bias,vec4 c){
|
|
206
|
+
if(c.w<=0.) return 1.; c.xyz/=c.w; ${test} }\n`;
|
|
207
|
+
if (shadowKinds.has('sampler2DShadow'))
|
|
208
|
+
shadowFunctions += plane('sampler2DShadow', `c.z+=bias;
|
|
209
|
+
if(c.x<0.||c.x>1.||c.y<0.||c.y>1.||c.z>1.) return 1.;
|
|
210
|
+
return texture(map,vec3(c.xy,c.z));`);
|
|
211
|
+
if (shadowKinds.has('sampler2D'))
|
|
212
|
+
shadowFunctions += plane('sampler2D', `
|
|
213
|
+
#ifdef VOLUME_REVERSED_DEPTH
|
|
214
|
+
c.z-=bias;
|
|
215
|
+
#else
|
|
216
|
+
c.z+=bias;
|
|
217
|
+
#endif
|
|
218
|
+
if(c.x<0.||c.x>1.||c.y<0.||c.y>1.||c.z>1.) return 1.;
|
|
219
|
+
float stored=texture(map,c.xy).r;
|
|
220
|
+
#ifdef VOLUME_REVERSED_DEPTH
|
|
221
|
+
return step(stored,c.z);
|
|
222
|
+
#else
|
|
223
|
+
return step(c.z,stored);
|
|
224
|
+
#endif`);
|
|
225
|
+
// A point light's cube holds each face's perspective depth along that face's axis (the largest component
|
|
226
|
+
// of the light-to-point vector), between the shadow camera's near and far.
|
|
227
|
+
const cube = (sampler: string, test: string) => `float cubeShadow(${sampler} map,float bias,float near,float far,vec3 v){
|
|
228
|
+
vec3 a=abs(v); float z=max(max(a.x,a.y),a.z);
|
|
229
|
+
if(z>far||z<near) return 1.;
|
|
230
|
+
${test} }\n`;
|
|
231
|
+
if (shadowKinds.has('samplerCubeShadow'))
|
|
232
|
+
shadowFunctions += cube('samplerCubeShadow', `
|
|
233
|
+
#ifdef VOLUME_REVERSED_DEPTH
|
|
234
|
+
float dp=near*(far-z)/(z*(far-near))-bias;
|
|
235
|
+
#else
|
|
236
|
+
float dp=far*(z-near)/(z*(far-near))+bias;
|
|
237
|
+
#endif
|
|
238
|
+
return texture(map,vec4(normalize(v),dp));`);
|
|
239
|
+
if (shadowKinds.has('samplerCube'))
|
|
240
|
+
shadowFunctions += cube('samplerCube', `float dp=far*(z-near)/(z*(far-near))+bias;
|
|
241
|
+
float stored=texture(map,normalize(v)).r;
|
|
242
|
+
#ifdef VOLUME_REVERSED_DEPTH
|
|
243
|
+
stored=1.-stored;
|
|
244
|
+
#endif
|
|
245
|
+
return step(dp,stored);`);
|
|
192
246
|
this.material = new THREE.ShaderMaterial({uniforms, depthTest: false, depthWrite: false, toneMapped: false,
|
|
193
|
-
defines: {USE_SHADOWMAP: '', NUM_DIR_LIGHT_SHADOWS: 0, NUM_SPOT_LIGHT_SHADOWS: 0, NUM_POINT_LIGHT_SHADOWS: 0, NUM_SPOT_LIGHT_COORDS: 0},
|
|
194
247
|
vertexShader: 'varying vec2 vUv; void main(){vUv=uv;gl_Position=vec4(position.xy,0.,1.);}',
|
|
195
248
|
fragmentShader: `
|
|
196
249
|
#include <common>
|
|
197
|
-
#include <packing>
|
|
198
|
-
#include <shadowmap_pars_fragment>
|
|
199
250
|
varying vec2 vUv; uniform sampler2D surface,depth;
|
|
200
251
|
uniform mat4 inverseProjection,cameraWorld; uniform bool orthographic;
|
|
201
252
|
uniform vec3 sigma,emission; uniform float range;
|
|
202
253
|
${declarations}
|
|
254
|
+
${shadowFunctions}
|
|
203
255
|
vec3 phase(float c){${phase} return result;}
|
|
204
256
|
vec3 incident(vec3 p,vec3 ray){vec3 sum=vec3(0.);${lighting}return sum;}
|
|
205
257
|
float integral(float s,float d){float x=s*d;return x<.001 ? d*(1.-x*.5+x*x/6.) : (1.-exp(-x))/s;}
|
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import { createBlenderDisplayTransform, blenderDisplaySettingsForRender } from './blender-display-transform';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* display transform, answer. The editor performs that act with its own stage
|
|
9
|
-
* and renderer (`packages/editor-blender/host/blender-runtime-host.ts`), which is why
|
|
10
|
-
* the editor never imports THIS file. A host that has no renderer — a bare
|
|
11
|
-
* page with `@volter/browser-wali`, an Emscripten page, the substrate
|
|
12
|
-
* workbench — attaches one of these to the Blender it started, and the guest's
|
|
13
|
-
* `bpy.ops.render.render` comes back as a three.js photograph exactly as it
|
|
14
|
-
* does in the editor.
|
|
15
|
-
*
|
|
16
|
-
* What it does NOT do, on purpose: overlays (modeling chrome, hidden for a
|
|
17
|
-
* render anyway), selection outlines, the editor's supersampled `captureImage`
|
|
18
|
-
* for the Standard transform. Every transform here goes through the linear
|
|
19
|
-
* capture and the same OCIO GPU display processors the editor uses.
|
|
3
|
+
* Present Blender frames for a host without an editor stage. Translate the scene through
|
|
4
|
+
* BlenderRuntimeView, render it with a dedicated camera into a scene-linear target, then apply
|
|
5
|
+
* Blender’s OCIO display transform and return the photograph. Editor hosts wrap the same
|
|
6
|
+
* translation with their own stage and renderer. This presenter supplies no modeling overlays or
|
|
7
|
+
* selection outlines.
|
|
20
8
|
*/
|
|
21
9
|
import * as THREE from 'three';
|
|
22
10
|
import type { CaptureRequest } from '../protocol';
|
|
23
11
|
import type { PresentAnswer } from '../runtime';
|
|
24
12
|
import { BlenderRuntimeView } from './blender-runtime-view';
|
|
13
|
+
import { bytesFromBase64 } from './blender-base64';
|
|
25
14
|
|
|
26
15
|
export interface PresenterOptions {
|
|
27
16
|
/** The canvas to render into; an OffscreenCanvas of 1x1 when absent. */
|
|
@@ -81,7 +70,8 @@ export function createPresenter(options: PresenterOptions = {}): Presenter {
|
|
|
81
70
|
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
|
|
82
71
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
83
72
|
renderer.shadowMap.enabled = true;
|
|
84
|
-
|
|
73
|
+
// PCFShadowMap supplies the soft-shadow filter supported by this three.js release.
|
|
74
|
+
renderer.shadowMap.type = THREE.PCFShadowMap;
|
|
85
75
|
renderer.setPixelRatio(1);
|
|
86
76
|
const near = options.near ?? 0.1;
|
|
87
77
|
const far = options.far ?? 1000;
|
|
@@ -107,11 +97,7 @@ export function createPresenter(options: PresenterOptions = {}): Presenter {
|
|
|
107
97
|
view: BlenderRuntimeView, scene: THREE.Scene, frame: unknown, description: unknown,
|
|
108
98
|
capture?: CaptureRequest,
|
|
109
99
|
): Promise<PresentAnswer> {
|
|
110
|
-
const
|
|
111
|
-
const held =
|
|
112
|
-
typeof applied === 'object' && applied !== null && 'held' in applied
|
|
113
|
-
? (applied.held as { session: string; revision: number } | null)
|
|
114
|
-
: null;
|
|
100
|
+
const { held } = view.applyFrame(frame);
|
|
115
101
|
view.recordPresentation(description);
|
|
116
102
|
const answer = (photograph?: unknown): PresentAnswer => ({
|
|
117
103
|
...(photograph === undefined ? {} : { capture: photograph }),
|
|
@@ -233,8 +219,6 @@ export function createPresenter(options: PresenterOptions = {}): Presenter {
|
|
|
233
219
|
|
|
234
220
|
/** A composited frame's scene-linear half floats, as `linearInput` carries them. */
|
|
235
221
|
function halfFloatFrame(base64: string): Uint16Array {
|
|
236
|
-
const
|
|
237
|
-
const bytes = new Uint8Array(binary.length);
|
|
238
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
222
|
+
const bytes = bytesFromBase64(base64);
|
|
239
223
|
return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 1);
|
|
240
224
|
}
|
package/browser/three/release.ts
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* delivered wherever the wasm is delivered. A host fetches it from the
|
|
8
|
-
* artifact base next to `blender.wasm`, imports it, and attaches ONE presenter
|
|
9
|
-
* to the Blender it started:
|
|
10
|
-
*
|
|
11
|
-
* const { createPresenter, attachPresenter } = await import(url);
|
|
12
|
-
* const detach = attachPresenter(filesystem, "/tmp/volter-presenter", createPresenter());
|
|
13
|
-
*
|
|
14
|
-
* NO HOST BUNDLES THIS. The module is GPL because it is part of the Blender
|
|
15
|
-
* release; a host loads it the way it loads the wasm -- by URL, under a pinned
|
|
16
|
-
* hash -- and its own build graph never touches this package. The editor is
|
|
17
|
-
* the one consumer that does NOT load this file: it imports the same source
|
|
18
|
-
* with its own three and wraps its own document around it, because two copies
|
|
19
|
-
* of three on one page is the thing this arrangement exists to prevent.
|
|
20
|
-
*
|
|
21
|
-
* `vite.release.config.ts` beside this package's manifest is the build;
|
|
22
|
-
* `npm run build:blender-three -w @volter/blender-engine` runs it.
|
|
2
|
+
* Release entry for the self-contained Blender three.js presenter module. The Blender release
|
|
3
|
+
* delivers it beside the Wasm artifact, with three.js inlined and display tables beside it. Hosts
|
|
4
|
+
* load the pinned module URL and attach createPresenter() through attachPresenter(). The editor
|
|
5
|
+
* imports the same source using its shared three.js instance and its own document stage. Build with
|
|
6
|
+
* vite.release.config.ts or build:blender-three.
|
|
23
7
|
*/
|
|
24
8
|
export { attachPresenter } from './attach-presenter';
|
|
25
9
|
export type { AttachablePresenter, PresenterFileSystem } from './attach-presenter';
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The sky precompute, in a Worker.
|
|
3
|
-
*
|
|
4
|
-
* `precomputeSkyTexture` is 512x256 texels at 64 in-scattering steps each and
|
|
5
|
-
* MEASURED 15.4 seconds. On the page's thread that is not "slow", it is a FROZEN
|
|
6
|
-
* EDITOR -- the owner saw exactly that, and a spinner could not have helped
|
|
7
|
-
* because a blocked main thread cannot paint one.
|
|
8
|
-
*
|
|
9
|
-
* This module is the whole worker: parameters in, one Float32Array out,
|
|
10
|
-
* TRANSFERRED rather than copied. It names no DOM and no renderer, which is what
|
|
11
|
-
* lets it be a worker at all; `blender-sky.ts` keeps the arithmetic.
|
|
12
|
-
*
|
|
13
|
-
* WHAT MAKES THIS DIFFERENT FROM THE ATTEMPT THAT WAS REVERTED (#6609): nothing
|
|
14
|
-
* here hands out a half-built texture. The worker answers with NUMBERS, the
|
|
15
|
-
* caller primes `blender-sky`'s texture cache with them, and only then does
|
|
16
|
-
* anything build a texture -- and `pendingWorld`/`worldReady()` hold the capture
|
|
17
|
-
* until it has. See `sky-worker.ts` and `WorldBackground.apply`.
|
|
18
|
-
*/
|
|
1
|
+
/** Precompute sky texels in a worker and transfer the Float32Array. The caller primes the sky cache before constructing textures, and WorldBackground.ready holds captures until it is complete. */
|
|
19
2
|
import { precomputeSkyTexture, type SkyParameters } from './blender-sky';
|
|
20
3
|
|
|
21
4
|
export interface SkyWorkerRequest {
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* WHY A CLIENT MODULE AND NOT A `new Worker` AT THE CALL SITE: the worker is
|
|
5
|
-
* built once and reused, because constructing one costs a module graph fetch and
|
|
6
|
-
* a sky world re-derives on every sun edit. Requests are keyed by id so several
|
|
7
|
-
* skies (a world split by `Is Camera Ray` has two) ride one worker.
|
|
8
|
-
*
|
|
9
|
-
* The type-only import of the worker module is LOAD-BEARING in two ways. It is
|
|
10
|
-
* the contract for what crosses the boundary, and it is also how the probe
|
|
11
|
-
* project's capability sync finds the worker file at all: that sync follows
|
|
12
|
-
* `from './x'` spellings, and `new URL('./x.ts', import.meta.url)` is not one.
|
|
13
|
-
* Delete the import and the worker silently stops being copied.
|
|
14
|
-
*/
|
|
1
|
+
/** Share one long-lived worker across sky requests, keyed by id. The worker request and
|
|
2
|
+
* response types define the transfer contract. */
|
|
15
3
|
import { precomputeSkyTexture, type SkyParameters } from './blender-sky';
|
|
16
4
|
import type { SkyWorkerRequest, SkyWorkerResponse } from './sky-precompute-worker';
|
|
17
5
|
|
|
@@ -46,7 +34,7 @@ function ensureWorker(): Worker | null {
|
|
|
46
34
|
else pending.reject(new Error(error ?? 'sky worker returned nothing'));
|
|
47
35
|
};
|
|
48
36
|
// A worker that dies takes every outstanding sky with it. Failing them LOUDLY
|
|
49
|
-
// is the point: `
|
|
37
|
+
// is the point: `WorldBackground.ready()` propagates, so a capture reports the error
|
|
50
38
|
// rather than photographing a scene with no sky in it.
|
|
51
39
|
worker.onerror = (event) => {
|
|
52
40
|
const failure = new Error(`sky worker failed: ${event.message || 'unknown error'}`);
|
|
@@ -59,14 +47,8 @@ function ensureWorker(): Worker | null {
|
|
|
59
47
|
return worker;
|
|
60
48
|
}
|
|
61
49
|
|
|
62
|
-
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* Falls back to deriving it inline when there is no Worker (Node, a test, a
|
|
66
|
-
* blocked construction). That fallback blocks -- which is the whole complaint --
|
|
67
|
-
* but it is CORRECT, and correct-and-slow is the trade this lane already made
|
|
68
|
-
* once by hand.
|
|
69
|
-
*/
|
|
50
|
+
/** Precompute a sky texture off-thread. Environments without a constructible Worker compute
|
|
51
|
+
* the same texture inline; failures after worker startup reject all pending requests. */
|
|
70
52
|
export function precomputeSkyOffThread(parameters: SkyParameters): Promise<Float32Array> {
|
|
71
53
|
const active = ensureWorker();
|
|
72
54
|
if (!active) return Promise.resolve(precomputeSkyTexture(parameters));
|
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sampling a world's radiance field — the expensive half of the world texture,
|
|
3
|
-
* kept in its own module so a WORKER can own it.
|
|
4
|
-
*
|
|
5
|
-
* This is where the time goes. A sky expression derives Blender's
|
|
6
|
-
* multiple-scattering model (`blender-sky.ts`, 512x256 with 64 in-scattering
|
|
7
|
-
* steps per pixel) and then this samples 32,768 directions out of it. MEASURED:
|
|
8
|
-
* 15.4 seconds. On the page's main thread that is not "slow", it is a FROZEN
|
|
9
|
-
* EDITOR — the owner saw exactly that, and a status-bar spinner could not have
|
|
10
|
-
* helped, because a blocked main thread cannot paint one.
|
|
11
|
-
*
|
|
12
|
-
* Nothing here touches the DOM, three's renderer, or any editor state: it takes
|
|
13
|
-
* a description and returns numbers. That is what makes it movable, and it is
|
|
14
|
-
* the reason the split is here rather than around something larger.
|
|
15
|
-
*/
|
|
1
|
+
/** Sample a world-radiance description into numeric data. This module touches no DOM, renderer or editor state and can run in a worker. */
|
|
16
2
|
import * as THREE from 'three';
|
|
17
3
|
import { gradientTexture } from './blender-gradient-texture';
|
|
18
4
|
// Type-only, so this module keeps NO runtime edge back to the contribution
|
|
@@ -100,10 +86,10 @@ export function worldField(
|
|
|
100
86
|
// Built ONCE per node: the model precomputes a 512x256 texture, and
|
|
101
87
|
// rebuilding it per direction would be a 65,536-pixel job per texel.
|
|
102
88
|
const field = skyField({
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
89
|
+
model: expression.sky_model,
|
|
90
|
+
sunDisc: expression.sun_disc,
|
|
91
|
+
sunSize: expression.sun_size,
|
|
92
|
+
sunIntensity: expression.sun_intensity,
|
|
107
93
|
sunElevation: expression.sun_elevation,
|
|
108
94
|
sunRotation: expression.sun_rotation,
|
|
109
95
|
altitude: expression.altitude,
|
|
@@ -257,10 +243,10 @@ export function collectSkyParameters(expression: WorldExpression): SkyParameters
|
|
|
257
243
|
switch (node.kind) {
|
|
258
244
|
case 'sky':
|
|
259
245
|
found.push({
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
246
|
+
model: node.sky_model,
|
|
247
|
+
sunDisc: node.sun_disc,
|
|
248
|
+
sunSize: node.sun_size,
|
|
249
|
+
sunIntensity: node.sun_intensity,
|
|
264
250
|
sunElevation: node.sun_elevation,
|
|
265
251
|
sunRotation: node.sun_rotation,
|
|
266
252
|
altitude: node.altitude,
|
|
@@ -317,7 +303,7 @@ export function withoutSolarDiscs(expression: WorldExpression): WorldExpression
|
|
|
317
303
|
|
|
318
304
|
export function worldSolarIrradiance(expression: WorldExpression): {direction: THREE.Vector3; irradiance: THREE.Vector3}[] {
|
|
319
305
|
const suns = collectSkyParameters(expression).filter(p => p.model === 'SINGLE_SCATTERING' &&
|
|
320
|
-
p.sunDisc &&
|
|
306
|
+
p.sunDisc && p.sunSize > 0 && p.sunIntensity > 0);
|
|
321
307
|
if (!suns.length) return [];
|
|
322
308
|
// Window-dependent world shaders need ray origins as well as directions;
|
|
323
309
|
// there is no position-independent directional light for that field.
|
|
@@ -336,10 +322,10 @@ export function worldSolarIrradiance(expression: WorldExpression): {direction: T
|
|
|
336
322
|
? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0)).normalize();
|
|
337
323
|
const v = new THREE.Vector3().crossVectors(direction, u);
|
|
338
324
|
const irradiance = new THREE.Vector3(), ray = new THREE.Vector3(), delta = new THREE.Vector3();
|
|
339
|
-
const solidAngle = 2 * Math.PI * (1 - Math.cos(p.sunSize
|
|
325
|
+
const solidAngle = 2 * Math.PI * (1 - Math.cos(p.sunSize / 2));
|
|
340
326
|
const radial = 32, azimuthal = 32;
|
|
341
327
|
for (let i = 0; i < radial; i++) {
|
|
342
|
-
const cos = 1 - (i + .5) / radial * (1 - Math.cos(p.sunSize
|
|
328
|
+
const cos = 1 - (i + .5) / radial * (1 - Math.cos(p.sunSize / 2));
|
|
343
329
|
const sin = Math.sqrt(1 - cos * cos);
|
|
344
330
|
for (let j = 0; j < azimuthal; j++) {
|
|
345
331
|
const phi = (j + .5) / azimuthal * 2 * Math.PI;
|