@zephyr3d/scene 0.9.24 → 0.9.26

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.
Files changed (264) hide show
  1. package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
  2. package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
  3. package/dist/animation/joint_dynamics/controller.js +7 -5
  4. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  5. package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
  6. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  7. package/dist/animation/morphtrack.js +3 -4
  8. package/dist/animation/morphtrack.js.map +1 -1
  9. package/dist/animation/skeleton.js +58 -24
  10. package/dist/animation/skeleton.js.map +1 -1
  11. package/dist/animation/spring/multi_chain_spring_system.js +577 -179
  12. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  13. package/dist/animation/spring/spring_collider.js +4 -3
  14. package/dist/animation/spring/spring_collider.js.map +1 -1
  15. package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
  16. package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
  17. package/dist/animation/spring/spring_particle.js +2 -0
  18. package/dist/animation/spring/spring_particle.js.map +1 -1
  19. package/dist/animation/spring/spring_system.js +11 -3
  20. package/dist/animation/spring/spring_system.js.map +1 -1
  21. package/dist/animation/spring_modifier.js +10 -0
  22. package/dist/animation/spring_modifier.js.map +1 -1
  23. package/dist/app/engine.js +53 -43
  24. package/dist/app/engine.js.map +1 -1
  25. package/dist/app/scriptingsystem.js +119 -6
  26. package/dist/app/scriptingsystem.js.map +1 -1
  27. package/dist/app/scriptregistry.js +3 -2
  28. package/dist/app/scriptregistry.js.map +1 -1
  29. package/dist/asset/assetmanager.js +284 -67
  30. package/dist/asset/assetmanager.js.map +1 -1
  31. package/dist/asset/loaders/zhair/zhair_format.js +288 -0
  32. package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
  33. package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
  34. package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
  35. package/dist/asset/model.js +178 -67
  36. package/dist/asset/model.js.map +1 -1
  37. package/dist/camera/camera.js +193 -21
  38. package/dist/camera/camera.js.map +1 -1
  39. package/dist/camera/perspectivecamera.js +3 -1
  40. package/dist/camera/perspectivecamera.js.map +1 -1
  41. package/dist/index.d.ts +4040 -1290
  42. package/dist/index.js +16 -6
  43. package/dist/index.js.map +1 -1
  44. package/dist/material/eye.js +932 -0
  45. package/dist/material/eye.js.map +1 -0
  46. package/dist/material/hair.js +917 -0
  47. package/dist/material/hair.js.map +1 -0
  48. package/dist/material/hairstrand.js +842 -0
  49. package/dist/material/hairstrand.js.map +1 -0
  50. package/dist/material/hairstrand_data.js +162 -0
  51. package/dist/material/hairstrand_data.js.map +1 -0
  52. package/dist/material/lambert.js +5 -2
  53. package/dist/material/lambert.js.map +1 -1
  54. package/dist/material/material.js +338 -300
  55. package/dist/material/material.js.map +1 -1
  56. package/dist/material/meshmaterial.js +120 -19
  57. package/dist/material/meshmaterial.js.map +1 -1
  58. package/dist/material/mixins/albedocolor.js +1 -1
  59. package/dist/material/mixins/albedocolor.js.map +1 -1
  60. package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
  61. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  62. package/dist/material/mixins/lightmodel/lambert.js +5 -4
  63. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  64. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
  65. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  66. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
  67. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  68. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
  69. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  70. package/dist/material/mixins/lit.js +73 -18
  71. package/dist/material/mixins/lit.js.map +1 -1
  72. package/dist/material/mixins/pbr/common.js +4 -4
  73. package/dist/material/mixins/pbr/common.js.map +1 -1
  74. package/dist/material/mixins/texture.js +48 -11
  75. package/dist/material/mixins/texture.js.map +1 -1
  76. package/dist/material/mtoon.js +6 -5
  77. package/dist/material/mtoon.js.map +1 -1
  78. package/dist/material/pbrblueprint.js +146 -28
  79. package/dist/material/pbrblueprint.js.map +1 -1
  80. package/dist/material/pbrblueprint_instance.js +501 -0
  81. package/dist/material/pbrblueprint_instance.js.map +1 -0
  82. package/dist/material/pbrmr.js +15 -16
  83. package/dist/material/pbrmr.js.map +1 -1
  84. package/dist/material/shader/helper.js +766 -456
  85. package/dist/material/shader/helper.js.map +1 -1
  86. package/dist/material/shader/terrain_blend.js +55 -0
  87. package/dist/material/shader/terrain_blend.js.map +1 -0
  88. package/dist/material/skin.js +192 -32
  89. package/dist/material/skin.js.map +1 -1
  90. package/dist/material/spriteblueprint.js +16 -1
  91. package/dist/material/spriteblueprint.js.map +1 -1
  92. package/dist/material/subsurfaceprofile.js +86 -23
  93. package/dist/material/subsurfaceprofile.js.map +1 -1
  94. package/dist/material/water.js +3 -1
  95. package/dist/material/water.js.map +1 -1
  96. package/dist/posteffect/bloom.js +167 -7
  97. package/dist/posteffect/bloom.js.map +1 -1
  98. package/dist/posteffect/coloradjust.js +2 -1
  99. package/dist/posteffect/coloradjust.js.map +1 -1
  100. package/dist/posteffect/compositor.js.map +1 -1
  101. package/dist/posteffect/fxaa.js +2 -1
  102. package/dist/posteffect/fxaa.js.map +1 -1
  103. package/dist/posteffect/grayscale.js +3 -1
  104. package/dist/posteffect/grayscale.js.map +1 -1
  105. package/dist/posteffect/motionblur.js +4 -1
  106. package/dist/posteffect/motionblur.js.map +1 -1
  107. package/dist/posteffect/posteffect.js.map +1 -1
  108. package/dist/posteffect/skinsss.js +368 -69
  109. package/dist/posteffect/skinsss.js.map +1 -1
  110. package/dist/posteffect/ssgi.js +16 -9
  111. package/dist/posteffect/ssgi.js.map +1 -1
  112. package/dist/posteffect/ssr.js +3 -0
  113. package/dist/posteffect/ssr.js.map +1 -1
  114. package/dist/posteffect/sss.js +91 -34
  115. package/dist/posteffect/sss.js.map +1 -1
  116. package/dist/posteffect/taa.js.map +1 -1
  117. package/dist/posteffect/tonemap.js +44 -1
  118. package/dist/posteffect/tonemap.js.map +1 -1
  119. package/dist/render/abuffer_oit.js +45 -39
  120. package/dist/render/abuffer_oit.js.map +1 -1
  121. package/dist/render/cluster_light.js +4 -4
  122. package/dist/render/cluster_light.js.map +1 -1
  123. package/dist/render/cull_visitor.js +19 -0
  124. package/dist/render/cull_visitor.js.map +1 -1
  125. package/dist/render/depthpass.js +36 -2
  126. package/dist/render/depthpass.js.map +1 -1
  127. package/dist/render/drawable_mixin.js +26 -1
  128. package/dist/render/drawable_mixin.js.map +1 -1
  129. package/dist/render/dualdepthpeeling_oit.js +52 -51
  130. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  131. package/dist/render/envlight.js +1 -1
  132. package/dist/render/envlight.js.map +1 -1
  133. package/dist/render/fbm_wavegenerator.js +3 -0
  134. package/dist/render/fbm_wavegenerator.js.map +1 -1
  135. package/dist/render/gerstner_wavegenerator.js +3 -0
  136. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  137. package/dist/render/globalbindgroup_allocator.js +3 -0
  138. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  139. package/dist/render/oit.js +29 -0
  140. package/dist/render/oit.js.map +1 -0
  141. package/dist/render/render_queue.js +3 -0
  142. package/dist/render/render_queue.js.map +1 -1
  143. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  144. package/dist/render/rendergraph/executor.js +2 -0
  145. package/dist/render/rendergraph/executor.js.map +1 -1
  146. package/dist/render/rendergraph/forward_plus_builder.js +61 -1
  147. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  148. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  149. package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
  150. package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
  151. package/dist/render/rendergraph/types.js.map +1 -1
  152. package/dist/render/shadow_mask_pass.js +73 -8
  153. package/dist/render/shadow_mask_pass.js.map +1 -1
  154. package/dist/render/sky.js +166 -126
  155. package/dist/render/sky.js.map +1 -1
  156. package/dist/render/weightedblended_oit.js +36 -35
  157. package/dist/render/weightedblended_oit.js.map +1 -1
  158. package/dist/scene/basesprite.js +8 -0
  159. package/dist/scene/basesprite.js.map +1 -1
  160. package/dist/scene/batchgroup.js +3 -0
  161. package/dist/scene/batchgroup.js.map +1 -1
  162. package/dist/scene/environment.js +4 -1
  163. package/dist/scene/environment.js.map +1 -1
  164. package/dist/scene/hair.js +963 -0
  165. package/dist/scene/hair.js.map +1 -0
  166. package/dist/scene/light.js +5 -1
  167. package/dist/scene/light.js.map +1 -1
  168. package/dist/scene/mesh.js +481 -47
  169. package/dist/scene/mesh.js.map +1 -1
  170. package/dist/scene/meshdrawable.js +6 -0
  171. package/dist/scene/meshdrawable.js.map +1 -1
  172. package/dist/scene/msdftext.js +3 -0
  173. package/dist/scene/msdftext.js.map +1 -1
  174. package/dist/scene/msdftextsprite.js +3 -0
  175. package/dist/scene/msdftextsprite.js.map +1 -1
  176. package/dist/scene/particlesys.js +6 -0
  177. package/dist/scene/particlesys.js.map +1 -1
  178. package/dist/scene/scene.js +3 -0
  179. package/dist/scene/scene.js.map +1 -1
  180. package/dist/scene/scene_node.js +6 -1
  181. package/dist/scene/scene_node.js.map +1 -1
  182. package/dist/scene/terrain-cm/grass.js +273 -249
  183. package/dist/scene/terrain-cm/grass.js.map +1 -1
  184. package/dist/scene/terrain-cm/grassmaterial.js +3 -0
  185. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  186. package/dist/scene/terrain-cm/terrain-cm.js +6 -0
  187. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  188. package/dist/scene/terrain-cm/texture_cache.js +309 -0
  189. package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
  190. package/dist/scene/water.js +6 -0
  191. package/dist/scene/water.js.map +1 -1
  192. package/dist/shaders/atmosphere.js.map +1 -1
  193. package/dist/shaders/diffusion.js +54 -0
  194. package/dist/shaders/diffusion.js.map +1 -0
  195. package/dist/shaders/shadow.js +33 -21
  196. package/dist/shaders/shadow.js.map +1 -1
  197. package/dist/shaders/temporal.js +71 -33
  198. package/dist/shaders/temporal.js.map +1 -1
  199. package/dist/shadow/dom.js +546 -0
  200. package/dist/shadow/dom.js.map +1 -0
  201. package/dist/shadow/pcf_opt.js +3 -5
  202. package/dist/shadow/pcf_opt.js.map +1 -1
  203. package/dist/shadow/pcf_pd.js +1 -1
  204. package/dist/shadow/pcf_pd.js.map +1 -1
  205. package/dist/shadow/receiver_bias.js +41 -0
  206. package/dist/shadow/receiver_bias.js.map +1 -0
  207. package/dist/shadow/shader.js +20 -60
  208. package/dist/shadow/shader.js.map +1 -1
  209. package/dist/shadow/shadow_impl.js +89 -0
  210. package/dist/shadow/shadow_impl.js.map +1 -1
  211. package/dist/shadow/shadow_region.js +50 -50
  212. package/dist/shadow/shadow_region.js.map +1 -1
  213. package/dist/shadow/shadowmapper.js +150 -37
  214. package/dist/shadow/shadowmapper.js.map +1 -1
  215. package/dist/shapes/shape.js +11 -2
  216. package/dist/shapes/shape.js.map +1 -1
  217. package/dist/shapes/sphere.js +50 -9
  218. package/dist/shapes/sphere.js.map +1 -1
  219. package/dist/utility/blueprint/common/constants.js +112 -2
  220. package/dist/utility/blueprint/common/constants.js.map +1 -1
  221. package/dist/utility/blueprint/material/func.js +142 -49
  222. package/dist/utility/blueprint/material/func.js.map +1 -1
  223. package/dist/utility/blueprint/material/ir.js +490 -25
  224. package/dist/utility/blueprint/material/ir.js.map +1 -1
  225. package/dist/utility/blueprint/material/texture.js +898 -4
  226. package/dist/utility/blueprint/material/texture.js.map +1 -1
  227. package/dist/utility/serialization/manager.js +431 -100
  228. package/dist/utility/serialization/manager.js.map +1 -1
  229. package/dist/utility/serialization/scene/animation.js +370 -3
  230. package/dist/utility/serialization/scene/animation.js.map +1 -1
  231. package/dist/utility/serialization/scene/batch.js +4 -0
  232. package/dist/utility/serialization/scene/batch.js.map +1 -1
  233. package/dist/utility/serialization/scene/camera.js +133 -1
  234. package/dist/utility/serialization/scene/camera.js.map +1 -1
  235. package/dist/utility/serialization/scene/common.js +26 -5
  236. package/dist/utility/serialization/scene/common.js.map +1 -1
  237. package/dist/utility/serialization/scene/hair.js +1141 -0
  238. package/dist/utility/serialization/scene/hair.js.map +1 -0
  239. package/dist/utility/serialization/scene/light.js +84 -4
  240. package/dist/utility/serialization/scene/light.js.map +1 -1
  241. package/dist/utility/serialization/scene/material.js +1867 -166
  242. package/dist/utility/serialization/scene/material.js.map +1 -1
  243. package/dist/utility/serialization/scene/mesh.js +231 -14
  244. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  245. package/dist/utility/serialization/scene/node.js +141 -7
  246. package/dist/utility/serialization/scene/node.js.map +1 -1
  247. package/dist/utility/serialization/scene/particle.js +4 -0
  248. package/dist/utility/serialization/scene/particle.js.map +1 -1
  249. package/dist/utility/serialization/scene/primitive.js +18 -0
  250. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  251. package/dist/utility/serialization/scene/sprite.js +4 -0
  252. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  253. package/dist/utility/serialization/scene/terrain.js +92 -69
  254. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  255. package/dist/utility/serialization/scene/text.js +4 -0
  256. package/dist/utility/serialization/scene/text.js.map +1 -1
  257. package/dist/utility/serialization/scene/water.js +4 -0
  258. package/dist/utility/serialization/scene/water.js.map +1 -1
  259. package/dist/utility/serialization/types.js.map +1 -1
  260. package/dist/utility/shprojector.js +96 -95
  261. package/dist/utility/shprojector.js.map +1 -1
  262. package/dist/values.js +105 -58
  263. package/dist/values.js.map +1 -1
  264. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  import { PBShaderExp } from '@zephyr3d/device';
2
2
  import { ConstantScalarNode, ConstantVec2Node, ConstantVec3Node, ConstantVec4Node, ConstantBooleanNode, ConstantBVec2Node, ConstantBVec3Node, ConstantBVec4Node } from '../common/constants.js';
3
3
  import { ASSERT } from '@zephyr3d/base';
4
- import { BaseTextureNode, TextureSampleNode, PannerNode } from './texture.js';
4
+ import { BaseTextureNode, TextureSampleNode, ChannelSDFMaskNode, MorphBlurNode, PannerNode, TexturePropertyNode } from './texture.js';
5
5
  import { MakeVectorNode, SwizzleNode, CompComparisonNode, AnyConditionNode, AllConditionNode, SelectionNode, Hash1Node, Hash2Node, Hash3Node, SimplexNoise2DNode, PerlinNoise2DNode, GenericMathNode, TransformNode, RotateAboutAxisNode } from '../common/math.js';
6
6
  import { ResolveVertexPositionNode, ResolveVertexNormalNode, ResolveVertexTangentNode, VertexOutputNode, VertexIndexNode, InstanceIndexNode, VertexColorNode, VertexUVNode, VertexPositionNode, PixelWorldPositionNode, VertexNormalNode, PixelNormalNode, VertexTangentNode, VertexBinormalNode, ViewMatrixNode, ProjectionMatrixNode, ViewProjMatrixNode, InvProjMatrixNode, InvViewProjMatrixNode, BillboardMatrixNode, CameraPositionNode, CameraVectorNode, CameraNearFarNode, SkyEnvTextureNode, ElapsedTimeNode } from './inputs.js';
7
7
  import { ShaderHelper } from '../../../material/shader/helper.js';
@@ -100,15 +100,19 @@ import { VertexBlockNode } from './pbr.js';
100
100
  */ class IRConstantf extends IRExpression {
101
101
  /** The constant float value */ value;
102
102
  /** The uniform parameter name, or empty string for literals */ name;
103
+ /** Optional lower bound for editor-facing scalar parameters */ minValue;
104
+ /** Optional upper bound for editor-facing scalar parameters */ maxValue;
103
105
  /**
104
106
  * Creates a constant float expression
105
107
  *
106
108
  * @param value - The float value
107
109
  * @param paramName - The uniform parameter name, or empty string for literals
108
- */ constructor(value, paramName){
110
+ */ constructor(value, paramName, minValue, maxValue){
109
111
  super();
110
112
  this.value = value;
111
113
  this.name = paramName;
114
+ this.minValue = minValue;
115
+ this.maxValue = maxValue;
112
116
  }
113
117
  /**
114
118
  * Generates shader code for this constant
@@ -139,7 +143,13 @@ import { VertexBlockNode } from './pbr.js';
139
143
  value: [
140
144
  this.value
141
145
  ],
142
- type: 'float'
146
+ type: 'float',
147
+ ...this.minValue !== undefined ? {
148
+ minValue: this.minValue
149
+ } : {},
150
+ ...this.maxValue !== undefined ? {
151
+ maxValue: this.maxValue
152
+ } : {}
143
153
  } : null;
144
154
  }
145
155
  }
@@ -418,6 +428,7 @@ import { VertexBlockNode } from './pbr.js';
418
428
  /** The function call node from the graph */ node;
419
429
  /** Array of argument expressions */ args;
420
430
  /** Cached temporary variable name if result is referenced multiple times */ tmpName;
431
+ textureArgSamplerNames;
421
432
  /**
422
433
  * Creates a function call expression
423
434
  *
@@ -428,9 +439,58 @@ import { VertexBlockNode } from './pbr.js';
428
439
  this.node = node;
429
440
  this.args = args;
430
441
  this.tmpName = '';
442
+ this.textureArgSamplerNames = new Map();
431
443
  }
432
444
  /** Reset for next creation */ reset() {
433
445
  this.tmpName = '';
446
+ this.textureArgSamplerNames.clear();
447
+ }
448
+ getTextureSamplerVarName(argName) {
449
+ let name = this.textureArgSamplerNames.get(argName);
450
+ if (!name) {
451
+ name = `${argName}__sampler`;
452
+ this.textureArgSamplerNames.set(argName, name);
453
+ }
454
+ return name;
455
+ }
456
+ buildTextureFunctionParams(pb) {
457
+ const params = [];
458
+ for (const arg of this.node.args){
459
+ // @ts-ignore
460
+ params.push(pb[arg.type](arg.name));
461
+ if (arg.type === 'tex2D' && pb.getDevice().type === 'webgpu') {
462
+ params.push(pb.sampler(this.getTextureSamplerVarName(arg.name)));
463
+ }
464
+ }
465
+ return params;
466
+ }
467
+ buildTextureFunctionCallArgs(pb) {
468
+ const args = [];
469
+ for(let i = 0; i < this.node.args.length; i++){
470
+ const argDef = this.node.args[i];
471
+ const argExp = this.args[i].create(pb);
472
+ args.push(argExp);
473
+ if (argDef.type === 'tex2D' && pb.getDevice().type === 'webgpu') {
474
+ if (!(argExp instanceof PBShaderExp)) {
475
+ throw new Error(`Material function '${this.node.name}' expects texture argument '${argDef.name}'`);
476
+ }
477
+ let sampler = pb.getDefaultSampler(argExp, false);
478
+ if (!sampler && argExp.$str) {
479
+ sampler = pb.getCurrentScope()[this.getTextureSamplerVarName(argExp.$str)];
480
+ }
481
+ if (!sampler) {
482
+ throw new Error(`Material function '${this.node.name}' cannot resolve sampler for texture argument '${argDef.name}'`);
483
+ }
484
+ args.push(sampler);
485
+ }
486
+ }
487
+ return args;
488
+ }
489
+ isSingleTextureOutput() {
490
+ return this.node.outs.length === 1 && this.node.outs[0].type === 'tex2D';
491
+ }
492
+ hasTextureOutput() {
493
+ return this.node.outs.some((output)=>output.type === 'tex2D');
434
494
  }
435
495
  /**
436
496
  * Generates shader code for the function definition and call
@@ -446,21 +506,34 @@ import { VertexBlockNode } from './pbr.js';
446
506
  if (this.tmpName) {
447
507
  return pb.getCurrentScope()[this.tmpName];
448
508
  }
509
+ if (this.hasTextureOutput() && !this.isSingleTextureOutput()) {
510
+ throw new Error(`Material function '${this.node.name}' only supports tex2D as a single standalone output`);
511
+ }
449
512
  const that = this;
450
513
  const ir = this.node.IR;
451
- // @ts-ignore
452
- const params = this.node.args.map((v)=>pb[v.type](v.name));
514
+ const params = this.buildTextureFunctionParams(pb);
453
515
  pb.func(this.node.name, params, function() {
454
516
  const outputs = ir.create(pb);
455
- const rettype = pb.defineStruct(that.node.outputs.map((output, index)=>{
456
- // @ts-ignore
457
- return pb[that.node.outs[index].type](output.swizzle);
458
- }));
459
- this.$return(rettype(...outputs.map((output)=>{
460
- return output.exp;
461
- })));
517
+ const orderedOutputs = that.node.outs.map((output)=>{
518
+ const index = ir.DAG.roots.indexOf(output.index);
519
+ if (index < 0 || !outputs[index]) {
520
+ throw new Error(`Material function '${that.node.name}' cannot resolve output '${output.name}'`);
521
+ }
522
+ return outputs[index];
523
+ });
524
+ if (that.isSingleTextureOutput()) {
525
+ this.$return(orderedOutputs[0].exp);
526
+ } else {
527
+ const rettype = pb.defineStruct(that.node.outputs.map((output, index)=>{
528
+ // @ts-ignore
529
+ return pb[that.node.outs[index].type](output.swizzle);
530
+ }));
531
+ this.$return(rettype(...orderedOutputs.map((output)=>{
532
+ return output.exp;
533
+ })));
534
+ }
462
535
  });
463
- const args = this.args.map((arg)=>arg.create(pb));
536
+ const args = this.buildTextureFunctionCallArgs(pb);
464
537
  const exp = pb.getGlobalScope()[this.node.name](...args);
465
538
  if (this._ref === 1) {
466
539
  return exp;
@@ -779,7 +852,21 @@ class IRComparison extends IRExpression {
779
852
  } else {
780
853
  throw new Error('Invalid texture coordinate');
781
854
  }
782
- let exp = pb.textureSample(texExp, coordExp);
855
+ let exp;
856
+ if (pb.getDevice().type === 'webgpu') {
857
+ let sampler = pb.getDefaultSampler(texExp, false);
858
+ if (!sampler && texExp.$str) {
859
+ sampler = pb.getCurrentScope()[`${texExp.$str}__sampler`];
860
+ }
861
+ if (sampler) {
862
+ // @ts-ignore ProgramBuilder runtime supports the explicit WebGPU sampler overload.
863
+ exp = pb.textureSample(texExp, sampler, coordExp);
864
+ } else {
865
+ exp = pb.textureSample(texExp, coordExp);
866
+ }
867
+ } else {
868
+ exp = pb.textureSample(texExp, coordExp);
869
+ }
783
870
  if (this.samplerType === 'Normal') {
784
871
  exp = pb.sub(pb.mul(exp, pb.vec4(2, 2, 2, 1)), pb.vec4(1, 1, 1, 0));
785
872
  }
@@ -792,6 +879,284 @@ class IRComparison extends IRExpression {
792
879
  }
793
880
  }
794
881
  }
882
+ class IRMorphBlur extends IRExpression {
883
+ tex;
884
+ coord;
885
+ morphRadius;
886
+ blurRadius;
887
+ channelMask;
888
+ tmpName;
889
+ constructor(tex, coord, morphRadius, blurRadius, channelMask){
890
+ super();
891
+ this.tex = tex.addRef();
892
+ this.coord = coord.addRef();
893
+ this.morphRadius = morphRadius instanceof IRExpression ? morphRadius.addRef() : morphRadius;
894
+ this.blurRadius = blurRadius instanceof IRExpression ? blurRadius.addRef() : blurRadius;
895
+ this.channelMask = channelMask instanceof IRExpression ? channelMask.addRef() : channelMask.slice();
896
+ this.tmpName = '';
897
+ }
898
+ reset() {
899
+ this.tmpName = '';
900
+ }
901
+ create(pb) {
902
+ if (this.tmpName) {
903
+ return pb.getCurrentScope()[this.tmpName];
904
+ }
905
+ const texExp = this.tex.create(pb);
906
+ const coordExp = this.coord.create(pb);
907
+ const morphExp = this.morphRadius instanceof IRExpression ? this.morphRadius.create(pb) : this.morphRadius;
908
+ const blurExp = this.blurRadius instanceof IRExpression ? this.blurRadius.create(pb) : this.blurRadius;
909
+ const maskExpRaw = this.channelMask instanceof IRExpression ? this.channelMask.create(pb) : pb.vec4(this.channelMask[0] ?? 0, this.channelMask[1] ?? 0, this.channelMask[2] ?? 0, this.channelMask[3] ?? 0);
910
+ if (!(texExp instanceof PBShaderExp) || !(coordExp instanceof PBShaderExp) || !(maskExpRaw instanceof PBShaderExp)) {
911
+ throw new Error('Invalid texture sampling input');
912
+ }
913
+ const maskExp = maskExpRaw;
914
+ const texVarName = texExp.$str || 'Texture';
915
+ const funcName = `Z_MorphBlur_${texVarName.replace(/[^A-Za-z0-9_]/g, '_')}`;
916
+ const sampleMaskedName = `${funcName}_sampleMasked`;
917
+ const morphValueName = `${funcName}_morphValue`;
918
+ const blurBaseName = `${funcName}_blurBase`;
919
+ const texelSize = pb.div(pb.vec2(1), pb.vec2(pb.textureDimensions(texExp, 0)));
920
+ pb.func(sampleMaskedName, [
921
+ pb.tex2D('texture'),
922
+ ...pb.getDevice().type === 'webgpu' ? [
923
+ pb.sampler('texture__sampler')
924
+ ] : [],
925
+ pb.vec2('uv'),
926
+ pb.vec4('channelMask')
927
+ ], function() {
928
+ this.$l.sampled = pb.getDevice().type === 'webgpu' ? pb.textureSample(this.texture, this.texture__sampler, this.uv) : pb.textureSample(this.texture, this.uv);
929
+ this.$return(pb.dot(this.sampled, this.channelMask));
930
+ });
931
+ pb.func(morphValueName, [
932
+ pb.tex2D('texture'),
933
+ ...pb.getDevice().type === 'webgpu' ? [
934
+ pb.sampler('texture__sampler')
935
+ ] : [],
936
+ pb.vec2('uv'),
937
+ pb.vec2('texelSize'),
938
+ pb.float('morphRadius'),
939
+ pb.vec4('channelMask')
940
+ ], function() {
941
+ this.$l.morphStep = pb.abs(this.morphRadius);
942
+ this.$l.morphOffsets = pb.vec2[5]();
943
+ this.morphOffsets.setAt(0, pb.vec2(0, 0));
944
+ this.morphOffsets.setAt(1, pb.vec2(this.morphStep, 0));
945
+ this.morphOffsets.setAt(2, pb.vec2(pb.neg(this.morphStep), 0));
946
+ this.morphOffsets.setAt(3, pb.vec2(0, this.morphStep));
947
+ this.morphOffsets.setAt(4, pb.vec2(0, pb.neg(this.morphStep)));
948
+ this.$if(pb.greaterThan(this.morphRadius, 0), function() {
949
+ this.$l.dilateValue = pb.float(0);
950
+ this.$for(pb.int('morphIndex'), 0, 5, function() {
951
+ this.$l.sampleUV = pb.add(this.uv, pb.mul(this.morphOffsets.at(this.morphIndex), this.texelSize));
952
+ this.$l.sampleValue = pb.getDevice().type === 'webgpu' ? this[sampleMaskedName](this.texture, this.texture__sampler, this.sampleUV, this.channelMask) : this[sampleMaskedName](this.texture, this.sampleUV, this.channelMask);
953
+ this.$if(pb.equal(this.morphIndex, 0), function() {
954
+ this.dilateValue = this.sampleValue;
955
+ }).$else(function() {
956
+ this.dilateValue = pb.max(this.dilateValue, this.sampleValue);
957
+ });
958
+ });
959
+ this.$return(this.dilateValue);
960
+ }).$elseif(pb.lessThan(this.morphRadius, 0), function() {
961
+ this.$l.erodeValue = pb.float(0);
962
+ this.$for(pb.int('morphIndex'), 0, 5, function() {
963
+ this.$l.sampleUV = pb.add(this.uv, pb.mul(this.morphOffsets.at(this.morphIndex), this.texelSize));
964
+ this.$l.sampleValue = pb.getDevice().type === 'webgpu' ? this[sampleMaskedName](this.texture, this.texture__sampler, this.sampleUV, this.channelMask) : this[sampleMaskedName](this.texture, this.sampleUV, this.channelMask);
965
+ this.$if(pb.equal(this.morphIndex, 0), function() {
966
+ this.erodeValue = this.sampleValue;
967
+ }).$else(function() {
968
+ this.erodeValue = pb.min(this.erodeValue, this.sampleValue);
969
+ });
970
+ });
971
+ this.$return(this.erodeValue);
972
+ }).$else(function() {
973
+ this.$return(pb.getDevice().type === 'webgpu' ? this[sampleMaskedName](this.texture, this.texture__sampler, this.uv, this.channelMask) : this[sampleMaskedName](this.texture, this.uv, this.channelMask));
974
+ });
975
+ });
976
+ pb.func(blurBaseName, [
977
+ pb.tex2D('texture'),
978
+ ...pb.getDevice().type === 'webgpu' ? [
979
+ pb.sampler('texture__sampler')
980
+ ] : [],
981
+ pb.vec2('uv'),
982
+ pb.vec2('texelSize'),
983
+ pb.float('blurRadius'),
984
+ pb.vec4('channelMask'),
985
+ pb.float('morphRadius'),
986
+ pb.bool('useMorph')
987
+ ], function() {
988
+ this.$l.radius = pb.max(this.blurRadius, 0);
989
+ this.$l.sigma = pb.max(pb.mul(this.radius, 0.6), 0.85);
990
+ this.$l.invSigma2 = pb.div(0.5, pb.mul(this.sigma, this.sigma));
991
+ this.$l.kernelRadius = pb.int(2);
992
+ this.$if(pb.greaterThan(this.radius, 1.5), function() {
993
+ this.kernelRadius = pb.int(3);
994
+ });
995
+ this.$if(pb.greaterThan(this.radius, 3), function() {
996
+ this.kernelRadius = pb.int(4);
997
+ });
998
+ this.$if(pb.greaterThan(this.radius, 5), function() {
999
+ this.kernelRadius = pb.int(5);
1000
+ });
1001
+ this.$if(pb.greaterThan(this.radius, 7.5), function() {
1002
+ this.kernelRadius = pb.int(6);
1003
+ });
1004
+ this.$if(pb.greaterThan(this.radius, 9), function() {
1005
+ this.kernelRadius = pb.int(7);
1006
+ });
1007
+ this.$if(pb.greaterThan(this.radius, 10.5), function() {
1008
+ this.kernelRadius = pb.int(8);
1009
+ });
1010
+ this.$if(pb.greaterThan(this.radius, 12), function() {
1011
+ this.kernelRadius = pb.int(9);
1012
+ });
1013
+ this.$if(pb.greaterThan(this.radius, 13.5), function() {
1014
+ this.kernelRadius = pb.int(10);
1015
+ });
1016
+ this.$if(pb.greaterThan(this.radius, 15), function() {
1017
+ this.kernelRadius = pb.int(11);
1018
+ });
1019
+ this.$if(pb.greaterThan(this.radius, 16.5), function() {
1020
+ this.kernelRadius = pb.int(12);
1021
+ });
1022
+ this.$l.kernelRadiusF = pb.max(pb.float(this.kernelRadius), 1);
1023
+ this.$l.sampleStep = pb.max(pb.div(this.radius, this.kernelRadiusF), 1);
1024
+ this.$l.supportRadius = pb.max(this.radius, 1);
1025
+ this.$l.supportRadius2 = pb.mul(this.supportRadius, this.supportRadius);
1026
+ this.$l.sum = pb.float(0);
1027
+ this.$l.weightSum = pb.float(0);
1028
+ this.$for(pb.int('blurY'), 0, 25, function() {
1029
+ this.$l.offsetY = pb.sub(this.blurY, 12);
1030
+ this.$if(pb.lessThanEqual(pb.abs(this.offsetY), this.kernelRadius), function() {
1031
+ this.$for(pb.int('blurX'), 0, 25, function() {
1032
+ this.$l.offsetX = pb.sub(this.blurX, 12);
1033
+ this.$if(pb.lessThanEqual(pb.abs(this.offsetX), this.kernelRadius), function() {
1034
+ this.$l.sampleOffset = pb.mul(pb.vec2(pb.float(this.offsetX), pb.float(this.offsetY)), this.sampleStep);
1035
+ this.$l.sampleRadius2 = pb.dot(this.sampleOffset, this.sampleOffset);
1036
+ this.$if(pb.lessThanEqual(this.sampleRadius2, this.supportRadius2), function() {
1037
+ this.$l.sampleUV = pb.add(this.uv, pb.mul(this.sampleOffset, this.texelSize));
1038
+ this.$l.sampleValue = this.useMorph ? pb.getDevice().type === 'webgpu' ? this[morphValueName](this.texture, this.texture__sampler, this.sampleUV, this.texelSize, this.morphRadius, this.channelMask) : this[morphValueName](this.texture, this.sampleUV, this.texelSize, this.morphRadius, this.channelMask) : pb.getDevice().type === 'webgpu' ? this[sampleMaskedName](this.texture, this.texture__sampler, this.sampleUV, this.channelMask) : this[sampleMaskedName](this.texture, this.sampleUV, this.channelMask);
1039
+ this.$l.weight = pb.exp(pb.neg(pb.mul(this.sampleRadius2, this.invSigma2)));
1040
+ this.sum = pb.add(this.sum, pb.mul(this.sampleValue, this.weight));
1041
+ this.weightSum = pb.add(this.weightSum, this.weight);
1042
+ });
1043
+ });
1044
+ });
1045
+ });
1046
+ });
1047
+ this.$return(pb.div(this.sum, pb.max(this.weightSum, 1e-5)));
1048
+ });
1049
+ pb.func(funcName, [
1050
+ pb.tex2D('texture'),
1051
+ ...pb.getDevice().type === 'webgpu' ? [
1052
+ pb.sampler('texture__sampler')
1053
+ ] : [],
1054
+ pb.vec2('uv'),
1055
+ pb.vec2('texelSize'),
1056
+ pb.float('morphRadius'),
1057
+ pb.float('blurRadius'),
1058
+ pb.vec4('channelMask')
1059
+ ], function() {
1060
+ this.$l.morphAbs = pb.abs(this.morphRadius);
1061
+ this.$l.blurAbs = pb.abs(this.blurRadius);
1062
+ this.$if(pb.lessThanEqual(this.morphAbs, 1e-4), function() {
1063
+ this.$if(pb.lessThanEqual(this.blurAbs, 1e-4), function() {
1064
+ this.$return(pb.getDevice().type === 'webgpu' ? this[sampleMaskedName](this.texture, this.texture__sampler, this.uv, this.channelMask) : this[sampleMaskedName](this.texture, this.uv, this.channelMask));
1065
+ }).$else(function() {
1066
+ this.$return(pb.getDevice().type === 'webgpu' ? this[blurBaseName](this.texture, this.texture__sampler, this.uv, this.texelSize, this.blurRadius, this.channelMask, this.morphRadius, false) : this[blurBaseName](this.texture, this.uv, this.texelSize, this.blurRadius, this.channelMask, this.morphRadius, false));
1067
+ });
1068
+ }).$elseif(pb.lessThanEqual(this.blurAbs, 1e-4), function() {
1069
+ this.$return(pb.getDevice().type === 'webgpu' ? this[morphValueName](this.texture, this.texture__sampler, this.uv, this.texelSize, this.morphRadius, this.channelMask) : this[morphValueName](this.texture, this.uv, this.texelSize, this.morphRadius, this.channelMask));
1070
+ }).$else(function() {
1071
+ this.$return(pb.getDevice().type === 'webgpu' ? this[blurBaseName](this.texture, this.texture__sampler, this.uv, this.texelSize, this.blurRadius, this.channelMask, this.morphRadius, true) : this[blurBaseName](this.texture, this.uv, this.texelSize, this.blurRadius, this.channelMask, this.morphRadius, true));
1072
+ });
1073
+ });
1074
+ const callArgs = [
1075
+ texExp,
1076
+ ...pb.getDevice().type === 'webgpu' ? [
1077
+ pb.getDefaultSampler(texExp, false) ?? pb.getCurrentScope()[`${texExp.$str}__sampler`]
1078
+ ] : [],
1079
+ coordExp,
1080
+ texelSize,
1081
+ morphExp,
1082
+ blurExp,
1083
+ maskExp
1084
+ ];
1085
+ if (pb.getDevice().type === 'webgpu' && !(callArgs[1] instanceof PBShaderExp)) {
1086
+ throw new Error('Cannot resolve sampler for MorphBlur node');
1087
+ }
1088
+ const exp = pb.getGlobalScope()[funcName](...callArgs);
1089
+ if (this._ref === 1) {
1090
+ return exp;
1091
+ }
1092
+ this.tmpName = this.getTmpName(pb.getCurrentScope());
1093
+ pb.getCurrentScope()[this.tmpName] = exp;
1094
+ return pb.getCurrentScope()[this.tmpName];
1095
+ }
1096
+ }
1097
+ class IRChannelSDFMask extends IRExpression {
1098
+ tex;
1099
+ coord;
1100
+ sizeOffset;
1101
+ feather;
1102
+ spread;
1103
+ edgeCurve;
1104
+ channelMask;
1105
+ tmpName;
1106
+ constructor(tex, coord, sizeOffset, feather, spread, edgeCurve, channelMask){
1107
+ super();
1108
+ this.tex = tex.addRef();
1109
+ this.coord = coord.addRef();
1110
+ this.sizeOffset = sizeOffset instanceof IRExpression ? sizeOffset.addRef() : sizeOffset;
1111
+ this.feather = feather instanceof IRExpression ? feather.addRef() : feather;
1112
+ this.spread = spread instanceof IRExpression ? spread.addRef() : spread;
1113
+ this.edgeCurve = edgeCurve instanceof IRExpression ? edgeCurve.addRef() : edgeCurve;
1114
+ this.channelMask = channelMask instanceof IRExpression ? channelMask.addRef() : channelMask.slice();
1115
+ this.tmpName = '';
1116
+ }
1117
+ reset() {
1118
+ this.tmpName = '';
1119
+ }
1120
+ create(pb) {
1121
+ if (this.tmpName) {
1122
+ return pb.getCurrentScope()[this.tmpName];
1123
+ }
1124
+ const texExp = this.tex.create(pb);
1125
+ const coordExp = this.coord.create(pb);
1126
+ const sizeExp = this.sizeOffset instanceof IRExpression ? this.sizeOffset.create(pb) : this.sizeOffset;
1127
+ const featherExp = this.feather instanceof IRExpression ? this.feather.create(pb) : this.feather;
1128
+ const spreadExp = this.spread instanceof IRExpression ? this.spread.create(pb) : this.spread;
1129
+ const edgeCurveExp = this.edgeCurve instanceof IRExpression ? this.edgeCurve.create(pb) : this.edgeCurve;
1130
+ const maskExpRaw = this.channelMask instanceof IRExpression ? this.channelMask.create(pb) : pb.vec4(this.channelMask[0] ?? 0, this.channelMask[1] ?? 0, this.channelMask[2] ?? 0, this.channelMask[3] ?? 0);
1131
+ if (!(texExp instanceof PBShaderExp) || !(coordExp instanceof PBShaderExp) || !(maskExpRaw instanceof PBShaderExp)) {
1132
+ throw new Error('Invalid texture sampling input');
1133
+ }
1134
+ const maskExp = maskExpRaw;
1135
+ const sampled = pb.dot(pb.textureSample(texExp, coordExp), maskExp);
1136
+ const spreadSafe = pb.max(pb.abs(spreadExp), 1e-5);
1137
+ const featherSafe = pb.max(pb.abs(featherExp), 1e-5);
1138
+ const edgeCurveSafe = pb.max(edgeCurveExp, 1e-5);
1139
+ const signedDistance = pb.mul(pb.sub(sampled, 0.5), pb.mul(spreadSafe, 2));
1140
+ const linearMask = pb.clamp(pb.add(pb.div(pb.add(signedDistance, sizeExp), pb.mul(featherSafe, 2)), 0.5), 0, 1);
1141
+ const linearMask2 = pb.mul(linearMask, linearMask);
1142
+ const linearMask3 = pb.mul(linearMask2, linearMask);
1143
+ const smootherMask = pb.mul(linearMask3, pb.add(pb.mul(linearMask, pb.sub(pb.mul(linearMask, 6), 15)), 10));
1144
+ const softAmount = pb.clamp(pb.sub(1, pb.div(1, edgeCurveSafe)), 0, 1);
1145
+ const softenedMask = pb.mix(linearMask, smootherMask, softAmount);
1146
+ const sharpPower = pb.div(1, edgeCurveSafe);
1147
+ const belowSharp = pb.mul(0.5, pb.pow(pb.mul(linearMask, 2), sharpPower));
1148
+ const aboveSharp = pb.sub(1, pb.mul(0.5, pb.pow(pb.mul(pb.sub(1, linearMask), 2), sharpPower)));
1149
+ const sharpMask = pb.select(aboveSharp, belowSharp, pb.lessThan(linearMask, 0.5));
1150
+ const sharpAmount = pb.clamp(pb.sub(1, edgeCurveSafe), 0, 1);
1151
+ const exp = pb.mix(softenedMask, sharpMask, sharpAmount);
1152
+ if (this._ref === 1) {
1153
+ return exp;
1154
+ }
1155
+ this.tmpName = this.getTmpName(pb.getCurrentScope());
1156
+ pb.getCurrentScope()[this.tmpName] = exp;
1157
+ return pb.getCurrentScope()[this.tmpName];
1158
+ }
1159
+ }
795
1160
  /**
796
1161
  * IR expression for texture uniform constants
797
1162
  *
@@ -823,6 +1188,7 @@ class IRComparison extends IRExpression {
823
1188
  /** Minification filter mode */ filterMin;
824
1189
  /** Magnification filter mode */ filterMag;
825
1190
  /** Mipmap filter mode */ filterMip;
1191
+ /** Whether this texture parameter should be exposed to material instances */ exposed;
826
1192
  /** Whether texture params is used */ useParams;
827
1193
  /**
828
1194
  * Creates a texture constant expression
@@ -836,7 +1202,7 @@ class IRComparison extends IRExpression {
836
1202
  * @param minFilter - Minification filter
837
1203
  * @param magFilter - Magnification filter
838
1204
  * @param mipFilter - Mipmap filter
839
- */ constructor(name, id, type, sRGB, addressU, addressV, minFilter, magFilter, mipFilter){
1205
+ */ constructor(name, id, type, sRGB, addressU, addressV, minFilter, magFilter, mipFilter, exposed = true){
840
1206
  super();
841
1207
  this.name = name;
842
1208
  this.id = id;
@@ -847,6 +1213,7 @@ class IRComparison extends IRExpression {
847
1213
  this.filterMin = minFilter;
848
1214
  this.filterMag = magFilter;
849
1215
  this.filterMip = mipFilter;
1216
+ this.exposed = exposed;
850
1217
  this.useParams = false;
851
1218
  }
852
1219
  /**
@@ -860,7 +1227,14 @@ class IRComparison extends IRExpression {
860
1227
  */ create(pb) {
861
1228
  if (!pb.getGlobalScope()[this.name]) {
862
1229
  // @ts-ignore
863
- pb.getGlobalScope()[this.name] = pb[this.type]().uniform(2);
1230
+ const exp = pb[this.type]().uniform(2).withSampler({
1231
+ addressU: this.addressU,
1232
+ addressV: this.addressV,
1233
+ minFilter: this.filterMin,
1234
+ magFilter: this.filterMag,
1235
+ mipFilter: this.filterMip
1236
+ });
1237
+ pb.getGlobalScope()[this.name] = exp;
864
1238
  }
865
1239
  return pb.getGlobalScope()[this.name];
866
1240
  }
@@ -873,6 +1247,7 @@ class IRComparison extends IRExpression {
873
1247
  return {
874
1248
  name: this.name,
875
1249
  texture: this.id,
1250
+ exposed: this.exposed,
876
1251
  wrapS: this.addressU,
877
1252
  wrapT: this.addressV,
878
1253
  minFilter: this.filterMin,
@@ -910,6 +1285,7 @@ class IRComparison extends IRExpression {
910
1285
  /** Array of uniform scalar/vector values to be set at runtime */ _uniformValues;
911
1286
  /** Array of uniform textures to be bound at runtime */ _uniformTextures;
912
1287
  /** Fallback texture uniforms generated for TextureSample nodes without texture input */ _textureSampleFallbackMap;
1288
+ /** Shared fallback texture uniforms keyed by texture identity and sampling config */ _sharedTextureUniformMap;
913
1289
  /** Flags indicating which shader features are used */ _behaviors;
914
1290
  /** Array of named output expressions (e.g., baseColor, normal, metallic) */ _outputs;
915
1291
  /**
@@ -1060,12 +1436,41 @@ class IRComparison extends IRExpression {
1060
1436
  this._uniformTextures = [];
1061
1437
  this._uniformValues = [];
1062
1438
  this._textureSampleFallbackMap = new Map();
1439
+ this._sharedTextureUniformMap = new Map();
1063
1440
  this._outputs = null;
1064
1441
  this._behaviors = {
1065
1442
  useVertexColor: false,
1066
1443
  useVertexUV: false
1067
1444
  };
1068
1445
  }
1446
+ /** Builds a stable key for reusing identical blueprint texture uniforms */ getSharedTextureUniformKey(name, textureId, type, exposed, sRGB, addressU, addressV, filterMin, filterMag, filterMip) {
1447
+ return [
1448
+ name,
1449
+ type,
1450
+ textureId || '__default__',
1451
+ exposed ? 'exposed' : 'hidden',
1452
+ sRGB ? 'srgb' : 'linear',
1453
+ addressU,
1454
+ addressV,
1455
+ filterMin,
1456
+ filterMag,
1457
+ filterMip
1458
+ ].join('|');
1459
+ }
1460
+ /** Gets or creates a shared texture uniform for identical fallback texture sample nodes */ getOrCreateSharedTextureUniform(name, textureId, type, exposed, sRGB, addressU, addressV, filterMin, filterMag, filterMip) {
1461
+ const key = this.getSharedTextureUniformKey(name, textureId, type, exposed, sRGB, addressU, addressV, filterMin, filterMag, filterMip);
1462
+ let ir = this._sharedTextureUniformMap.get(key);
1463
+ if (!ir) {
1464
+ ir = new IRConstantTexture(name, textureId, type, sRGB, addressU, addressV, filterMin, filterMag, filterMip, exposed);
1465
+ this._sharedTextureUniformMap.set(key, ir);
1466
+ this._expressions.push(ir);
1467
+ const uniformTexture = ir.asUniformTexture();
1468
+ if (uniformTexture) {
1469
+ this._uniformTextures.push(uniformTexture);
1470
+ }
1471
+ }
1472
+ return ir;
1473
+ }
1069
1474
  /**
1070
1475
  * Converts a graph node to an IR expression
1071
1476
  *
@@ -1097,6 +1502,10 @@ class IRComparison extends IRExpression {
1097
1502
  expr = this.constantTexture(node, output);
1098
1503
  } else if (node instanceof TextureSampleNode) {
1099
1504
  expr = this.textureSample(node, output);
1505
+ } else if (node instanceof ChannelSDFMaskNode) {
1506
+ expr = this.channelSDFMask(node, output);
1507
+ } else if (node instanceof MorphBlurNode) {
1508
+ expr = this.morphBlur(node, output);
1100
1509
  } else if (node instanceof PannerNode) {
1101
1510
  expr = this.panner(node, output);
1102
1511
  } else if (node instanceof MakeVectorNode) {
@@ -1183,6 +1592,8 @@ class IRComparison extends IRExpression {
1183
1592
  expr = this.functionInput(node, output);
1184
1593
  } else if (node instanceof FunctionOutputNode) {
1185
1594
  expr = this.functionOutput(node, output);
1595
+ } else if (node instanceof TexturePropertyNode) {
1596
+ expr = this.textureProperty(node, output);
1186
1597
  }
1187
1598
  if (expr && originType) {
1188
1599
  const outputType = node.getOutputType(output);
@@ -1611,7 +2022,7 @@ class IRComparison extends IRExpression {
1611
2022
  return this.getOrCreateIRExpression(node, output, IRCallFunc, node, args);
1612
2023
  }
1613
2024
  /** Converts a scalar constant node to IR */ constantf(node, output) {
1614
- return this.getOrCreateIRExpression(node, output, IRConstantf, node.x, node.paramName);
2025
+ return this.getOrCreateIRExpression(node, output, IRConstantf, node.x, node.isUniform ? node.paramName : '', node.isUniform && node.useRange ? node.minValue : undefined, node.isUniform && node.useRange ? node.maxValue : undefined);
1615
2026
  }
1616
2027
  /** Converts a vector constant node to IR */ constantfv(node, output) {
1617
2028
  const value = node instanceof ConstantVec2Node ? [
@@ -1627,7 +2038,7 @@ class IRComparison extends IRExpression {
1627
2038
  node.z,
1628
2039
  node.w
1629
2040
  ];
1630
- return this.getOrCreateIRExpression(node, output, IRConstantfv, value, node.paramName, `vec${value.length}`);
2041
+ return this.getOrCreateIRExpression(node, output, IRConstantfv, value, node.isUniform ? node.paramName : '', `vec${value.length}`);
1631
2042
  }
1632
2043
  /** Converts a scalar boolean constant node to IR */ constantb(node, output) {
1633
2044
  return this.getOrCreateIRExpression(node, output, IRConstantb, node.x);
@@ -1649,7 +2060,19 @@ class IRComparison extends IRExpression {
1649
2060
  return this.getOrCreateIRExpression(node, output, IRConstantbv, value);
1650
2061
  }
1651
2062
  /** Converts a texture constant node to IR */ constantTexture(node, output) {
1652
- return this.getOrCreateIRExpression(node, output, IRConstantTexture, node.paramName, node.textureId, node.getOutputType(1), node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip);
2063
+ return this.getOrCreateIRExpression(node, output, class SharedIRConstantTextureProxy extends IRExpression {
2064
+ _shared;
2065
+ constructor(shared){
2066
+ super();
2067
+ this._shared = shared;
2068
+ }
2069
+ create(pb) {
2070
+ return this._shared.create(pb);
2071
+ }
2072
+ asUniformTexture() {
2073
+ return null;
2074
+ }
2075
+ }, this.getOrCreateSharedTextureUniform(node.paramName, node.textureId, node.getOutputType(1), node.expose, node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip));
1653
2076
  }
1654
2077
  /** Converts a texture sample node to IR */ textureSample(node, output) {
1655
2078
  const tex = node.inputs[0].inputNode ? this.ir(node.inputs[0]) : this.textureSampleFallbackTexture(node);
@@ -1659,16 +2082,58 @@ class IRComparison extends IRExpression {
1659
2082
  })();
1660
2083
  return this.getOrCreateIRExpression(node, output, IRSampleTexture, tex, coord, node.samplerType);
1661
2084
  }
2085
+ /** Converts a texture property node to IR */ textureProperty(node, output) {
2086
+ const tex = this.ir(node.inputs[0]);
2087
+ const func = node.property === 'TextureSize' ? (pb, ...params)=>{
2088
+ const texture = params[0];
2089
+ return pb.vec2(pb.textureDimensions(texture, 0));
2090
+ } : (pb, ...params)=>{
2091
+ const texture = params[0];
2092
+ return pb.div(pb.vec2(1), pb.vec2(pb.textureDimensions(texture, 0)));
2093
+ };
2094
+ return this.getOrCreateIRExpression(node, output, IRFunc, [
2095
+ tex
2096
+ ], func);
2097
+ }
2098
+ channelSDFMask(node, output) {
2099
+ const tex = node.inputs[0].inputNode ? this.ir(node.inputs[0]) : this.getOrCreateSharedTextureUniform(node.paramName, node.textureId, 'tex2D', node.expose, node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip);
2100
+ const coord = node.inputs[1].inputNode ? this.ir(node.inputs[1]) : (()=>{
2101
+ this._behaviors.useVertexUV = true;
2102
+ return new IRInput((scope)=>scope.zVertexUV);
2103
+ })();
2104
+ const sizeOffset = node.inputs[2].inputNode ? this.ir(node.inputs[2]) : node.inputs[2].defaultValue?.[0] ?? 0;
2105
+ const feather = node.inputs[3].inputNode ? this.ir(node.inputs[3]) : node.inputs[3].defaultValue?.[0] ?? 0;
2106
+ const spread = node.inputs[4].inputNode ? this.ir(node.inputs[4]) : node.inputs[4].defaultValue?.[0] ?? 16;
2107
+ const edgeCurve = node.inputs[5].inputNode ? this.ir(node.inputs[5]) : node.inputs[5].defaultValue?.[0] ?? 1;
2108
+ const channelMask = node.inputs[6].inputNode ? this.ir(node.inputs[6]) : node.inputs[6].defaultValue ?? [
2109
+ 1,
2110
+ 0,
2111
+ 0,
2112
+ 0
2113
+ ];
2114
+ return this.getOrCreateIRExpression(node, output, IRChannelSDFMask, tex, coord, sizeOffset, feather, spread, edgeCurve, channelMask);
2115
+ }
2116
+ morphBlur(node, output) {
2117
+ const tex = node.inputs[0].inputNode ? this.ir(node.inputs[0]) : this.getOrCreateSharedTextureUniform(node.paramName, node.textureId, 'tex2D', node.expose, node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip);
2118
+ const coord = node.inputs[1].inputNode ? this.ir(node.inputs[1]) : (()=>{
2119
+ this._behaviors.useVertexUV = true;
2120
+ return new IRInput((scope)=>scope.zVertexUV);
2121
+ })();
2122
+ const morphRadius = node.inputs[2].inputNode ? this.ir(node.inputs[2]) : node.inputs[2].defaultValue?.[0] ?? 0;
2123
+ const blurRadius = node.inputs[3].inputNode ? this.ir(node.inputs[3]) : node.inputs[3].defaultValue?.[0] ?? 0;
2124
+ const channelMask = node.inputs[4].inputNode ? this.ir(node.inputs[4]) : node.inputs[4].defaultValue ?? [
2125
+ 1,
2126
+ 0,
2127
+ 0,
2128
+ 0
2129
+ ];
2130
+ return this.getOrCreateIRExpression(node, output, IRMorphBlur, tex, coord, morphRadius, blurRadius, channelMask);
2131
+ }
1662
2132
  /** Gets or creates fallback texture uniform expression for texture sample nodes */ textureSampleFallbackTexture(node) {
1663
2133
  let ir = this._textureSampleFallbackMap.get(node);
1664
2134
  if (!ir) {
1665
- ir = new IRConstantTexture(node.paramName, node.textureId, 'tex2D', node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip);
1666
- this._expressions.push(ir);
2135
+ ir = this.getOrCreateSharedTextureUniform(node.paramName, node.textureId, 'tex2D', node.expose, node.sRGB, node.addressU, node.addressV, node.filterMin, node.filterMag, node.filterMip);
1667
2136
  this._textureSampleFallbackMap.set(node, ir);
1668
- const uniformTexture = ir.asUniformTexture();
1669
- if (uniformTexture) {
1670
- this._uniformTextures.push(uniformTexture);
1671
- }
1672
2137
  }
1673
2138
  return ir;
1674
2139
  }