@zephyr3d/scene 0.9.25 → 0.9.27
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/dist/animation/animationset.js +92 -30
- package/dist/animation/animationset.js.map +1 -1
- package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
- package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
- package/dist/animation/joint_dynamics/controller.js +7 -5
- package/dist/animation/joint_dynamics/controller.js.map +1 -1
- package/dist/animation/joint_dynamics/convex_collider.js +320 -0
- package/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
- package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
- package/dist/animation/morphtrack.js +3 -4
- package/dist/animation/morphtrack.js.map +1 -1
- package/dist/animation/skeleton.js +79 -25
- package/dist/animation/skeleton.js.map +1 -1
- package/dist/animation/spring/multi_chain_spring_system.js +577 -179
- package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
- package/dist/animation/spring/spring_collider.js +4 -3
- package/dist/animation/spring/spring_collider.js.map +1 -1
- package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
- package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
- package/dist/animation/spring/spring_particle.js +2 -0
- package/dist/animation/spring/spring_particle.js.map +1 -1
- package/dist/animation/spring/spring_system.js +11 -3
- package/dist/animation/spring/spring_system.js.map +1 -1
- package/dist/animation/spring_modifier.js +10 -0
- package/dist/animation/spring_modifier.js.map +1 -1
- package/dist/app/engine.js +53 -43
- package/dist/app/engine.js.map +1 -1
- package/dist/app/scriptingsystem.js +119 -6
- package/dist/app/scriptingsystem.js.map +1 -1
- package/dist/app/scriptregistry.js +3 -2
- package/dist/app/scriptregistry.js.map +1 -1
- package/dist/asset/assetmanager.js +284 -67
- package/dist/asset/assetmanager.js.map +1 -1
- package/dist/asset/loaders/zhair/zhair_format.js +288 -0
- package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
- package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
- package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
- package/dist/asset/model.js +184 -69
- package/dist/asset/model.js.map +1 -1
- package/dist/camera/camera.js +268 -44
- package/dist/camera/camera.js.map +1 -1
- package/dist/camera/perspectivecamera.js +3 -1
- package/dist/camera/perspectivecamera.js.map +1 -1
- package/dist/index.d.ts +9044 -5139
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/dist/material/eye.js +932 -0
- package/dist/material/eye.js.map +1 -0
- package/dist/material/hair.js +917 -0
- package/dist/material/hair.js.map +1 -0
- package/dist/material/hairstrand.js +842 -0
- package/dist/material/hairstrand.js.map +1 -0
- package/dist/material/hairstrand_data.js +162 -0
- package/dist/material/hairstrand_data.js.map +1 -0
- package/dist/material/lambert.js +5 -2
- package/dist/material/lambert.js.map +1 -1
- package/dist/material/material.js +48 -10
- package/dist/material/material.js.map +1 -1
- package/dist/material/meshmaterial.js +120 -19
- package/dist/material/meshmaterial.js.map +1 -1
- package/dist/material/mixins/albedocolor.js +1 -1
- package/dist/material/mixins/albedocolor.js.map +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
- package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
- package/dist/material/mixins/lightmodel/lambert.js +5 -4
- package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
- package/dist/material/mixins/lit.js +106 -19
- package/dist/material/mixins/lit.js.map +1 -1
- package/dist/material/mixins/pbr/common.js +4 -4
- package/dist/material/mixins/pbr/common.js.map +1 -1
- package/dist/material/mixins/texture.js +48 -11
- package/dist/material/mixins/texture.js.map +1 -1
- package/dist/material/mtoon.js +6 -5
- package/dist/material/mtoon.js.map +1 -1
- package/dist/material/pbrblueprint.js +146 -28
- package/dist/material/pbrblueprint.js.map +1 -1
- package/dist/material/pbrblueprint_instance.js +501 -0
- package/dist/material/pbrblueprint_instance.js.map +1 -0
- package/dist/material/pbrmr.js +15 -16
- package/dist/material/pbrmr.js.map +1 -1
- package/dist/material/shader/helper.js +734 -23
- package/dist/material/shader/helper.js.map +1 -1
- package/dist/material/shader/terrain_blend.js +55 -0
- package/dist/material/shader/terrain_blend.js.map +1 -0
- package/dist/material/skin.js +192 -32
- package/dist/material/skin.js.map +1 -1
- package/dist/material/spriteblueprint.js +16 -1
- package/dist/material/spriteblueprint.js.map +1 -1
- package/dist/material/subsurfaceprofile.js +86 -23
- package/dist/material/subsurfaceprofile.js.map +1 -1
- package/dist/material/water.js +2059 -72
- package/dist/material/water.js.map +1 -1
- package/dist/posteffect/bloom.js +167 -7
- package/dist/posteffect/bloom.js.map +1 -1
- package/dist/posteffect/coloradjust.js +2 -1
- package/dist/posteffect/coloradjust.js.map +1 -1
- package/dist/posteffect/compositor.js.map +1 -1
- package/dist/posteffect/fxaa.js +2 -1
- package/dist/posteffect/fxaa.js.map +1 -1
- package/dist/posteffect/grayscale.js +3 -1
- package/dist/posteffect/grayscale.js.map +1 -1
- package/dist/posteffect/motionblur.js +664 -56
- package/dist/posteffect/motionblur.js.map +1 -1
- package/dist/posteffect/posteffect.js +12 -1
- package/dist/posteffect/posteffect.js.map +1 -1
- package/dist/posteffect/sao.js +46 -44
- package/dist/posteffect/sao.js.map +1 -1
- package/dist/posteffect/skinsss.js +368 -69
- package/dist/posteffect/skinsss.js.map +1 -1
- package/dist/posteffect/ssgi.js +16 -9
- package/dist/posteffect/ssgi.js.map +1 -1
- package/dist/posteffect/ssr.js +3 -0
- package/dist/posteffect/ssr.js.map +1 -1
- package/dist/posteffect/sss.js +121 -36
- package/dist/posteffect/sss.js.map +1 -1
- package/dist/posteffect/taa.js.map +1 -1
- package/dist/posteffect/tonemap.js +44 -1
- package/dist/posteffect/tonemap.js.map +1 -1
- package/dist/render/abuffer_oit.js +8 -2
- package/dist/render/abuffer_oit.js.map +1 -1
- package/dist/render/clipmap.js +156 -5
- package/dist/render/clipmap.js.map +1 -1
- package/dist/render/cull_visitor.js +20 -0
- package/dist/render/cull_visitor.js.map +1 -1
- package/dist/render/depthpass.js +36 -2
- package/dist/render/depthpass.js.map +1 -1
- package/dist/render/drawable_mixin.js +26 -1
- package/dist/render/drawable_mixin.js.map +1 -1
- package/dist/render/dualdepthpeeling_oit.js +3 -2
- package/dist/render/dualdepthpeeling_oit.js.map +1 -1
- package/dist/render/envlight.js +1 -1
- package/dist/render/envlight.js.map +1 -1
- package/dist/render/fbm_wavegenerator.js +6 -3
- package/dist/render/fbm_wavegenerator.js.map +1 -1
- package/dist/render/fft_wavegenerator.js +57 -15
- package/dist/render/fft_wavegenerator.js.map +1 -1
- package/dist/render/gerstner_wavegenerator.js +130 -20
- package/dist/render/gerstner_wavegenerator.js.map +1 -1
- package/dist/render/globalbindgroup_allocator.js +3 -0
- package/dist/render/globalbindgroup_allocator.js.map +1 -1
- package/dist/render/hzb.js +80 -22
- package/dist/render/hzb.js.map +1 -1
- package/dist/render/lightpass.js +6 -3
- package/dist/render/lightpass.js.map +1 -1
- package/dist/render/oit.js +29 -0
- package/dist/render/oit.js.map +1 -0
- package/dist/render/render_queue.js +39 -0
- package/dist/render/render_queue.js.map +1 -1
- package/dist/render/renderer.js +4 -0
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/rendergraph/blackboard.js +1 -0
- package/dist/render/rendergraph/blackboard.js.map +1 -1
- package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
- package/dist/render/rendergraph/executor.js +2 -0
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +379 -10
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
- package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
- package/dist/render/rendergraph/history_resources.js +8 -1
- package/dist/render/rendergraph/history_resources.js.map +1 -1
- package/dist/render/rendergraph/rendergraph.js.map +1 -1
- package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
- package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/render/shadow_mask_pass.js +73 -8
- package/dist/render/shadow_mask_pass.js.map +1 -1
- package/dist/render/sky.js +166 -126
- package/dist/render/sky.js.map +1 -1
- package/dist/render/underwater.js +624 -0
- package/dist/render/underwater.js.map +1 -0
- package/dist/render/water_caustics.js +1380 -0
- package/dist/render/water_caustics.js.map +1 -0
- package/dist/render/weightedblended_oit.js +3 -2
- package/dist/render/weightedblended_oit.js.map +1 -1
- package/dist/scene/basesprite.js +8 -0
- package/dist/scene/basesprite.js.map +1 -1
- package/dist/scene/batchgroup.js +3 -0
- package/dist/scene/batchgroup.js.map +1 -1
- package/dist/scene/environment.js +4 -1
- package/dist/scene/environment.js.map +1 -1
- package/dist/scene/hair.js +963 -0
- package/dist/scene/hair.js.map +1 -0
- package/dist/scene/light.js +5 -1
- package/dist/scene/light.js.map +1 -1
- package/dist/scene/mesh.js +481 -47
- 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 +3 -0
- package/dist/scene/msdftext.js.map +1 -1
- package/dist/scene/msdftextsprite.js +3 -0
- package/dist/scene/msdftextsprite.js.map +1 -1
- package/dist/scene/particlesys.js +6 -0
- package/dist/scene/particlesys.js.map +1 -1
- package/dist/scene/scene.js +3 -0
- package/dist/scene/scene.js.map +1 -1
- package/dist/scene/scene_node.js +6 -1
- package/dist/scene/scene_node.js.map +1 -1
- package/dist/scene/terrain-cm/grass.js +3 -0
- package/dist/scene/terrain-cm/grass.js.map +1 -1
- package/dist/scene/terrain-cm/grassmaterial.js +3 -0
- package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
- package/dist/scene/terrain-cm/terrain-cm.js +6 -0
- package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
- package/dist/scene/terrain-cm/texture_cache.js +309 -0
- package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
- package/dist/scene/water.js +546 -4
- package/dist/scene/water.js.map +1 -1
- package/dist/shaders/atmosphere.js.map +1 -1
- package/dist/shaders/diffusion.js +54 -0
- package/dist/shaders/diffusion.js.map +1 -0
- package/dist/shaders/shadow.js +33 -21
- package/dist/shaders/shadow.js.map +1 -1
- package/dist/shaders/temporal.js +71 -33
- package/dist/shaders/temporal.js.map +1 -1
- package/dist/shaders/water.js +22 -19
- package/dist/shaders/water.js.map +1 -1
- package/dist/shaders/water_medium.js +56 -0
- package/dist/shaders/water_medium.js.map +1 -0
- package/dist/shadow/dom.js +546 -0
- package/dist/shadow/dom.js.map +1 -0
- package/dist/shadow/pcf_opt.js +3 -5
- package/dist/shadow/pcf_opt.js.map +1 -1
- package/dist/shadow/pcf_pd.js +1 -1
- package/dist/shadow/pcf_pd.js.map +1 -1
- package/dist/shadow/receiver_bias.js +41 -0
- package/dist/shadow/receiver_bias.js.map +1 -0
- package/dist/shadow/shader.js +20 -60
- package/dist/shadow/shader.js.map +1 -1
- package/dist/shadow/shadow_impl.js +89 -0
- package/dist/shadow/shadow_impl.js.map +1 -1
- package/dist/shadow/shadow_region.js +1 -1
- package/dist/shadow/shadow_region.js.map +1 -1
- package/dist/shadow/shadowmapper.js +150 -37
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shapes/shape.js +11 -2
- package/dist/shapes/shape.js.map +1 -1
- package/dist/shapes/sphere.js +50 -9
- package/dist/shapes/sphere.js.map +1 -1
- package/dist/utility/blueprint/common/constants.js +112 -2
- package/dist/utility/blueprint/common/constants.js.map +1 -1
- package/dist/utility/blueprint/material/func.js +142 -49
- package/dist/utility/blueprint/material/func.js.map +1 -1
- package/dist/utility/blueprint/material/ir.js +490 -25
- package/dist/utility/blueprint/material/ir.js.map +1 -1
- package/dist/utility/blueprint/material/texture.js +898 -4
- package/dist/utility/blueprint/material/texture.js.map +1 -1
- package/dist/utility/serialization/manager.js +434 -101
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +401 -5
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/batch.js +4 -0
- package/dist/utility/serialization/scene/batch.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js +186 -26
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/utility/serialization/scene/common.js +26 -5
- package/dist/utility/serialization/scene/common.js.map +1 -1
- package/dist/utility/serialization/scene/hair.js +1141 -0
- package/dist/utility/serialization/scene/hair.js.map +1 -0
- package/dist/utility/serialization/scene/light.js +84 -4
- package/dist/utility/serialization/scene/light.js.map +1 -1
- package/dist/utility/serialization/scene/material.js +1867 -166
- package/dist/utility/serialization/scene/material.js.map +1 -1
- package/dist/utility/serialization/scene/mesh.js +231 -14
- package/dist/utility/serialization/scene/mesh.js.map +1 -1
- package/dist/utility/serialization/scene/node.js +141 -7
- package/dist/utility/serialization/scene/node.js.map +1 -1
- package/dist/utility/serialization/scene/particle.js +4 -0
- package/dist/utility/serialization/scene/particle.js.map +1 -1
- package/dist/utility/serialization/scene/primitive.js +18 -0
- package/dist/utility/serialization/scene/primitive.js.map +1 -1
- package/dist/utility/serialization/scene/sprite.js +4 -0
- package/dist/utility/serialization/scene/sprite.js.map +1 -1
- package/dist/utility/serialization/scene/terrain.js +4 -0
- package/dist/utility/serialization/scene/terrain.js.map +1 -1
- package/dist/utility/serialization/scene/text.js +4 -0
- package/dist/utility/serialization/scene/text.js.map +1 -1
- package/dist/utility/serialization/scene/water.js +1105 -21
- package/dist/utility/serialization/scene/water.js.map +1 -1
- package/dist/utility/serialization/types.js.map +1 -1
- package/dist/utility/shprojector.js +9 -8
- package/dist/utility/shprojector.js.map +1 -1
- package/dist/values.js +49 -2
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
- package/dist/render/rendergraph/frame_graph_context.js +0 -41
- package/dist/render/rendergraph/frame_graph_context.js.map +0 -1
- package/dist/shaders/ssgi.js +0 -63
- package/dist/shaders/ssgi.js.map +0 -1
- package/dist/shaders/ssgi_reprojection.js +0 -118
- package/dist/shaders/ssgi_reprojection.js.map +0 -1
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Vector4, Vector3, Vector2, 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 '../material/subsurfaceprofile.js';
|
|
8
|
-
import '../material/skin.js';
|
|
7
|
+
import { SubsurfaceProfile } from '../material/subsurfaceprofile.js';
|
|
8
|
+
import { SKIN_SSS_LDR_ENCODE_RANGE } from '../material/skin.js';
|
|
9
|
+
import '../material/eye.js';
|
|
9
10
|
import '@zephyr3d/device';
|
|
10
11
|
import '../values.js';
|
|
11
12
|
import '../material/meshmaterial.js';
|
|
13
|
+
import '../material/hair.js';
|
|
14
|
+
import '../material/hairstrand.js';
|
|
12
15
|
import '../material/grassmaterial.js';
|
|
13
16
|
import '../material/terrain-cm.js';
|
|
14
17
|
import '../material/pbrmr.js';
|
|
@@ -18,55 +21,183 @@ import '../material/pbrblueprint.js';
|
|
|
18
21
|
import '../material/sprite.js';
|
|
19
22
|
import '../material/sprite_std.js';
|
|
20
23
|
import '../utility/blueprint/material/texture.js';
|
|
24
|
+
import { burleyDiffusionWeight } from '../shaders/diffusion.js';
|
|
21
25
|
import { linearToGamma } from '../shaders/misc.js';
|
|
22
26
|
import { fetchSampler } from '../utility/misc.js';
|
|
23
27
|
import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
24
28
|
|
|
29
|
+
/** Taps per side of the separable kernel. The full kernel is `2 * TAPS + 1` wide. */ const SKIN_SSS_TAPS = 8;
|
|
25
30
|
/**
|
|
26
31
|
* Stylized screen-space skin scattering pass.
|
|
27
32
|
*
|
|
28
33
|
* @remarks
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
34
|
+
* {@link SkinMaterial} writes the diffusible part of its lit color - the stylized diffuse ramp
|
|
35
|
+
* plus back-lit transmission, without specular - into a side buffer. This pass diffuses that
|
|
36
|
+
* buffer with a channel-dependent kernel and swaps it back in:
|
|
37
|
+
*
|
|
38
|
+
* ```
|
|
39
|
+
* result = base + (diffused - original) * scatterTint * strength + diffused * glow
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Because the term added back replaces exactly the term subtracted, the redistribution is energy
|
|
43
|
+
* conserving: light that appears on the dark side of the terminator is light that left the lit
|
|
44
|
+
* side, rather than light invented on top of a finished image. With `scatterTint` white and
|
|
45
|
+
* `glow` zero the total radiance is preserved to within the kernel's normalization error.
|
|
46
|
+
*
|
|
47
|
+
* Two things follow from that formulation and are worth knowing:
|
|
48
|
+
*
|
|
49
|
+
* - Nothing happens where the diffusion changes nothing, so a disabled or zero-radius pass is a
|
|
50
|
+
* true no-op, and quantization from an 8-bit side buffer largely cancels between the subtracted
|
|
51
|
+
* and added terms instead of banding across the whole face.
|
|
52
|
+
* - `scatterTint` colors only the light that actually moved. Tinting the whole diffuse - what the
|
|
53
|
+
* material used to do with `scatterColor` - is what made the old look read as red haze.
|
|
54
|
+
*
|
|
55
|
+
* Stylization survives in two places. The material owns the direct lighting ramp (wrap, softness,
|
|
56
|
+
* shadow tint, brightening) and this pass diffuses whatever ramp it produced; and {@link profile}
|
|
57
|
+
* selects the channel radii, so the `wax` and `jade` presets are as reachable as `skin`.
|
|
58
|
+
*
|
|
59
|
+
* This is intentionally separate from the profile-based {@link SSS} pass, which supports per-pixel
|
|
60
|
+
* profiles via a profile-slot MRT. Here a single global profile lives in uniforms.
|
|
33
61
|
*
|
|
34
62
|
* @public
|
|
35
63
|
*/ class SkinSSS extends AbstractPostEffect {
|
|
36
|
-
static
|
|
37
|
-
|
|
64
|
+
static _blurProgram = null;
|
|
65
|
+
static _combineProgram = null;
|
|
66
|
+
_blurBindGroupH;
|
|
67
|
+
_blurBindGroupV;
|
|
68
|
+
_combineBindGroup;
|
|
69
|
+
_profile;
|
|
38
70
|
_strength;
|
|
71
|
+
_glow;
|
|
39
72
|
_opacity;
|
|
40
73
|
_sampleStep;
|
|
41
74
|
_depthScale;
|
|
42
75
|
_colorBoost;
|
|
76
|
+
_scatterRadius;
|
|
77
|
+
_smoothness;
|
|
78
|
+
_scatterTint;
|
|
79
|
+
_channelRadius;
|
|
80
|
+
_channelFalloff;
|
|
81
|
+
_blurDirection;
|
|
82
|
+
_radiusParams;
|
|
83
|
+
_targetSize;
|
|
84
|
+
_cameraNearFar;
|
|
43
85
|
constructor(){
|
|
44
86
|
super();
|
|
45
87
|
this._layer = PostEffectLayer.opaque;
|
|
46
|
-
this.
|
|
88
|
+
this._blurBindGroupH = null;
|
|
89
|
+
this._blurBindGroupV = null;
|
|
90
|
+
this._combineBindGroup = null;
|
|
91
|
+
this._profile = null;
|
|
47
92
|
this._strength = 1;
|
|
93
|
+
this._glow = 0;
|
|
48
94
|
this._opacity = 0.18;
|
|
49
|
-
this._sampleStep =
|
|
95
|
+
this._sampleStep = 3;
|
|
50
96
|
this._depthScale = 80;
|
|
51
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();
|
|
105
|
+
this._targetSize = new Vector4();
|
|
106
|
+
this._cameraNearFar = new Vector2();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Subsurface profile supplying the per-channel scatter radii and falloff.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* Defaults to {@link SubsurfaceProfile.getDefaultSkinProfile} when unset. Only the channel
|
|
113
|
+
* ratios of `scatterRadius` and the `falloffColor` are read; the absolute distance comes from
|
|
114
|
+
* {@link SkinSSS.scatterRadius}, so switching profiles changes the color of the bleed without
|
|
115
|
+
* changing how far it reaches.
|
|
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.
|
|
120
|
+
*/ get profile() {
|
|
121
|
+
return this._profile;
|
|
52
122
|
}
|
|
53
|
-
|
|
123
|
+
set profile(val) {
|
|
124
|
+
this._profile = val ?? null;
|
|
125
|
+
}
|
|
126
|
+
/** Final blend strength of the conserving redistribution. 1 fully diffuses the skin diffuse. */ get strength() {
|
|
54
127
|
return this._strength;
|
|
55
128
|
}
|
|
56
129
|
set strength(val) {
|
|
57
130
|
this._strength = Math.max(0, val ?? 0);
|
|
58
131
|
}
|
|
59
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Additive, deliberately non-conserving bleed layered on top of the conserving result.
|
|
134
|
+
*
|
|
135
|
+
* @remarks
|
|
136
|
+
* 0 (the default) keeps the pass energy conserving. Raising it adds the diffused term a second
|
|
137
|
+
* time without subtracting anything, which is the glow-heavy look the pass produced before it
|
|
138
|
+
* conserved energy; around 1 approximates it. Use it when the art direction wants skin to read
|
|
139
|
+
* as lit from within rather than as a physical surface.
|
|
140
|
+
*/ get glow() {
|
|
141
|
+
return this._glow;
|
|
142
|
+
}
|
|
143
|
+
set glow(val) {
|
|
144
|
+
this._glow = Math.max(0, val ?? 0);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Tint applied to the redistributed light.
|
|
148
|
+
*
|
|
149
|
+
* @remarks
|
|
150
|
+
* White (the default) leaves the pass energy conserving. A warm tint pushes the classic reddish
|
|
151
|
+
* terminator further than the profile's channel radii do on their own. Because it multiplies
|
|
152
|
+
* only the difference between the diffused and original diffuse, it cannot wash the whole
|
|
153
|
+
* surface - only the light that moved.
|
|
154
|
+
*/ get scatterTint() {
|
|
155
|
+
return this._scatterTint;
|
|
156
|
+
}
|
|
157
|
+
set scatterTint(val) {
|
|
158
|
+
this._scatterTint.set(val);
|
|
159
|
+
}
|
|
160
|
+
/** Skin mask coverage threshold. Scattering fades in as the blurred mask coverage exceeds this. */ get opacity() {
|
|
60
161
|
return this._opacity;
|
|
61
162
|
}
|
|
62
163
|
set opacity(val) {
|
|
63
164
|
this._opacity = Math.max(0, Math.min(1, val ?? 0));
|
|
64
165
|
}
|
|
65
|
-
/**
|
|
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() {
|
|
66
176
|
return this._sampleStep;
|
|
67
177
|
}
|
|
68
178
|
set sampleStep(val) {
|
|
69
|
-
this._sampleStep = Math.max(0.25, val ?? 0.25);
|
|
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));
|
|
70
201
|
}
|
|
71
202
|
/** Depth rejection scale. The reference shader uses 80. */ get depthScale() {
|
|
72
203
|
return this._depthScale;
|
|
@@ -74,7 +205,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
|
74
205
|
set depthScale(val) {
|
|
75
206
|
this._depthScale = Math.max(0, val ?? 0);
|
|
76
207
|
}
|
|
77
|
-
/** Multiplier applied to the
|
|
208
|
+
/** Multiplier applied to the diffused irradiance before compositing. */ get colorBoost() {
|
|
78
209
|
return this._colorBoost;
|
|
79
210
|
}
|
|
80
211
|
set colorBoost(val) {
|
|
@@ -89,55 +220,204 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
|
89
220
|
return;
|
|
90
221
|
}
|
|
91
222
|
const device = ctx.device;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
SkinSSS.
|
|
223
|
+
const outputFramebuffer = device.getFramebuffer();
|
|
224
|
+
const skinTex = ctx.SkinSSSTexture;
|
|
225
|
+
if (!SkinSSS._blurProgram) {
|
|
226
|
+
SkinSSS._blurProgram = this.createBlurProgram(ctx);
|
|
227
|
+
}
|
|
228
|
+
if (!SkinSSS._combineProgram) {
|
|
229
|
+
SkinSSS._combineProgram = this.createCombineProgram(ctx);
|
|
96
230
|
}
|
|
97
|
-
|
|
98
|
-
|
|
231
|
+
const blurProgram = SkinSSS._blurProgram;
|
|
232
|
+
const combineProgram = SkinSSS._combineProgram;
|
|
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]);
|
|
99
239
|
}
|
|
100
|
-
this.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
this.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
240
|
+
if (!this._combineBindGroup) {
|
|
241
|
+
this._combineBindGroup = device.createBindGroup(combineProgram.bindGroupLayouts[0]);
|
|
242
|
+
}
|
|
243
|
+
const profile = this._profile ?? SubsurfaceProfile.getDefaultSkinProfile();
|
|
244
|
+
const radius = profile.scatterRadius;
|
|
245
|
+
const maxChannel = Math.max(radius.x, radius.y, radius.z, 1e-5);
|
|
246
|
+
this._channelRadius.setXYZ(radius.x / maxChannel, radius.y / maxChannel, radius.z / maxChannel);
|
|
247
|
+
const falloff = profile.falloffColor;
|
|
248
|
+
this._channelFalloff.setXYZ(falloff.x, falloff.y, falloff.z);
|
|
249
|
+
// Pixels per world unit at unit view depth. The shader divides by view depth
|
|
250
|
+
// so the footprint stays a constant world-space radius.
|
|
251
|
+
const projScale = 0.5 * inputColorTexture.height * ctx.camera.getProjectionMatrix().m11;
|
|
252
|
+
this._radiusParams.setXYZW(this._scatterRadius * projScale, ctx.camera.isPerspective() ? 1 : 0, this._sampleStep, this._depthScale);
|
|
253
|
+
this._targetSize.setXYZW(inputColorTexture.width, inputColorTexture.height, 1 / inputColorTexture.width, 1 / inputColorTexture.height);
|
|
254
|
+
this._cameraNearFar.setXY(ctx.camera.getNearPlane(), ctx.camera.getFarPlane());
|
|
255
|
+
const encodeScale = skinTex.format === 'rgba8unorm' ? SKIN_SSS_LDR_ENCODE_RANGE : 1;
|
|
256
|
+
const blurH = device.pool.fetchTemporalFramebuffer(false, skinTex.width, skinTex.height, skinTex.format, null, false);
|
|
257
|
+
const blurV = device.pool.fetchTemporalFramebuffer(false, skinTex.width, skinTex.height, skinTex.format, null, false);
|
|
258
|
+
device.setFramebuffer(blurH);
|
|
259
|
+
this.blur(ctx, blurProgram, this._blurBindGroupH, skinTex, sceneDepthTexture, true);
|
|
260
|
+
device.setFramebuffer(blurV);
|
|
261
|
+
this.blur(ctx, blurProgram, this._blurBindGroupV, blurH.getColorAttachments()[0], sceneDepthTexture, false);
|
|
262
|
+
device.setFramebuffer(outputFramebuffer);
|
|
263
|
+
this.combine(ctx, combineProgram, inputColorTexture, skinTex, blurV.getColorAttachments()[0], sceneDepthTexture, encodeScale, srgbOutput);
|
|
264
|
+
device.pool.releaseFrameBuffer(blurH);
|
|
265
|
+
device.pool.releaseFrameBuffer(blurV);
|
|
266
|
+
}
|
|
267
|
+
blur(ctx, program, bindGroup, source, sceneDepthTexture, horizontal) {
|
|
268
|
+
const device = ctx.device;
|
|
269
|
+
this._blurDirection.setXY(horizontal ? 1 : 0, horizontal ? 0 : 1);
|
|
270
|
+
bindGroup.setTexture('skinTex', source, fetchSampler('clamp_linear'));
|
|
271
|
+
bindGroup.setTexture('depthTex', sceneDepthTexture, fetchSampler('clamp_nearest_nomip'));
|
|
272
|
+
bindGroup.setValue('cameraNearFar', this._cameraNearFar);
|
|
273
|
+
bindGroup.setValue('targetSize', this._targetSize);
|
|
274
|
+
bindGroup.setValue('radiusParams', this._radiusParams);
|
|
275
|
+
bindGroup.setValue('channelRadius', this._channelRadius);
|
|
276
|
+
bindGroup.setValue('channelFalloff', this._channelFalloff);
|
|
277
|
+
bindGroup.setValue('blurDirection', this._blurDirection);
|
|
278
|
+
bindGroup.setValue('flip', 0);
|
|
112
279
|
device.setProgram(program);
|
|
113
|
-
device.setBindGroup(0,
|
|
280
|
+
device.setBindGroup(0, bindGroup);
|
|
114
281
|
this.drawFullscreenQuad();
|
|
115
282
|
}
|
|
116
|
-
|
|
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);
|
|
304
|
+
this.drawFullscreenQuad();
|
|
305
|
+
}
|
|
306
|
+
/** Shared fullscreen vertex stage for both passes. */ static fullscreenVertex(pb) {
|
|
307
|
+
pb.getGlobalScope().flip = pb.int().uniform(0);
|
|
308
|
+
pb.getGlobalScope().$inputs.pos = pb.vec2().attrib('position');
|
|
309
|
+
pb.getGlobalScope().$outputs.uv = pb.vec2();
|
|
310
|
+
pb.main(function() {
|
|
311
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
312
|
+
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
313
|
+
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
314
|
+
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
createBlurProgram(ctx) {
|
|
117
319
|
const program = ctx.device.buildRenderProgram({
|
|
118
320
|
vertex (pb) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
321
|
+
SkinSSS.fullscreenVertex(pb);
|
|
322
|
+
},
|
|
323
|
+
fragment (pb) {
|
|
324
|
+
this.skinTex = pb.tex2D().uniform(0);
|
|
325
|
+
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
326
|
+
this.cameraNearFar = pb.vec2().uniform(0);
|
|
327
|
+
this.targetSize = pb.vec4().uniform(0);
|
|
328
|
+
// x: world scatter radius in pixels at unit depth, y: 1 for perspective,
|
|
329
|
+
// z: max tap spacing, w: depth rejection scale.
|
|
330
|
+
this.radiusParams = pb.vec4().uniform(0);
|
|
331
|
+
this.channelRadius = pb.vec3().uniform(0);
|
|
332
|
+
this.channelFalloff = pb.vec3().uniform(0);
|
|
333
|
+
this.blurDirection = pb.vec2().uniform(0);
|
|
334
|
+
this.$outputs.outColor = pb.vec4();
|
|
335
|
+
pb.func('readDepth01', [
|
|
336
|
+
pb.vec2('uv')
|
|
337
|
+
], function() {
|
|
338
|
+
this.$return(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0));
|
|
339
|
+
});
|
|
122
340
|
pb.main(function() {
|
|
123
|
-
this.$
|
|
124
|
-
this.$
|
|
125
|
-
this.$
|
|
126
|
-
|
|
341
|
+
this.$l.uv = this.$inputs.uv;
|
|
342
|
+
this.$l.center = pb.textureSampleLevel(this.skinTex, this.uv, 0);
|
|
343
|
+
this.$l.centerDepth01 = this.readDepth01(this.uv);
|
|
344
|
+
this.$outputs.outColor = this.center;
|
|
345
|
+
// Background, or a pixel no skin material wrote: nothing to diffuse.
|
|
346
|
+
this.$if(pb.and(pb.lessThan(this.centerDepth01, 1), pb.greaterThan(this.center.a, 1e-4)), function() {
|
|
347
|
+
this.$l.centerDepth = pb.max(pb.mul(this.centerDepth01, this.cameraNearFar.y), 1e-4);
|
|
348
|
+
// Per-channel radius in pixels. Red reaches several times further
|
|
349
|
+
// than blue, which is the whole point - a shared radius is what
|
|
350
|
+
// makes Gaussian SSS read as a flat haze.
|
|
351
|
+
this.$l.viewScale = pb.max(pb.mix(pb.float(1), this.centerDepth, this.radiusParams.y), 1e-4);
|
|
352
|
+
this.$l.radiusPx = pb.div(pb.mul(this.channelRadius, this.radiusParams.x), this.viewScale);
|
|
353
|
+
// Burley shaping distance per channel; falloff tightens or widens
|
|
354
|
+
// the profile without changing its reach.
|
|
355
|
+
this.$l.shape = pb.vec3(pb.mix(0.72, 1.46, pb.clamp(this.channelFalloff.x, 0.05, 1)), pb.mix(0.72, 1.46, pb.clamp(this.channelFalloff.y, 0.05, 1)), pb.mix(0.72, 1.46, pb.clamp(this.channelFalloff.z, 0.05, 1)));
|
|
356
|
+
this.$l.shapeDist = pb.max(pb.mul(this.radiusPx, this.shape), pb.vec3(1e-3));
|
|
357
|
+
// Tap spacing follows the widest channel so the longest tail is
|
|
358
|
+
// resolved; the narrower channels simply weight down sooner.
|
|
359
|
+
this.$l.maxRadiusPx = pb.max(pb.max(this.radiusPx.x, this.radiusPx.y), this.radiusPx.z);
|
|
360
|
+
this.$l.stepPx = pb.clamp(pb.div(this.maxRadiusPx, SKIN_SSS_TAPS), 0, this.radiusParams.z);
|
|
361
|
+
this.$l.stepUV = pb.mul(this.blurDirection, this.stepPx, this.targetSize.zw);
|
|
362
|
+
this.$l.sum = pb.vec3(0);
|
|
363
|
+
this.$l.weightSum = pb.vec3(0);
|
|
364
|
+
this.$l.coverage = pb.float(0);
|
|
365
|
+
this.$l.coverageWeight = pb.float(0);
|
|
366
|
+
this.$for(pb.int('i'), -8, SKIN_SSS_TAPS + 1, function() {
|
|
367
|
+
this.$l.fi = pb.float(this.i);
|
|
368
|
+
this.$l.sampleUV = pb.clamp(pb.add(this.uv, pb.mul(this.stepUV, this.fi)), pb.vec2(0), pb.vec2(1));
|
|
369
|
+
this.$l.dist = pb.mul(pb.abs(this.fi), this.stepPx);
|
|
370
|
+
this.$l.w = pb.vec3(burleyDiffusionWeight(this, this.dist, this.shapeDist.x), burleyDiffusionWeight(this, this.dist, this.shapeDist.y), burleyDiffusionWeight(this, this.dist, this.shapeDist.z));
|
|
371
|
+
this.$l.sampleDepth01 = this.readDepth01(this.sampleUV);
|
|
372
|
+
this.$l.sampleDepth = pb.max(pb.mul(this.sampleDepth01, this.cameraNearFar.y), 1e-4);
|
|
373
|
+
this.$l.depthWeight = pb.clamp(pb.sub(1, pb.div(pb.mul(pb.abs(pb.sub(this.centerDepth, this.sampleDepth)), this.radiusParams.w), this.centerDepth)), 0, 1);
|
|
374
|
+
this.depthWeight = pb.mul(this.depthWeight, this.depthWeight, pb.sub(3, pb.mul(this.depthWeight, 2)));
|
|
375
|
+
this.$l.tapWeight = pb.mul(this.w, this.depthWeight);
|
|
376
|
+
this.$l.tapSample = pb.textureSampleLevel(this.skinTex, this.sampleUV, 0);
|
|
377
|
+
// Non-skin taps are excluded from the numerator *and* the
|
|
378
|
+
// denominator. Leaving them in the denominator - as this pass
|
|
379
|
+
// used to - drags the diffuse toward zero near every silhouette,
|
|
380
|
+
// which was a mild darkening under the old additive composite
|
|
381
|
+
// but would eat real light now that the term is subtracted back
|
|
382
|
+
// out of the base color.
|
|
383
|
+
this.$l.isSkin = pb.float(pb.greaterThan(this.tapSample.a, 1e-4));
|
|
384
|
+
this.sum = pb.add(this.sum, pb.mul(this.tapSample.rgb, this.tapWeight, this.isSkin));
|
|
385
|
+
this.weightSum = pb.add(this.weightSum, pb.mul(this.tapWeight, this.isSkin));
|
|
386
|
+
// Coverage keeps its own accumulator: it is the skin fraction of
|
|
387
|
+
// the neighbourhood, so it must count every tap in the
|
|
388
|
+
// denominator, skin or not.
|
|
389
|
+
this.coverage = pb.add(this.coverage, pb.mul(this.tapWeight.x, this.isSkin, this.tapSample.a));
|
|
390
|
+
this.coverageWeight = pb.add(this.coverageWeight, this.tapWeight.x);
|
|
391
|
+
});
|
|
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)));
|
|
127
394
|
});
|
|
128
395
|
});
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
program.name = '@SkinSSSBlur';
|
|
399
|
+
return program;
|
|
400
|
+
}
|
|
401
|
+
createCombineProgram(ctx) {
|
|
402
|
+
const program = ctx.device.buildRenderProgram({
|
|
403
|
+
vertex (pb) {
|
|
404
|
+
SkinSSS.fullscreenVertex(pb);
|
|
129
405
|
},
|
|
130
406
|
fragment (pb) {
|
|
131
407
|
this.colorTex = pb.tex2D().uniform(0);
|
|
132
408
|
this.skinTex = pb.tex2D().uniform(0);
|
|
409
|
+
this.blurredTex = pb.tex2D().uniform(0);
|
|
133
410
|
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
134
411
|
this.cameraNearFar = pb.vec2().uniform(0);
|
|
135
412
|
this.targetSize = pb.vec4().uniform(0);
|
|
413
|
+
this.radiusParams = pb.vec4().uniform(0);
|
|
414
|
+
this.scatterTint = pb.vec4().uniform(0);
|
|
136
415
|
this.strength = pb.float().uniform(0);
|
|
416
|
+
this.glow = pb.float().uniform(0);
|
|
137
417
|
this.opacity = pb.float().uniform(0);
|
|
138
|
-
this.sampleStep = pb.float().uniform(0);
|
|
139
|
-
this.depthScale = pb.float().uniform(0);
|
|
140
418
|
this.colorBoost = pb.float().uniform(0);
|
|
419
|
+
this.encodeScale = pb.float().uniform(0);
|
|
420
|
+
this.smoothness = pb.float().uniform(0);
|
|
141
421
|
this.srgbOut = pb.int().uniform(0);
|
|
142
422
|
this.$outputs.outColor = pb.vec4();
|
|
143
423
|
pb.func('readDepth01', [
|
|
@@ -149,33 +429,52 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
|
149
429
|
this.$l.uv = this.$inputs.uv;
|
|
150
430
|
this.$l.baseColor = pb.textureSampleLevel(this.colorTex, this.uv, 0);
|
|
151
431
|
this.$l.result = this.baseColor.rgb;
|
|
432
|
+
this.$l.centerSkin = pb.textureSampleLevel(this.skinTex, this.uv, 0);
|
|
152
433
|
this.$l.centerDepth01 = this.readDepth01(this.uv);
|
|
153
|
-
this.$if(pb.lessThan(this.centerDepth01, 1), function() {
|
|
154
|
-
this.$l.
|
|
155
|
-
|
|
156
|
-
this.$l.
|
|
157
|
-
this.$
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
434
|
+
this.$if(pb.and(pb.lessThan(this.centerDepth01, 1), pb.greaterThan(this.centerSkin.a, 1e-4)), function() {
|
|
435
|
+
this.$l.blurred = pb.textureSampleLevel(this.blurredTex, this.uv, 0);
|
|
436
|
+
// Both buffers carry the same LDR encoding, so decode once here.
|
|
437
|
+
this.$l.original = pb.mul(this.centerSkin.rgb, this.encodeScale);
|
|
438
|
+
this.$l.diffused = pb.mul(this.blurred.rgb, this.encodeScale, this.colorBoost);
|
|
439
|
+
this.$l.coverage = pb.smoothStep(this.opacity, pb.add(this.opacity, 0.35), this.blurred.a);
|
|
440
|
+
// Energy conserving core: swap the original diffuse for the
|
|
441
|
+
// diffused one. The added term is exactly the subtracted term, so
|
|
442
|
+
// brightening the dark side of the terminator darkens the lit side
|
|
443
|
+
// by the same amount rather than adding light to the frame.
|
|
444
|
+
this.$l.redistributed = pb.mul(pb.sub(this.diffused, this.original), this.scatterTint.rgb, this.strength, this.coverage);
|
|
445
|
+
this.result = pb.add(this.baseColor.rgb, this.redistributed);
|
|
446
|
+
// Explicit, opt-in departure from conservation.
|
|
447
|
+
this.$if(pb.greaterThan(this.glow, 0), function() {
|
|
448
|
+
this.result = pb.add(this.result, pb.mul(this.diffused, this.scatterTint.rgb, this.glow, this.coverage));
|
|
449
|
+
});
|
|
450
|
+
// Beauty filter. Unlike the diffusion above this blurs the shaded
|
|
451
|
+
// color itself, so it removes albedo detail (pores, blemishes)
|
|
452
|
+
// that scattering physically leaves alone. Uniform branch: free
|
|
453
|
+
// when disabled.
|
|
454
|
+
this.$if(pb.greaterThan(this.smoothness, 0), function() {
|
|
455
|
+
this.$l.centerDepth = pb.max(pb.mul(this.centerDepth01, this.cameraNearFar.y), 1e-4);
|
|
456
|
+
this.$l.viewScale = pb.max(pb.mix(pb.float(1), this.centerDepth, this.radiusParams.y), 1e-4);
|
|
457
|
+
this.$l.smoothStepPx = pb.clamp(pb.div(pb.mul(this.radiusParams.x, 0.5), this.viewScale), 0, this.radiusParams.z);
|
|
458
|
+
this.$l.colorSum = pb.vec3(0);
|
|
459
|
+
this.$l.colorWeight = pb.float(0);
|
|
460
|
+
this.$for(pb.int('y'), -2, 3, function() {
|
|
461
|
+
this.$for(pb.int('x'), -2, 3, function() {
|
|
462
|
+
this.$l.offset = pb.mul(pb.vec2(pb.float(this.x), pb.float(this.y)), this.smoothStepPx, this.targetSize.zw);
|
|
463
|
+
this.$l.sampleUV = pb.clamp(pb.add(this.uv, this.offset), pb.vec2(0), pb.vec2(1));
|
|
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);
|
|
164
470
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this
|
|
168
|
-
this.depthWeight = pb.mul(this.depthWeight, this.depthWeight, pb.sub(3, pb.mul(this.depthWeight, 2)));
|
|
169
|
-
this.sum = pb.add(this.sum, pb.mul(this.skinSample, this.depthWeight));
|
|
170
|
-
this.weightSum = pb.add(this.weightSum, this.depthWeight);
|
|
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));
|
|
171
474
|
});
|
|
172
475
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.$l.alphaDelta = pb.sub(this.blurredSkin.a, this.opacity);
|
|
176
|
-
this.$l.referenceLit = pb.mul(this.baseColor.rgb, pb.add(pb.vec3(pb.sub(1, this.alphaDelta)), pb.mul(this.blurredSkin.rgb, pb.max(pb.sub(1, this.opacity), 0), this.colorBoost)));
|
|
177
|
-
this.result = pb.add(this.baseColor.rgb, pb.mul(pb.sub(this.referenceLit, this.baseColor.rgb), this.strength));
|
|
178
|
-
});
|
|
476
|
+
// Precision can push the subtraction a hair below zero.
|
|
477
|
+
this.result = pb.max(this.result, pb.vec3(0));
|
|
179
478
|
});
|
|
180
479
|
this.$if(pb.equal(this.srgbOut, 0), function() {
|
|
181
480
|
this.$outputs.outColor = pb.vec4(this.result, this.baseColor.a);
|
|
@@ -185,7 +484,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
|
185
484
|
});
|
|
186
485
|
}
|
|
187
486
|
});
|
|
188
|
-
program.name = '@
|
|
487
|
+
program.name = '@SkinSSSCombine';
|
|
189
488
|
return program;
|
|
190
489
|
}
|
|
191
490
|
}
|