@zephyr3d/scene 0.9.27 → 0.10.0
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 -27
- package/dist/animation/animation_bank.js +634 -0
- package/dist/animation/animation_bank.js.map +1 -0
- package/dist/animation/animation_bank_gpu.js +256 -0
- package/dist/animation/animation_bank_gpu.js.map +1 -0
- package/dist/animation/animationset.js +54 -0
- package/dist/animation/animationset.js.map +1 -1
- package/dist/animation/cloth/gpu_cloth_component.js +406 -0
- package/dist/animation/cloth/gpu_cloth_component.js.map +1 -0
- package/dist/animation/cloth/gpu_cloth_system.js +332 -202
- package/dist/animation/cloth/gpu_cloth_system.js.map +1 -1
- package/dist/animation/gpu_animation_driver.js +289 -0
- package/dist/animation/gpu_animation_driver.js.map +1 -0
- package/dist/animation/joint_dynamics/collision.js +112 -1
- package/dist/animation/joint_dynamics/collision.js.map +1 -1
- package/dist/animation/joint_dynamics/controller.js +6 -0
- package/dist/animation/joint_dynamics/controller.js.map +1 -1
- package/dist/animation/joint_dynamics/joint_dynamics_system.js +11 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
- package/dist/animation/joint_dynamics/solver.js +50 -12
- package/dist/animation/joint_dynamics/solver.js.map +1 -1
- package/dist/animation/joint_dynamics/types.js.map +1 -1
- package/dist/animation/skeleton.js +279 -55
- package/dist/animation/skeleton.js.map +1 -1
- package/dist/animation/skin_palette_atlas.js +252 -0
- package/dist/animation/skin_palette_atlas.js.map +1 -0
- package/dist/animation/spring/multi_chain_spring_system.js +298 -38
- package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
- package/dist/animation/spring/spring_chain.js +2 -0
- package/dist/animation/spring/spring_chain.js.map +1 -1
- package/dist/animation/spring/spring_collider.js +159 -21
- package/dist/animation/spring/spring_collider.js.map +1 -1
- package/dist/animation/spring/spring_particle.js +1 -0
- package/dist/animation/spring/spring_particle.js.map +1 -1
- package/dist/animation/spring/spring_solver.js +173 -0
- package/dist/animation/spring/spring_solver.js.map +1 -0
- package/dist/animation/spring/spring_system.js +249 -24
- package/dist/animation/spring/spring_system.js.map +1 -1
- package/dist/animation/spring_modifier.js +32 -1
- package/dist/animation/spring_modifier.js.map +1 -1
- package/dist/app/app.js +26 -0
- package/dist/app/app.js.map +1 -1
- package/dist/app/engine.js +86 -15
- package/dist/app/engine.js.map +1 -1
- package/dist/app/gamepad.js +191 -0
- package/dist/app/gamepad.js.map +1 -0
- package/dist/app/inputactions.js +271 -0
- package/dist/app/inputactions.js.map +1 -0
- package/dist/app/inputmgr.js +331 -22
- package/dist/app/inputmgr.js.map +1 -1
- package/dist/app/loading.js +138 -0
- package/dist/app/loading.js.map +1 -0
- package/dist/app/pipelinecache.js +209 -0
- package/dist/app/pipelinecache.js.map +1 -0
- package/dist/app/runtimescript.js +25 -0
- package/dist/app/runtimescript.js.map +1 -1
- package/dist/app/scriptingsystem.js +54 -0
- package/dist/app/scriptingsystem.js.map +1 -1
- package/dist/app/scriptregistry.js +7 -2
- package/dist/app/scriptregistry.js.map +1 -1
- package/dist/asset/asset_manifest.js +59 -0
- package/dist/asset/asset_manifest.js.map +1 -0
- package/dist/asset/asset_preload.js +258 -0
- package/dist/asset/asset_preload.js.map +1 -0
- package/dist/asset/assetmanager.js +117 -65
- package/dist/asset/assetmanager.js.map +1 -1
- package/dist/asset/loaders/ktx2/basis/LICENSE +201 -0
- package/dist/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
- package/dist/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
- package/dist/asset/loaders/ktx2/basis_location.js +17 -0
- package/dist/asset/loaders/ktx2/basis_location.js.map +1 -0
- package/dist/asset/loaders/ktx2/ktx2_loader.js +88 -0
- package/dist/asset/loaders/ktx2/ktx2_loader.js.map +1 -0
- package/dist/asset/loaders/ktx2/ktx2_targets.js +118 -0
- package/dist/asset/loaders/ktx2/ktx2_targets.js.map +1 -0
- package/dist/asset/loaders/ktx2/transcoder_pool.js +259 -0
- package/dist/asset/loaders/ktx2/transcoder_pool.js.map +1 -0
- package/dist/asset/mesh_lod_builder.js +104 -0
- package/dist/asset/mesh_lod_builder.js.map +1 -0
- package/dist/asset/mesh_settings.js +111 -0
- package/dist/asset/mesh_settings.js.map +1 -0
- package/dist/asset/meshopt_decoder.js +18 -0
- package/dist/asset/meshopt_decoder.js.map +1 -0
- package/dist/asset/meshopt_simplifier.js +26 -0
- package/dist/asset/meshopt_simplifier.js.map +1 -0
- package/dist/asset/model.js +171 -1
- package/dist/asset/model.js.map +1 -1
- package/dist/asset/texture_settings.js +231 -0
- package/dist/asset/texture_settings.js.map +1 -0
- package/dist/asset/zmsh_binary.js +131 -0
- package/dist/asset/zmsh_binary.js.map +1 -0
- package/dist/asset/zsdf_binary.js +111 -0
- package/dist/asset/zsdf_binary.js.map +1 -0
- package/dist/blitter/bilateralblur.js +1 -2
- package/dist/blitter/bilateralblur.js.map +1 -1
- package/dist/blitter/gaussianblur.js +1 -6
- package/dist/blitter/gaussianblur.js.map +1 -1
- package/dist/camera/base.js +31 -10
- package/dist/camera/base.js.map +1 -1
- package/dist/camera/camera.js +104 -178
- package/dist/camera/camera.js.map +1 -1
- package/dist/camera/follow.js +342 -0
- package/dist/camera/follow.js.map +1 -0
- package/dist/camera/fps.js +9 -7
- package/dist/camera/fps.js.map +1 -1
- package/dist/camera/orbit.js +46 -19
- package/dist/camera/orbit.js.map +1 -1
- package/dist/index.d.ts +25436 -15990
- package/dist/index.js +65 -11
- package/dist/index.js.map +1 -1
- package/dist/material/blinn.js +3 -0
- package/dist/material/blinn.js.map +1 -1
- package/dist/material/eye.js +1 -1
- package/dist/material/eye.js.map +1 -1
- package/dist/material/hair.js +2 -3
- package/dist/material/hair.js.map +1 -1
- package/dist/material/lambert.js +4 -1
- package/dist/material/lambert.js.map +1 -1
- package/dist/material/material.js +66 -4
- package/dist/material/material.js.map +1 -1
- package/dist/material/meshmaterial.js +118 -45
- package/dist/material/meshmaterial.js.map +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
- package/dist/material/mixins/lightmodel/lambert.js +1 -1
- package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
- package/dist/material/mixins/lit.js +191 -83
- package/dist/material/mixins/lit.js.map +1 -1
- package/dist/material/mixins/pbr/common.js +31 -206
- package/dist/material/mixins/pbr/common.js.map +1 -1
- package/dist/material/mtoon.js +1 -1
- package/dist/material/mtoon.js.map +1 -1
- package/dist/material/pbrmr.js +3 -0
- package/dist/material/pbrmr.js.map +1 -1
- package/dist/material/pbrsg.js +3 -0
- package/dist/material/pbrsg.js.map +1 -1
- package/dist/material/sdf_debug.js +78 -0
- package/dist/material/sdf_debug.js.map +1 -0
- package/dist/material/shader/helper.js +333 -191
- package/dist/material/shader/helper.js.map +1 -1
- package/dist/material/shader/terrain_blend.js +204 -43
- package/dist/material/shader/terrain_blend.js.map +1 -1
- package/dist/material/skin.js +331 -297
- package/dist/material/skin.js.map +1 -1
- package/dist/material/skinprofile.js +1058 -0
- package/dist/material/skinprofile.js.map +1 -0
- package/dist/material/sss.js +353 -0
- package/dist/material/sss.js.map +1 -0
- package/dist/material/sss_kernel.js +265 -0
- package/dist/material/sss_kernel.js.map +1 -0
- package/dist/material/sssprofile.js +879 -0
- package/dist/material/sssprofile.js.map +1 -0
- package/dist/material/subsurfaceprofile.js +4 -8
- package/dist/material/subsurfaceprofile.js.map +1 -1
- package/dist/material/terrain-cm.js +464 -188
- package/dist/material/terrain-cm.js.map +1 -1
- package/dist/material/unlit.js +3 -0
- package/dist/material/unlit.js.map +1 -1
- package/dist/material/water.js +1261 -1438
- package/dist/material/water.js.map +1 -1
- package/dist/node_modules/meshoptimizer/LICENSE +21 -0
- package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
- package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
- package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
- package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
- package/dist/physics/character.js +226 -0
- package/dist/physics/character.js.map +1 -0
- package/dist/physics/collider.js +209 -0
- package/dist/physics/collider.js.map +1 -0
- package/dist/physics/events.js +79 -0
- package/dist/physics/events.js.map +1 -0
- package/dist/physics/joint.js +272 -0
- package/dist/physics/joint.js.map +1 -0
- package/dist/physics/node_physics.js +234 -0
- package/dist/physics/node_physics.js.map +1 -0
- package/dist/physics/part.js +91 -0
- package/dist/physics/part.js.map +1 -0
- package/dist/physics/rigid_body.js +379 -0
- package/dist/physics/rigid_body.js.map +1 -0
- package/dist/physics/vehicle.js +335 -0
- package/dist/physics/vehicle.js.map +1 -0
- package/dist/physics/world.js +25 -0
- package/dist/physics/world.js.map +1 -0
- package/dist/posteffect/bloom.js +49 -14
- package/dist/posteffect/bloom.js.map +1 -1
- package/dist/posteffect/motionblur.js +106 -106
- package/dist/posteffect/motionblur.js.map +1 -1
- package/dist/posteffect/postsss.js +572 -0
- package/dist/posteffect/postsss.js.map +1 -0
- package/dist/posteffect/sao.js +2 -6
- package/dist/posteffect/sao.js.map +1 -1
- package/dist/posteffect/skinsss.js +605 -355
- package/dist/posteffect/skinsss.js.map +1 -1
- package/dist/posteffect/ssgi.js +91 -162
- package/dist/posteffect/ssgi.js.map +1 -1
- package/dist/posteffect/ssr.js +3 -11
- package/dist/posteffect/ssr.js.map +1 -1
- package/dist/posteffect/sss.js +4 -13
- package/dist/posteffect/sss.js.map +1 -1
- package/dist/posteffect/taa.js +1 -0
- package/dist/posteffect/taa.js.map +1 -1
- package/dist/render/abuffer_oit.js +3 -2
- package/dist/render/abuffer_oit.js.map +1 -1
- package/dist/render/clipmap.js +179 -64
- package/dist/render/clipmap.js.map +1 -1
- package/dist/render/cluster_light.js +958 -224
- package/dist/render/cluster_light.js.map +1 -1
- package/dist/render/cull_visitor.js +25 -5
- package/dist/render/cull_visitor.js.map +1 -1
- package/dist/render/debug_views.js +37 -0
- package/dist/render/debug_views.js.map +1 -0
- package/dist/render/depthpass.js +5 -1
- package/dist/render/depthpass.js.map +1 -1
- package/dist/render/drawable_mixin.js +53 -6
- package/dist/render/drawable_mixin.js.map +1 -1
- package/dist/render/dualdepthpeeling_oit.js +5 -5
- package/dist/render/dualdepthpeeling_oit.js.map +1 -1
- package/dist/render/envlight.js +196 -113
- package/dist/render/envlight.js.map +1 -1
- package/dist/render/fbm_wavegenerator.js +5 -10
- package/dist/render/fbm_wavegenerator.js.map +1 -1
- package/dist/render/fft_wavegenerator.js +247 -157
- package/dist/render/fft_wavegenerator.js.map +1 -1
- package/dist/render/gerstner_wavegenerator.js +16 -38
- package/dist/render/gerstner_wavegenerator.js.map +1 -1
- package/dist/render/gi/probe_volume.js +1334 -0
- package/dist/render/gi/probe_volume.js.map +1 -0
- package/dist/render/gi/scene_gi.js +282 -0
- package/dist/render/gi/scene_gi.js.map +1 -0
- package/dist/render/gi/voxel_atlas.js +198 -0
- package/dist/render/gi/voxel_atlas.js.map +1 -0
- package/dist/render/gi/voxel_lighting.js +652 -0
- package/dist/render/gi/voxel_lighting.js.map +1 -0
- package/dist/render/gi/voxel_scene.js +642 -0
- package/dist/render/gi/voxel_scene.js.map +1 -0
- package/dist/render/gi/voxelize_pass.js +223 -0
- package/dist/render/gi/voxelize_pass.js.map +1 -0
- package/dist/render/globalbindgroup_allocator.js +3 -2
- package/dist/render/globalbindgroup_allocator.js.map +1 -1
- package/dist/render/gpu_picking.js +9 -36
- package/dist/render/gpu_picking.js.map +1 -1
- package/dist/render/instance_culling.js +499 -0
- package/dist/render/instance_culling.js.map +1 -0
- package/dist/render/lightpass.js +19 -8
- package/dist/render/lightpass.js.map +1 -1
- package/dist/render/lod.js +46 -0
- package/dist/render/lod.js.map +1 -0
- package/dist/render/primitive.js +301 -9
- package/dist/render/primitive.js.map +1 -1
- package/dist/render/primitive_readback.js +209 -0
- package/dist/render/primitive_readback.js.map +1 -0
- package/dist/render/render_queue.js +162 -11
- package/dist/render/render_queue.js.map +1 -1
- package/dist/render/renderer.js +10 -7
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/rendergraph/blackboard.js +14 -1
- package/dist/render/rendergraph/blackboard.js.map +1 -1
- package/dist/render/rendergraph/executor.js +136 -0
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +272 -55
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/frame_graph_context.js +41 -0
- package/dist/render/rendergraph/frame_graph_context.js.map +1 -0
- package/dist/render/rendergraph/scene_render_context.js +31 -2
- package/dist/render/rendergraph/scene_render_context.js.map +1 -1
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/render/renderpass.js +28 -5
- package/dist/render/renderpass.js.map +1 -1
- package/dist/render/sdf/box_allocator.js +98 -0
- package/dist/render/sdf/box_allocator.js.map +1 -0
- package/dist/render/sdf/global_sdf.js +865 -0
- package/dist/render/sdf/global_sdf.js.map +1 -0
- package/dist/render/sdf/scene_sdf.js +562 -0
- package/dist/render/sdf/scene_sdf.js.map +1 -0
- package/dist/render/sdf/sdf_atlas.js +227 -0
- package/dist/render/sdf/sdf_atlas.js.map +1 -0
- package/dist/render/sdf/sdf_programs.js +42 -0
- package/dist/render/sdf/sdf_programs.js.map +1 -0
- package/dist/render/sdf/sdf_view.js +199 -0
- package/dist/render/sdf/sdf_view.js.map +1 -0
- package/dist/render/sdf/waterline.js +610 -0
- package/dist/render/sdf/waterline.js.map +1 -0
- package/dist/render/shadow_mask_pass.js +142 -25
- package/dist/render/shadow_mask_pass.js.map +1 -1
- package/dist/render/shore_wave_generator.js +1193 -0
- package/dist/render/shore_wave_generator.js.map +1 -0
- package/dist/render/sky.js +516 -86
- package/dist/render/sky.js.map +1 -1
- package/dist/render/transmission_thickness_pass.js +477 -0
- package/dist/render/transmission_thickness_pass.js.map +1 -0
- package/dist/render/underwater.js +58 -158
- package/dist/render/underwater.js.map +1 -1
- package/dist/render/virtualtexture/virtual_texture.js +1214 -0
- package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
- package/dist/render/volumetric_cloud/cloud_common.js +472 -0
- package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
- package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
- package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
- package/dist/render/volumetric_cloud/debug_view.js +131 -0
- package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
- package/dist/render/volumetric_cloud/resources.js +380 -0
- package/dist/render/volumetric_cloud/resources.js.map +1 -0
- package/dist/render/volumetric_cloud/sky_cache.js +380 -0
- package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
- package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
- package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
- package/dist/render/water_caustics.js +134 -319
- package/dist/render/water_caustics.js.map +1 -1
- package/dist/render/water_foam.js +627 -0
- package/dist/render/water_foam.js.map +1 -0
- package/dist/render/water_interaction.js +1795 -0
- package/dist/render/water_interaction.js.map +1 -0
- package/dist/render/weightedblended_oit.js +3 -3
- package/dist/render/weightedblended_oit.js.map +1 -1
- package/dist/scene/basesprite.js +9 -4
- package/dist/scene/basesprite.js.map +1 -1
- package/dist/scene/batchgroup.js +108 -12
- package/dist/scene/batchgroup.js.map +1 -1
- package/dist/scene/environment.js +28 -7
- package/dist/scene/environment.js.map +1 -1
- package/dist/scene/foliage.js +1020 -0
- package/dist/scene/foliage.js.map +1 -0
- package/dist/scene/foliage_buckets.js +147 -0
- package/dist/scene/foliage_buckets.js.map +1 -0
- package/dist/scene/foliage_mask.js +358 -0
- package/dist/scene/foliage_mask.js.map +1 -0
- package/dist/scene/hair.js +15 -4
- package/dist/scene/hair.js.map +1 -1
- package/dist/scene/light.js +179 -13
- package/dist/scene/light.js.map +1 -1
- package/dist/scene/mesh.js +327 -45
- package/dist/scene/mesh.js.map +1 -1
- package/dist/scene/meshdrawable.js +6 -0
- package/dist/scene/meshdrawable.js.map +1 -1
- package/dist/scene/msdftext.js +9 -3
- package/dist/scene/msdftext.js.map +1 -1
- package/dist/scene/msdftextsprite.js +9 -3
- package/dist/scene/msdftextsprite.js.map +1 -1
- package/dist/scene/particlesys.js +8 -2
- package/dist/scene/particlesys.js.map +1 -1
- package/dist/scene/physics_settings.js +157 -0
- package/dist/scene/physics_settings.js.map +1 -0
- package/dist/scene/scene.js +190 -44
- package/dist/scene/scene.js.map +1 -1
- package/dist/scene/scene_node.js +147 -6
- package/dist/scene/scene_node.js.map +1 -1
- package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
- package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
- package/dist/scene/terrain-cm/grass.js +881 -42
- package/dist/scene/terrain-cm/grass.js.map +1 -1
- package/dist/scene/terrain-cm/grass_gpu.js +929 -0
- package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
- package/dist/scene/terrain-cm/grassmaterial.js +180 -9
- package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
- package/dist/scene/terrain-cm/terrain-cm.js +465 -36
- package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
- package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
- package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
- package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
- package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
- package/dist/scene/update_queue.js +54 -0
- package/dist/scene/update_queue.js.map +1 -0
- package/dist/scene/water.js +633 -268
- package/dist/scene/water.js.map +1 -1
- package/dist/scene/water_surface_sampler.js +324 -0
- package/dist/scene/water_surface_sampler.js.map +1 -0
- package/dist/scene/wind.js +317 -0
- package/dist/scene/wind.js.map +1 -0
- package/dist/shaders/atmosphere.js +388 -166
- package/dist/shaders/atmosphere.js.map +1 -1
- package/dist/shaders/fog.js +100 -32
- package/dist/shaders/fog.js.map +1 -1
- package/dist/shaders/gi.js +365 -0
- package/dist/shaders/gi.js.map +1 -0
- package/dist/shaders/ltc_rect.js +202 -0
- package/dist/shaders/ltc_rect.js.map +1 -0
- package/dist/shaders/misc.js +1 -10
- package/dist/shaders/misc.js.map +1 -1
- package/dist/shaders/noise.js +5 -1
- package/dist/shaders/noise.js.map +1 -1
- package/dist/shaders/probes.js +309 -0
- package/dist/shaders/probes.js.map +1 -0
- package/dist/shaders/sdf.js +537 -0
- package/dist/shaders/sdf.js.map +1 -0
- package/dist/shaders/shadow.js +104 -70
- package/dist/shaders/shadow.js.map +1 -1
- package/dist/shaders/skin_brdf.js +243 -0
- package/dist/shaders/skin_brdf.js.map +1 -0
- package/dist/shaders/ssgi.js +63 -0
- package/dist/shaders/ssgi.js.map +1 -0
- package/dist/shaders/ssgi_reprojection.js +118 -0
- package/dist/shaders/ssgi_reprojection.js.map +1 -0
- package/dist/shaders/ssr.js +6 -159
- package/dist/shaders/ssr.js.map +1 -1
- package/dist/shaders/temporal.js +38 -27
- package/dist/shaders/temporal.js.map +1 -1
- package/dist/shaders/water.js +32 -82
- package/dist/shaders/water.js.map +1 -1
- package/dist/shaders/water_medium.js +7 -21
- package/dist/shaders/water_medium.js.map +1 -1
- package/dist/shadow/dom.js +110 -19
- package/dist/shadow/dom.js.map +1 -1
- package/dist/shadow/esm.js +3 -3
- package/dist/shadow/esm.js.map +1 -1
- package/dist/shadow/pcf_opt.js +15 -48
- package/dist/shadow/pcf_opt.js.map +1 -1
- package/dist/shadow/pcf_pd.js +15 -53
- package/dist/shadow/pcf_pd.js.map +1 -1
- package/dist/shadow/pcss.js +34 -9
- package/dist/shadow/pcss.js.map +1 -1
- package/dist/shadow/shadow_impl.js +3 -8
- package/dist/shadow/shadow_impl.js.map +1 -1
- package/dist/shadow/shadow_region.js +40 -2
- package/dist/shadow/shadow_region.js.map +1 -1
- package/dist/shadow/shadowmapper.js +93 -53
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shadow/ssm.js +18 -72
- package/dist/shadow/ssm.js.map +1 -1
- package/dist/shadow/vsm.js +3 -3
- package/dist/shadow/vsm.js.map +1 -1
- package/dist/shapes/shape.js +44 -1
- package/dist/shapes/shape.js.map +1 -1
- package/dist/utility/buoyancy.js +287 -0
- package/dist/utility/buoyancy.js.map +1 -0
- package/dist/utility/physical.js +10 -1
- package/dist/utility/physical.js.map +1 -1
- package/dist/utility/pmrem.js +17 -43
- package/dist/utility/pmrem.js.map +1 -1
- package/dist/utility/sdf/bake.js +435 -0
- package/dist/utility/sdf/bake.js.map +1 -0
- package/dist/utility/sdf/bvh.js +576 -0
- package/dist/utility/sdf/bvh.js.map +1 -0
- package/dist/utility/serialization/manager.js +34 -6
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +46 -5
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/batch.js +11 -4
- package/dist/utility/serialization/scene/batch.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js +87 -254
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/utility/serialization/scene/cloth.js +25 -0
- package/dist/utility/serialization/scene/cloth.js.map +1 -0
- package/dist/utility/serialization/scene/common.js +14 -6
- package/dist/utility/serialization/scene/common.js.map +1 -1
- package/dist/utility/serialization/scene/foliage.js +821 -0
- package/dist/utility/serialization/scene/foliage.js.map +1 -0
- package/dist/utility/serialization/scene/hair.js +11 -4
- package/dist/utility/serialization/scene/hair.js.map +1 -1
- package/dist/utility/serialization/scene/light.js +115 -7
- package/dist/utility/serialization/scene/light.js.map +1 -1
- package/dist/utility/serialization/scene/material.js +388 -220
- package/dist/utility/serialization/scene/material.js.map +1 -1
- package/dist/utility/serialization/scene/mesh.js +69 -4
- package/dist/utility/serialization/scene/mesh.js.map +1 -1
- package/dist/utility/serialization/scene/node.js +140 -5
- package/dist/utility/serialization/scene/node.js.map +1 -1
- package/dist/utility/serialization/scene/node_physics.js +1318 -0
- package/dist/utility/serialization/scene/node_physics.js.map +1 -0
- package/dist/utility/serialization/scene/particle.js +11 -4
- package/dist/utility/serialization/scene/particle.js.map +1 -1
- package/dist/utility/serialization/scene/physics.js +120 -0
- package/dist/utility/serialization/scene/physics.js.map +1 -0
- package/dist/utility/serialization/scene/primitive.js +124 -10
- package/dist/utility/serialization/scene/primitive.js.map +1 -1
- package/dist/utility/serialization/scene/scene.js +1687 -51
- package/dist/utility/serialization/scene/scene.js.map +1 -1
- package/dist/utility/serialization/scene/sprite.js +11 -4
- package/dist/utility/serialization/scene/sprite.js.map +1 -1
- package/dist/utility/serialization/scene/terrain.js +163 -33
- package/dist/utility/serialization/scene/terrain.js.map +1 -1
- package/dist/utility/serialization/scene/text.js +11 -4
- package/dist/utility/serialization/scene/text.js.map +1 -1
- package/dist/utility/serialization/scene/water.js +1543 -264
- package/dist/utility/serialization/scene/water.js.map +1 -1
- package/dist/utility/serialization/types.js.map +1 -1
- package/dist/utility/shprojector.js +10 -22
- package/dist/utility/shprojector.js.map +1 -1
- package/dist/utility/textures/foam.js +170 -0
- package/dist/utility/textures/foam.js.map +1 -0
- package/dist/utility/textures/ggxlut.js +17 -43
- package/dist/utility/textures/ggxlut.js.map +1 -1
- package/dist/utility/textures/ltcdata.js +4 -4
- package/dist/utility/textures/ltcdata.js.map +1 -1
- package/dist/utility/textures/ltclut.js +16 -6
- package/dist/utility/textures/ltclut.js.map +1 -1
- package/dist/utility/unsupported.js +24 -0
- package/dist/utility/unsupported.js.map +1 -0
- package/dist/values.js +13 -1
- package/dist/values.js.map +1 -1
- package/package.json +25 -12
- package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
- package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
- package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
- package/src/asset/meshoptimizer/LICENSE +21 -0
- package/dist/animation/joint_dynamics/convex_collider.js +0 -320
- package/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Vector2, Vector4, DEPTH_FARTHEST } from '@zephyr3d/base';
|
|
2
2
|
import { ShaderHelper } from '../material/shader/helper.js';
|
|
3
3
|
import '../material/lambert.js';
|
|
4
4
|
import '../material/blinn.js';
|
|
5
5
|
import '../material/unlit.js';
|
|
6
6
|
import '../material/particle.js';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
7
|
+
import '../material/subsurfaceprofile.js';
|
|
8
|
+
import { SSSProfile } from '../material/sssprofile.js';
|
|
9
|
+
import '../material/skin.js';
|
|
9
10
|
import '../material/eye.js';
|
|
10
11
|
import '@zephyr3d/device';
|
|
11
12
|
import '../values.js';
|
|
@@ -21,289 +22,247 @@ import '../material/pbrblueprint.js';
|
|
|
21
22
|
import '../material/sprite.js';
|
|
22
23
|
import '../material/sprite_std.js';
|
|
23
24
|
import '../utility/blueprint/material/texture.js';
|
|
24
|
-
import {
|
|
25
|
+
import { hash21 } from '../shaders/noise.js';
|
|
25
26
|
import { linearToGamma } from '../shaders/misc.js';
|
|
26
27
|
import { fetchSampler } from '../utility/misc.js';
|
|
27
28
|
import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* -
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @
|
|
30
|
+
const SKIN_SSS_DEBUG_OUTPUTS = [
|
|
31
|
+
'none',
|
|
32
|
+
'diffusible',
|
|
33
|
+
'diffuseAmount',
|
|
34
|
+
'profileId',
|
|
35
|
+
'normal',
|
|
36
|
+
'diffusionDistance',
|
|
37
|
+
'sampleRadius',
|
|
38
|
+
'weight',
|
|
39
|
+
'acceptance',
|
|
40
|
+
'centerWeight',
|
|
41
|
+
'diffused',
|
|
42
|
+
'thickness'
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Radial-disc sample count, matching UE5's `BURLEY_NUM_SAMPLES`.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* UE5 drives the count from a variance history and resolves the remainder
|
|
49
|
+
* temporally. Without that history every frame stands on its own, so the full
|
|
50
|
+
* count is used throughout.
|
|
51
|
+
*/ const DEFAULT_SAMPLE_COUNT = 64;
|
|
52
|
+
/**
|
|
53
|
+
* Screen-space skin scattering — 3-pass pipeline aligned with UE5.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Pass 1 (Burley): Radial-disc Burley diffusion with Halton + inverse-CDF
|
|
57
|
+
* importance sampling, 3D bilateral distance, per-channel kernel.
|
|
58
|
+
* Pass 2 (BVar): Luminance variance + depth gradient → transmission estimate.
|
|
59
|
+
* Pass 3 (Recombine): Specular/diffuse separation via SceneColor.a, then the
|
|
60
|
+
* diffused half summed back with the specular remainder.
|
|
61
|
+
*
|
|
62
|
+
* The pass exposes no scattering parameters of its own — every one of them
|
|
63
|
+
* belongs to the {@link SSSProfile} the material points at, as in UE5.
|
|
64
|
+
*
|
|
65
|
+
* WebGPU only.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
63
68
|
*/ class SkinSSS extends AbstractPostEffect {
|
|
64
|
-
static
|
|
65
|
-
static
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
static _burleyProgram = null;
|
|
70
|
+
static _bvarProgram = null;
|
|
71
|
+
static _recombineProgram = null;
|
|
72
|
+
_burleyBindGroup;
|
|
73
|
+
_bvarBindGroup;
|
|
74
|
+
_recombineBindGroup;
|
|
69
75
|
_profile;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
_colorBoost;
|
|
76
|
-
_scatterRadius;
|
|
77
|
-
_smoothness;
|
|
78
|
-
_scatterTint;
|
|
79
|
-
_channelRadius;
|
|
80
|
-
_channelFalloff;
|
|
81
|
-
_blurDirection;
|
|
82
|
-
_radiusParams;
|
|
76
|
+
_debugOutput;
|
|
77
|
+
_debugExposure;
|
|
78
|
+
_sampleCount;
|
|
79
|
+
_projScale;
|
|
80
|
+
_profileParams;
|
|
83
81
|
_targetSize;
|
|
84
82
|
_cameraNearFar;
|
|
85
83
|
constructor(){
|
|
86
84
|
super();
|
|
87
85
|
this._layer = PostEffectLayer.opaque;
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
86
|
+
this._burleyBindGroup = null;
|
|
87
|
+
this._bvarBindGroup = null;
|
|
88
|
+
this._recombineBindGroup = null;
|
|
91
89
|
this._profile = null;
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
97
|
-
this._colorBoost = 1;
|
|
98
|
-
this._scatterRadius = 0.02;
|
|
99
|
-
this._smoothness = 0;
|
|
100
|
-
this._scatterTint = new Vector4(1, 1, 1, 1);
|
|
101
|
-
this._channelRadius = new Vector3(1, 1, 1);
|
|
102
|
-
this._channelFalloff = new Vector3(1, 1, 1);
|
|
103
|
-
this._blurDirection = new Vector2();
|
|
104
|
-
this._radiusParams = new Vector4();
|
|
90
|
+
this._debugOutput = 'none';
|
|
91
|
+
this._debugExposure = 1;
|
|
92
|
+
this._sampleCount = DEFAULT_SAMPLE_COUNT;
|
|
93
|
+
this._projScale = new Vector2();
|
|
94
|
+
this._profileParams = new Vector4();
|
|
105
95
|
this._targetSize = new Vector4();
|
|
106
96
|
this._cameraNearFar = new Vector2();
|
|
107
97
|
}
|
|
108
|
-
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* @remarks
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* The channel ratios are what make skin read as skin - red travels several times further than
|
|
118
|
-
* blue, which is the red-to-orange-to-yellow gradient at the terminator. They are also the main
|
|
119
|
-
* stylization lever: the `wax` and `jade` presets are the same code path with different ratios.
|
|
98
|
+
/**
|
|
99
|
+
* Fallback profile for pixels whose id is not in the table.
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* Scattering parameters normally come from the per-pixel profile id the skin
|
|
103
|
+
* material writes, so this only applies when that lookup misses.
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
120
106
|
*/ get profile() {
|
|
121
107
|
return this._profile;
|
|
122
108
|
}
|
|
123
109
|
set profile(val) {
|
|
124
110
|
this._profile = val ?? null;
|
|
125
111
|
}
|
|
126
|
-
|
|
127
|
-
return this.
|
|
112
|
+
get sampleCount() {
|
|
113
|
+
return this._sampleCount;
|
|
128
114
|
}
|
|
129
|
-
set
|
|
130
|
-
this.
|
|
115
|
+
set sampleCount(val) {
|
|
116
|
+
this._sampleCount = Math.max(8, Math.min(64, Math.round(val ?? DEFAULT_SAMPLE_COUNT)));
|
|
131
117
|
}
|
|
132
|
-
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* @remarks
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
|
|
141
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Multiplier applied to whatever {@link SkinSSS.debugOutput} renders.
|
|
120
|
+
*
|
|
121
|
+
* @remarks
|
|
122
|
+
* Defaults to 1. Several intermediates sit in a narrow band that reads as a
|
|
123
|
+
* flat tone at unit exposure, so raising this is how "no variation" is told
|
|
124
|
+
* apart from "variation too small to see". No effect unless a debug output is
|
|
125
|
+
* selected.
|
|
126
|
+
*
|
|
127
|
+
* @public
|
|
128
|
+
*/ get debugExposure() {
|
|
129
|
+
return this._debugExposure;
|
|
142
130
|
}
|
|
143
|
-
set
|
|
144
|
-
this.
|
|
131
|
+
set debugExposure(val) {
|
|
132
|
+
this._debugExposure = Math.max(0, val ?? 1);
|
|
145
133
|
}
|
|
146
|
-
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* @remarks
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return this._scatterTint;
|
|
134
|
+
/**
|
|
135
|
+
* Intermediate quantity to render instead of the shaded result.
|
|
136
|
+
*
|
|
137
|
+
* @remarks
|
|
138
|
+
* Defaults to `'none'`. See {@link SkinSSSDebugOutput}.
|
|
139
|
+
*
|
|
140
|
+
* @public
|
|
141
|
+
*/ get debugOutput() {
|
|
142
|
+
return this._debugOutput;
|
|
156
143
|
}
|
|
157
|
-
set
|
|
158
|
-
this.
|
|
159
|
-
}
|
|
160
|
-
/** Skin mask coverage threshold. Scattering fades in as the blurred mask coverage exceeds this. */ get opacity() {
|
|
161
|
-
return this._opacity;
|
|
162
|
-
}
|
|
163
|
-
set opacity(val) {
|
|
164
|
-
this._opacity = Math.max(0, Math.min(1, val ?? 0));
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Maximum pixel spacing between blur taps. Caps the projected scatter radius for close-ups.
|
|
168
|
-
*
|
|
169
|
-
* @remarks
|
|
170
|
-
* With `SKIN_SSS_TAPS` taps per side the kernel reaches `sampleStep * SKIN_SSS_TAPS` pixels, so
|
|
171
|
-
* this is the real limit on scatter width. The separable form makes taps cheap enough that the
|
|
172
|
-
* default no longer has to be tight: the old single-pass 9x9 kernel capped out at 8 pixels, and
|
|
173
|
-
* a face in close-up projects a 2 cm scatter radius far past that - the shot that needs the most
|
|
174
|
-
* scattering used to get the least.
|
|
175
|
-
*/ get sampleStep() {
|
|
176
|
-
return this._sampleStep;
|
|
177
|
-
}
|
|
178
|
-
set sampleStep(val) {
|
|
179
|
-
this._sampleStep = Math.max(0.25, Math.min(6, val ?? 0.25));
|
|
180
|
-
}
|
|
181
|
-
/** World-space scatter radius. The blur width shrinks with distance to keep this constant. */ get scatterRadius() {
|
|
182
|
-
return this._scatterRadius;
|
|
183
|
-
}
|
|
184
|
-
set scatterRadius(val) {
|
|
185
|
-
this._scatterRadius = Math.max(0, val ?? 0);
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Skin smoothing amount ("beauty filter"). Blends the lit color toward a mask- and depth-weighted
|
|
189
|
-
* blur of itself on skin pixels, removing pore and albedo detail while facial features stay sharp
|
|
190
|
-
* wherever the skin mask excludes them (eyes, brows, lips). 0 disables smoothing.
|
|
191
|
-
*
|
|
192
|
-
* @remarks
|
|
193
|
-
* This is separate from the diffusion and stacks on top of it. The diffusion blurs *lighting*
|
|
194
|
-
* only; albedo detail such as pores and blemishes passes through it untouched, which is correct
|
|
195
|
-
* but is not what a beauty filter is for.
|
|
196
|
-
*/ get smoothness() {
|
|
197
|
-
return this._smoothness;
|
|
198
|
-
}
|
|
199
|
-
set smoothness(val) {
|
|
200
|
-
this._smoothness = Math.max(0, Math.min(1, val ?? 0));
|
|
201
|
-
}
|
|
202
|
-
/** Depth rejection scale. The reference shader uses 80. */ get depthScale() {
|
|
203
|
-
return this._depthScale;
|
|
204
|
-
}
|
|
205
|
-
set depthScale(val) {
|
|
206
|
-
this._depthScale = Math.max(0, val ?? 0);
|
|
207
|
-
}
|
|
208
|
-
/** Multiplier applied to the diffused irradiance before compositing. */ get colorBoost() {
|
|
209
|
-
return this._colorBoost;
|
|
210
|
-
}
|
|
211
|
-
set colorBoost(val) {
|
|
212
|
-
this._colorBoost = Math.max(0, val ?? 0);
|
|
144
|
+
set debugOutput(val) {
|
|
145
|
+
this._debugOutput = SKIN_SSS_DEBUG_OUTPUTS.includes(val) ? val : 'none';
|
|
213
146
|
}
|
|
214
147
|
requireLinearDepthTexture() {
|
|
215
148
|
return true;
|
|
216
149
|
}
|
|
217
150
|
apply(ctx, inputColorTexture, sceneDepthTexture, srgbOutput) {
|
|
218
|
-
if (!ctx.
|
|
151
|
+
if (!ctx.SSSMaskTexture || !ctx.SSSProfileIdTexture || ctx.device.type !== 'webgpu') {
|
|
219
152
|
this.passThrough(ctx, inputColorTexture, srgbOutput);
|
|
220
153
|
return;
|
|
221
154
|
}
|
|
222
155
|
const device = ctx.device;
|
|
223
156
|
const outputFramebuffer = device.getFramebuffer();
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
157
|
+
const maskTex = ctx.SSSMaskTexture;
|
|
158
|
+
const profileIdTex = ctx.SSSProfileIdTexture;
|
|
159
|
+
const width = inputColorTexture.width;
|
|
160
|
+
const height = inputColorTexture.height;
|
|
161
|
+
const scatterFormat = ctx.colorFormat;
|
|
162
|
+
const profileTable = SSSProfile.getTable(device);
|
|
163
|
+
if (!profileTable) {
|
|
164
|
+
this.passThrough(ctx, inputColorTexture, srgbOutput);
|
|
165
|
+
return;
|
|
227
166
|
}
|
|
228
|
-
|
|
229
|
-
|
|
167
|
+
const fallback = this._profile ?? SSSProfile.getDefault();
|
|
168
|
+
// Projection of a world-space length at unit depth into UV, per axis. Two
|
|
169
|
+
// factors, not one: `m00` and `m11` differ by the aspect ratio, so a single
|
|
170
|
+
// one turns the sampling disc into an ellipse. UE5 reaches the same pair by
|
|
171
|
+
// building x from m00 and correcting y by `Extent.x / Extent.y`.
|
|
172
|
+
const projMatrix = ctx.camera.getProjectionMatrix();
|
|
173
|
+
this._projScale.setXY(// x: world length at unit depth, in horizontal UV
|
|
174
|
+
0.5 * projMatrix.m00, // y: world length at unit depth, in vertical UV
|
|
175
|
+
0.5 * projMatrix.m11);
|
|
176
|
+
// Row of the fallback profile, used when a pixel's id is missing.
|
|
177
|
+
this._profileParams.setXYZW(fallback.encodedId, 1 / SSSProfile.tableColumns, 1 / SSSProfile.tableRows, SSSProfile.tableRows);
|
|
178
|
+
this._targetSize.setXYZW(width, height, 1 / width, 1 / height);
|
|
179
|
+
this._cameraNearFar.setXY(ctx.camera.getNearPlane(), ctx.camera.getFarPlane());
|
|
180
|
+
if (!SkinSSS._burleyProgram) {
|
|
181
|
+
SkinSSS._burleyProgram = this.createBurleyProgram(ctx);
|
|
230
182
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (!this._blurBindGroupH) {
|
|
234
|
-
// Two bind groups rather than one program per direction: the only thing
|
|
235
|
-
// that differs is the step vector, and rewriting it between draws would
|
|
236
|
-
// race the first draw's read of it.
|
|
237
|
-
this._blurBindGroupH = device.createBindGroup(blurProgram.bindGroupLayouts[0]);
|
|
238
|
-
this._blurBindGroupV = device.createBindGroup(blurProgram.bindGroupLayouts[0]);
|
|
183
|
+
if (!SkinSSS._bvarProgram) {
|
|
184
|
+
SkinSSS._bvarProgram = this.createBVarProgram(ctx);
|
|
239
185
|
}
|
|
240
|
-
if (!
|
|
241
|
-
|
|
186
|
+
if (!SkinSSS._recombineProgram) {
|
|
187
|
+
SkinSSS._recombineProgram = this.createRecombineProgram(ctx);
|
|
242
188
|
}
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
189
|
+
const diffusedFB = device.pool.fetchTemporalFramebuffer(false, width, height, scatterFormat, null, false);
|
|
190
|
+
const bvarFB = device.pool.fetchTemporalFramebuffer(false, width, height, scatterFormat, null, false);
|
|
191
|
+
// --- Pass 1: Burley diffusion ---
|
|
192
|
+
device.pushDeviceStates();
|
|
193
|
+
try {
|
|
194
|
+
if (!this._burleyBindGroup) {
|
|
195
|
+
this._burleyBindGroup = device.createBindGroup(SkinSSS._burleyProgram.bindGroupLayouts[0]);
|
|
196
|
+
}
|
|
197
|
+
const bg = this._burleyBindGroup;
|
|
198
|
+
bg.setTexture('sceneTex', inputColorTexture, fetchSampler('clamp_linear'));
|
|
199
|
+
bg.setTexture('maskTex', maskTex, fetchSampler('clamp_linear'));
|
|
200
|
+
// Nearest only: the id is a table row, and an interpolated one addresses a
|
|
201
|
+
// profile neither neighbour has.
|
|
202
|
+
bg.setTexture('profileIdTex', profileIdTex, fetchSampler('clamp_nearest_nomip'));
|
|
203
|
+
bg.setTexture('depthTex', sceneDepthTexture, fetchSampler('clamp_nearest_nomip'));
|
|
204
|
+
bg.setValue('cameraNearFar', this._cameraNearFar);
|
|
205
|
+
bg.setValue('targetSize', this._targetSize);
|
|
206
|
+
bg.setValue('projScale', this._projScale);
|
|
207
|
+
bg.setValue('perspective', ctx.camera.isPerspective() ? 1 : 0);
|
|
208
|
+
bg.setValue('profileParams', this._profileParams);
|
|
209
|
+
bg.setTexture('profileTex', profileTable, fetchSampler('clamp_nearest_nomip'));
|
|
210
|
+
bg.setTexture('thicknessTex', ctx.transmissionThicknessTexture ?? ShaderHelper.getDummyTransmissionThickness(device), fetchSampler('clamp_nearest'));
|
|
211
|
+
bg.setValue('sampleCount', this._sampleCount);
|
|
212
|
+
bg.setValue('debugMode', SKIN_SSS_DEBUG_OUTPUTS.indexOf(this._debugOutput));
|
|
213
|
+
bg.setValue('debugExposure', this._debugExposure);
|
|
214
|
+
// Intermediate passes always render into a texture, so on WebGPU they flip
|
|
215
|
+
// unconditionally. `needFlip` cannot be used: it reports whatever target is
|
|
216
|
+
// bound at the time, and these values are set before the pass binds its own.
|
|
217
|
+
bg.setValue('flip', device.type === 'webgpu' ? 1 : 0);
|
|
218
|
+
device.setProgram(SkinSSS._burleyProgram);
|
|
219
|
+
device.setBindGroup(0, bg);
|
|
220
|
+
device.setFramebuffer(diffusedFB);
|
|
221
|
+
this.drawFullscreenQuad();
|
|
222
|
+
} finally{
|
|
223
|
+
device.popDeviceStates();
|
|
224
|
+
}
|
|
225
|
+
// --- Pass 2: BVar transmission ---
|
|
226
|
+
device.pushDeviceStates();
|
|
227
|
+
try {
|
|
228
|
+
if (!this._bvarBindGroup) {
|
|
229
|
+
this._bvarBindGroup = device.createBindGroup(SkinSSS._bvarProgram.bindGroupLayouts[0]);
|
|
230
|
+
}
|
|
231
|
+
const bg = this._bvarBindGroup;
|
|
232
|
+
bg.setTexture('diffusedTex', diffusedFB.getColorAttachments()[0], fetchSampler('clamp_linear'));
|
|
233
|
+
bg.setTexture('depthTex', sceneDepthTexture, fetchSampler('clamp_nearest_nomip'));
|
|
234
|
+
bg.setValue('targetSize', this._targetSize);
|
|
235
|
+
bg.setValue('cameraNearFar', this._cameraNearFar);
|
|
236
|
+
bg.setValue('flip', device.type === 'webgpu' ? 1 : 0);
|
|
237
|
+
device.setProgram(SkinSSS._bvarProgram);
|
|
238
|
+
device.setBindGroup(0, bg);
|
|
239
|
+
device.setFramebuffer(bvarFB);
|
|
240
|
+
this.drawFullscreenQuad();
|
|
241
|
+
} finally{
|
|
242
|
+
device.popDeviceStates();
|
|
243
|
+
}
|
|
244
|
+
// --- Pass 3: Recombine ---
|
|
262
245
|
device.setFramebuffer(outputFramebuffer);
|
|
263
|
-
this.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
bindGroup.setValue('flip', 0);
|
|
279
|
-
device.setProgram(program);
|
|
280
|
-
device.setBindGroup(0, bindGroup);
|
|
281
|
-
this.drawFullscreenQuad();
|
|
282
|
-
}
|
|
283
|
-
combine(ctx, program, colorTexture, skinTexture, blurredTexture, sceneDepthTexture, encodeScale, srgbOutput) {
|
|
284
|
-
const device = ctx.device;
|
|
285
|
-
const bindGroup = this._combineBindGroup;
|
|
286
|
-
bindGroup.setTexture('colorTex', colorTexture, fetchSampler('clamp_linear'));
|
|
287
|
-
bindGroup.setTexture('skinTex', skinTexture, fetchSampler('clamp_linear'));
|
|
288
|
-
bindGroup.setTexture('blurredTex', blurredTexture, fetchSampler('clamp_linear'));
|
|
289
|
-
bindGroup.setTexture('depthTex', sceneDepthTexture, fetchSampler('clamp_nearest_nomip'));
|
|
290
|
-
bindGroup.setValue('cameraNearFar', this._cameraNearFar);
|
|
291
|
-
bindGroup.setValue('targetSize', this._targetSize);
|
|
292
|
-
bindGroup.setValue('radiusParams', this._radiusParams);
|
|
293
|
-
bindGroup.setValue('scatterTint', this._scatterTint);
|
|
294
|
-
bindGroup.setValue('strength', this._strength);
|
|
295
|
-
bindGroup.setValue('glow', this._glow);
|
|
296
|
-
bindGroup.setValue('opacity', this._opacity);
|
|
297
|
-
bindGroup.setValue('colorBoost', this._colorBoost);
|
|
298
|
-
bindGroup.setValue('encodeScale', encodeScale);
|
|
299
|
-
bindGroup.setValue('smoothness', this._smoothness);
|
|
300
|
-
bindGroup.setValue('flip', this.needFlip(device) ? 1 : 0);
|
|
301
|
-
bindGroup.setValue('srgbOut', srgbOutput ? 1 : 0);
|
|
302
|
-
device.setProgram(program);
|
|
303
|
-
device.setBindGroup(0, bindGroup);
|
|
246
|
+
if (!this._recombineBindGroup) {
|
|
247
|
+
this._recombineBindGroup = device.createBindGroup(SkinSSS._recombineProgram.bindGroupLayouts[0]);
|
|
248
|
+
}
|
|
249
|
+
const rbg = this._recombineBindGroup;
|
|
250
|
+
rbg.setTexture('colorTex', inputColorTexture, fetchSampler('clamp_linear'));
|
|
251
|
+
rbg.setTexture('maskTex', maskTex, fetchSampler('clamp_linear'));
|
|
252
|
+
rbg.setTexture('bvarTex', bvarFB.getColorAttachments()[0], fetchSampler('clamp_linear'));
|
|
253
|
+
rbg.setTexture('depthTex', sceneDepthTexture, fetchSampler('clamp_nearest_nomip'));
|
|
254
|
+
rbg.setValue('cameraNearFar', this._cameraNearFar);
|
|
255
|
+
rbg.setValue('targetSize', this._targetSize);
|
|
256
|
+
rbg.setValue('debugMode', SKIN_SSS_DEBUG_OUTPUTS.indexOf(this._debugOutput));
|
|
257
|
+
rbg.setValue('flip', this.needFlip(device) ? 1 : 0);
|
|
258
|
+
rbg.setValue('srgbOut', srgbOutput ? 1 : 0);
|
|
259
|
+
device.setProgram(SkinSSS._recombineProgram);
|
|
260
|
+
device.setBindGroup(0, rbg);
|
|
304
261
|
this.drawFullscreenQuad();
|
|
262
|
+
device.pool.releaseFrameBuffer(diffusedFB);
|
|
263
|
+
device.pool.releaseFrameBuffer(bvarFB);
|
|
305
264
|
}
|
|
306
|
-
|
|
265
|
+
static fullscreenVertex(pb) {
|
|
307
266
|
pb.getGlobalScope().flip = pb.int().uniform(0);
|
|
308
267
|
pb.getGlobalScope().$inputs.pos = pb.vec2().attrib('position');
|
|
309
268
|
pb.getGlobalScope().$outputs.uv = pb.vec2();
|
|
@@ -315,109 +274,415 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
|
315
274
|
});
|
|
316
275
|
});
|
|
317
276
|
}
|
|
318
|
-
|
|
277
|
+
createBurleyProgram(ctx) {
|
|
278
|
+
// WebGL1 has no texture array sampling. This effect is WebGPU-only at
|
|
279
|
+
// runtime, but its programs are still built for WebGL by the shader
|
|
280
|
+
// generation tests, so the thickness debug view has to compile out there.
|
|
281
|
+
const hasTextureArrays = ctx.device.type !== 'webgl';
|
|
319
282
|
const program = ctx.device.buildRenderProgram({
|
|
320
283
|
vertex (pb) {
|
|
321
284
|
SkinSSS.fullscreenVertex(pb);
|
|
322
285
|
},
|
|
323
286
|
fragment (pb) {
|
|
324
|
-
this.skinTex = pb.tex2D().uniform(0);
|
|
325
287
|
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
288
|
+
this.sceneTex = pb.tex2D().uniform(0);
|
|
289
|
+
this.maskTex = pb.tex2D().uniform(0);
|
|
290
|
+
this.profileIdTex = pb.tex2D().uniform(0);
|
|
326
291
|
this.cameraNearFar = pb.vec2().uniform(0);
|
|
327
292
|
this.targetSize = pb.vec4().uniform(0);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
this.
|
|
331
|
-
this.
|
|
332
|
-
|
|
333
|
-
|
|
293
|
+
this.projScale = pb.vec2().uniform(0);
|
|
294
|
+
this.perspective = pb.int().uniform(0);
|
|
295
|
+
this.profileParams = pb.vec4().uniform(0);
|
|
296
|
+
this.profileTex = pb.tex2D().uniform(0);
|
|
297
|
+
// Always declared so the cached program has one layout; a 1x1 dummy is
|
|
298
|
+
// bound on frames without a transmission pass. Only the debug view reads
|
|
299
|
+
// it — shading takes its thickness through the material, per light.
|
|
300
|
+
if (hasTextureArrays) {
|
|
301
|
+
this.thicknessTex = pb.tex2DArray().uniform(0);
|
|
302
|
+
}
|
|
303
|
+
this.sampleCount = pb.int().uniform(0);
|
|
304
|
+
this.debugMode = pb.int().uniform(0);
|
|
305
|
+
this.debugExposure = pb.float().uniform(0);
|
|
334
306
|
this.$outputs.outColor = pb.vec4();
|
|
335
307
|
pb.func('readDepth01', [
|
|
336
308
|
pb.vec2('uv')
|
|
337
309
|
], function() {
|
|
338
310
|
this.$return(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0));
|
|
339
311
|
});
|
|
312
|
+
// UE5's spec/diff separation (SSS::Setup): SceneColor.a holds the diffuse
|
|
313
|
+
// luminance, so the diffusible fraction is its ratio to the total.
|
|
314
|
+
//
|
|
315
|
+
// The skin mask comes from its own channel, never SceneColor.a, which
|
|
316
|
+
// every opaque material writes 1 to. The alpha returned here is UE5's
|
|
317
|
+
// subsurface Opacity, a continuous 0..1 scattering weight.
|
|
318
|
+
pb.func('readDiffusible', [
|
|
319
|
+
pb.vec2('uv')
|
|
320
|
+
], function() {
|
|
321
|
+
this.$l.scene = pb.textureSampleLevel(this.sceneTex, this.uv, 0);
|
|
322
|
+
this.$l.opacity = pb.clamp(pb.textureSampleLevel(this.maskTex, this.uv, 0).a, 0, 1);
|
|
323
|
+
this.$l.lum = pb.dot(this.scene.rgb, pb.vec3(0.2126, 0.7152, 0.0722));
|
|
324
|
+
// Below the floor the ratio tracks brightness rather than the split, so
|
|
325
|
+
// treat those pixels as fully diffusible - one that dark has no specular
|
|
326
|
+
// worth preserving.
|
|
327
|
+
//
|
|
328
|
+
// `pb.select(x, y, cond)` is `cond ? y : x`, following WGSL's builtin
|
|
329
|
+
// where the *false* value comes first. It reads like a ternary and is
|
|
330
|
+
// not one.
|
|
331
|
+
this.$l.diffAmt = pb.select(pb.float(1), pb.clamp(pb.div(this.scene.a, pb.max(this.lum, 1e-4)), 0, 1), pb.greaterThan(this.lum, 1e-4));
|
|
332
|
+
this.$return(pb.vec4(pb.mul(this.scene.rgb, this.diffAmt), this.opacity));
|
|
333
|
+
});
|
|
334
|
+
// Per-pixel profile id, from the depth prepass rather than the mask
|
|
335
|
+
// buffer. Keeping them apart is what lets the opacity above be
|
|
336
|
+
// continuous: one channel cannot carry a table row and a weight at once.
|
|
337
|
+
pb.func('readProfileId', [
|
|
338
|
+
pb.vec2('uv')
|
|
339
|
+
], function() {
|
|
340
|
+
this.$return(pb.textureSampleLevel(this.profileIdTex, this.uv, 0).r);
|
|
341
|
+
});
|
|
342
|
+
// --- Burley diffusion, transcribed from UE5's BurleyNormalizedSSSCommon.ush ---
|
|
343
|
+
//
|
|
344
|
+
// Radii are in profile space and unbounded: the inverse CDF produces them
|
|
345
|
+
// directly and `burleyScale` converts them to a UV offset. There is
|
|
346
|
+
// deliberately no sampling disc to normalize against - one would couple
|
|
347
|
+
// the kernel shape to the mean free path.
|
|
348
|
+
// GetSearchLightDiffuseScalingFactor: s = 3.5 + 100 (A - 0.33)^4.
|
|
349
|
+
// This is the default of UE5's three scaling-factor variants.
|
|
350
|
+
pb.func('scalingFactor', [
|
|
351
|
+
pb.float('a')
|
|
352
|
+
], function() {
|
|
353
|
+
this.$l.v = pb.sub(this.a, 0.33);
|
|
354
|
+
this.$l.v2 = pb.mul(this.v, this.v);
|
|
355
|
+
this.$return(pb.add(3.5, pb.mul(100, this.v2, this.v2)));
|
|
356
|
+
});
|
|
357
|
+
pb.func('scalingFactor3D', [
|
|
358
|
+
pb.vec3('a')
|
|
359
|
+
], function() {
|
|
360
|
+
this.$l.v = pb.sub(this.a, pb.vec3(0.33));
|
|
361
|
+
this.$l.v2 = pb.mul(this.v, this.v);
|
|
362
|
+
this.$return(pb.add(pb.vec3(3.5), pb.mul(pb.vec3(100), this.v2, this.v2)));
|
|
363
|
+
});
|
|
364
|
+
// RadiusRootFindByApproximation: d ((2 - 2.6) xi - 2) ln(1 - xi).
|
|
365
|
+
pb.func('radiusRootApprox', [
|
|
366
|
+
pb.float('d'),
|
|
367
|
+
pb.float('xi')
|
|
368
|
+
], function() {
|
|
369
|
+
this.$l.x = pb.min(this.xi, 0.9999);
|
|
370
|
+
this.$return(pb.mul(this.d, pb.sub(pb.mul(pb.sub(2, 2.6), this.x), 2), pb.log(pb.max(pb.sub(1, this.x), 1e-6))));
|
|
371
|
+
});
|
|
372
|
+
// GetCDFDeriv1: 0.25/d (e^{-r/d} + e^{-r/3d}), clamped as CLAMP_PDF does.
|
|
373
|
+
pb.func('burleyPdf', [
|
|
374
|
+
pb.float('r'),
|
|
375
|
+
pb.float('l'),
|
|
376
|
+
pb.float('s')
|
|
377
|
+
], function() {
|
|
378
|
+
this.$l.d = pb.max(pb.div(this.l, this.s), 1e-6);
|
|
379
|
+
this.$l.e1 = pb.exp(pb.neg(pb.min(pb.div(this.r, this.d), 40)));
|
|
380
|
+
this.$l.e2 = pb.exp(pb.neg(pb.min(pb.div(this.r, pb.mul(this.d, 3)), 40)));
|
|
381
|
+
this.$return(pb.max(pb.mul(pb.div(0.25, this.d), pb.add(this.e1, this.e2)), 1e-5));
|
|
382
|
+
});
|
|
383
|
+
// GetDiffuseReflectProfileWithDiffuseMeanFreePath: r R(r), per channel.
|
|
384
|
+
// D = 1/s3d, R = radius/L, (e^{-R/D} + e^{-R/3D}) / (D L) / 8pi
|
|
385
|
+
pb.func('diffusionProfile', [
|
|
386
|
+
pb.vec3('l'),
|
|
387
|
+
pb.vec3('s3d'),
|
|
388
|
+
pb.float('radius')
|
|
389
|
+
], function() {
|
|
390
|
+
this.$l.D = pb.div(pb.vec3(1), pb.max(this.s3d, pb.vec3(1e-6)));
|
|
391
|
+
this.$l.L = pb.max(this.l, pb.vec3(1e-6));
|
|
392
|
+
this.$l.R = pb.div(pb.vec3(this.radius), this.L);
|
|
393
|
+
this.$l.negRD = pb.div(pb.neg(this.R), this.D);
|
|
394
|
+
this.$l.e1 = pb.exp(pb.max(this.negRD, pb.vec3(-40)));
|
|
395
|
+
this.$l.e2 = pb.exp(pb.max(pb.div(this.negRD, 3), pb.vec3(-40)));
|
|
396
|
+
this.$return(pb.max(pb.div(pb.add(this.e1, this.e2), pb.mul(this.D, this.L, 8 * Math.PI)), pb.vec3(1e-12)));
|
|
397
|
+
});
|
|
398
|
+
// GetCDF with XI = 0, used to weight the centre sample.
|
|
399
|
+
pb.func('burleyCdf', [
|
|
400
|
+
pb.float('d'),
|
|
401
|
+
pb.float('x')
|
|
402
|
+
], function() {
|
|
403
|
+
this.$l.sd = pb.max(this.d, 1e-6);
|
|
404
|
+
this.$return(pb.clamp(pb.sub(pb.sub(1, pb.mul(0.25, pb.exp(pb.neg(pb.min(pb.div(this.x, this.sd), 40))))), pb.mul(0.75, pb.exp(pb.neg(pb.min(pb.div(this.x, pb.mul(this.sd, 3)), 40))))), 0, 1));
|
|
405
|
+
});
|
|
406
|
+
// R2Sequence: the low-discrepancy pair UE5 samples radius and angle with.
|
|
407
|
+
// The index must stay an integer - offsetting it by a per-pixel fraction
|
|
408
|
+
// turns the sequence into a phase sweep correlated with pixel position,
|
|
409
|
+
// which shows up as banding.
|
|
410
|
+
pb.func('r2Sequence', [
|
|
411
|
+
pb.int('i')
|
|
412
|
+
], function() {
|
|
413
|
+
this.$return(pb.fract(pb.mul(pb.vec2(0.7548776662466927, 0.5698402909980532), pb.float(this.i))));
|
|
414
|
+
});
|
|
415
|
+
// Fetch one parameter column of a profile row. `id` is the normalized
|
|
416
|
+
// per-pixel profile id; rows are addressed by it directly, as UE5 does
|
|
417
|
+
// with its SSProfiles table.
|
|
418
|
+
pb.func('readProfile', [
|
|
419
|
+
pb.float('id'),
|
|
420
|
+
pb.float('column')
|
|
421
|
+
], function() {
|
|
422
|
+
this.$l.u = pb.mul(pb.add(this.column, 0.5), this.profileParams.y);
|
|
423
|
+
this.$l.v = pb.mul(pb.add(pb.mul(pb.clamp(this.id, 0, 1), 255), 0.5), this.profileParams.z);
|
|
424
|
+
this.$return(pb.textureSampleLevel(this.profileTex, pb.vec2(this.u, this.v), 0));
|
|
425
|
+
});
|
|
426
|
+
pb.func('readNormal', [
|
|
427
|
+
pb.vec2('uv')
|
|
428
|
+
], function() {
|
|
429
|
+
this.$return(pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.maskTex, this.uv, 0).rgb, 2), pb.vec3(1))));
|
|
430
|
+
});
|
|
340
431
|
pb.main(function() {
|
|
341
432
|
this.$l.uv = this.$inputs.uv;
|
|
342
|
-
this.$l.center =
|
|
433
|
+
this.$l.center = this.readDiffusible(this.uv);
|
|
434
|
+
this.$l.centerId = this.readProfileId(this.uv);
|
|
343
435
|
this.$l.centerDepth01 = this.readDepth01(this.uv);
|
|
344
436
|
this.$outputs.outColor = this.center;
|
|
345
|
-
//
|
|
346
|
-
|
|
437
|
+
// Two distinct tests: a pixel can carry a profile with zero opacity, or
|
|
438
|
+
// an opacity with no profile where the prepass and the light pass
|
|
439
|
+
// disagree about coverage. Neither scatters.
|
|
440
|
+
this.$if(pb.and(pb.lessThan(this.centerDepth01, 1), pb.and(pb.greaterThan(this.center.a, 1e-4), pb.greaterThan(this.centerId, 0.5 / 255))), function() {
|
|
347
441
|
this.$l.centerDepth = pb.max(pb.mul(this.centerDepth01, this.cameraNearFar.y), 1e-4);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
this.$l.
|
|
352
|
-
this.$l.
|
|
353
|
-
|
|
354
|
-
// the
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.$l.
|
|
360
|
-
this.$l.
|
|
361
|
-
this.$l.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
442
|
+
this.$l.centerNormal = this.readNormal(this.uv);
|
|
443
|
+
this.$l.scaling = this.readProfile(this.centerId, 0);
|
|
444
|
+
this.$l.albedo = this.readProfile(this.centerId, 1);
|
|
445
|
+
this.$l.mfp = this.readProfile(this.centerId, 2);
|
|
446
|
+
this.$l.boundaryBleed = this.readProfile(this.centerId, 3).rgb;
|
|
447
|
+
this.$l.worldUnitScale = pb.max(this.scaling.x, 1e-3);
|
|
448
|
+
// UE5 draws the radius from one representative channel
|
|
449
|
+
// (SurfaceAlbedo.a / DiffuseMeanFreePath.a) and evaluates all three
|
|
450
|
+
// channel kernels at those radii.
|
|
451
|
+
this.$l.aForSampling = this.albedo.w;
|
|
452
|
+
this.$l.lForSampling = pb.max(this.mfp.w, 1e-6);
|
|
453
|
+
this.$l.S = this.scalingFactor(this.aForSampling);
|
|
454
|
+
this.$l.S3D = this.scalingFactor3D(this.albedo.rgb);
|
|
455
|
+
this.$l.dForSampling = pb.div(this.lForSampling, pb.max(this.S, 1e-6));
|
|
456
|
+
// CalculateBurleyScale: profile space to UV. Purely unit conversion,
|
|
457
|
+
// including the 1/depth perspective term - nothing here shapes the
|
|
458
|
+
// kernel.
|
|
459
|
+
// Perspective divides the world radius by the view depth so the disc
|
|
460
|
+
// keeps a constant world size as the subject recedes; orthographic
|
|
461
|
+
// has no such term and takes 1.
|
|
462
|
+
this.$l.viewScale = pb.max(pb.select(this.centerDepth, pb.float(1), pb.equal(this.perspective, 0)), 1e-4);
|
|
463
|
+
// The world unit scale is applied here and only here; the packed
|
|
464
|
+
// mean free path leaves it out, as UE5 does. Applying it in both
|
|
465
|
+
// places would make the diffusion scale with its square. UE5's
|
|
466
|
+
// extra cm-to-mm cast has no counterpart in this metric engine.
|
|
467
|
+
this.$l.burleyScale = pb.div(pb.mul(this.projScale, this.worldUnitScale), this.viewScale);
|
|
468
|
+
// Centre-sample reweighting: the radius within one texel and the CDF
|
|
469
|
+
// mass it accounts for. Sampling then covers only [cdf, 1] and the
|
|
470
|
+
// centre is lerped back in at the end. Averaged over the two axes,
|
|
471
|
+
// as UE5's CalculateCenterSampleRadiusInMM does, since they carry
|
|
472
|
+
// different scales.
|
|
473
|
+
this.$l.centerRadiusMM = pb.mul(0.5, pb.add(pb.div(this.targetSize.z, pb.max(this.burleyScale.x, 1e-9)), pb.div(this.targetSize.w, pb.max(this.burleyScale.y, 1e-9))));
|
|
474
|
+
this.$l.centerCdf = this.burleyCdf(this.dForSampling, this.centerRadiusMM);
|
|
475
|
+
// Per-channel diffusion distance, the same `d` the kernel below is
|
|
476
|
+
// evaluated with. UE5's scalar DiffuseMeanFreePath works there
|
|
477
|
+
// because its `.a` and `.rgb` are related by construction; here `.a`
|
|
478
|
+
// is simply the widest channel, so substituting it would flatten the
|
|
479
|
+
// channel ratio - and invert it, since a smaller `d` concentrates
|
|
480
|
+
// more CDF mass inside one texel.
|
|
481
|
+
this.$l.dPerChannel = pb.div(this.mfp.rgb, pb.max(this.S3D, pb.vec3(1e-6)));
|
|
482
|
+
this.$l.centerWeight = pb.vec3(this.burleyCdf(this.dPerChannel.x, this.centerRadiusMM), this.burleyCdf(this.dPerChannel.y, this.centerRadiusMM), this.burleyCdf(this.dPerChannel.z, this.centerRadiusMM));
|
|
483
|
+
// Integer sequence start, rebased per pixel so neighbours do not
|
|
484
|
+
// draw the identical 64 taps. It must land on an integer to keep the
|
|
485
|
+
// low-discrepancy spacing, as UE5's 16-bit `Rand3DPCG16` rebase does.
|
|
486
|
+
//
|
|
487
|
+
// UE5 also advances the seed per frame and leans on TAA to resolve
|
|
488
|
+
// the remainder. Without that history a per-frame seed would read as
|
|
489
|
+
// flicker, so this is held fixed and trades it for a stable error.
|
|
490
|
+
this.$l.seedStart = pb.int(pb.mul(hash21(this, pb.mul(this.uv, this.targetSize.xy)), 65536));
|
|
491
|
+
this.$l.radianceAccum = pb.vec3(0);
|
|
492
|
+
this.$l.weightAccum = pb.vec3(0);
|
|
493
|
+
this.$l.bleedAccum = pb.vec3(0);
|
|
494
|
+
this.$l.acceptedCount = pb.float(0);
|
|
495
|
+
this.$l.radiusSum = pb.float(0);
|
|
496
|
+
this.$for(pb.int('i'), 0, DEFAULT_SAMPLE_COUNT, function() {
|
|
497
|
+
this.$l.rand = this.r2Sequence(pb.add(this.seedStart, this.i));
|
|
498
|
+
// Restrict the radius draw to the range the centre sample does not
|
|
499
|
+
// already account for.
|
|
500
|
+
this.$l.xi = pb.add(this.centerCdf, pb.mul(this.rand.x, pb.sub(1, this.centerCdf)));
|
|
501
|
+
this.$l.radiusMM = pb.max(this.radiusRootApprox(this.dForSampling, this.xi), 1e-5);
|
|
502
|
+
this.$l.pdf = this.burleyPdf(this.radiusMM, this.lForSampling, this.S);
|
|
503
|
+
this.$l.theta = pb.mul(this.rand.y, 2 * Math.PI);
|
|
504
|
+
this.$l.uvOffset = pb.mul(pb.vec2(pb.cos(this.theta), pb.sin(this.theta)), pb.mul(this.radiusMM, this.burleyScale));
|
|
505
|
+
this.$l.sampleUV = pb.clamp(pb.add(this.uv, this.uvOffset), pb.vec2(0), pb.vec2(1));
|
|
506
|
+
this.$l.tapSample = this.readDiffusible(this.sampleUV);
|
|
371
507
|
this.$l.sampleDepth01 = this.readDepth01(this.sampleUV);
|
|
372
508
|
this.$l.sampleDepth = pb.max(pb.mul(this.sampleDepth01, this.cameraNearFar.y), 1e-4);
|
|
373
|
-
this.$l.
|
|
374
|
-
this.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
|
|
509
|
+
this.$l.tapNormal = this.readNormal(this.sampleUV);
|
|
510
|
+
this.$l.normalWeight = pb.sqrt(pb.clamp(pb.add(pb.mul(pb.dot(this.tapNormal, this.centerNormal), 0.5), 0.5), 0, 1));
|
|
511
|
+
// Bilateral term: the depth difference in the same millimetre
|
|
512
|
+
// space as the radius, so a large world unit scale does not
|
|
513
|
+
// over-penalize the sample. UE5 compares the two one-to-one.
|
|
514
|
+
this.$l.deltaDepth = pb.div(pb.sub(this.sampleDepth, this.centerDepth), this.worldUnitScale);
|
|
515
|
+
this.$l.radiusSampledMM = pb.sqrt(pb.add(pb.mul(this.radiusMM, this.radiusMM), pb.mul(this.deltaDepth, this.deltaDepth)));
|
|
516
|
+
this.$l.profile = this.diffusionProfile(this.mfp.rgb, this.S3D, this.radiusSampledMM);
|
|
517
|
+
this.$l.tapId = this.readProfileId(this.sampleUV);
|
|
518
|
+
// The tap's own opacity, UE5's Opacity semantics: 0 contributes
|
|
519
|
+
// nothing, 1 contributes fully, and the values between give the
|
|
520
|
+
// smooth transition a painted mask is for.
|
|
521
|
+
this.$l.tapOpacity = pb.select(pb.float(0), this.tapSample.a, pb.greaterThan(this.tapId, 0.5 / 255));
|
|
522
|
+
// A tap that is not skin falls back to the centre pixel rather
|
|
523
|
+
// than dropping out of the estimator, for two reasons.
|
|
524
|
+
//
|
|
525
|
+
// Variance: this is self-normalized, so a dropped tap leaves both
|
|
526
|
+
// sums. Beside a large non-skin region most of the disc lands in
|
|
527
|
+
// it, and since the per-pixel seed is fixed, neighbouring pixels
|
|
528
|
+
// survive on different taps - spatial white noise.
|
|
529
|
+
//
|
|
530
|
+
// Numerics: importance sampling is exact only for the channel the
|
|
531
|
+
// radii were drawn from, so `profile/pdf` is constant for red
|
|
532
|
+
// while blue decays to ~7e-9 in the tail. Dropping the near taps
|
|
533
|
+
// leaves blue's `weightAccum` on the order of the 1e-9 floor
|
|
534
|
+
// below, which clamps it to zero.
|
|
535
|
+
//
|
|
536
|
+
// The substitution assumes the hole scatters like the pixel under
|
|
537
|
+
// consideration - the same assumption the centre-weighted blend
|
|
538
|
+
// already makes - and costs a slightly firmer edge at the hole.
|
|
539
|
+
this.$l.tapColor = pb.mix(this.center.rgb, this.tapSample.rgb, this.tapOpacity);
|
|
540
|
+
this.$l.sampleWeight = pb.mul(pb.div(this.profile, this.pdf), this.normalWeight);
|
|
541
|
+
// Taps from another profile are tinted rather than dropped, so a
|
|
542
|
+
// face/lip boundary softens instead of seaming. The comparison is
|
|
543
|
+
// against the prepass id now; 0.002 is still half a step of the
|
|
544
|
+
// 8-bit channel it rides in.
|
|
545
|
+
this.$l.sameProfile = pb.float(pb.or(pb.lessThan(pb.abs(pb.sub(this.tapId, this.centerId)), 0.002), pb.lessThan(this.tapOpacity, 1e-4)));
|
|
546
|
+
// Only taps that landed on skin contribute a bleed factor, and
|
|
547
|
+
// both sides of the ratio carry the same opacity, so the mean
|
|
548
|
+
// stays a tint in [bleed, 1]. Taps that fell back to the centre
|
|
549
|
+
// are excluded: they carry the centre's own profile, so counting
|
|
550
|
+
// them would dilute a real boundary tint towards white.
|
|
551
|
+
this.bleedAccum = pb.add(this.bleedAccum, pb.mul(pb.mix(this.boundaryBleed, pb.vec3(1), this.sameProfile), this.tapOpacity));
|
|
552
|
+
this.acceptedCount = pb.add(this.acceptedCount, this.tapOpacity);
|
|
553
|
+
this.radianceAccum = pb.add(this.radianceAccum, pb.mul(this.sampleWeight, this.tapColor));
|
|
554
|
+
this.weightAccum = pb.add(this.weightAccum, this.sampleWeight);
|
|
555
|
+
this.radiusSum = pb.add(this.radiusSum, this.radiusMM);
|
|
556
|
+
});
|
|
557
|
+
// Energy normalization, matching UE5's 1/0.99995 compensation.
|
|
558
|
+
this.$l.diffused = pb.mul(pb.div(this.radianceAccum, pb.max(this.weightAccum, pb.vec3(1e-9))), 1 / 0.99995);
|
|
559
|
+
// Mean bleed over the taps that landed on skin: a tint in [bleed, 1],
|
|
560
|
+
// never a gain. A branch rather than `max(acceptedCount, 1)`, which
|
|
561
|
+
// would only be correct for an integer count - this is a sum of
|
|
562
|
+
// opacities, so a half-masked neighbourhood would be darkened by
|
|
563
|
+
// exactly its own mask on top of the fade applied below.
|
|
564
|
+
this.$l.bleedTint = pb.vec3(1);
|
|
565
|
+
this.$if(pb.greaterThan(this.acceptedCount, 1e-4), function() {
|
|
566
|
+
this.bleedTint = pb.div(this.bleedAccum, this.acceptedCount);
|
|
567
|
+
});
|
|
568
|
+
this.diffused = pb.mul(this.diffused, this.bleedTint);
|
|
569
|
+
// Blend the centre pixel back in with the CDF mass it represents.
|
|
570
|
+
this.diffused = pb.mix(this.diffused, this.center.rgb, this.centerWeight);
|
|
571
|
+
// Per channel: importance sampling is exact only for the one the
|
|
572
|
+
// radii were drawn from, so the three weights differ by orders of
|
|
573
|
+
// magnitude and a single scalar test cannot speak for all of them.
|
|
574
|
+
// Built as a mask rather than a vector-conditioned select, which
|
|
575
|
+
// has no legal GLSL form - the ternary there takes a scalar only.
|
|
576
|
+
this.$l.weightValid = pb.vec3(pb.float(pb.greaterThan(this.weightAccum.x, 0)), pb.float(pb.greaterThan(this.weightAccum.y, 0)), pb.float(pb.greaterThan(this.weightAccum.z, 0)));
|
|
577
|
+
this.diffused = pb.mix(this.center.rgb, this.diffused, this.weightValid);
|
|
578
|
+
// The centre's own opacity fades the whole result back towards the
|
|
579
|
+
// unscattered pixel. Taps were already weighted by *their* opacity,
|
|
580
|
+
// which controls how much the neighbourhood contributes; this
|
|
581
|
+
// controls how much this pixel accepts. UE5 applies the same value
|
|
582
|
+
// at both ends, which is what makes a painted mask read as a
|
|
583
|
+
// gradient in scattering strength rather than as an edge.
|
|
584
|
+
this.diffused = pb.mix(this.center.rgb, this.diffused, this.center.a);
|
|
585
|
+
// Alpha is unused downstream; Recombine reads its own mask.
|
|
586
|
+
this.$outputs.outColor = pb.vec4(pb.max(this.diffused, pb.vec3(0)), this.center.a);
|
|
587
|
+
this.$if(pb.notEqual(this.debugMode, 0), function() {
|
|
588
|
+
this.$l.dbg = pb.vec3(0);
|
|
589
|
+
this.$if(pb.equal(this.debugMode, 1), function() {
|
|
590
|
+
this.dbg = this.center.rgb;
|
|
591
|
+
});
|
|
592
|
+
this.$if(pb.equal(this.debugMode, 2), function() {
|
|
593
|
+
this.$l.lum = pb.dot(pb.textureSampleLevel(this.sceneTex, this.uv, 0).rgb, pb.vec3(0.2126, 0.7152, 0.0722));
|
|
594
|
+
this.dbg = pb.vec3(pb.clamp(pb.div(pb.textureSampleLevel(this.sceneTex, this.uv, 0).a, pb.max(this.lum, 1e-4)), 0, 1));
|
|
595
|
+
});
|
|
596
|
+
this.$if(pb.equal(this.debugMode, 3), function() {
|
|
597
|
+
this.dbg = pb.vec3(pb.mul(this.centerId, 255 / 8));
|
|
598
|
+
});
|
|
599
|
+
this.$if(pb.equal(this.debugMode, 4), function() {
|
|
600
|
+
this.dbg = pb.add(pb.mul(this.centerNormal, 0.5), pb.vec3(0.5));
|
|
601
|
+
});
|
|
602
|
+
this.$if(pb.equal(this.debugMode, 5), function() {
|
|
603
|
+
// d in millimetres of world space, so skin reads around 1.
|
|
604
|
+
this.dbg = pb.mul(this.dPerChannel, 1000);
|
|
605
|
+
});
|
|
606
|
+
this.$if(pb.equal(this.debugMode, 6), function() {
|
|
607
|
+
// Mean vertical sample radius in pixels, 32 px mapped to white.
|
|
608
|
+
this.dbg = pb.vec3(pb.div(pb.mul(pb.div(this.radiusSum, DEFAULT_SAMPLE_COUNT), this.burleyScale.y, this.targetSize.y), 32));
|
|
609
|
+
});
|
|
610
|
+
this.$if(pb.equal(this.debugMode, 7), function() {
|
|
611
|
+
this.dbg = this.weightAccum;
|
|
612
|
+
});
|
|
613
|
+
this.$if(pb.equal(this.debugMode, 8), function() {
|
|
614
|
+
this.dbg = pb.vec3(pb.div(this.acceptedCount, DEFAULT_SAMPLE_COUNT));
|
|
615
|
+
});
|
|
616
|
+
this.$if(pb.equal(this.debugMode, 9), function() {
|
|
617
|
+
this.dbg = this.centerWeight;
|
|
618
|
+
});
|
|
619
|
+
this.$if(pb.equal(this.debugMode, 10), function() {
|
|
620
|
+
this.dbg = this.diffused;
|
|
621
|
+
});
|
|
622
|
+
if (hasTextureArrays) {
|
|
623
|
+
this.$if(pb.equal(this.debugMode, 11), function() {
|
|
624
|
+
// Shown as optical depth (bright = thick), not the stored
|
|
625
|
+
// encoding. An unobstructed surface stores 0.92, close enough
|
|
626
|
+
// to the 1.0 of "no light wrote this" that blue calls the
|
|
627
|
+
// latter out separately. The minimum over layer 0's four
|
|
628
|
+
// channels shows whichever light sees the most material.
|
|
629
|
+
this.$l.th = pb.textureArraySampleLevel(this.thicknessTex, this.uv, 0, 0);
|
|
630
|
+
this.$l.enc = pb.min(pb.min(this.th.r, this.th.g), pb.min(this.th.b, this.th.a));
|
|
631
|
+
this.$if(pb.greaterThan(this.enc, 0.999), function() {
|
|
632
|
+
this.dbg = pb.vec3(0, 0, 1);
|
|
633
|
+
}).$else(function() {
|
|
634
|
+
this.dbg = pb.vec3(pb.sub(1, this.enc));
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
this.$outputs.outColor = pb.vec4(pb.mul(this.dbg, this.debugExposure), this.center.a);
|
|
391
639
|
});
|
|
392
|
-
this.$l.diffused = pb.div(this.sum, pb.max(this.weightSum, pb.vec3(1e-6)));
|
|
393
|
-
this.$outputs.outColor = pb.vec4(this.diffused, pb.div(this.coverage, pb.max(this.coverageWeight, 1e-6)));
|
|
394
640
|
});
|
|
395
641
|
});
|
|
396
642
|
}
|
|
397
643
|
});
|
|
398
|
-
program.name = '@
|
|
644
|
+
program.name = '@SkinSSSBurley';
|
|
399
645
|
return program;
|
|
400
646
|
}
|
|
401
|
-
|
|
647
|
+
createBVarProgram(ctx) {
|
|
648
|
+
// Placeholder for UE5's `UpdateQualityVariance`, a luminance-residual history
|
|
649
|
+
// that drives the next frame's adaptive sample count. Without a history buffer
|
|
650
|
+
// there is nothing to accumulate, so this is a straight copy and the diffusion
|
|
651
|
+
// always runs at full sample count; the pass is the slot history would occupy.
|
|
652
|
+
const program = ctx.device.buildRenderProgram({
|
|
653
|
+
vertex (pb) {
|
|
654
|
+
SkinSSS.fullscreenVertex(pb);
|
|
655
|
+
},
|
|
656
|
+
fragment (pb) {
|
|
657
|
+
this.diffusedTex = pb.tex2D().uniform(0);
|
|
658
|
+
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
659
|
+
this.targetSize = pb.vec4().uniform(0);
|
|
660
|
+
this.cameraNearFar = pb.vec2().uniform(0);
|
|
661
|
+
this.$outputs.outColor = pb.vec4();
|
|
662
|
+
pb.main(function() {
|
|
663
|
+
this.$l.diffused = pb.textureSampleLevel(this.diffusedTex, this.$inputs.uv, 0);
|
|
664
|
+
// Alpha is the subsurface opacity the diffusion wrote; it is passed
|
|
665
|
+
// through unchanged rather than repurposed.
|
|
666
|
+
this.$outputs.outColor = pb.vec4(this.diffused.rgb, this.diffused.a);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
program.name = '@SkinSSSBVar';
|
|
671
|
+
return program;
|
|
672
|
+
}
|
|
673
|
+
createRecombineProgram(ctx) {
|
|
402
674
|
const program = ctx.device.buildRenderProgram({
|
|
403
675
|
vertex (pb) {
|
|
404
676
|
SkinSSS.fullscreenVertex(pb);
|
|
405
677
|
},
|
|
406
678
|
fragment (pb) {
|
|
407
679
|
this.colorTex = pb.tex2D().uniform(0);
|
|
408
|
-
this.
|
|
409
|
-
this.
|
|
680
|
+
this.maskTex = pb.tex2D().uniform(0);
|
|
681
|
+
this.bvarTex = pb.tex2D().uniform(0);
|
|
410
682
|
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
411
683
|
this.cameraNearFar = pb.vec2().uniform(0);
|
|
412
684
|
this.targetSize = pb.vec4().uniform(0);
|
|
413
|
-
this.
|
|
414
|
-
this.scatterTint = pb.vec4().uniform(0);
|
|
415
|
-
this.strength = pb.float().uniform(0);
|
|
416
|
-
this.glow = pb.float().uniform(0);
|
|
417
|
-
this.opacity = pb.float().uniform(0);
|
|
418
|
-
this.colorBoost = pb.float().uniform(0);
|
|
419
|
-
this.encodeScale = pb.float().uniform(0);
|
|
420
|
-
this.smoothness = pb.float().uniform(0);
|
|
685
|
+
this.debugMode = pb.int().uniform(0);
|
|
421
686
|
this.srgbOut = pb.int().uniform(0);
|
|
422
687
|
this.$outputs.outColor = pb.vec4();
|
|
423
688
|
pb.func('readDepth01', [
|
|
@@ -429,52 +694,37 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
|
429
694
|
this.$l.uv = this.$inputs.uv;
|
|
430
695
|
this.$l.baseColor = pb.textureSampleLevel(this.colorTex, this.uv, 0);
|
|
431
696
|
this.$l.result = this.baseColor.rgb;
|
|
432
|
-
this.$l.centerSkin = pb.textureSampleLevel(this.skinTex, this.uv, 0);
|
|
433
697
|
this.$l.centerDepth01 = this.readDepth01(this.uv);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
//
|
|
447
|
-
this.$
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
this.$
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// The skin mask is the edge stop, so eyes, brows and lips
|
|
465
|
-
// (mask 0) never bleed onto the surrounding skin.
|
|
466
|
-
this.$l.maskSample = pb.textureSampleLevel(this.skinTex, this.sampleUV, 0).a;
|
|
467
|
-
this.$l.w = pb.float(pb.greaterThan(this.maskSample, 1e-4));
|
|
468
|
-
this.colorSum = pb.add(this.colorSum, pb.mul(pb.textureSampleLevel(this.colorTex, this.sampleUV, 0).rgb, this.w));
|
|
469
|
-
this.colorWeight = pb.add(this.colorWeight, this.w);
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
this.$if(pb.greaterThan(this.colorWeight, 1e-4), function() {
|
|
473
|
-
this.result = pb.mix(this.result, pb.div(this.colorSum, this.colorWeight), pb.mul(this.smoothness, this.coverage, this.centerSkin.a));
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
// Precision can push the subtraction a hair below zero.
|
|
477
|
-
this.result = pb.max(this.result, pb.vec3(0));
|
|
698
|
+
// Subsurface opacity. A plain gate is right here: how strongly the
|
|
699
|
+
// pixel scattered is already baked into the diffusion buffer, and
|
|
700
|
+
// weighting by the opacity a second time would count it twice.
|
|
701
|
+
this.$l.centerMask = pb.textureSampleLevel(this.maskTex, this.uv, 0).a;
|
|
702
|
+
this.$if(pb.and(pb.equal(this.debugMode, 0), pb.and(pb.lessThan(this.centerDepth01, 1), pb.greaterThan(this.centerMask, 1e-4))), function() {
|
|
703
|
+
// UE5 SSS::Recombine (lines 32-40): split the lit pixel into the
|
|
704
|
+
// diffuse part that was allowed to scatter and the specular part
|
|
705
|
+
// that must survive untouched, then swap the diffuse for the
|
|
706
|
+
// diffused version.
|
|
707
|
+
this.$l.lum = pb.dot(this.baseColor.rgb, pb.vec3(0.2126, 0.7152, 0.0722));
|
|
708
|
+
// Must match Burley's split exactly: that pass diffused
|
|
709
|
+
// `baseColor.rgb * diffAmt` and this one keeps the remainder, so a
|
|
710
|
+
// disagreement leaves the halves not adding up to the original.
|
|
711
|
+
this.$l.diffAmt = pb.select(pb.float(1), pb.clamp(pb.div(this.baseColor.a, pb.max(this.lum, 1e-4)), 0, 1), pb.greaterThan(this.lum, 1e-4));
|
|
712
|
+
this.$l.specKeep = pb.mul(this.baseColor.rgb, pb.sub(1, this.diffAmt));
|
|
713
|
+
// The diffusion buffer's alpha is the subsurface opacity, not a
|
|
714
|
+
// transmission term - nothing in this pipeline produces one here.
|
|
715
|
+
// UE5's transmission likewise comes from SubsurfaceProfileBxDF per
|
|
716
|
+
// light, and the back-lit term on SSSMaterial that stands in for
|
|
717
|
+
// it is already part of SceneColor.
|
|
718
|
+
this.$l.diffused = pb.textureSampleLevel(this.bvarTex, this.uv, 0).rgb;
|
|
719
|
+
// Straight sum, as UE5 does it. No blend weight or tint: how far and
|
|
720
|
+
// in what colour the light travels is the profile's business, and a
|
|
721
|
+
// second knob on top could only disagree with it.
|
|
722
|
+
this.result = pb.max(pb.add(this.diffused, this.specKeep), pb.vec3(0));
|
|
723
|
+
});
|
|
724
|
+
// A debug channel must reach the screen unmodified, so recombination is
|
|
725
|
+
// skipped and the diffusion buffer is shown as it was written.
|
|
726
|
+
this.$if(pb.notEqual(this.debugMode, 0), function() {
|
|
727
|
+
this.result = pb.textureSampleLevel(this.bvarTex, this.uv, 0).rgb;
|
|
478
728
|
});
|
|
479
729
|
this.$if(pb.equal(this.srgbOut, 0), function() {
|
|
480
730
|
this.$outputs.outColor = pb.vec4(this.result, this.baseColor.a);
|
|
@@ -484,7 +734,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
|
484
734
|
});
|
|
485
735
|
}
|
|
486
736
|
});
|
|
487
|
-
program.name = '@
|
|
737
|
+
program.name = '@SkinSSSRecombine';
|
|
488
738
|
return program;
|
|
489
739
|
}
|
|
490
740
|
}
|