@driftengine/core 4.8.4 → 4.8.6
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/CHANGELOG.json +4332 -4250
- package/dist/build/shaderComments.js +0 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +11 -1
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/areaLights.d.ts +19 -0
- package/dist/render/areaLights.js +20 -0
- package/dist/render/backend/api.d.ts +13 -2
- package/dist/render/backend/webgl2/instanced.d.ts +2 -0
- package/dist/render/backend/webgl2/instanced.js +11 -2
- package/dist/render/backend/webgl2/renderer.d.ts +74 -1
- package/dist/render/backend/webgl2/renderer.js +229 -37
- package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
- package/dist/render/backend/webgl2/skinScatter.js +88 -26
- package/dist/render/backend/webgpu/buffers.js +2 -1
- package/dist/render/backend/webgpu/clusterBinner.js +3 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
- package/dist/render/backend/webgpu/flatPass.js +15 -13
- package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
- package/dist/render/backend/webgpu/particlePass.js +80 -12
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/postPass.js +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +58 -7
- package/dist/render/backend/webgpu/renderer.js +329 -78
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
- package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
- package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +2 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +18 -4
- package/dist/render/bloomChain.d.ts +44 -0
- package/dist/render/bloomChain.js +28 -0
- package/dist/render/bloomPass.d.ts +3 -1
- package/dist/render/bloomPass.js +16 -2
- package/dist/render/clusteredLights.d.ts +55 -1
- package/dist/render/clusteredLights.js +182 -8
- package/dist/render/fixtureAtlas.js +1 -1
- package/dist/render/instanceCull.js +7 -0
- package/dist/render/instances.d.ts +32 -10
- package/dist/render/instances.js +17 -8
- package/dist/render/lightBudget.d.ts +26 -1
- package/dist/render/lightBudget.js +26 -3
- package/dist/render/lightmap.d.ts +102 -0
- package/dist/render/lightmap.js +118 -0
- package/dist/render/mesh.js +2 -1
- package/dist/render/particleBatch.d.ts +59 -3
- package/dist/render/particleBatch.js +53 -2
- package/dist/render/particleMaterial.d.ts +6 -1
- package/dist/render/particleMaterial.js +10 -2
- package/dist/render/particlePool.d.ts +11 -0
- package/dist/render/particlePool.js +2 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/flat/areaLight.d.ts +10 -0
- package/dist/render/shaders/flat/areaLight.js +96 -0
- package/dist/render/shaders/flat/index.js +30 -4
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +107 -105
- package/dist/render/shaders/flat/models.d.ts +16 -4
- package/dist/render/shaders/flat/models.js +64 -4
- package/dist/render/shaders/flat/normalMap.d.ts +10 -0
- package/dist/render/shaders/flat/normalMap.js +25 -0
- package/dist/render/shaders/flat/preamble.js +74 -37
- package/dist/render/shaders/flat/skin.js +6 -5
- package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
- package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1631 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1669 -2082
- package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
- package/dist/render/shaders/generated/particle.wgsl.js +115 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
- package/dist/render/shaders/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- package/dist/render/shaders/skinBlur.js +25 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/surfaceModel.d.ts +21 -1
- package/dist/render/surfaceModel.js +22 -0
- package/dist/render/surfaceTexture.d.ts +14 -1
- package/dist/render/surfaceTexture.js +15 -2
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/index.ts +20 -1
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/areaLights.ts +39 -0
- package/src/render/backend/api.ts +14 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +292 -35
- package/src/render/backend/webgl2/skinScatter.ts +120 -43
- package/src/render/backend/webgpu/buffers.ts +2 -1
- package/src/render/backend/webgpu/clusterBinner.ts +3 -1
- package/src/render/backend/webgpu/flatPass.ts +18 -13
- package/src/render/backend/webgpu/particlePass.ts +118 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +396 -95
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
- package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
- package/src/render/backend/webgpu/surfaceTexturePass.ts +26 -5
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +221 -7
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/instanceCull.ts +5 -0
- package/src/render/instances.ts +40 -11
- package/src/render/lightBudget.ts +48 -2
- package/src/render/lightmap.ts +202 -0
- package/src/render/mesh.ts +2 -1
- package/src/render/particleBatch.ts +95 -4
- package/src/render/particleMaterial.ts +11 -1
- package/src/render/particlePool.ts +13 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/sceneTarget.ts +18 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/flat/areaLight.ts +96 -0
- package/src/render/shaders/flat/index.ts +30 -4
- package/src/render/shaders/flat/main.ts +107 -105
- package/src/render/shaders/flat/models.ts +73 -8
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +74 -37
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/flat.wgsl.ts +1669 -2082
- package/src/render/shaders/generated/particle.wgsl.ts +116 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
- package/src/render/shaders/particle.ts +134 -1
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/surfaceModel.ts +36 -1
- package/src/render/surfaceTexture.ts +41 -3
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The GLSL beside this file is the source of truth. A hand edit here is discarded by
|
|
5
5
|
* the next generation, and `npm run wgsl:check` fails the build when this is stale.
|
|
6
6
|
*/
|
|
7
|
-
export const SKIN_BLUR_FRAG_WGSL = "struct Uniforms {\n uStep: vec2<f32>,\n uDepthToViewZ: vec4<f32>,\n uFocal: f32,\n uProfiles: array<vec4<f32>, 8>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(
|
|
7
|
+
export const SKIN_BLUR_FRAG_WGSL = "struct Uniforms {\n uStep: vec2<f32>,\n uDepthToViewZ: vec4<f32>,\n uFocal: f32,\n uProfiles: array<vec4<f32>, 8>,\n uApplyAlbedo: f32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(36) \nvar uAlbedo_t: texture_2d<f32>;\n@group(0) @binding(37) \nvar uAlbedo_s: sampler;\nvar<private> vUv_1: vec2<f32>;\n@group(0) @binding(32) \nvar uSkin_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSkin_s: sampler;\nvar<private> fragColor: vec4<f32>;\n@group(0) @binding(34) \nvar uDepth_t: texture_2d<f32>;\n@group(0) @binding(35) \nvar uDepth_s: sampler;\n\nfn burleyMass_u0028_f1_u003b_vf3_u003b(x: ptr<function, f32>, d: ptr<function, vec3<f32>>) -> vec3<f32> {\n let _e54 = (*x);\n let _e56 = (*d);\n let _e60 = (*x);\n let _e62 = (*d);\n return (vec3(0.5f) - ((exp((vec3(-(_e54)) / _e56)) + (exp((vec3(-(_e60)) / (_e62 * 3f))) * 3f)) * 0.125f));\n}\n\nfn viewZ_u0028_f1_u003b(depth: ptr<function, f32>) -> f32 {\n var ndc: f32;\n\n let _e54 = (*depth);\n ndc = (1f - (_e54 * 2f));\n let _e59 = unnamed.uDepthToViewZ[0u];\n let _e60 = ndc;\n let _e64 = unnamed.uDepthToViewZ[1u];\n let _e68 = unnamed.uDepthToViewZ[2u];\n let _e69 = ndc;\n let _e73 = unnamed.uDepthToViewZ[3u];\n return (((_e59 * _e60) + _e64) / ((_e68 * _e69) + _e73));\n}\n\nfn withAlbedo_u0028_vf3_u003b(light: ptr<function, vec3<f32>>) -> vec3<f32> {\n var albedo: vec4<f32>;\n\n let _e55 = unnamed.uApplyAlbedo;\n if (_e55 < 0.5f) {\n let _e57 = (*light);\n return _e57;\n }\n let _e58 = vUv_1;\n let _e59 = textureSampleLevel(uAlbedo_t, uAlbedo_s, _e58, 0f);\n albedo = _e59;\n let _e60 = (*light);\n let _e61 = albedo;\n let _e65 = albedo[3u];\n return ((_e60 * _e61.xyz) / vec3(max(_e65, 0.0001f)));\n}\n\nfn main_1() {\n var own: vec4<f32>;\n var depth_1: f32;\n var param: vec3<f32>;\n var profile: i32;\n var d_1: vec3<f32>;\n var widest: f32;\n var z: f32;\n var param_1: f32;\n var pixels: f32;\n var param_2: vec3<f32>;\n var centre: vec3<f32>;\n var param_3: f32;\n var param_4: vec3<f32>;\n var sum: vec3<f32>;\n var weight: vec3<f32>;\n var i: i32;\n var outer: vec3<f32>;\n var local: vec3<f32>;\n var indexable: array<f32, 6>;\n var param_5: f32;\n var param_6: vec3<f32>;\n var w: vec3<f32>;\n var indexable_1: array<f32, 6>;\n var param_7: f32;\n var param_8: vec3<f32>;\n var side: i32;\n var uv: vec2<f32>;\n var indexable_2: array<f32, 7>;\n var tap: vec4<f32>;\n var tapZ: f32;\n var param_9: f32;\n var keep: f32;\n var cover: f32;\n var param_10: vec3<f32>;\n\n let _e86 = vUv_1;\n let _e87 = textureSampleLevel(uSkin_t, uSkin_s, _e86, 0f);\n own = _e87;\n let _e89 = own[3u];\n if (_e89 < 0.0001f) {\n fragColor = vec4<f32>(0f, 0f, 0f, 0f);\n return;\n }\n let _e91 = vUv_1;\n let _e92 = textureSampleLevel(uDepth_t, uDepth_s, _e91, 0f);\n depth_1 = _e92.x;\n let _e94 = depth_1;\n if (_e94 <= 0f) {\n let _e96 = own;\n param = _e96.xyz;\n let _e98 = withAlbedo_u0028_vf3_u003b((¶m));\n let _e100 = own[3u];\n fragColor = vec4<f32>(_e98.x, _e98.y, _e98.z, _e100);\n return;\n }\n let _e106 = own[3u];\n profile = clamp((i32(((_e106 * 8f) + 0.5f)) - 1i), 0i, 7i);\n let _e112 = profile;\n let _e115 = unnamed.uProfiles[_e112];\n d_1 = max(_e115.xyz, vec3<f32>(0.000001f, 0.000001f, 0.000001f));\n let _e119 = d_1[0u];\n let _e121 = d_1[1u];\n let _e123 = d_1[2u];\n widest = max(_e119, max(_e121, _e123));\n let _e126 = depth_1;\n param_1 = _e126;\n let _e127 = viewZ_u0028_f1_u003b((¶m_1));\n z = abs(_e127);\n let _e129 = widest;\n let _e131 = unnamed.uFocal;\n let _e133 = z;\n pixels = ((_e129 * _e131) / max(_e133, 0.0001f));\n let _e136 = pixels;\n if (_e136 < 0.5f) {\n let _e138 = own;\n param_2 = _e138.xyz;\n let _e140 = withAlbedo_u0028_vf3_u003b((¶m_2));\n let _e142 = own[3u];\n fragColor = vec4<f32>(_e140.x, _e140.y, _e140.z, _e142);\n return;\n }\n let _e147 = widest;\n param_3 = (0.2f * _e147);\n let _e149 = d_1;\n param_4 = _e149;\n let _e150 = burleyMass_u0028_f1_u003b_vf3_u003b((¶m_3), (¶m_4));\n centre = (_e150 * 2f);\n let _e152 = own;\n let _e154 = centre;\n sum = (_e152.xyz * _e154);\n let _e156 = centre;\n weight = _e156;\n i = 1i;\n loop {\n let _e157 = i;\n if (_e157 < 7i) {\n let _e159 = i;\n if (_e159 < 6i) {\n let _e161 = i;\n indexable = array<f32, 6>(0.2f, 0.75f, 1.6f, 2.8f, 4.55f, 7.3f);\n let _e164 = indexable[min(_e161, 5i)];\n let _e165 = widest;\n param_5 = (_e164 * _e165);\n let _e167 = d_1;\n param_6 = _e167;\n let _e168 = burleyMass_u0028_f1_u003b_vf3_u003b((¶m_5), (¶m_6));\n local = _e168;\n } else {\n local = vec3<f32>(0.5f, 0.5f, 0.5f);\n }\n let _e169 = local;\n outer = _e169;\n let _e170 = outer;\n let _e171 = i;\n indexable_1 = array<f32, 6>(0.2f, 0.75f, 1.6f, 2.8f, 4.55f, 7.3f);\n let _e174 = indexable_1[(_e171 - 1i)];\n let _e175 = widest;\n param_7 = (_e174 * _e175);\n let _e177 = d_1;\n param_8 = _e177;\n let _e178 = burleyMass_u0028_f1_u003b_vf3_u003b((¶m_7), (¶m_8));\n w = (_e170 - _e178);\n side = -1i;\n loop {\n let _e180 = side;\n if (_e180 <= 1i) {\n let _e182 = vUv_1;\n let _e184 = unnamed.uStep;\n let _e185 = side;\n let _e187 = i;\n indexable_2 = array<f32, 7>(0f, 0.4f, 1.1f, 2.1f, 3.5f, 5.6f, 9f);\n let _e189 = indexable_2[_e187];\n let _e191 = pixels;\n uv = (_e182 + (_e184 * ((f32(_e185) * _e189) * _e191)));\n let _e195 = uv;\n let _e196 = textureSampleLevel(uSkin_t, uSkin_s, _e195, 0f);\n tap = _e196;\n let _e197 = uv;\n let _e198 = textureSampleLevel(uDepth_t, uDepth_s, _e197, 0f);\n param_9 = _e198.x;\n let _e200 = viewZ_u0028_f1_u003b((¶m_9));\n tapZ = abs(_e200);\n let _e202 = tapZ;\n let _e203 = z;\n let _e206 = widest;\n keep = clamp((1f - (abs((_e202 - _e203)) / (4f * _e206))), 0f, 1f);\n let _e212 = tap[3u];\n let _e214 = own[3u];\n cover = clamp((_e212 / _e214), 0f, 1f);\n let _e217 = tap;\n let _e219 = w;\n let _e221 = keep;\n let _e223 = sum;\n sum = (_e223 + ((_e217.xyz * _e219) * _e221));\n let _e225 = w;\n let _e226 = cover;\n let _e227 = keep;\n let _e230 = weight;\n weight = (_e230 + (_e225 * (_e226 * _e227)));\n continue;\n } else {\n break;\n }\n continuing {\n let _e232 = side;\n side = (_e232 + 2i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e234 = i;\n i = (_e234 + 1i);\n }\n }\n let _e236 = sum;\n let _e237 = weight;\n param_10 = (_e236 / _e237);\n let _e239 = withAlbedo_u0028_vf3_u003b((¶m_10));\n let _e241 = own[3u];\n fragColor = vec4<f32>(_e239.x, _e239.y, _e239.z, _e241);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> {\n vUv_1 = vUv;\n main_1();\n let _e3 = fragColor;\n return _e3;\n}\n";
|
|
8
8
|
/**
|
|
9
9
|
* What the transform assigned, so the renderer binds the same numbers.
|
|
10
10
|
*
|
|
@@ -13,7 +13,7 @@ export const SKIN_BLUR_FRAG_WGSL = "struct Uniforms {\n uStep: vec2<f32>,\n
|
|
|
13
13
|
export const SKINBLUR_BINDINGS = {
|
|
14
14
|
"SKIN_BLUR_FRAG": {
|
|
15
15
|
"uniforms": 1,
|
|
16
|
-
"uniformSize":
|
|
16
|
+
"uniformSize": 192,
|
|
17
17
|
"fields": {
|
|
18
18
|
"uStep": {
|
|
19
19
|
"offset": 0,
|
|
@@ -36,6 +36,11 @@ export const SKINBLUR_BINDINGS = {
|
|
|
36
36
|
"type": "vec4",
|
|
37
37
|
"length": 8,
|
|
38
38
|
"stride": 16
|
|
39
|
+
},
|
|
40
|
+
"uApplyAlbedo": {
|
|
41
|
+
"offset": 176,
|
|
42
|
+
"size": 4,
|
|
43
|
+
"type": "float"
|
|
39
44
|
}
|
|
40
45
|
},
|
|
41
46
|
"textures": {
|
|
@@ -48,6 +53,11 @@ export const SKINBLUR_BINDINGS = {
|
|
|
48
53
|
"texture": 34,
|
|
49
54
|
"sampler": 35,
|
|
50
55
|
"type": "sampler2D"
|
|
56
|
+
},
|
|
57
|
+
"uAlbedo": {
|
|
58
|
+
"texture": 36,
|
|
59
|
+
"sampler": 37,
|
|
60
|
+
"type": "sampler2D"
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
export declare const PARTICLE_BLADES = 2;
|
|
38
38
|
export declare const PARTICLE_VERTS: number;
|
|
39
39
|
export declare const PARTICLE_INDICES: number;
|
|
40
|
-
export declare const PARTICLE_VERT = "#version 300 es\n/** Per-vertex: the quad corner, and which blade of the cross it belongs to. */\nlayout(location = 0) in vec2 aCorner;\nlayout(location = 1) in float aBlade;\n/** Per-instance. */\nlayout(location = 2) in vec3 aPos;\nlayout(location = 3) in float aSize;\nlayout(location = 4) in float aSpin;\nlayout(location = 5) in vec3 aColor;\n/** Opacity, age as a share of life, and this particle's own seed. */\nlayout(location = 6) in float aAlpha;\nlayout(location = 7) in float aAge;\nlayout(location = 8) in float aSeed;\nlayout(location = 9) in vec3 aVelocity;\n\nuniform mat4 uViewProj;\nuniform vec3 uCameraPos;\n/**\n * How far a particle is stretched along its own motion, in seconds of travel.\n *\n * Zero for smoke, which has no direction worth seeing. A spark is the opposite\n * case: an ember crossing a metre in a frame drawn as a round dot reads as a\n * floating pixel, and the same ember drawn as a short streak along its velocity\n * reads as a thrown spark. The eye gets its motion cue from the shape.\n */\nuniform float uStretchSec;\n/**\n * 1 to turn a single quad to face the camera, 0 to draw the world-fixed cross.\n *\n * **Camera-facing is the default, and the cross is the option.** It was the other way\n * round until a caller drew an additive glow with a camera looking down -Z and got a\n * bright straight line through the middle of every sprite. The cross is two blades at\n * right angles in *world* space: blade 0 spans X, blade 1 spans Z. Look down either\n * axis and that blade is edge-on, and a disc squeezed into a one-pixel column does not\n * lose the energy it was drawn with \u2014 it puts all of it in that column. Alpha-blended\n * smoke half hides it behind its own noise. Additive has nothing to hide it with.\n *\n * Fading a blade out as it turns edge-on fixes that one case and not the matching one:\n * both blades contain world Y, so a camera looking straight down sees *both* edge-on\n * and the puff is a plus sign of two lines, or nothing at all once they are faded. A\n * camera-facing quad has no angle at which it thins, which is why it is what particle\n * systems generally draw.\n *\n * The cross is still worth keeping. Two blades give real parallax when a camera moves\n * through a puff, and that is a volume cue a single quad cannot fake \u2014 worth having in\n * a ground-level world whose camera never looks far off the horizontal, where the\n * edge-on blade stays a thin sliver inside soft smoke rather than a line across a\n * spark. It also costs a brightness that swings with the view: a cross covers one\n * sprite of area seen down an axis and about 1.4 seen from the diagonal, so a puff\n * quietly pulses as the camera orbits it. A camera-facing quad covers the same area\n * from everywhere.\n */\nuniform float uCameraFacing;\n\nout vec2 vUv;\nout vec3 vColor;\nout float vAlpha;\nout float vAge;\nout float vSeed;\nout vec3 vWorldPos;\nout vec3 vNormal;\n\nvoid main() {\n float s = sin(aSpin);\n float c = cos(aSpin);\n // Roll within the blade's own plane, so the two blades never look like one\n // rigid object turning.\n vec2 rolled = vec2(aCorner.x * c - aCorner.y * s, aCorner.x * s + aCorner.y * c);\n\n /*\n * The plane the corners are laid out in, built from the particle's centre rather than\n * from the corner being transformed, so all four corners of a quad share one basis and\n * the quad stays planar and rectangular.\n */\n vec3 across;\n vec3 up;\n float bladeGain;\n if (uCameraFacing > 0.5) {\n vec3 toCamera = normalize(uCameraPos - aPos);\n /* Any reference not parallel to the view. World up serves everywhere except looking\n straight up or down it, where the two are the same line and the cross product\n vanishes; +Z takes over there. */\n vec3 reference = abs(toCamera.y) > 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n across = normalize(cross(reference, toCamera));\n up = cross(toCamera, across);\n /*\n * One quad, not two. Both blades face the camera here, so they would land on exactly\n * the same pixels and the second would be nothing but the first drawn again: double\n * the fill for double the brightness no caller asked for. Collapsing it to zero area\n * costs nothing \u2014 the rasteriser drops a degenerate triangle before it shades \u2014 and\n * leaves the vertex and index buffers the shape both backends already build, so a\n * batch picks its facing without either one rebuilding its geometry.\n */\n bladeGain = aBlade < 0.5 ? 1.0 : 0.0;\n } else {\n across = aBlade < 0.5 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0);\n up = vec3(0.0, 1.0, 0.0);\n bladeGain = 1.0;\n }\n vec3 offset = (across * rolled.x + up * rolled.y) * aSize;\n\n /*\n * Stretched along travel. Applied as an extra displacement on the component of\n * the corner that already points along the motion, so a stationary particle is\n * untouched and a fast one lengthens without also getting fatter.\n */\n if (uStretchSec > 0.0) {\n vec3 travel = aVelocity * uStretchSec;\n float along = dot(normalize(offset + vec3(1e-6)), normalize(travel + vec3(1e-6)));\n offset += travel * along * 0.5;\n }\n\n /* bladeGain applied here, after the stretch rather than before it, so a dropped blade\n is degenerate whatever the stretch added: all four of its corners land on aPos. */\n vec3 world = aPos + offset * bladeGain;\n vUv = aCorner;\n vColor = aColor;\n vAlpha = aAlpha;\n vAge = aAge;\n vSeed = aSeed;\n vWorldPos = world;\n /*\n * The blade's own facing, flipped toward the viewer. A cross has no meaningful\n * outward normal, and a puff of smoke is not a surface \u2014 but it does have to be\n * *lit*, and a normal facing the camera is the standard, honest cheat for a\n * volume approximated by cards: it makes the puff read as a lump catching light\n * from wherever the light is, rather than as a flat panel that goes black at\n * some angles.\n */\n vec3 face = cross(across, up);\n vec3 toEye = normalize(uCameraPos - world);\n vNormal = dot(face, toEye) < 0.0 ? -face : face;\n\n gl_Position = uViewProj * vec4(world, 1.0);\n}\n";
|
|
40
|
+
export declare const PARTICLE_VERT = "#version 300 es\n/** Per-vertex: the quad corner, and which blade of the cross it belongs to. */\nlayout(location = 0) in vec2 aCorner;\nlayout(location = 1) in float aBlade;\n/** Per-instance. */\nlayout(location = 2) in vec3 aPos;\nlayout(location = 3) in float aSize;\nlayout(location = 4) in float aSpin;\nlayout(location = 5) in vec3 aColor;\n/** Opacity, age as a share of life, and this particle's own seed. */\nlayout(location = 6) in float aAlpha;\nlayout(location = 7) in float aAge;\nlayout(location = 8) in float aSeed;\nlayout(location = 9) in vec3 aVelocity;\n/**\n * A sprite's flipbook frame and its half-height: `ParticleInstances.frames` and `heights`. The\n * procedural materials read neither, and a height of 0 is a square, its height its width.\n */\nlayout(location = 10) in vec2 aSprite;\n\nuniform mat4 uViewProj;\nuniform vec3 uCameraPos;\n/**\n * How far a particle is stretched along its own motion, in seconds of travel.\n *\n * Zero for smoke, which has no direction worth seeing. A spark is the opposite\n * case: an ember crossing a metre in a frame drawn as a round dot reads as a\n * floating pixel, and the same ember drawn as a short streak along its velocity\n * reads as a thrown spark. The eye gets its motion cue from the shape.\n */\nuniform float uStretchSec;\n/**\n * 1 to turn a single quad to face the camera, 0 to draw the world-fixed cross.\n *\n * **Camera-facing is the default, and the cross is the option.** It was the other way\n * round until a caller drew an additive glow with a camera looking down -Z and got a\n * bright straight line through the middle of every sprite. The cross is two blades at\n * right angles in *world* space: blade 0 spans X, blade 1 spans Z. Look down either\n * axis and that blade is edge-on, and a disc squeezed into a one-pixel column does not\n * lose the energy it was drawn with \u2014 it puts all of it in that column. Alpha-blended\n * smoke half hides it behind its own noise. Additive has nothing to hide it with.\n *\n * Fading a blade out as it turns edge-on fixes that one case and not the matching one:\n * both blades contain world Y, so a camera looking straight down sees *both* edge-on\n * and the puff is a plus sign of two lines, or nothing at all once they are faded. A\n * camera-facing quad has no angle at which it thins, which is why it is what particle\n * systems generally draw.\n *\n * The cross is still worth keeping. Two blades give real parallax when a camera moves\n * through a puff, and that is a volume cue a single quad cannot fake \u2014 worth having in\n * a ground-level world whose camera never looks far off the horizontal, where the\n * edge-on blade stays a thin sliver inside soft smoke rather than a line across a\n * spark. It also costs a brightness that swings with the view: a cross covers one\n * sprite of area seen down an axis and about 1.4 seen from the diagonal, so a puff\n * quietly pulses as the camera orbits it. A camera-facing quad covers the same area\n * from everywhere.\n */\nuniform float uCameraFacing;\n/** How far each particle is moved toward the eye, in metres: `ParticleBatchOptions.cameraOffset`. */\nuniform float uCameraOffset;\n\nout vec2 vUv;\nout vec3 vColor;\nout float vAlpha;\nout float vAge;\nout float vSeed;\nout vec3 vWorldPos;\nout vec3 vNormal;\n/** The flipbook frame, for the sprite material; and the depth from the eye, for its fades. */\nout float vFrame;\nout float vViewDepth;\n\nvoid main() {\n float s = sin(aSpin);\n float c = cos(aSpin);\n // Roll within the blade's own plane, so the two blades never look like one\n // rigid object turning.\n vec2 rolled = vec2(aCorner.x * c - aCorner.y * s, aCorner.x * s + aCorner.y * c);\n\n /*\n * The plane the corners are laid out in, built from the particle's centre rather than\n * from the corner being transformed, so all four corners of a quad share one basis and\n * the quad stays planar and rectangular.\n */\n vec3 across;\n vec3 up;\n float bladeGain;\n /*\n * **Along its travel**, `facing: 'velocity'` (2): the quad's up is the velocity seen across\n * the view, so a streak of light or a stretched spark lies along its motion and still faces the\n * eye. A particle at rest has no such direction and faces the camera as the default does.\n */\n vec3 viewer = normalize(uCameraPos - aPos);\n vec3 alongView = aVelocity - viewer * dot(aVelocity, viewer);\n if (uCameraFacing > 1.5 && dot(alongView, alongView) > 1e-10) {\n up = normalize(alongView);\n across = cross(up, viewer);\n bladeGain = aBlade < 0.5 ? 1.0 : 0.0;\n } else if (uCameraFacing > 0.5) {\n vec3 toCamera = normalize(uCameraPos - aPos);\n /* Any reference not parallel to the view. World up serves everywhere except looking\n straight up or down it, where the two are the same line and the cross product\n vanishes; +Z takes over there. */\n vec3 reference = abs(toCamera.y) > 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n across = normalize(cross(reference, toCamera));\n up = cross(toCamera, across);\n /*\n * One quad, not two. Both blades face the camera here, so they would land on exactly\n * the same pixels and the second would be nothing but the first drawn again: double\n * the fill for double the brightness no caller asked for. Collapsing it to zero area\n * costs nothing \u2014 the rasteriser drops a degenerate triangle before it shades \u2014 and\n * leaves the vertex and index buffers the shape both backends already build, so a\n * batch picks its facing without either one rebuilding its geometry.\n */\n bladeGain = aBlade < 0.5 ? 1.0 : 0.0;\n } else {\n across = aBlade < 0.5 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0);\n up = vec3(0.0, 1.0, 0.0);\n bladeGain = 1.0;\n }\n vec3 offset = (across * rolled.x + up * rolled.y) * aSize;\n /*\n * **A sprite of two sizes** \u2014 a half-width and a half-height \u2014 rolls as a rectangle: the corner\n * is scaled first and turned after. Only where a height was given, so a square particle keeps\n * the arithmetic it always had to the bit.\n */\n if (aSprite.y > 0.0) {\n vec2 extent = vec2(aCorner.x * aSize, aCorner.y * aSprite.y);\n vec2 turned = vec2(extent.x * c - extent.y * s, extent.x * s + extent.y * c);\n offset = across * turned.x + up * turned.y;\n }\n\n /*\n * Stretched along travel. Applied as an extra displacement on the component of\n * the corner that already points along the motion, so a stationary particle is\n * untouched and a fast one lengthens without also getting fatter.\n */\n if (uStretchSec > 0.0) {\n vec3 travel = aVelocity * uStretchSec;\n float along = dot(normalize(offset + vec3(1e-6)), normalize(travel + vec3(1e-6)));\n offset += travel * along * 0.5;\n }\n\n /* bladeGain applied here, after the stretch rather than before it, so a dropped blade\n is degenerate whatever the stretch added: all four of its corners land on aPos. */\n vec3 world = aPos + offset * bladeGain;\n /* Toward the eye, so a sprite set into a surface is drawn in front of it. 0 moves nothing. */\n world += viewer * uCameraOffset * bladeGain;\n vUv = aCorner;\n vFrame = aSprite.x;\n vColor = aColor;\n vAlpha = aAlpha;\n vAge = aAge;\n vSeed = aSeed;\n vWorldPos = world;\n /*\n * The blade's own facing, flipped toward the viewer. A cross has no meaningful\n * outward normal, and a puff of smoke is not a surface \u2014 but it does have to be\n * *lit*, and a normal facing the camera is the standard, honest cheat for a\n * volume approximated by cards: it makes the puff read as a lump catching light\n * from wherever the light is, rather than as a flat panel that goes black at\n * some angles.\n */\n vec3 face = cross(across, up);\n vec3 toEye = normalize(uCameraPos - world);\n vNormal = dot(face, toEye) < 0.0 ? -face : face;\n\n gl_Position = uViewProj * vec4(world, 1.0);\n /* A perspective projection's w is the depth from the eye, which is what the fades measure. */\n vViewDepth = gl_Position.w;\n}\n";
|
|
41
41
|
/**
|
|
42
42
|
* Smoke, grit and dust: a soft body eroded by noise, alpha blended, and lit.
|
|
43
43
|
*
|
|
@@ -80,3 +80,19 @@ export declare const PARTICLE_SPARK_FRAG = "#version 300 es\nprecision highp flo
|
|
|
80
80
|
* existed; see `ParticleBatchOptions.fog`.
|
|
81
81
|
*/
|
|
82
82
|
export declare const PARTICLE_MOTE_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\nin vec3 vColor;\nin float vAlpha;\nin float vAge;\nin float vSeed;\nin vec3 vWorldPos;\nin vec3 vNormal;\n\nuniform vec3 uCameraPos;\n/**\n * Whether the fragment stage mixes toward the medium's own colour at all. 0 \u2014 what a batch\n * gets by leaving ParticleBatchOptions.fog unset \u2014 leaves vColor untouched by distance, which\n * is what a caller reaching for this material over 'spark'/'smoke' is usually asking for: an\n * unlit point drawn exactly as its own colour says. 1 mixes toward mediumColor() by\n * mediumFog(), the same medium every other pass in the frame recedes into.\n */\nuniform int uFogEnabled;\n\nuniform vec3 uFogColor;\n/** Extinction per metre **at the camera's own height**, not at the base. */\nuniform float uFogDensity;\n/** Reciprocal scale height of the haze, per metre. 0 is a uniform medium. */\nuniform float uFogHeightFalloff;\n/** The camera's height, so a fragment's own height is one subtraction away. */\nuniform float uFogEyeY;\nuniform vec3 uUnderwaterColor;\nuniform float uUnderwaterFogDensity;\nuniform float uUnderwaterFactor;\n/** 0 integrates a medium (Beer-Lambert); 1 ramps linearly between near and far. */\nuniform int uFogMode;\n/** Linear mode only: where haze begins, and where it is total. */\nuniform float uFogNear;\nuniform float uFogFar;\n\n/** What distance converges toward: the day's haze, or the water column. */\nvec3 mediumColor() {\n return mix(uFogColor, uUnderwaterColor, uUnderwaterFactor);\n}\n\n/**\n * How much of a surface standing at height pointY, dist metres away, is lost to\n * the medium in between. 0 is perfectly clear, 1 is the medium's own colour.\n */\nfloat mediumFog(float dist, float pointY) {\n /*\n * A depth ramp rather than a medium, for worlds authored against one.\n *\n * Beer-Lambert is the honest model and stays the default: it integrates something\n * physically there, so extinction begins at the eye and never stops. A linear near/far\n * fog is not that \u2014 it is a *look*, with a hard start distance before which the air is\n * perfectly clear, and it is what the fixed-function pipeline offered and what several\n * renderers still expose. Approximating one with the other is not a matter of tuning:\n * matched at half occlusion, the exponential curve still lays haze over everything\n * inside near, where the ramp lays none, and near-field colour desaturates toward the\n * fog colour with nothing to show for it.\n *\n * Height falloff has no meaning here and is ignored: a ramp is defined on distance\n * alone.\n */\n if (uFogMode == 1) {\n float span = max(uFogFar - uFogNear, 1e-4);\n float ramp = clamp((dist - uFogNear) / span, 0.0, 1.0);\n if (uUnderwaterFactor <= 0.0) return ramp;\n float wetLinear = uUnderwaterFogDensity * dist;\n return mix(ramp, 1.0 - exp2(-wetLinear * wetLinear * 1.442695), uUnderwaterFactor);\n }\n\n float t = (pointY - uFogEyeY) * uFogHeightFalloff;\n // (1 - e^-t)/t, which tends to 1 as the ray levels out. Guarded rather than\n // branched: at t = 0 the quotient is 0/0, and 1e-4 of a scale height is 8 mm.\n float denom = abs(t) < 1e-4 ? 1e-4 : t;\n float air = 1.0 - exp(-uFogDensity * dist * (1.0 - exp(-denom)) / denom);\n if (uUnderwaterFactor <= 0.0) return air;\n // Water is its own medium and keeps its own curve, so crossing the surface\n // changes nothing about how the water column reads.\n float wet = uUnderwaterFogDensity * dist;\n return mix(air, 1.0 - exp2(-wet * wet * 1.442695), uUnderwaterFactor);\n}\n\n\nout vec4 outColor;\n\n/** 0 none, 1 sRGB, 2 ACES then sRGB, 3 the highlight shoulder then sRGB. See renderQuality.ts. */\nuniform int uOutputTransform;\n/** Scales the scene into the tone curve. 1 is the reference grade. See renderQuality.ts. */\nuniform float uOutputExposure;\n\n/*\n * ACES: the reference fit, not the cheap one.\n *\n * This was Narkowicz's single rational function, on the argument that the difference from\n * the reference curve is invisible next to the gain. That argument holds for a look and\n * fails for a *match*, which is what this option is for \u2014 aces exists so a world ported\n * from three.js reads the same, and three.js applies the full fit: into AP1 through\n * ACESInputMat, the RRT and ODT together, then back out through ACESOutputMat.\n *\n * The two curves are not a shade apart. Narkowicz is markedly brighter through the\n * midtones, which is exactly where an interior scene lives, and it is a large part of a\n * measured ~1.7x that survived correcting the colour space and the missing Lambert pi.\n * An approximation of a standard is the wrong thing to hold when the standard is the\n * specification.\n */\nvec3 rrtAndOdtFit(vec3 v) {\n vec3 a = v * (v + 0.0245786) - 0.000090537;\n vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return a / b;\n}\n\nvec3 acesFilmic(vec3 x) {\n /*\n * Exposure first, because a tone curve is only defined relative to one.\n *\n * The reference RRT and ODT are graded for a dark cinema. Every renderer that ships ACES\n * scales its input to suit whatever it is actually shown on, and the factor each one\n * picks is a *look*, not a standard - so this engine takes no position and reads it from\n * the caller. Neutral by default, which is the reference grade unmodified. three.js's own\n * pick is 1 / 0.6, for a consumer matching one.\n */\n x *= uOutputExposure;\n // Columns, as GLSL reads them: sRGB -> XYZ -> D65_2_D60 -> AP1 -> RRT_SAT.\n const mat3 ACES_INPUT = mat3(\n 0.59719, 0.07600, 0.02840,\n 0.35458, 0.90834, 0.13383,\n 0.04823, 0.01566, 0.83777\n );\n // ODT_SAT -> XYZ -> D60_2_D65 -> sRGB.\n const mat3 ACES_OUTPUT = mat3(\n 1.60475, -0.10208, -0.00327,\n -0.53108, 1.10813, -0.07276,\n -0.07367, -0.00605, 1.07602\n );\n return clamp(ACES_OUTPUT * rrtAndOdtFit(ACES_INPUT * x), 0.0, 1.0);\n}\n\n/*\n * Linear to sRGB. The piecewise form rather than a plain pow: the linear toe near black\n * is where banding lives in an 8-bit buffer, and the cheap approximation puts it in the\n * wrong place.\n */\nvec3 linearToSrgb(vec3 c) {\n vec3 low = c * 12.92;\n vec3 high = 1.055 * pow(max(c, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055;\n return mix(high, low, step(c, vec3(0.0031308)));\n}\n\n\nvec3 highlightShoulder(vec3 c) {\n float m = max(c.r, max(c.g, c.b));\n if (m <= 0.8) return c;\n float e = m - 0.8;\n return c * ((0.8 + 0.2 * e / (e + 0.2)) / m);\n}\n\nvec3 applyOutputTransform(vec3 c) {\n if (uOutputTransform == 0) return c;\n if (uOutputTransform == 2) c = acesFilmic(c);\n if (uOutputTransform == 3) c = highlightShoulder(c * uOutputExposure);\n return linearToSrgb(c);\n}\n\n\nvoid main() {\n /*\n * One soft, round falloff \u2014 no erosion, no core past white, because this material draws\n * neither a body being eaten by noise nor a light source. Squared rather than linear so the\n * edge feathers instead of ramping, which is what keeps a field of these reading as dust\n * rather than as a scatter of hard-edged discs.\n */\n float r = length(vUv);\n float body = 1.0 - smoothstep(0.4, 1.0, r);\n if (body <= 0.0) discard;\n float alpha = body * body * vAlpha;\n if (alpha <= 0.002) discard;\n\n vec3 color = vColor;\n if (uFogEnabled != 0) {\n float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);\n color = mix(color, mediumColor(), fog);\n }\n /*\n * A real alpha blend (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) rather than spark's energy-in-colour\n * trick: this point genuinely occludes what little is behind it instead of adding light to it.\n */\n outColor = vec4(applyOutputTransform(color), alpha);\n}\n";
|
|
83
|
+
/**
|
|
84
|
+
* A textured sprite: an image a consumer supplies, a cell of it at a time, at the colour and opacity
|
|
85
|
+
* each particle carries — what another engine's sprite renderer draws, and the material the three
|
|
86
|
+
* procedural ones above cannot be. Unlit: the colour is the particle's times the image's.
|
|
87
|
+
*
|
|
88
|
+
* **A flipbook** of `uCells.x` by `uCells.y` cells, numbered across then down from the top-left,
|
|
89
|
+
* a particle's frame naming one; with `uCells.z` at 1 the frame's fraction blends toward the next,
|
|
90
|
+
* so a slow flipbook does not step.
|
|
91
|
+
*
|
|
92
|
+
* **Two fades, each 0 for off.** A soft edge where the sprite meets the scene, by how far in front
|
|
93
|
+
* of the opaque depth it is over `uFade.x` metres — the hard line a flat card draws where it cuts
|
|
94
|
+
* a floor — and a fade over `uFade.y` metres in front of the eye, so a card the camera passes
|
|
95
|
+
* through does not fill the screen and vanish. The first reads a copy of the frame's depth, which
|
|
96
|
+
* is `uFade.z` at 1; where no copy can be had it is 0 and the edge stays hard.
|
|
97
|
+
*/
|
|
98
|
+
export declare const PARTICLE_SPRITE_FRAG: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FOG_GLSL } from './fog.js';
|
|
2
2
|
import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.js';
|
|
3
|
+
import { glslIsFarDepth, glslSceneDepthToNdc } from '../depthConvention.js';
|
|
3
4
|
import { MAX_POINT_LIGHTS } from '../lightBudget.js';
|
|
4
5
|
/**
|
|
5
6
|
* Particle materials: soft smoke, hot sparks, and unlit motes.
|
|
@@ -83,6 +84,11 @@ layout(location = 6) in float aAlpha;
|
|
|
83
84
|
layout(location = 7) in float aAge;
|
|
84
85
|
layout(location = 8) in float aSeed;
|
|
85
86
|
layout(location = 9) in vec3 aVelocity;
|
|
87
|
+
/**
|
|
88
|
+
* A sprite's flipbook frame and its half-height: \`ParticleInstances.frames\` and \`heights\`. The
|
|
89
|
+
* procedural materials read neither, and a height of 0 is a square, its height its width.
|
|
90
|
+
*/
|
|
91
|
+
layout(location = 10) in vec2 aSprite;
|
|
86
92
|
|
|
87
93
|
uniform mat4 uViewProj;
|
|
88
94
|
uniform vec3 uCameraPos;
|
|
@@ -122,6 +128,8 @@ uniform float uStretchSec;
|
|
|
122
128
|
* from everywhere.
|
|
123
129
|
*/
|
|
124
130
|
uniform float uCameraFacing;
|
|
131
|
+
/** How far each particle is moved toward the eye, in metres: \`ParticleBatchOptions.cameraOffset\`. */
|
|
132
|
+
uniform float uCameraOffset;
|
|
125
133
|
|
|
126
134
|
out vec2 vUv;
|
|
127
135
|
out vec3 vColor;
|
|
@@ -130,6 +138,9 @@ out float vAge;
|
|
|
130
138
|
out float vSeed;
|
|
131
139
|
out vec3 vWorldPos;
|
|
132
140
|
out vec3 vNormal;
|
|
141
|
+
/** The flipbook frame, for the sprite material; and the depth from the eye, for its fades. */
|
|
142
|
+
out float vFrame;
|
|
143
|
+
out float vViewDepth;
|
|
133
144
|
|
|
134
145
|
void main() {
|
|
135
146
|
float s = sin(aSpin);
|
|
@@ -146,7 +157,18 @@ void main() {
|
|
|
146
157
|
vec3 across;
|
|
147
158
|
vec3 up;
|
|
148
159
|
float bladeGain;
|
|
149
|
-
|
|
160
|
+
/*
|
|
161
|
+
* **Along its travel**, \`facing: 'velocity'\` (2): the quad's up is the velocity seen across
|
|
162
|
+
* the view, so a streak of light or a stretched spark lies along its motion and still faces the
|
|
163
|
+
* eye. A particle at rest has no such direction and faces the camera as the default does.
|
|
164
|
+
*/
|
|
165
|
+
vec3 viewer = normalize(uCameraPos - aPos);
|
|
166
|
+
vec3 alongView = aVelocity - viewer * dot(aVelocity, viewer);
|
|
167
|
+
if (uCameraFacing > 1.5 && dot(alongView, alongView) > 1e-10) {
|
|
168
|
+
up = normalize(alongView);
|
|
169
|
+
across = cross(up, viewer);
|
|
170
|
+
bladeGain = aBlade < 0.5 ? 1.0 : 0.0;
|
|
171
|
+
} else if (uCameraFacing > 0.5) {
|
|
150
172
|
vec3 toCamera = normalize(uCameraPos - aPos);
|
|
151
173
|
/* Any reference not parallel to the view. World up serves everywhere except looking
|
|
152
174
|
straight up or down it, where the two are the same line and the cross product
|
|
@@ -169,6 +191,16 @@ void main() {
|
|
|
169
191
|
bladeGain = 1.0;
|
|
170
192
|
}
|
|
171
193
|
vec3 offset = (across * rolled.x + up * rolled.y) * aSize;
|
|
194
|
+
/*
|
|
195
|
+
* **A sprite of two sizes** — a half-width and a half-height — rolls as a rectangle: the corner
|
|
196
|
+
* is scaled first and turned after. Only where a height was given, so a square particle keeps
|
|
197
|
+
* the arithmetic it always had to the bit.
|
|
198
|
+
*/
|
|
199
|
+
if (aSprite.y > 0.0) {
|
|
200
|
+
vec2 extent = vec2(aCorner.x * aSize, aCorner.y * aSprite.y);
|
|
201
|
+
vec2 turned = vec2(extent.x * c - extent.y * s, extent.x * s + extent.y * c);
|
|
202
|
+
offset = across * turned.x + up * turned.y;
|
|
203
|
+
}
|
|
172
204
|
|
|
173
205
|
/*
|
|
174
206
|
* Stretched along travel. Applied as an extra displacement on the component of
|
|
@@ -184,7 +216,10 @@ void main() {
|
|
|
184
216
|
/* bladeGain applied here, after the stretch rather than before it, so a dropped blade
|
|
185
217
|
is degenerate whatever the stretch added: all four of its corners land on aPos. */
|
|
186
218
|
vec3 world = aPos + offset * bladeGain;
|
|
219
|
+
/* Toward the eye, so a sprite set into a surface is drawn in front of it. 0 moves nothing. */
|
|
220
|
+
world += viewer * uCameraOffset * bladeGain;
|
|
187
221
|
vUv = aCorner;
|
|
222
|
+
vFrame = aSprite.x;
|
|
188
223
|
vColor = aColor;
|
|
189
224
|
vAlpha = aAlpha;
|
|
190
225
|
vAge = aAge;
|
|
@@ -203,6 +238,8 @@ void main() {
|
|
|
203
238
|
vNormal = dot(face, toEye) < 0.0 ? -face : face;
|
|
204
239
|
|
|
205
240
|
gl_Position = uViewProj * vec4(world, 1.0);
|
|
241
|
+
/* A perspective projection's w is the depth from the eye, which is what the fades measure. */
|
|
242
|
+
vViewDepth = gl_Position.w;
|
|
206
243
|
}
|
|
207
244
|
`;
|
|
208
245
|
/**
|
|
@@ -429,3 +466,98 @@ void main() {
|
|
|
429
466
|
outColor = vec4(applyOutputTransform(color), alpha);
|
|
430
467
|
}
|
|
431
468
|
`;
|
|
469
|
+
/**
|
|
470
|
+
* A textured sprite: an image a consumer supplies, a cell of it at a time, at the colour and opacity
|
|
471
|
+
* each particle carries — what another engine's sprite renderer draws, and the material the three
|
|
472
|
+
* procedural ones above cannot be. Unlit: the colour is the particle's times the image's.
|
|
473
|
+
*
|
|
474
|
+
* **A flipbook** of `uCells.x` by `uCells.y` cells, numbered across then down from the top-left,
|
|
475
|
+
* a particle's frame naming one; with `uCells.z` at 1 the frame's fraction blends toward the next,
|
|
476
|
+
* so a slow flipbook does not step.
|
|
477
|
+
*
|
|
478
|
+
* **Two fades, each 0 for off.** A soft edge where the sprite meets the scene, by how far in front
|
|
479
|
+
* of the opaque depth it is over `uFade.x` metres — the hard line a flat card draws where it cuts
|
|
480
|
+
* a floor — and a fade over `uFade.y` metres in front of the eye, so a card the camera passes
|
|
481
|
+
* through does not fill the screen and vanish. The first reads a copy of the frame's depth, which
|
|
482
|
+
* is `uFade.z` at 1; where no copy can be had it is 0 and the edge stays hard.
|
|
483
|
+
*/
|
|
484
|
+
export const PARTICLE_SPRITE_FRAG = `#version 300 es
|
|
485
|
+
precision highp float;
|
|
486
|
+
|
|
487
|
+
/* Every output of the vertex stage, in its order, read or not: the generated stages number their
|
|
488
|
+
locations in declaration order, so a fragment declaring a subset reads the wrong ones. */
|
|
489
|
+
in vec2 vUv;
|
|
490
|
+
in vec3 vColor;
|
|
491
|
+
in float vAlpha;
|
|
492
|
+
in float vAge;
|
|
493
|
+
in float vSeed;
|
|
494
|
+
in vec3 vWorldPos;
|
|
495
|
+
in vec3 vNormal;
|
|
496
|
+
in float vFrame;
|
|
497
|
+
in float vViewDepth;
|
|
498
|
+
|
|
499
|
+
/** The image, a surface texture: its first layer. */
|
|
500
|
+
uniform mediump sampler2DArray uSprite;
|
|
501
|
+
/** The frame's opaque depth, copied, for the soft edge. highp for the reason depth always is. */
|
|
502
|
+
uniform highp sampler2D uDepth;
|
|
503
|
+
/** Columns, rows, and 1 to blend between cells. */
|
|
504
|
+
uniform vec4 uCells;
|
|
505
|
+
/** The soft edge's distance, the eye fade's, and whether the depth copy is there to read. */
|
|
506
|
+
uniform vec4 uFade;
|
|
507
|
+
/** A depth sample to view-space metres, as the skin blur takes it. */
|
|
508
|
+
uniform vec4 uDepthToViewZ;
|
|
509
|
+
/** The target's size in pixels, so a fragment finds its own texel of the depth copy. */
|
|
510
|
+
uniform vec2 uViewport;
|
|
511
|
+
uniform vec3 uCameraPos;
|
|
512
|
+
/** Whether the colour recedes into the medium: \`ParticleBatchOptions.fog\`. */
|
|
513
|
+
uniform int uFogEnabled;
|
|
514
|
+
${FOG_GLSL}
|
|
515
|
+
|
|
516
|
+
out vec4 outColor;
|
|
517
|
+
${OUTPUT_TRANSFORM_GLSL}
|
|
518
|
+
|
|
519
|
+
/** Cell \`cell\` of the flipbook, at \`uv\` inside it. Integer arithmetic: see AGENTS.md. */
|
|
520
|
+
vec2 cellUv(int cell, vec2 uv) {
|
|
521
|
+
int columns = max(int(uCells.x + 0.5), 1);
|
|
522
|
+
int rows = max(int(uCells.y + 0.5), 1);
|
|
523
|
+
int wrapped = cell % (columns * rows);
|
|
524
|
+
int column = wrapped % columns;
|
|
525
|
+
int row = wrapped / columns;
|
|
526
|
+
return (vec2(float(column), float(row)) + uv) / vec2(float(columns), float(rows));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
float viewZ(float depth) {
|
|
530
|
+
float ndc = ${glslSceneDepthToNdc('depth')};
|
|
531
|
+
return (uDepthToViewZ.x * ndc + uDepthToViewZ.y) / (uDepthToViewZ.z * ndc + uDepthToViewZ.w);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
void main() {
|
|
535
|
+
/* The corner's square to the image's: v runs down an image, and up a quad. */
|
|
536
|
+
vec2 uv = vec2(vUv.x * 0.5 + 0.5, 0.5 - vUv.y * 0.5);
|
|
537
|
+
float frame = max(vFrame, 0.0);
|
|
538
|
+
int first = int(floor(frame));
|
|
539
|
+
vec4 image = texture(uSprite, vec3(cellUv(first, uv), 0.0));
|
|
540
|
+
if (uCells.z > 0.5) {
|
|
541
|
+
vec4 next = texture(uSprite, vec3(cellUv(first + 1, uv), 0.0));
|
|
542
|
+
image = mix(image, next, fract(frame));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
float fade = 1.0;
|
|
546
|
+
if (uFade.z > 0.5 && uFade.x > 0.0) {
|
|
547
|
+
float depth = textureLod(uDepth, gl_FragCoord.xy / uViewport, 0.0).r;
|
|
548
|
+
if (!(${glslIsFarDepth('depth')})) {
|
|
549
|
+
fade = clamp((abs(viewZ(depth)) - vViewDepth) / uFade.x, 0.0, 1.0);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (uFade.y > 0.0) fade *= clamp(vViewDepth / uFade.y, 0.0, 1.0);
|
|
553
|
+
|
|
554
|
+
float alpha = vAlpha * image.a * fade;
|
|
555
|
+
if (alpha <= 0.001) discard;
|
|
556
|
+
vec3 color = vColor * image.rgb;
|
|
557
|
+
if (uFogEnabled != 0) {
|
|
558
|
+
float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);
|
|
559
|
+
color = mix(color, mediumColor(), fog);
|
|
560
|
+
}
|
|
561
|
+
outColor = vec4(applyOutputTransform(color), alpha);
|
|
562
|
+
}
|
|
563
|
+
`;
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* **One pass, run twice**: along x into a target of its own, keeping the profile in the alpha, then
|
|
7
7
|
* along y blended additively into the frame — its colour added, the frame's alpha kept.
|
|
8
8
|
*
|
|
9
|
+
* **What it spreads is light, and the colour is applied after**, from the skin's own colour half at
|
|
10
|
+
* this pixel. Light travels beneath skin; the pigment a brow, a lip line or a freckle is made of
|
|
11
|
+
* does not, so spreading the coloured diffuse — as this did until 4.8.6 — smeared every mark on a
|
|
12
|
+
* face into the skin around it, and the blur's taps laid faint copies of each mark beside it.
|
|
13
|
+
*
|
|
9
14
|
* **Refuses what is not the same skin**: a neighbour that wrote no skin, or one further in depth
|
|
10
15
|
* than four of the profile's widest distances, takes no weight, and what is kept is divided by
|
|
11
16
|
* what was kept, so a cheek's light does not pull toward the black beside its silhouette. A tap
|
|
@@ -32,6 +37,12 @@ uniform vec4 uDepthToViewZ;
|
|
|
32
37
|
uniform float uFocal;
|
|
33
38
|
/** Each profile's scatter distance per channel, in metres. */
|
|
34
39
|
uniform vec4 uProfiles[${SKIN_PROFILES}];
|
|
40
|
+
/**
|
|
41
|
+
* The skin's colour, and its coverage in the alpha: what the spread light is multiplied by on the
|
|
42
|
+
* second axis, where \`uApplyAlbedo\` is 1. Read at this pixel alone, because pigment does not move.
|
|
43
|
+
*/
|
|
44
|
+
uniform sampler2D uAlbedo;
|
|
45
|
+
uniform float uApplyAlbedo;
|
|
35
46
|
|
|
36
47
|
out vec4 fragColor;
|
|
37
48
|
|
|
@@ -48,6 +59,17 @@ vec3 burleyMass(float x, vec3 d) {
|
|
|
48
59
|
return 0.5 - (exp(-x / d) + 3.0 * exp(-x / (3.0 * d))) * 0.125;
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The spread light times the skin's colour here, on the second axis; the light as it is on the
|
|
64
|
+
* first. The colour target holds colour times coverage over coverage, so dividing gives the skin's
|
|
65
|
+
* own colour at an edge, which the light — weighted for coverage already — is then multiplied by.
|
|
66
|
+
*/
|
|
67
|
+
vec3 withAlbedo(vec3 light) {
|
|
68
|
+
if (uApplyAlbedo < 0.5) return light;
|
|
69
|
+
vec4 albedo = textureLod(uAlbedo, vUv, 0.0);
|
|
70
|
+
return light * albedo.rgb / max(albedo.a, 1e-4);
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
void main() {
|
|
52
74
|
vec4 own = textureLod(uSkin, vUv, 0.0);
|
|
53
75
|
/*
|
|
@@ -61,7 +83,7 @@ void main() {
|
|
|
61
83
|
}
|
|
62
84
|
float depth = textureLod(uDepth, vUv, 0.0).r;
|
|
63
85
|
if (${glslIsFarDepth('depth')}) {
|
|
64
|
-
fragColor = own;
|
|
86
|
+
fragColor = vec4(withAlbedo(own.rgb), own.a);
|
|
65
87
|
return;
|
|
66
88
|
}
|
|
67
89
|
/* A part-covered pixel's code is its share of the whole: read it as the nearest, which is the
|
|
@@ -73,7 +95,7 @@ void main() {
|
|
|
73
95
|
/* The widest distance in pixels here: under half of one, there is nothing to spread across. */
|
|
74
96
|
float pixels = widest * uFocal / max(z, 1e-4);
|
|
75
97
|
if (pixels < 0.5) {
|
|
76
|
-
fragColor = own;
|
|
98
|
+
fragColor = vec4(withAlbedo(own.rgb), own.a);
|
|
77
99
|
return;
|
|
78
100
|
}
|
|
79
101
|
vec3 centre = 2.0 * burleyMass(EDGES[0] * widest, d);
|
|
@@ -98,6 +120,6 @@ void main() {
|
|
|
98
120
|
weight += w * (cover * keep);
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
|
-
fragColor = vec4(sum / weight, own.a);
|
|
123
|
+
fragColor = vec4(withAlbedo(sum / weight), own.a);
|
|
102
124
|
}
|
|
103
125
|
`;
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* the kernel to, and nothing at run time calls it.
|
|
4
4
|
*
|
|
5
5
|
* **What it spreads.** With `skinScattering: 'screen-space'`, a skin draw writes its specular to the
|
|
6
|
-
* frame
|
|
7
|
-
* profile it scatters by in the alpha
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* frame, the light its diffuse is made of — the light that went beneath the surface, shaded on a
|
|
7
|
+
* white surface — to a target of its own with the profile it scatters by in the alpha, and its
|
|
8
|
+
* colour to a third. Before anything blended is drawn, the light is blurred along the screen's two
|
|
9
|
+
* axes by Burley's normalised diffusion, one width per channel, multiplied by the colour at each
|
|
10
|
+
* pixel and added back: light that entered a cheek leaves it a centimetre away, reddest furthest,
|
|
11
|
+
* across the pixels that centimetre covers at the cheek's distance, and a brow stays where it is
|
|
12
|
+
* drawn. **Until 4.8.6 the coloured diffuse was spread**, which smeared every mark on a face into
|
|
13
|
+
* the skin around it and laid faint copies of each mark at the taps' offsets. The pre-integrated fit stays in the lit
|
|
11
14
|
* stage for what this cannot see — a terminator's curvature — and this carries what that cannot:
|
|
12
15
|
* light crossing a shadow's edge, or a nostril's rim, in the picture rather than in a fit.
|
|
13
16
|
*
|
package/dist/render/skinBlur.js
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* the kernel to, and nothing at run time calls it.
|
|
4
4
|
*
|
|
5
5
|
* **What it spreads.** With `skinScattering: 'screen-space'`, a skin draw writes its specular to the
|
|
6
|
-
* frame
|
|
7
|
-
* profile it scatters by in the alpha
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* frame, the light its diffuse is made of — the light that went beneath the surface, shaded on a
|
|
7
|
+
* white surface — to a target of its own with the profile it scatters by in the alpha, and its
|
|
8
|
+
* colour to a third. Before anything blended is drawn, the light is blurred along the screen's two
|
|
9
|
+
* axes by Burley's normalised diffusion, one width per channel, multiplied by the colour at each
|
|
10
|
+
* pixel and added back: light that entered a cheek leaves it a centimetre away, reddest furthest,
|
|
11
|
+
* across the pixels that centimetre covers at the cheek's distance, and a brow stays where it is
|
|
12
|
+
* drawn. **Until 4.8.6 the coloured diffuse was spread**, which smeared every mark on a face into
|
|
13
|
+
* the skin around it and laid faint copies of each mark at the taps' offsets. The pre-integrated fit stays in the lit
|
|
11
14
|
* stage for what this cannot see — a terminator's curvature — and this carries what that cannot:
|
|
12
15
|
* light crossing a shadow's edge, or a nostril's rim, in the picture rather than in a fit.
|
|
13
16
|
*
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* the draw's model matrix and the eye's joint each draw, with `eyeAxisInWorld`.
|
|
19
19
|
*/
|
|
20
20
|
import type { Vec3 } from '../math/color.ts';
|
|
21
|
+
import type { LightmapRegion } from './lightmap.ts';
|
|
21
22
|
/** Strands of brushed metal, satin or hair-like fibre: a highlight stretched across them. */
|
|
22
23
|
export interface AnisotropicModel {
|
|
23
24
|
readonly kind: 'anisotropic';
|
|
@@ -72,8 +73,18 @@ export interface EyeModel {
|
|
|
72
73
|
/** The joint the eye turns with, or -1 for an eye placed by its model matrix alone. */
|
|
73
74
|
readonly joint: number;
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* A surface whose static light was baked: the standard model, plus a lightmap page's light read at
|
|
78
|
+
* the mesh's second coordinates (`MeshData.lightmapUvs`). The material's `modelMap` is the page,
|
|
79
|
+
* from `createLightmap`; see `lightmap.ts` for what it adds and what it gives up.
|
|
80
|
+
*/
|
|
81
|
+
export interface LightmapModel {
|
|
82
|
+
readonly kind: 'lightmap';
|
|
83
|
+
/** Where this material's surfaces are on the page: `uv2 * scale + bias`. See `LightmapRegion`. */
|
|
84
|
+
readonly region: LightmapRegion;
|
|
85
|
+
}
|
|
75
86
|
/** Every model a material can name, by its `kind`: see `SurfaceMaterial.model`. */
|
|
76
|
-
export type SurfaceModel = AnisotropicModel | HairModel | SkinModel | EyeModel;
|
|
87
|
+
export type SurfaceModel = AnisotropicModel | HairModel | SkinModel | EyeModel | LightmapModel;
|
|
77
88
|
/** A model's name, which also picks its pipeline switch: `SURFACE_MODEL_SWITCH`. */
|
|
78
89
|
export type SurfaceModelKind = SurfaceModel['kind'];
|
|
79
90
|
/** Every kind, in the order the lit stage declares their switches. */
|
|
@@ -109,6 +120,14 @@ export declare function eyeModel(options?: {
|
|
|
109
120
|
axis?: Vec3;
|
|
110
121
|
joint?: number;
|
|
111
122
|
}): EyeModel;
|
|
123
|
+
/**
|
|
124
|
+
* A baked surface, reading its region of a lightmap page: the whole page where none is named. An
|
|
125
|
+
* instanced batch's regions compose with this one, each instance's applied first: see
|
|
126
|
+
* `MeshInstances.lightmapRegions`.
|
|
127
|
+
*/
|
|
128
|
+
export declare function lightmapModel(options?: {
|
|
129
|
+
region?: LightmapRegion;
|
|
130
|
+
}): LightmapModel;
|
|
112
131
|
/**
|
|
113
132
|
* A model's numbers into `out` — eight floats, `uModelParams[0]` then `[1]` — and whether a model
|
|
114
133
|
* map is bound into the last. Zeros for the standard model. An eye's axis goes in with the
|
|
@@ -118,6 +137,7 @@ export declare function eyeModel(options?: {
|
|
|
118
137
|
* - hair: shift, scatter, backlit, − · −, −, −, map
|
|
119
138
|
* - skin: the scatter colour, radius · transmission, profile, −, map
|
|
120
139
|
* - eye: iris radius, iris depth, ior, cornea roughness · the axis, map
|
|
140
|
+
* - lightmap: the region's scale and bias · −, −, −, map
|
|
121
141
|
*/
|
|
122
142
|
export declare function packModel(model: SurfaceModel | null, mapped: boolean, out: Float32Array): void;
|
|
123
143
|
/**
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { lightmapRegionOf } from './lightmap.js';
|
|
1
2
|
/** Every kind, in the order the lit stage declares their switches. */
|
|
2
3
|
export const SURFACE_MODEL_KINDS = [
|
|
3
4
|
'anisotropic',
|
|
4
5
|
'hair',
|
|
5
6
|
'skin',
|
|
6
7
|
'eye',
|
|
8
|
+
'lightmap',
|
|
7
9
|
];
|
|
8
10
|
/** The lit stage's switch for each kind. */
|
|
9
11
|
export const SURFACE_MODEL_SWITCH = {
|
|
@@ -11,6 +13,7 @@ export const SURFACE_MODEL_SWITCH = {
|
|
|
11
13
|
hair: 'MODEL_HAIR',
|
|
12
14
|
skin: 'MODEL_SKIN',
|
|
13
15
|
eye: 'MODEL_EYE',
|
|
16
|
+
lightmap: 'MODEL_LIGHTMAP',
|
|
14
17
|
};
|
|
15
18
|
/** Floats a material's model takes: `uModelParams[2]`. */
|
|
16
19
|
export const MODEL_PARAM_FLOATS = 8;
|
|
@@ -75,6 +78,18 @@ export function eyeModel(options = {}) {
|
|
|
75
78
|
joint,
|
|
76
79
|
});
|
|
77
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* A baked surface, reading its region of a lightmap page: the whole page where none is named. An
|
|
83
|
+
* instanced batch's regions compose with this one, each instance's applied first: see
|
|
84
|
+
* `MeshInstances.lightmapRegions`.
|
|
85
|
+
*/
|
|
86
|
+
export function lightmapModel(options = {}) {
|
|
87
|
+
const region = lightmapRegionOf(options.region);
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
kind: 'lightmap',
|
|
90
|
+
region: Object.freeze([region[0], region[1], region[2], region[3]]),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
78
93
|
/**
|
|
79
94
|
* A model's numbers into `out` — eight floats, `uModelParams[0]` then `[1]` — and whether a model
|
|
80
95
|
* map is bound into the last. Zeros for the standard model. An eye's axis goes in with the
|
|
@@ -84,6 +99,7 @@ export function eyeModel(options = {}) {
|
|
|
84
99
|
* - hair: shift, scatter, backlit, − · −, −, −, map
|
|
85
100
|
* - skin: the scatter colour, radius · transmission, profile, −, map
|
|
86
101
|
* - eye: iris radius, iris depth, ior, cornea roughness · the axis, map
|
|
102
|
+
* - lightmap: the region's scale and bias · −, −, −, map
|
|
87
103
|
*/
|
|
88
104
|
export function packModel(model, mapped, out) {
|
|
89
105
|
out.fill(0);
|
|
@@ -118,6 +134,12 @@ export function packModel(model, mapped, out) {
|
|
|
118
134
|
out[5] = model.axis[1];
|
|
119
135
|
out[6] = model.axis[2];
|
|
120
136
|
return;
|
|
137
|
+
case 'lightmap':
|
|
138
|
+
out[0] = model.region[0];
|
|
139
|
+
out[1] = model.region[1];
|
|
140
|
+
out[2] = model.region[2];
|
|
141
|
+
out[3] = model.region[3];
|
|
142
|
+
return;
|
|
121
143
|
}
|
|
122
144
|
}
|
|
123
145
|
/**
|
|
@@ -12,6 +12,7 @@ import type { SurfaceModel } from './surfaceModel.ts';
|
|
|
12
12
|
import type { CompressedTextureFormat, SurfaceSource } from './compressedSource.ts';
|
|
13
13
|
import type { SurfaceLayerEffect } from './surfaceEffects.ts';
|
|
14
14
|
import type { CutoutMode } from './cutoutDither.ts';
|
|
15
|
+
import type { LightmapTexels } from './lightmap.ts';
|
|
15
16
|
/**
|
|
16
17
|
* How a texture behaves past its edges and between its texels.
|
|
17
18
|
*
|
|
@@ -100,6 +101,18 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
|
|
|
100
101
|
* without culling, and a back face is lit as its front, with the normal turned to the viewer.
|
|
101
102
|
*/
|
|
102
103
|
doubleSided?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* The lighting channels this surface takes light from, a mask: 1, 2, 3 … 255. **1 by default**,
|
|
106
|
+
* which is every light's default too, so a scene that names no channel is lit as it always was.
|
|
107
|
+
*
|
|
108
|
+
* A point or spot light shades this surface only where its `PointLightSet.lightChannels` and this
|
|
109
|
+
* share a bit — Unreal's lighting channels. A character's own key and rim lights on channel 2,
|
|
110
|
+
* and the character's materials on 3, light the character and leave the floor around it as the
|
|
111
|
+
* stage's lights alone have it. **What it does not reach**: the sun, the sky and the probes, area
|
|
112
|
+
* lights, DriftLight's volume and the GPU-driven pipeline light every surface whatever its mask.
|
|
113
|
+
* A number that is not a whole mask from 1 to 255 is channel 1.
|
|
114
|
+
*/
|
|
115
|
+
lightChannels?: number;
|
|
103
116
|
/**
|
|
104
117
|
* How hard the normal map turns the shading normal.
|
|
105
118
|
*
|
|
@@ -249,7 +262,7 @@ export declare class SurfaceTexture {
|
|
|
249
262
|
* Uploaded unflipped either way; see the constructor body for why. Or blocks, uploaded as they are
|
|
250
263
|
* where `compressed` — the formats this context samples — has theirs; see `compressedSource.ts`.
|
|
251
264
|
*/
|
|
252
|
-
constructor(gl: WebGL2RenderingContext, source: SurfaceSource | readonly SurfaceSource[], options?: SurfaceTextureOptions, compressed?: readonly CompressedTextureFormat[]);
|
|
265
|
+
constructor(gl: WebGL2RenderingContext, source: SurfaceSource | readonly SurfaceSource[] | LightmapTexels, options?: SurfaceTextureOptions, compressed?: readonly CompressedTextureFormat[]);
|
|
253
266
|
/** Whether this texture carries an effects table, which the lit program reads only once one exists. */
|
|
254
267
|
get hasEffects(): boolean;
|
|
255
268
|
bindEffects(gl: WebGL2RenderingContext, unit: number, fallback: WebGLTexture): void;
|