@aura3d/rendering 2.0.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/dist/AtmosphereWetness.d.ts +61 -0
  2. package/dist/AtmosphereWetness.d.ts.map +1 -0
  3. package/dist/AtmosphereWetness.js +99 -0
  4. package/dist/AtmosphereWetness.js.map +1 -0
  5. package/dist/ClusteredForwardLighting.d.ts +25 -1
  6. package/dist/ClusteredForwardLighting.d.ts.map +1 -1
  7. package/dist/ClusteredForwardLighting.js +80 -3
  8. package/dist/ClusteredForwardLighting.js.map +1 -1
  9. package/dist/DayNightSky.d.ts +63 -0
  10. package/dist/DayNightSky.d.ts.map +1 -0
  11. package/dist/DayNightSky.js +156 -0
  12. package/dist/DayNightSky.js.map +1 -0
  13. package/dist/EnvironmentPresetPack.d.ts +30 -0
  14. package/dist/EnvironmentPresetPack.d.ts.map +1 -0
  15. package/dist/EnvironmentPresetPack.js +49 -0
  16. package/dist/EnvironmentPresetPack.js.map +1 -0
  17. package/dist/ForwardPass.d.ts +67 -0
  18. package/dist/ForwardPass.d.ts.map +1 -1
  19. package/dist/ForwardPass.js +196 -2
  20. package/dist/ForwardPass.js.map +1 -1
  21. package/dist/InstancedPBRMaterial.d.ts +1 -0
  22. package/dist/InstancedPBRMaterial.d.ts.map +1 -1
  23. package/dist/InstancedPBRMaterial.js +3 -0
  24. package/dist/InstancedPBRMaterial.js.map +1 -1
  25. package/dist/InstancingDiagnostics.d.ts +69 -0
  26. package/dist/InstancingDiagnostics.d.ts.map +1 -0
  27. package/dist/InstancingDiagnostics.js +98 -0
  28. package/dist/InstancingDiagnostics.js.map +1 -0
  29. package/dist/LightingRig.d.ts +19 -1
  30. package/dist/LightingRig.d.ts.map +1 -1
  31. package/dist/LightingRig.js +61 -1
  32. package/dist/LightingRig.js.map +1 -1
  33. package/dist/MeshConsolidation.d.ts +12 -0
  34. package/dist/MeshConsolidation.d.ts.map +1 -1
  35. package/dist/MeshConsolidation.js +28 -0
  36. package/dist/MeshConsolidation.js.map +1 -1
  37. package/dist/MorphTargetPlan.d.ts +29 -2
  38. package/dist/MorphTargetPlan.d.ts.map +1 -1
  39. package/dist/MorphTargetPlan.js +23 -4
  40. package/dist/MorphTargetPlan.js.map +1 -1
  41. package/dist/NormalMappedPBRMaterial.d.ts.map +1 -1
  42. package/dist/NormalMappedPBRMaterial.js +22 -2
  43. package/dist/NormalMappedPBRMaterial.js.map +1 -1
  44. package/dist/OceanSurface.d.ts +75 -0
  45. package/dist/OceanSurface.d.ts.map +1 -1
  46. package/dist/OceanSurface.js +211 -0
  47. package/dist/OceanSurface.js.map +1 -1
  48. package/dist/PBRMaterial.d.ts +1 -0
  49. package/dist/PBRMaterial.d.ts.map +1 -1
  50. package/dist/PBRMaterial.js +23 -2
  51. package/dist/PBRMaterial.js.map +1 -1
  52. package/dist/PbrReference.d.ts +48 -0
  53. package/dist/PbrReference.d.ts.map +1 -1
  54. package/dist/PbrReference.js +126 -0
  55. package/dist/PbrReference.js.map +1 -1
  56. package/dist/PlanarReflection.d.ts +221 -0
  57. package/dist/PlanarReflection.d.ts.map +1 -0
  58. package/dist/PlanarReflection.js +541 -0
  59. package/dist/PlanarReflection.js.map +1 -0
  60. package/dist/PostProcessPass.d.ts +13 -0
  61. package/dist/PostProcessPass.d.ts.map +1 -1
  62. package/dist/PostProcessPass.js +15 -4
  63. package/dist/PostProcessPass.js.map +1 -1
  64. package/dist/ReflectionSurfaces.d.ts +14 -0
  65. package/dist/ReflectionSurfaces.d.ts.map +1 -1
  66. package/dist/ReflectionSurfaces.js +93 -0
  67. package/dist/ReflectionSurfaces.js.map +1 -1
  68. package/dist/RenderDevice.d.ts +87 -0
  69. package/dist/RenderDevice.d.ts.map +1 -1
  70. package/dist/RenderDevice.js +85 -0
  71. package/dist/RenderDevice.js.map +1 -1
  72. package/dist/Renderer.js +8 -5
  73. package/dist/Renderer.js.map +1 -1
  74. package/dist/RendererPostprocessPlan.d.ts +48 -0
  75. package/dist/RendererPostprocessPlan.d.ts.map +1 -1
  76. package/dist/RendererPostprocessPlan.js +101 -11
  77. package/dist/RendererPostprocessPlan.js.map +1 -1
  78. package/dist/RendererVisualPipelineReport.d.ts.map +1 -1
  79. package/dist/RendererVisualPipelineReport.js +3 -1
  80. package/dist/RendererVisualPipelineReport.js.map +1 -1
  81. package/dist/Sampler.d.ts +29 -0
  82. package/dist/Sampler.d.ts.map +1 -1
  83. package/dist/Sampler.js +47 -0
  84. package/dist/Sampler.js.map +1 -1
  85. package/dist/SceneOptimization.d.ts +1 -0
  86. package/dist/SceneOptimization.d.ts.map +1 -1
  87. package/dist/SceneOptimization.js +4 -2
  88. package/dist/SceneOptimization.js.map +1 -1
  89. package/dist/SdfText.d.ts +250 -0
  90. package/dist/SdfText.d.ts.map +1 -0
  91. package/dist/SdfText.js +551 -0
  92. package/dist/SdfText.js.map +1 -0
  93. package/dist/ShaderChunks.d.ts.map +1 -1
  94. package/dist/ShaderChunks.js +30 -12
  95. package/dist/ShaderChunks.js.map +1 -1
  96. package/dist/ShaderLibrary.d.ts.map +1 -1
  97. package/dist/ShaderLibrary.js +478 -34
  98. package/dist/ShaderLibrary.js.map +1 -1
  99. package/dist/ShaderLibraryCore.d.ts.map +1 -1
  100. package/dist/ShaderLibraryCore.js +70 -12
  101. package/dist/ShaderLibraryCore.js.map +1 -1
  102. package/dist/ShadowMap.d.ts +24 -0
  103. package/dist/ShadowMap.d.ts.map +1 -1
  104. package/dist/ShadowMap.js +43 -0
  105. package/dist/ShadowMap.js.map +1 -1
  106. package/dist/SkinnedLitMaterial.d.ts.map +1 -1
  107. package/dist/SkinnedLitMaterial.js +2 -0
  108. package/dist/SkinnedLitMaterial.js.map +1 -1
  109. package/dist/SkinningBounds.d.ts +10 -0
  110. package/dist/SkinningBounds.d.ts.map +1 -1
  111. package/dist/SkinningBounds.js +27 -0
  112. package/dist/SkinningBounds.js.map +1 -1
  113. package/dist/SpriteFlipbook.d.ts +60 -0
  114. package/dist/SpriteFlipbook.d.ts.map +1 -0
  115. package/dist/SpriteFlipbook.js +128 -0
  116. package/dist/SpriteFlipbook.js.map +1 -0
  117. package/dist/TerrainTiles.d.ts +120 -0
  118. package/dist/TerrainTiles.d.ts.map +1 -0
  119. package/dist/TerrainTiles.js +221 -0
  120. package/dist/TerrainTiles.js.map +1 -0
  121. package/dist/TexturedPBRMaterial.d.ts +1 -0
  122. package/dist/TexturedPBRMaterial.d.ts.map +1 -1
  123. package/dist/TexturedPBRMaterial.js +23 -2
  124. package/dist/TexturedPBRMaterial.js.map +1 -1
  125. package/dist/TexturedUnlitMaterial.d.ts.map +1 -1
  126. package/dist/TexturedUnlitMaterial.js +20 -2
  127. package/dist/TexturedUnlitMaterial.js.map +1 -1
  128. package/dist/UnlitMaterial.d.ts.map +1 -1
  129. package/dist/UnlitMaterial.js +20 -2
  130. package/dist/UnlitMaterial.js.map +1 -1
  131. package/dist/VertexFormat.d.ts +6 -0
  132. package/dist/VertexFormat.d.ts.map +1 -1
  133. package/dist/VertexFormat.js +12 -0
  134. package/dist/VertexFormat.js.map +1 -1
  135. package/dist/VolumetricFog.d.ts +55 -0
  136. package/dist/VolumetricFog.d.ts.map +1 -0
  137. package/dist/VolumetricFog.js +91 -0
  138. package/dist/VolumetricFog.js.map +1 -0
  139. package/dist/WaterSurface.d.ts +77 -0
  140. package/dist/WaterSurface.d.ts.map +1 -0
  141. package/dist/WaterSurface.js +133 -0
  142. package/dist/WaterSurface.js.map +1 -0
  143. package/dist/WebGL2Device.d.ts +58 -0
  144. package/dist/WebGL2Device.d.ts.map +1 -1
  145. package/dist/WebGL2Device.js +536 -17
  146. package/dist/WebGL2Device.js.map +1 -1
  147. package/dist/WebGPUDevice.d.ts +50 -0
  148. package/dist/WebGPUDevice.d.ts.map +1 -1
  149. package/dist/WebGPUDevice.js +504 -10
  150. package/dist/WebGPUDevice.js.map +1 -1
  151. package/dist/WebGPUSkinningLimits.d.ts +40 -0
  152. package/dist/WebGPUSkinningLimits.d.ts.map +1 -1
  153. package/dist/WebGPUSkinningLimits.js +32 -0
  154. package/dist/WebGPUSkinningLimits.js.map +1 -1
  155. package/dist/effects/CollisionModule.d.ts +9 -0
  156. package/dist/effects/CollisionModule.d.ts.map +1 -1
  157. package/dist/effects/CollisionModule.js +10 -0
  158. package/dist/effects/CollisionModule.js.map +1 -1
  159. package/dist/effects/ColorModule.d.ts +7 -0
  160. package/dist/effects/ColorModule.d.ts.map +1 -1
  161. package/dist/effects/ColorModule.js +23 -0
  162. package/dist/effects/ColorModule.js.map +1 -1
  163. package/dist/effects/ForceModule.d.ts +35 -0
  164. package/dist/effects/ForceModule.d.ts.map +1 -1
  165. package/dist/effects/ForceModule.js +49 -0
  166. package/dist/effects/ForceModule.js.map +1 -1
  167. package/dist/effects/GPUParticleBackend.d.ts +186 -0
  168. package/dist/effects/GPUParticleBackend.d.ts.map +1 -1
  169. package/dist/effects/GPUParticleBackend.js +692 -1
  170. package/dist/effects/GPUParticleBackend.js.map +1 -1
  171. package/dist/effects/HeightfieldModule.d.ts +58 -0
  172. package/dist/effects/HeightfieldModule.d.ts.map +1 -0
  173. package/dist/effects/HeightfieldModule.js +129 -0
  174. package/dist/effects/HeightfieldModule.js.map +1 -0
  175. package/dist/effects/LightingModule.d.ts +34 -0
  176. package/dist/effects/LightingModule.d.ts.map +1 -0
  177. package/dist/effects/LightingModule.js +54 -0
  178. package/dist/effects/LightingModule.js.map +1 -0
  179. package/dist/effects/ParticleDiagnostics.d.ts +8 -0
  180. package/dist/effects/ParticleDiagnostics.d.ts.map +1 -1
  181. package/dist/effects/ParticleDiagnostics.js +3 -0
  182. package/dist/effects/ParticleDiagnostics.js.map +1 -1
  183. package/dist/effects/ParticleModule.d.ts +8 -0
  184. package/dist/effects/ParticleModule.d.ts.map +1 -1
  185. package/dist/effects/ParticleModule.js.map +1 -1
  186. package/dist/effects/ParticleRenderer.d.ts +20 -0
  187. package/dist/effects/ParticleRenderer.d.ts.map +1 -1
  188. package/dist/effects/ParticleRenderer.js +57 -7
  189. package/dist/effects/ParticleRenderer.js.map +1 -1
  190. package/dist/effects/ParticleSystem.d.ts +45 -1
  191. package/dist/effects/ParticleSystem.d.ts.map +1 -1
  192. package/dist/effects/ParticleSystem.js +373 -4
  193. package/dist/effects/ParticleSystem.js.map +1 -1
  194. package/dist/effects/SizeModule.d.ts +4 -0
  195. package/dist/effects/SizeModule.d.ts.map +1 -1
  196. package/dist/effects/SizeModule.js +16 -0
  197. package/dist/effects/SizeModule.js.map +1 -1
  198. package/dist/effects/SubEmitterModule.d.ts +61 -0
  199. package/dist/effects/SubEmitterModule.d.ts.map +1 -0
  200. package/dist/effects/SubEmitterModule.js +119 -0
  201. package/dist/effects/SubEmitterModule.js.map +1 -0
  202. package/dist/effects/TrailModule.d.ts +38 -0
  203. package/dist/effects/TrailModule.d.ts.map +1 -1
  204. package/dist/effects/TrailModule.js +95 -2
  205. package/dist/effects/TrailModule.js.map +1 -1
  206. package/dist/effects/TurbulenceModule.d.ts +43 -0
  207. package/dist/effects/TurbulenceModule.d.ts.map +1 -0
  208. package/dist/effects/TurbulenceModule.js +130 -0
  209. package/dist/effects/TurbulenceModule.js.map +1 -0
  210. package/dist/effects/index.d.ts +4 -0
  211. package/dist/effects/index.d.ts.map +1 -1
  212. package/dist/effects/index.js +4 -0
  213. package/dist/effects/index.js.map +1 -1
  214. package/dist/index.d.ts +57 -19
  215. package/dist/index.d.ts.map +1 -1
  216. package/dist/index.js +31 -10
  217. package/dist/index.js.map +1 -1
  218. package/dist/materials/MaterialExtensions.js +3 -3
  219. package/dist/materials/MaterialExtensions.js.map +1 -1
  220. package/dist/postprocess/NativeBloomPyramid.d.ts +28 -0
  221. package/dist/postprocess/NativeBloomPyramid.d.ts.map +1 -0
  222. package/dist/postprocess/NativeBloomPyramid.js +54 -0
  223. package/dist/postprocess/NativeBloomPyramid.js.map +1 -0
  224. package/dist/postprocess/NativeLdrEffectLuts.d.ts +10 -1
  225. package/dist/postprocess/NativeLdrEffectLuts.d.ts.map +1 -1
  226. package/dist/postprocess/NativeLdrEffectLuts.js +19 -2
  227. package/dist/postprocess/NativeLdrEffectLuts.js.map +1 -1
  228. package/dist/postprocess/index.d.ts +2 -0
  229. package/dist/postprocess/index.d.ts.map +1 -1
  230. package/dist/postprocess/index.js +1 -0
  231. package/dist/postprocess/index.js.map +1 -1
  232. package/dist/production-runtime/PBRHDRPipeline.d.ts +22 -0
  233. package/dist/production-runtime/PBRHDRPipeline.d.ts.map +1 -1
  234. package/dist/production-runtime/PBRHDRPipeline.js +125 -0
  235. package/dist/production-runtime/PBRHDRPipeline.js.map +1 -1
  236. package/dist/production-runtime/ProductionWebGPURenderer.d.ts +52 -0
  237. package/dist/production-runtime/ProductionWebGPURenderer.d.ts.map +1 -1
  238. package/dist/production-runtime/ProductionWebGPURenderer.js +101 -0
  239. package/dist/production-runtime/ProductionWebGPURenderer.js.map +1 -1
  240. package/dist/production-runtime/index.d.ts +5 -4
  241. package/dist/production-runtime/index.d.ts.map +1 -1
  242. package/dist/production-runtime/index.js +3 -2
  243. package/dist/production-runtime/index.js.map +1 -1
  244. package/dist/production-runtime/passes/DepthPrepass.d.ts +26 -1
  245. package/dist/production-runtime/passes/DepthPrepass.d.ts.map +1 -1
  246. package/dist/production-runtime/passes/DepthPrepass.js +49 -2
  247. package/dist/production-runtime/passes/DepthPrepass.js.map +1 -1
  248. package/dist/production-runtime/passes/FramegraphTopology.d.ts +13 -0
  249. package/dist/production-runtime/passes/FramegraphTopology.d.ts.map +1 -0
  250. package/dist/production-runtime/passes/FramegraphTopology.js +59 -0
  251. package/dist/production-runtime/passes/FramegraphTopology.js.map +1 -0
  252. package/dist/production-runtime/passes/OpaquePass.d.ts +24 -1
  253. package/dist/production-runtime/passes/OpaquePass.d.ts.map +1 -1
  254. package/dist/production-runtime/passes/OpaquePass.js +41 -2
  255. package/dist/production-runtime/passes/OpaquePass.js.map +1 -1
  256. package/dist/production-runtime/passes/ShadowPass.d.ts +24 -1
  257. package/dist/production-runtime/passes/ShadowPass.d.ts.map +1 -1
  258. package/dist/production-runtime/passes/ShadowPass.js +43 -2
  259. package/dist/production-runtime/passes/ShadowPass.js.map +1 -1
  260. package/dist/production-runtime/passes/SkyboxPass.d.ts +21 -1
  261. package/dist/production-runtime/passes/SkyboxPass.d.ts.map +1 -1
  262. package/dist/production-runtime/passes/SkyboxPass.js +39 -2
  263. package/dist/production-runtime/passes/SkyboxPass.js.map +1 -1
  264. package/dist/production-runtime/passes/ToneMappingPass.d.ts +29 -1
  265. package/dist/production-runtime/passes/ToneMappingPass.d.ts.map +1 -1
  266. package/dist/production-runtime/passes/ToneMappingPass.js +53 -2
  267. package/dist/production-runtime/passes/ToneMappingPass.js.map +1 -1
  268. package/dist/production-runtime/passes/TransparentPass.d.ts +24 -1
  269. package/dist/production-runtime/passes/TransparentPass.d.ts.map +1 -1
  270. package/dist/production-runtime/passes/TransparentPass.js +42 -2
  271. package/dist/production-runtime/passes/TransparentPass.js.map +1 -1
  272. package/dist/shadows/CascadeHysteresis.d.ts +67 -0
  273. package/dist/shadows/CascadeHysteresis.d.ts.map +1 -0
  274. package/dist/shadows/CascadeHysteresis.js +104 -0
  275. package/dist/shadows/CascadeHysteresis.js.map +1 -0
  276. package/dist/shadows/ContactShadows.d.ts +52 -0
  277. package/dist/shadows/ContactShadows.d.ts.map +1 -1
  278. package/dist/shadows/ContactShadows.js +122 -0
  279. package/dist/shadows/ContactShadows.js.map +1 -1
  280. package/dist/shadows/SpotShadowMaps.d.ts +77 -0
  281. package/dist/shadows/SpotShadowMaps.d.ts.map +1 -0
  282. package/dist/shadows/SpotShadowMaps.js +106 -0
  283. package/dist/shadows/SpotShadowMaps.js.map +1 -0
  284. package/dist/webgpu/WebGPUPostProcess.d.ts +2 -0
  285. package/dist/webgpu/WebGPUPostProcess.d.ts.map +1 -1
  286. package/dist/webgpu/WebGPUPostProcess.js +1 -0
  287. package/dist/webgpu/WebGPUPostProcess.js.map +1 -1
  288. package/dist/webgpu/WebGPUPostShaders.d.ts +78 -0
  289. package/dist/webgpu/WebGPUPostShaders.d.ts.map +1 -0
  290. package/dist/webgpu/WebGPUPostShaders.js +262 -0
  291. package/dist/webgpu/WebGPUPostShaders.js.map +1 -0
  292. package/package.json +3 -3
@@ -94,6 +94,7 @@ uniform float u_metallic;
94
94
  uniform float u_roughness;
95
95
  uniform vec3 u_environmentColor;
96
96
  uniform float u_environmentIntensity;
97
+ uniform float u_materialEnvironmentIntensity;
97
98
  uniform vec3 u_environmentSkyColor;
98
99
  uniform vec3 u_environmentHorizonColor;
99
100
  uniform vec3 u_environmentGroundColor;
@@ -141,6 +142,19 @@ uniform float u_pointShadowSlopeBias;
141
142
  uniform vec2 u_pointShadowTexelSize;
142
143
  uniform float u_pointShadowPcfSampleCount;
143
144
  uniform vec4 u_pointShadowPcfSamples[32];
145
+ uniform sampler2D u_spotShadowMapTexture;
146
+ uniform float u_spotShadowMapEnabled;
147
+ uniform vec3 u_spotShadowLightPosition;
148
+ uniform vec3 u_spotShadowLightDirection;
149
+ uniform mat4 u_spotShadowMatrix;
150
+ uniform vec2 u_spotShadowCone;
151
+ uniform float u_spotShadowRange;
152
+ uniform float u_spotShadowStrength;
153
+ uniform float u_spotShadowBias;
154
+ uniform float u_spotShadowSlopeBias;
155
+ uniform vec2 u_spotShadowTexelSize;
156
+ uniform float u_spotShadowPcfSampleCount;
157
+ uniform vec4 u_spotShadowPcfSamples[32];
144
158
  uniform float u_outputColorSpace;
145
159
  uniform vec3 u_cameraPosition;
146
160
  uniform float u_environmentFogEnabled;
@@ -150,6 +164,9 @@ uniform float u_environmentFogNear;
150
164
  uniform float u_environmentFogFar;
151
165
  uniform float u_environmentFogDensity;
152
166
  uniform float u_environmentFogHeightFalloff;
167
+ uniform float u_volumetricIntensity;
168
+ uniform vec3 u_volumetricLightDirection;
169
+ uniform vec3 u_volumetricLightColor;
153
170
  uniform float u_environmentFogHeightReference;
154
171
  uniform float u_environmentFogMaxOpacity;
155
172
  in vec3 v_normal;
@@ -228,6 +245,50 @@ float a3dPointShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection)
228
245
  float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
229
246
  return mix(1.0, 1.0 - occlusion, clamp(u_pointShadowStrength, 0.0, 1.0));
230
247
  }
248
+ float a3dSpotShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection) {
249
+ if (u_spotShadowMapEnabled < 0.5) return 1.0;
250
+ vec3 toFragment = worldPosition - u_spotShadowLightPosition;
251
+ float distanceToLight = max(length(toFragment), 0.0001);
252
+ if (distanceToLight > u_spotShadowRange) return 1.0;
253
+ vec3 coneAxis = u_spotShadowLightDirection / max(length(u_spotShadowLightDirection), 0.0001);
254
+ if (dot(toFragment / distanceToLight, coneAxis) < cos(u_spotShadowCone.x)) return 1.0;
255
+ vec4 lightPosition = u_spotShadowMatrix * vec4(worldPosition, 1.0);
256
+ vec3 projected = lightPosition.xyz / max(lightPosition.w, 0.0001);
257
+ vec2 uv = projected.xy * 0.5 + 0.5;
258
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 1.0;
259
+ vec3 receiverNormal = normalize(normal);
260
+ vec3 receiverLightDirection = lightDirection / max(length(lightDirection), 0.0001);
261
+ float normalDotLight = clamp(abs(dot(receiverNormal, receiverLightDirection)), 0.0, 1.0);
262
+ // Shared bias-table policy: the same per-sample, tangent-scaled slope bias as the
263
+ // directional and point-cube paths. A centre-only bias under-compensates outer PCF
264
+ // taps on sloped receivers, so each tap scales by its own texel distance.
265
+ float slopeTangent = min(sqrt(max(1.0 - normalDotLight * normalDotLight, 0.0)) / max(normalDotLight, 0.05), 8.0);
266
+ float slopeTexelBias = slopeTangent * u_spotShadowSlopeBias * max(u_spotShadowTexelSize.x, u_spotShadowTexelSize.y);
267
+ float projectedDepth = projected.z * 0.5 + 0.5;
268
+ float shadowed = 0.0;
269
+ float totalWeight = 0.0;
270
+ int sampleCount = clamp(int(u_spotShadowPcfSampleCount), 1, 32);
271
+ for (int i = 0; i < 32; ++i) {
272
+ if (i >= sampleCount) break;
273
+ vec4 sampleData = u_spotShadowPcfSamples[i];
274
+ float weight = max(sampleData.z, 0.0);
275
+ vec2 offset = sampleData.xy * u_spotShadowTexelSize;
276
+ float storedDepth = texture(u_spotShadowMapTexture, uv + offset).r;
277
+ float sampleTexelDistance = max(1.0, length(sampleData.xy));
278
+ float receiverDepth = projectedDepth - u_spotShadowBias - slopeTexelBias * sampleTexelDistance;
279
+ shadowed += (receiverDepth > storedDepth ? 1.0 : 0.0) * weight;
280
+ totalWeight += weight;
281
+ }
282
+ float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
283
+ return mix(1.0, 1.0 - occlusion, clamp(u_spotShadowStrength, 0.0, 1.0));
284
+ }
285
+ float a3dResolveSpotShadowOverride(float baseFactor, float kind, float shadowFlag, vec3 worldPosition, vec3 normal, vec3 lightDirection) {
286
+ // Spot-kind lights (kind == 2) sample the perspective spot map only when it is
287
+ // bound. Every gate term is 0 when no spot map is present, so mix() returns
288
+ // baseFactor exactly and scenes without spot lights render unchanged.
289
+ float spotGate = step(1.5, kind) * (1.0 - step(2.5, kind)) * step(0.5, u_spotShadowMapEnabled) * step(0.5, shadowFlag);
290
+ return mix(baseFactor, a3dSpotShadowFactor(worldPosition, normal, lightDirection), spotGate);
291
+ }
231
292
  vec2 a3dEnvironmentEquirectUv(vec3 direction, float rotation) {
232
293
  vec3 d = normalize(direction);
233
294
  float u = atan(d.z, d.x) / 6.28318530718 + 0.5 + rotation;
@@ -282,7 +343,7 @@ vec3 a3dPbrEnvironmentDiffuseInput(vec3 normal) {
282
343
  float sampledEnvironmentWeight = step(0.0001, u_environmentMapTextureEnabled * u_environmentMapTextureIntensity);
283
344
  float diffuseEnvironmentLod = max(u_environmentMapTextureMipCount - 1.0, 0.0);
284
345
  vec3 sampledDiffuse = a3dPbrDecodeEnvironmentSample(a3dPbrEnvironmentSampleRaw(normal, diffuseEnvironmentLod));
285
- return mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity, sampledEnvironmentWeight);
346
+ return mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity * u_materialEnvironmentIntensity, sampledEnvironmentWeight);
286
347
  }
287
348
  vec3 a3dPbrEnvironmentSpecularInput(vec3 normal, vec3 viewDirection, float roughness) {
288
349
  float proceduralEnvironmentWeight = step(0.0001, u_environmentMapIntensity);
@@ -304,7 +365,7 @@ vec3 a3dPbrEnvironmentSpecularInput(vec3 normal, vec3 viewDirection, float rough
304
365
  vec3 sampledSpecular = a3dPbrBoundHdrSpecularRadiance(a3dPbrDecodeEnvironmentSample(a3dPbrEnvironmentSampleRaw(reflectionDirection, environmentLod)));
305
366
  float nDotV = clamp(dot(normal, viewDirection), 0.0, 1.0);
306
367
  sampledSpecular = a3dPbrClampSampledSpecularEdgeEnergy(sampledSpecular, nDotV, clampedRoughness);
307
- sampledSpecular *= u_environmentMapTextureSpecularIntensity * sampledEnvironmentWeight * mix(0.84, 0.58, clampedRoughness);
368
+ sampledSpecular *= u_environmentMapTextureSpecularIntensity * u_materialEnvironmentIntensity * sampledEnvironmentWeight * mix(0.84, 0.58, clampedRoughness);
308
369
  return proceduralSpecular + sampledSpecular;
309
370
  }
310
371
  vec2 a3dPbrEnvironmentBrdfInput(vec3 normal, vec3 viewDirection, float roughness) {
@@ -312,10 +373,16 @@ vec2 a3dPbrEnvironmentBrdfInput(vec3 normal, vec3 viewDirection, float roughness
312
373
  vec2 lut = texture(u_environmentBrdfLutTexture, vec2(nDotV, clamp(roughness, 0.0, 1.0))).rg;
313
374
  return mix(vec2(1.0, 0.0), lut, step(0.0001, u_environmentBrdfLutEnabled));
314
375
  }
376
+ vec3 a3dLinearToSrgb(vec3 linear) {
377
+ vec3 clamped = max(linear, vec3(0.0));
378
+ vec3 low = clamped * 12.92;
379
+ vec3 high = 1.055 * pow(clamped, vec3(1.0 / 2.4)) - 0.055;
380
+ return mix(low, high, step(vec3(0.0031308), clamped));
381
+ }
315
382
  vec3 a3dPbrEncodeOutput(vec3 linearColor) {
316
383
  vec3 color = max(linearColor, vec3(0.0));
317
384
  vec3 filmic = clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));
318
- vec3 srgb = pow(filmic, vec3(1.0 / 2.2));
385
+ vec3 srgb = a3dLinearToSrgb(filmic);
319
386
  return mix(color, srgb, step(0.5, u_outputColorSpace));
320
387
  }
321
388
  void main() {
@@ -376,7 +443,7 @@ void main() {
376
443
  float inner = cos(spotShadowLayer.x * max(1.0 - spotShadowLayer.y, 0.001));
377
444
  attenuation *= smoothstep(outer, inner, cone);
378
445
  }
379
- float shadowFactor = mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, normal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, normal, lightDirection), step(0.5, spotShadowLayer.z));
446
+ float shadowFactor = a3dResolveSpotShadowOverride(mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, normal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, normal, lightDirection), step(0.5, spotShadowLayer.z)), kind, spotShadowLayer.z, v_worldPosition, normal, lightDirection);
380
447
  shaded += a3dPbrDirectLight(normal, viewDirection, lightDirection, colorIntensity.rgb, colorIntensity.a * attenuation * shadowFactor, base, u_metallic, u_roughness, 1.0, vec3(1.0));
381
448
  }
382
449
  float alpha = u_baseColor.a * v_instanceColor.a;
@@ -395,10 +462,18 @@ precision highp float;
395
462
  layout(location = 0) in vec3 a_position;
396
463
  layout(location = 2) in vec2 a_uv;
397
464
  layout(location = 4) in vec4 a_color;
465
+ layout(location = 8) in vec4 a_instanceMatrix0;
466
+ layout(location = 9) in vec4 a_instanceMatrix1;
467
+ layout(location = 10) in vec4 a_instanceMatrix2;
468
+ layout(location = 11) in vec4 a_instanceMatrix3;
469
+ layout(location = 12) in vec4 a_instanceColor;
398
470
  uniform mat4 u_modelViewProjection;
399
471
  uniform vec2 u_baseColorTextureOffset;
400
472
  uniform vec2 u_baseColorTextureScale;
401
473
  uniform float u_baseColorTextureRotation;
474
+ uniform mat4 u_instanceMatrices[64];
475
+ uniform float u_instanceCount;
476
+ uniform float u_instanceAttributeMode;
402
477
  out vec2 v_uv;
403
478
  out vec4 v_vertexColor;
404
479
  void main() {
@@ -406,8 +481,19 @@ void main() {
406
481
  float c = cos(u_baseColorTextureRotation);
407
482
  float s = sin(u_baseColorTextureRotation);
408
483
  v_uv = vec2(scaledUv.x * c - scaledUv.y * s, scaledUv.x * s + scaledUv.y * c) + u_baseColorTextureOffset;
409
- v_vertexColor = a_color;
410
- gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
484
+ // P2 instanced-GLB path (muse3jsparity-PRD): the u_instanceCount branch is
485
+ // taken only for items carrying instanceTransforms; every other draw keeps
486
+ // the legacy math bit-exact.
487
+ if (u_instanceCount > 0.5) {
488
+ int instanceIndex = clamp(gl_InstanceID, 0, max(int(u_instanceCount) - 1, 0));
489
+ mat4 attributeMatrix = mat4(a_instanceMatrix0, a_instanceMatrix1, a_instanceMatrix2, a_instanceMatrix3);
490
+ mat4 instanceMatrix = u_instanceAttributeMode > 0.5 ? attributeMatrix : u_instanceMatrices[instanceIndex];
491
+ v_vertexColor = a_color * a_instanceColor;
492
+ gl_Position = u_modelViewProjection * instanceMatrix * vec4(a_position, 1.0);
493
+ } else {
494
+ v_vertexColor = a_color;
495
+ gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
496
+ }
411
497
  }
412
498
  `,
413
499
  fragment: `#version 300 es
@@ -508,6 +594,7 @@ uniform float u_baseColorTextureEnabled;
508
594
  uniform sampler2D u_normalTexture;
509
595
  uniform float u_normalTextureEnabled;
510
596
  uniform float u_normalScale;
597
+ uniform float u_wrinkleStrength;
511
598
  uniform sampler2D u_metallicRoughnessTexture;
512
599
  uniform float u_metallicRoughnessTextureEnabled;
513
600
  uniform sampler2D u_occlusionTexture;
@@ -520,6 +607,7 @@ uniform float u_metallic;
520
607
  uniform float u_roughness;
521
608
  uniform vec3 u_environmentColor;
522
609
  uniform float u_environmentIntensity;
610
+ uniform float u_materialEnvironmentIntensity;
523
611
  uniform vec3 u_environmentSkyColor;
524
612
  uniform vec3 u_environmentHorizonColor;
525
613
  uniform vec3 u_environmentGroundColor;
@@ -593,6 +681,19 @@ uniform float u_pointShadowSlopeBias;
593
681
  uniform vec2 u_pointShadowTexelSize;
594
682
  uniform float u_pointShadowPcfSampleCount;
595
683
  uniform vec4 u_pointShadowPcfSamples[32];
684
+ uniform sampler2D u_spotShadowMapTexture;
685
+ uniform float u_spotShadowMapEnabled;
686
+ uniform vec3 u_spotShadowLightPosition;
687
+ uniform vec3 u_spotShadowLightDirection;
688
+ uniform mat4 u_spotShadowMatrix;
689
+ uniform vec2 u_spotShadowCone;
690
+ uniform float u_spotShadowRange;
691
+ uniform float u_spotShadowStrength;
692
+ uniform float u_spotShadowBias;
693
+ uniform float u_spotShadowSlopeBias;
694
+ uniform vec2 u_spotShadowTexelSize;
695
+ uniform float u_spotShadowPcfSampleCount;
696
+ uniform vec4 u_spotShadowPcfSamples[32];
596
697
  uniform float u_outputColorSpace;
597
698
  uniform vec3 u_cameraPosition;
598
699
  uniform float u_environmentFogEnabled;
@@ -602,6 +703,9 @@ uniform float u_environmentFogNear;
602
703
  uniform float u_environmentFogFar;
603
704
  uniform float u_environmentFogDensity;
604
705
  uniform float u_environmentFogHeightFalloff;
706
+ uniform float u_volumetricIntensity;
707
+ uniform vec3 u_volumetricLightDirection;
708
+ uniform vec3 u_volumetricLightColor;
605
709
  uniform float u_environmentFogHeightReference;
606
710
  uniform float u_environmentFogMaxOpacity;
607
711
  in vec3 v_normal;
@@ -649,6 +753,17 @@ vec4 a3dPbrEnvironmentSampleRaw(vec3 direction, float lod) {
649
753
  vec4 cubeSample = textureLod(u_environmentCubeMapTexture, a3dEnvironmentCubeDirection(direction, u_environmentMapTextureRotation), lod);
650
754
  return mix(equirectSample, cubeSample, step(0.5, u_environmentCubeMapTextureEnabled));
651
755
  }
756
+ /**
757
+ * Procedural wrinkle detail (E1 face-rig demo): high-frequency normal perturbation driven
758
+ * by u_wrinkleStrength, which the engine resolves per frame from live morph weights
759
+ * (resolveWrinkleMapStrength). Stable in world space (no time input); strength 0 uploads
760
+ * 0 and the guarded call site below leaves the normal bit-identical.
761
+ */
762
+ vec3 a3dWrinkleDetail(vec3 p) {
763
+ float f = sin(p.x * 230.0) * sin(p.y * 197.0 + 1.7) * sin(p.z * 211.0 + 0.6);
764
+ float g = sin(p.x * 97.0 + 0.4) * sin(p.y * 113.0 + 2.1) * sin(p.z * 103.0 + 1.2);
765
+ return vec3(f, g, f * g) * 0.35;
766
+ }
652
767
  vec3 a3dSkinnedPbrNormal(vec3 baseNormal, vec4 tangentFrame, vec2 uv) {
653
768
  vec3 sampled = texture(u_normalTexture, uv).xyz * 2.0 - 1.0;
654
769
  vec3 n = normalize(baseNormal);
@@ -744,15 +859,68 @@ float a3dPointShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection)
744
859
  float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
745
860
  return mix(1.0, 1.0 - occlusion, clamp(u_pointShadowStrength, 0.0, 1.0));
746
861
  }
862
+ float a3dSpotShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection) {
863
+ if (u_spotShadowMapEnabled < 0.5) return 1.0;
864
+ vec3 toFragment = worldPosition - u_spotShadowLightPosition;
865
+ float distanceToLight = max(length(toFragment), 0.0001);
866
+ if (distanceToLight > u_spotShadowRange) return 1.0;
867
+ vec3 coneAxis = u_spotShadowLightDirection / max(length(u_spotShadowLightDirection), 0.0001);
868
+ if (dot(toFragment / distanceToLight, coneAxis) < cos(u_spotShadowCone.x)) return 1.0;
869
+ vec4 lightPosition = u_spotShadowMatrix * vec4(worldPosition, 1.0);
870
+ vec3 projected = lightPosition.xyz / max(lightPosition.w, 0.0001);
871
+ vec2 uv = projected.xy * 0.5 + 0.5;
872
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 1.0;
873
+ vec3 receiverNormal = normalize(normal);
874
+ vec3 receiverLightDirection = lightDirection / max(length(lightDirection), 0.0001);
875
+ float normalDotLight = clamp(abs(dot(receiverNormal, receiverLightDirection)), 0.0, 1.0);
876
+ // Shared bias-table policy: the same per-sample, tangent-scaled slope bias as the
877
+ // directional and point-cube paths. A centre-only bias under-compensates outer PCF
878
+ // taps on sloped receivers, so each tap scales by its own texel distance.
879
+ float slopeTangent = min(sqrt(max(1.0 - normalDotLight * normalDotLight, 0.0)) / max(normalDotLight, 0.05), 8.0);
880
+ float slopeTexelBias = slopeTangent * u_spotShadowSlopeBias * max(u_spotShadowTexelSize.x, u_spotShadowTexelSize.y);
881
+ float projectedDepth = projected.z * 0.5 + 0.5;
882
+ float shadowed = 0.0;
883
+ float totalWeight = 0.0;
884
+ int sampleCount = clamp(int(u_spotShadowPcfSampleCount), 1, 32);
885
+ for (int i = 0; i < 32; ++i) {
886
+ if (i >= sampleCount) break;
887
+ vec4 sampleData = u_spotShadowPcfSamples[i];
888
+ float weight = max(sampleData.z, 0.0);
889
+ vec2 offset = sampleData.xy * u_spotShadowTexelSize;
890
+ float storedDepth = texture(u_spotShadowMapTexture, uv + offset).r;
891
+ float sampleTexelDistance = max(1.0, length(sampleData.xy));
892
+ float receiverDepth = projectedDepth - u_spotShadowBias - slopeTexelBias * sampleTexelDistance;
893
+ shadowed += (receiverDepth > storedDepth ? 1.0 : 0.0) * weight;
894
+ totalWeight += weight;
895
+ }
896
+ float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
897
+ return mix(1.0, 1.0 - occlusion, clamp(u_spotShadowStrength, 0.0, 1.0));
898
+ }
899
+ float a3dResolveSpotShadowOverride(float baseFactor, float kind, float shadowFlag, vec3 worldPosition, vec3 normal, vec3 lightDirection) {
900
+ // Spot-kind lights (kind == 2) sample the perspective spot map only when it is
901
+ // bound. Every gate term is 0 when no spot map is present, so mix() returns
902
+ // baseFactor exactly and scenes without spot lights render unchanged.
903
+ float spotGate = step(1.5, kind) * (1.0 - step(2.5, kind)) * step(0.5, u_spotShadowMapEnabled) * step(0.5, shadowFlag);
904
+ return mix(baseFactor, a3dSpotShadowFactor(worldPosition, normal, lightDirection), spotGate);
905
+ }
906
+ vec3 a3dLinearToSrgb(vec3 linear) {
907
+ vec3 clamped = max(linear, vec3(0.0));
908
+ vec3 low = clamped * 12.92;
909
+ vec3 high = 1.055 * pow(clamped, vec3(1.0 / 2.4)) - 0.055;
910
+ return mix(low, high, step(vec3(0.0031308), clamped));
911
+ }
747
912
  vec3 a3dPbrEncodeOutput(vec3 linearColor) {
748
913
  vec3 color = max(linearColor, vec3(0.0));
749
914
  vec3 filmic = clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));
750
- vec3 srgb = pow(filmic, vec3(1.0 / 2.2));
915
+ vec3 srgb = a3dLinearToSrgb(filmic);
751
916
  return mix(color, srgb, step(0.5, u_outputColorSpace));
752
917
  }
753
918
  void main() {
754
919
  vec3 normal = normalize(v_normal);
755
920
  normal = mix(normal, a3dSkinnedPbrNormal(v_normal, v_tangent, v_uv), step(0.5, u_normalTextureEnabled));
921
+ if (u_wrinkleStrength > 0.0) {
922
+ normal = normalize(normal + u_wrinkleStrength * a3dWrinkleDetail(v_worldPosition));
923
+ }
756
924
  if (!gl_FrontFacing) normal = -normal;
757
925
  vec3 viewDirection = normalize(u_cameraPosition - v_worldPosition);
758
926
  vec4 sampledBaseColor = texture(u_baseColorTexture, v_uv);
@@ -822,11 +990,11 @@ void main() {
822
990
  float nDotV = clamp(dot(normal, viewDirection), 0.0, 1.0);
823
991
  vec2 brdfLut = texture(u_environmentBrdfLutTexture, vec2(nDotV, clampedRoughness)).rg;
824
992
  sampledSpecular = a3dPbrClampSampledSpecularEdgeEnergy(sampledSpecular, nDotV, clampedRoughness);
825
- sampledSpecular *= u_environmentMapTextureSpecularIntensity * sampledEnvironmentWeight * mix(1.1, 0.65, roughness);
993
+ sampledSpecular *= u_environmentMapTextureSpecularIntensity * u_materialEnvironmentIntensity * sampledEnvironmentWeight * mix(1.1, 0.65, roughness);
826
994
  vec3 shaded = a3dPbrEnvironmentLightSplitSum(
827
995
  normal,
828
996
  viewDirection,
829
- mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity, sampledEnvironmentWeight),
997
+ mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity * u_materialEnvironmentIntensity, sampledEnvironmentWeight),
830
998
  proceduralSpecular + sampledSpecular,
831
999
  mix(vec2(1.0, 0.0), brdfLut, step(0.0001, u_environmentBrdfLutEnabled)),
832
1000
  materialBase,
@@ -881,7 +1049,7 @@ void main() {
881
1049
  viewDirection,
882
1050
  lightDirection,
883
1051
  colorIntensity.rgb,
884
- colorIntensity.a * attenuation * mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, normal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, normal, lightDirection), step(0.5, spotShadowLayer.z)),
1052
+ colorIntensity.a * attenuation * a3dResolveSpotShadowOverride(mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, normal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, normal, lightDirection), step(0.5, spotShadowLayer.z)), kind, spotShadowLayer.z, v_worldPosition, normal, lightDirection),
885
1053
  materialBase,
886
1054
  metallic,
887
1055
  roughness,
@@ -987,6 +1155,7 @@ uniform float u_baseColorTextureEnabled;
987
1155
  uniform sampler2D u_normalTexture;
988
1156
  uniform float u_normalTextureEnabled;
989
1157
  uniform float u_normalScale;
1158
+ uniform float u_wrinkleStrength;
990
1159
  uniform sampler2D u_metallicRoughnessTexture;
991
1160
  uniform float u_metallicRoughnessTextureEnabled;
992
1161
  uniform sampler2D u_occlusionTexture;
@@ -999,6 +1168,7 @@ uniform float u_metallic;
999
1168
  uniform float u_roughness;
1000
1169
  uniform vec3 u_environmentColor;
1001
1170
  uniform float u_environmentIntensity;
1171
+ uniform float u_materialEnvironmentIntensity;
1002
1172
  uniform vec3 u_environmentSkyColor;
1003
1173
  uniform vec3 u_environmentHorizonColor;
1004
1174
  uniform vec3 u_environmentGroundColor;
@@ -1072,6 +1242,19 @@ uniform float u_pointShadowSlopeBias;
1072
1242
  uniform vec2 u_pointShadowTexelSize;
1073
1243
  uniform float u_pointShadowPcfSampleCount;
1074
1244
  uniform vec4 u_pointShadowPcfSamples[32];
1245
+ uniform sampler2D u_spotShadowMapTexture;
1246
+ uniform float u_spotShadowMapEnabled;
1247
+ uniform vec3 u_spotShadowLightPosition;
1248
+ uniform vec3 u_spotShadowLightDirection;
1249
+ uniform mat4 u_spotShadowMatrix;
1250
+ uniform vec2 u_spotShadowCone;
1251
+ uniform float u_spotShadowRange;
1252
+ uniform float u_spotShadowStrength;
1253
+ uniform float u_spotShadowBias;
1254
+ uniform float u_spotShadowSlopeBias;
1255
+ uniform vec2 u_spotShadowTexelSize;
1256
+ uniform float u_spotShadowPcfSampleCount;
1257
+ uniform vec4 u_spotShadowPcfSamples[32];
1075
1258
  uniform float u_outputColorSpace;
1076
1259
  uniform vec3 u_cameraPosition;
1077
1260
  uniform float u_environmentFogEnabled;
@@ -1081,6 +1264,9 @@ uniform float u_environmentFogNear;
1081
1264
  uniform float u_environmentFogFar;
1082
1265
  uniform float u_environmentFogDensity;
1083
1266
  uniform float u_environmentFogHeightFalloff;
1267
+ uniform float u_volumetricIntensity;
1268
+ uniform vec3 u_volumetricLightDirection;
1269
+ uniform vec3 u_volumetricLightColor;
1084
1270
  uniform float u_environmentFogHeightReference;
1085
1271
  uniform float u_environmentFogMaxOpacity;
1086
1272
  in vec3 v_normal;
@@ -1128,6 +1314,17 @@ vec4 a3dPbrEnvironmentSampleRaw(vec3 direction, float lod) {
1128
1314
  vec4 cubeSample = textureLod(u_environmentCubeMapTexture, a3dEnvironmentCubeDirection(direction, u_environmentMapTextureRotation), lod);
1129
1315
  return mix(equirectSample, cubeSample, step(0.5, u_environmentCubeMapTextureEnabled));
1130
1316
  }
1317
+ /**
1318
+ * Procedural wrinkle detail (E1 face-rig demo): high-frequency normal perturbation driven
1319
+ * by u_wrinkleStrength, which the engine resolves per frame from live morph weights
1320
+ * (resolveWrinkleMapStrength). Stable in world space (no time input); strength 0 uploads
1321
+ * 0 and the guarded call site below leaves the normal bit-identical.
1322
+ */
1323
+ vec3 a3dWrinkleDetail(vec3 p) {
1324
+ float f = sin(p.x * 230.0) * sin(p.y * 197.0 + 1.7) * sin(p.z * 211.0 + 0.6);
1325
+ float g = sin(p.x * 97.0 + 0.4) * sin(p.y * 113.0 + 2.1) * sin(p.z * 103.0 + 1.2);
1326
+ return vec3(f, g, f * g) * 0.35;
1327
+ }
1131
1328
  vec3 a3dSkinnedPbrNormal(vec3 baseNormal, vec4 tangentFrame, vec2 uv) {
1132
1329
  vec3 sampled = texture(u_normalTexture, uv).xyz * 2.0 - 1.0;
1133
1330
  vec3 n = normalize(baseNormal);
@@ -1223,15 +1420,68 @@ float a3dPointShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection)
1223
1420
  float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
1224
1421
  return mix(1.0, 1.0 - occlusion, clamp(u_pointShadowStrength, 0.0, 1.0));
1225
1422
  }
1423
+ float a3dSpotShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection) {
1424
+ if (u_spotShadowMapEnabled < 0.5) return 1.0;
1425
+ vec3 toFragment = worldPosition - u_spotShadowLightPosition;
1426
+ float distanceToLight = max(length(toFragment), 0.0001);
1427
+ if (distanceToLight > u_spotShadowRange) return 1.0;
1428
+ vec3 coneAxis = u_spotShadowLightDirection / max(length(u_spotShadowLightDirection), 0.0001);
1429
+ if (dot(toFragment / distanceToLight, coneAxis) < cos(u_spotShadowCone.x)) return 1.0;
1430
+ vec4 lightPosition = u_spotShadowMatrix * vec4(worldPosition, 1.0);
1431
+ vec3 projected = lightPosition.xyz / max(lightPosition.w, 0.0001);
1432
+ vec2 uv = projected.xy * 0.5 + 0.5;
1433
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 1.0;
1434
+ vec3 receiverNormal = normalize(normal);
1435
+ vec3 receiverLightDirection = lightDirection / max(length(lightDirection), 0.0001);
1436
+ float normalDotLight = clamp(abs(dot(receiverNormal, receiverLightDirection)), 0.0, 1.0);
1437
+ // Shared bias-table policy: the same per-sample, tangent-scaled slope bias as the
1438
+ // directional and point-cube paths. A centre-only bias under-compensates outer PCF
1439
+ // taps on sloped receivers, so each tap scales by its own texel distance.
1440
+ float slopeTangent = min(sqrt(max(1.0 - normalDotLight * normalDotLight, 0.0)) / max(normalDotLight, 0.05), 8.0);
1441
+ float slopeTexelBias = slopeTangent * u_spotShadowSlopeBias * max(u_spotShadowTexelSize.x, u_spotShadowTexelSize.y);
1442
+ float projectedDepth = projected.z * 0.5 + 0.5;
1443
+ float shadowed = 0.0;
1444
+ float totalWeight = 0.0;
1445
+ int sampleCount = clamp(int(u_spotShadowPcfSampleCount), 1, 32);
1446
+ for (int i = 0; i < 32; ++i) {
1447
+ if (i >= sampleCount) break;
1448
+ vec4 sampleData = u_spotShadowPcfSamples[i];
1449
+ float weight = max(sampleData.z, 0.0);
1450
+ vec2 offset = sampleData.xy * u_spotShadowTexelSize;
1451
+ float storedDepth = texture(u_spotShadowMapTexture, uv + offset).r;
1452
+ float sampleTexelDistance = max(1.0, length(sampleData.xy));
1453
+ float receiverDepth = projectedDepth - u_spotShadowBias - slopeTexelBias * sampleTexelDistance;
1454
+ shadowed += (receiverDepth > storedDepth ? 1.0 : 0.0) * weight;
1455
+ totalWeight += weight;
1456
+ }
1457
+ float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
1458
+ return mix(1.0, 1.0 - occlusion, clamp(u_spotShadowStrength, 0.0, 1.0));
1459
+ }
1460
+ float a3dResolveSpotShadowOverride(float baseFactor, float kind, float shadowFlag, vec3 worldPosition, vec3 normal, vec3 lightDirection) {
1461
+ // Spot-kind lights (kind == 2) sample the perspective spot map only when it is
1462
+ // bound. Every gate term is 0 when no spot map is present, so mix() returns
1463
+ // baseFactor exactly and scenes without spot lights render unchanged.
1464
+ float spotGate = step(1.5, kind) * (1.0 - step(2.5, kind)) * step(0.5, u_spotShadowMapEnabled) * step(0.5, shadowFlag);
1465
+ return mix(baseFactor, a3dSpotShadowFactor(worldPosition, normal, lightDirection), spotGate);
1466
+ }
1467
+ vec3 a3dLinearToSrgb(vec3 linear) {
1468
+ vec3 clamped = max(linear, vec3(0.0));
1469
+ vec3 low = clamped * 12.92;
1470
+ vec3 high = 1.055 * pow(clamped, vec3(1.0 / 2.4)) - 0.055;
1471
+ return mix(low, high, step(vec3(0.0031308), clamped));
1472
+ }
1226
1473
  vec3 a3dPbrEncodeOutput(vec3 linearColor) {
1227
1474
  vec3 color = max(linearColor, vec3(0.0));
1228
1475
  vec3 filmic = clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));
1229
- vec3 srgb = pow(filmic, vec3(1.0 / 2.2));
1476
+ vec3 srgb = a3dLinearToSrgb(filmic);
1230
1477
  return mix(color, srgb, step(0.5, u_outputColorSpace));
1231
1478
  }
1232
1479
  void main() {
1233
1480
  vec3 normal = normalize(v_normal);
1234
1481
  normal = mix(normal, a3dSkinnedPbrNormal(v_normal, v_tangent, v_uv), step(0.5, u_normalTextureEnabled));
1482
+ if (u_wrinkleStrength > 0.0) {
1483
+ normal = normalize(normal + u_wrinkleStrength * a3dWrinkleDetail(v_worldPosition));
1484
+ }
1235
1485
  if (!gl_FrontFacing) normal = -normal;
1236
1486
  vec3 viewDirection = normalize(u_cameraPosition - v_worldPosition);
1237
1487
  vec4 sampledBaseColor = texture(u_baseColorTexture, v_uv);
@@ -1301,11 +1551,11 @@ void main() {
1301
1551
  float nDotV = clamp(dot(normal, viewDirection), 0.0, 1.0);
1302
1552
  vec2 brdfLut = texture(u_environmentBrdfLutTexture, vec2(nDotV, clampedRoughness)).rg;
1303
1553
  sampledSpecular = a3dPbrClampSampledSpecularEdgeEnergy(sampledSpecular, nDotV, clampedRoughness);
1304
- sampledSpecular *= u_environmentMapTextureSpecularIntensity * sampledEnvironmentWeight * mix(1.1, 0.65, roughness);
1554
+ sampledSpecular *= u_environmentMapTextureSpecularIntensity * u_materialEnvironmentIntensity * sampledEnvironmentWeight * mix(1.1, 0.65, roughness);
1305
1555
  vec3 shaded = a3dPbrEnvironmentLightSplitSum(
1306
1556
  normal,
1307
1557
  viewDirection,
1308
- mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity, sampledEnvironmentWeight),
1558
+ mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity * u_materialEnvironmentIntensity, sampledEnvironmentWeight),
1309
1559
  proceduralSpecular + sampledSpecular,
1310
1560
  mix(vec2(1.0, 0.0), brdfLut, step(0.0001, u_environmentBrdfLutEnabled)),
1311
1561
  materialBase,
@@ -1360,7 +1610,7 @@ void main() {
1360
1610
  viewDirection,
1361
1611
  lightDirection,
1362
1612
  colorIntensity.rgb,
1363
- colorIntensity.a * attenuation * mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, normal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, normal, lightDirection), step(0.5, spotShadowLayer.z)),
1613
+ colorIntensity.a * attenuation * a3dResolveSpotShadowOverride(mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, normal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, normal, lightDirection), step(0.5, spotShadowLayer.z)), kind, spotShadowLayer.z, v_worldPosition, normal, lightDirection),
1364
1614
  materialBase,
1365
1615
  metallic,
1366
1616
  roughness,
@@ -1443,21 +1693,45 @@ layout(location = 2) in vec2 a_uv;
1443
1693
  layout(location = 3) in vec4 a_tangent;
1444
1694
  layout(location = 4) in vec4 a_color;
1445
1695
  layout(location = 7) in vec2 a_uv1;
1696
+ layout(location = 8) in vec4 a_instanceMatrix0;
1697
+ layout(location = 9) in vec4 a_instanceMatrix1;
1698
+ layout(location = 10) in vec4 a_instanceMatrix2;
1699
+ layout(location = 11) in vec4 a_instanceMatrix3;
1700
+ layout(location = 12) in vec4 a_instanceColor;
1446
1701
  uniform mat4 u_modelViewProjection;
1447
1702
  uniform mat4 u_modelMatrix;
1448
1703
  uniform mat4 u_normalMatrix;
1704
+ uniform mat4 u_instanceMatrices[64];
1705
+ uniform float u_instanceCount;
1706
+ uniform float u_instanceAttributeMode;
1449
1707
  out vec3 v_normal;
1450
1708
  out vec4 v_tangent;
1451
1709
  out vec3 v_worldPosition;
1452
1710
  out vec2 v_uv;
1453
1711
  out vec4 v_vertexColor;
1454
1712
  void main() {
1455
- v_normal = mat3(u_normalMatrix) * a_normal;
1456
- v_tangent = vec4(mat3(u_normalMatrix) * a_tangent.xyz, a_tangent.w);
1457
- v_worldPosition = (u_modelMatrix * vec4(a_position, 1.0)).xyz;
1458
- v_uv = a_uv;
1459
- v_vertexColor = a_color;
1460
- gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
1713
+ // P2 instanced-GLB path (muse3jsparity-PRD): the u_instanceCount branch is
1714
+ // taken only for items carrying instanceTransforms; every other draw keeps
1715
+ // the legacy math bit-exact. Mirrors the instanced-PBR vertex convention.
1716
+ if (u_instanceCount > 0.5) {
1717
+ int instanceIndex = clamp(gl_InstanceID, 0, max(int(u_instanceCount) - 1, 0));
1718
+ mat4 attributeMatrix = mat4(a_instanceMatrix0, a_instanceMatrix1, a_instanceMatrix2, a_instanceMatrix3);
1719
+ mat4 instanceMatrix = u_instanceAttributeMode > 0.5 ? attributeMatrix : u_instanceMatrices[instanceIndex];
1720
+ vec4 worldPosition = u_modelMatrix * instanceMatrix * vec4(a_position, 1.0);
1721
+ v_normal = mat3(u_normalMatrix) * transpose(inverse(mat3(instanceMatrix))) * a_normal;
1722
+ v_tangent = vec4(mat3(u_normalMatrix) * (mat3(instanceMatrix) * a_tangent.xyz), a_tangent.w);
1723
+ v_worldPosition = worldPosition.xyz;
1724
+ v_uv = a_uv;
1725
+ v_vertexColor = a_color * a_instanceColor;
1726
+ gl_Position = u_modelViewProjection * instanceMatrix * vec4(a_position, 1.0);
1727
+ } else {
1728
+ v_normal = mat3(u_normalMatrix) * a_normal;
1729
+ v_tangent = vec4(mat3(u_normalMatrix) * a_tangent.xyz, a_tangent.w);
1730
+ v_worldPosition = (u_modelMatrix * vec4(a_position, 1.0)).xyz;
1731
+ v_uv = a_uv;
1732
+ v_vertexColor = a_color;
1733
+ gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
1734
+ }
1461
1735
  }
1462
1736
  `,
1463
1737
  fragment: `#version 300 es
@@ -1471,6 +1745,7 @@ uniform float u_metallic;
1471
1745
  uniform float u_roughness;
1472
1746
  uniform vec3 u_environmentColor;
1473
1747
  uniform float u_environmentIntensity;
1748
+ uniform float u_materialEnvironmentIntensity;
1474
1749
  uniform vec3 u_environmentSkyColor;
1475
1750
  uniform vec3 u_environmentHorizonColor;
1476
1751
  uniform vec3 u_environmentGroundColor;
@@ -1539,6 +1814,19 @@ uniform float u_pointShadowSlopeBias;
1539
1814
  uniform vec2 u_pointShadowTexelSize;
1540
1815
  uniform float u_pointShadowPcfSampleCount;
1541
1816
  uniform vec4 u_pointShadowPcfSamples[32];
1817
+ uniform sampler2D u_spotShadowMapTexture;
1818
+ uniform float u_spotShadowMapEnabled;
1819
+ uniform vec3 u_spotShadowLightPosition;
1820
+ uniform vec3 u_spotShadowLightDirection;
1821
+ uniform mat4 u_spotShadowMatrix;
1822
+ uniform vec2 u_spotShadowCone;
1823
+ uniform float u_spotShadowRange;
1824
+ uniform float u_spotShadowStrength;
1825
+ uniform float u_spotShadowBias;
1826
+ uniform float u_spotShadowSlopeBias;
1827
+ uniform vec2 u_spotShadowTexelSize;
1828
+ uniform float u_spotShadowPcfSampleCount;
1829
+ uniform vec4 u_spotShadowPcfSamples[32];
1542
1830
  uniform float u_outputColorSpace;
1543
1831
  uniform vec3 u_cameraPosition;
1544
1832
  uniform float u_environmentFogEnabled;
@@ -1548,6 +1836,9 @@ uniform float u_environmentFogNear;
1548
1836
  uniform float u_environmentFogFar;
1549
1837
  uniform float u_environmentFogDensity;
1550
1838
  uniform float u_environmentFogHeightFalloff;
1839
+ uniform float u_volumetricIntensity;
1840
+ uniform vec3 u_volumetricLightDirection;
1841
+ uniform vec3 u_volumetricLightColor;
1551
1842
  uniform float u_environmentFogHeightReference;
1552
1843
  uniform float u_environmentFogMaxOpacity;
1553
1844
  in vec3 v_normal;
@@ -1635,6 +1926,50 @@ float a3dPointShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection)
1635
1926
  float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
1636
1927
  return mix(1.0, 1.0 - occlusion, clamp(u_pointShadowStrength, 0.0, 1.0));
1637
1928
  }
1929
+ float a3dSpotShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection) {
1930
+ if (u_spotShadowMapEnabled < 0.5) return 1.0;
1931
+ vec3 toFragment = worldPosition - u_spotShadowLightPosition;
1932
+ float distanceToLight = max(length(toFragment), 0.0001);
1933
+ if (distanceToLight > u_spotShadowRange) return 1.0;
1934
+ vec3 coneAxis = u_spotShadowLightDirection / max(length(u_spotShadowLightDirection), 0.0001);
1935
+ if (dot(toFragment / distanceToLight, coneAxis) < cos(u_spotShadowCone.x)) return 1.0;
1936
+ vec4 lightPosition = u_spotShadowMatrix * vec4(worldPosition, 1.0);
1937
+ vec3 projected = lightPosition.xyz / max(lightPosition.w, 0.0001);
1938
+ vec2 uv = projected.xy * 0.5 + 0.5;
1939
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 1.0;
1940
+ vec3 receiverNormal = normalize(normal);
1941
+ vec3 receiverLightDirection = lightDirection / max(length(lightDirection), 0.0001);
1942
+ float normalDotLight = clamp(abs(dot(receiverNormal, receiverLightDirection)), 0.0, 1.0);
1943
+ // Shared bias-table policy: the same per-sample, tangent-scaled slope bias as the
1944
+ // directional and point-cube paths. A centre-only bias under-compensates outer PCF
1945
+ // taps on sloped receivers, so each tap scales by its own texel distance.
1946
+ float slopeTangent = min(sqrt(max(1.0 - normalDotLight * normalDotLight, 0.0)) / max(normalDotLight, 0.05), 8.0);
1947
+ float slopeTexelBias = slopeTangent * u_spotShadowSlopeBias * max(u_spotShadowTexelSize.x, u_spotShadowTexelSize.y);
1948
+ float projectedDepth = projected.z * 0.5 + 0.5;
1949
+ float shadowed = 0.0;
1950
+ float totalWeight = 0.0;
1951
+ int sampleCount = clamp(int(u_spotShadowPcfSampleCount), 1, 32);
1952
+ for (int i = 0; i < 32; ++i) {
1953
+ if (i >= sampleCount) break;
1954
+ vec4 sampleData = u_spotShadowPcfSamples[i];
1955
+ float weight = max(sampleData.z, 0.0);
1956
+ vec2 offset = sampleData.xy * u_spotShadowTexelSize;
1957
+ float storedDepth = texture(u_spotShadowMapTexture, uv + offset).r;
1958
+ float sampleTexelDistance = max(1.0, length(sampleData.xy));
1959
+ float receiverDepth = projectedDepth - u_spotShadowBias - slopeTexelBias * sampleTexelDistance;
1960
+ shadowed += (receiverDepth > storedDepth ? 1.0 : 0.0) * weight;
1961
+ totalWeight += weight;
1962
+ }
1963
+ float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
1964
+ return mix(1.0, 1.0 - occlusion, clamp(u_spotShadowStrength, 0.0, 1.0));
1965
+ }
1966
+ float a3dResolveSpotShadowOverride(float baseFactor, float kind, float shadowFlag, vec3 worldPosition, vec3 normal, vec3 lightDirection) {
1967
+ // Spot-kind lights (kind == 2) sample the perspective spot map only when it is
1968
+ // bound. Every gate term is 0 when no spot map is present, so mix() returns
1969
+ // baseFactor exactly and scenes without spot lights render unchanged.
1970
+ float spotGate = step(1.5, kind) * (1.0 - step(2.5, kind)) * step(0.5, u_spotShadowMapEnabled) * step(0.5, shadowFlag);
1971
+ return mix(baseFactor, a3dSpotShadowFactor(worldPosition, normal, lightDirection), spotGate);
1972
+ }
1638
1973
  vec2 a3dEnvironmentEquirectUv(vec3 direction, float rotation) {
1639
1974
  vec3 d = normalize(direction);
1640
1975
  float u = atan(d.z, d.x) / 6.28318530718 + 0.5 + rotation;
@@ -1689,7 +2024,7 @@ vec3 a3dPbrEnvironmentDiffuseInput(vec3 normal) {
1689
2024
  float sampledEnvironmentWeight = step(0.0001, u_environmentMapTextureEnabled * u_environmentMapTextureIntensity);
1690
2025
  float diffuseEnvironmentLod = max(u_environmentMapTextureMipCount - 1.0, 0.0);
1691
2026
  vec3 sampledDiffuse = a3dPbrDecodeEnvironmentSample(a3dPbrEnvironmentSampleRaw(normal, diffuseEnvironmentLod));
1692
- return mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity, sampledEnvironmentWeight);
2027
+ return mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity * u_materialEnvironmentIntensity, sampledEnvironmentWeight);
1693
2028
  }
1694
2029
  vec3 a3dPbrEnvironmentSpecularInput(vec3 normal, vec3 viewDirection, float roughness) {
1695
2030
  float proceduralEnvironmentWeight = step(0.0001, u_environmentMapIntensity);
@@ -1704,7 +2039,7 @@ vec3 a3dPbrEnvironmentSpecularInput(vec3 normal, vec3 viewDirection, float rough
1704
2039
  vec3 sampledSpecular = a3dPbrBoundHdrSpecularRadiance(a3dPbrDecodeEnvironmentSample(a3dPbrEnvironmentSampleRaw(reflectionDirection, environmentLod)));
1705
2040
  float nDotV = clamp(dot(normal, viewDirection), 0.0, 1.0);
1706
2041
  sampledSpecular = a3dPbrClampSampledSpecularEdgeEnergy(sampledSpecular, nDotV, clampedRoughness);
1707
- sampledSpecular *= u_environmentMapTextureSpecularIntensity * sampledEnvironmentWeight * mix(0.84, 0.58, clampedRoughness);
2042
+ sampledSpecular *= u_environmentMapTextureSpecularIntensity * u_materialEnvironmentIntensity * sampledEnvironmentWeight * mix(0.84, 0.58, clampedRoughness);
1708
2043
  return proceduralSpecular + sampledSpecular;
1709
2044
  }
1710
2045
  vec2 a3dPbrEnvironmentBrdfInput(vec3 normal, vec3 viewDirection, float roughness) {
@@ -1712,10 +2047,16 @@ vec2 a3dPbrEnvironmentBrdfInput(vec3 normal, vec3 viewDirection, float roughness
1712
2047
  vec2 lut = texture(u_environmentBrdfLutTexture, vec2(nDotV, clamp(roughness, 0.0, 1.0))).rg;
1713
2048
  return mix(vec2(1.0, 0.0), lut, step(0.0001, u_environmentBrdfLutEnabled));
1714
2049
  }
2050
+ vec3 a3dLinearToSrgb(vec3 linear) {
2051
+ vec3 clamped = max(linear, vec3(0.0));
2052
+ vec3 low = clamped * 12.92;
2053
+ vec3 high = 1.055 * pow(clamped, vec3(1.0 / 2.4)) - 0.055;
2054
+ return mix(low, high, step(vec3(0.0031308), clamped));
2055
+ }
1715
2056
  vec3 a3dPbrEncodeOutput(vec3 linearColor) {
1716
2057
  vec3 color = max(linearColor, vec3(0.0));
1717
2058
  vec3 filmic = clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));
1718
- vec3 srgb = pow(filmic, vec3(1.0 / 2.2));
2059
+ vec3 srgb = a3dLinearToSrgb(filmic);
1719
2060
  return mix(color, srgb, step(0.5, u_outputColorSpace));
1720
2061
  }
1721
2062
  void main() {
@@ -1776,7 +2117,7 @@ void main() {
1776
2117
  float inner = cos(spotShadowLayer.x * max(1.0 - spotShadowLayer.y, 0.001));
1777
2118
  attenuation *= smoothstep(outer, inner, cone);
1778
2119
  }
1779
- float shadowFactor = mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, mappedNormal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, mappedNormal, lightDirection), step(0.5, spotShadowLayer.z));
2120
+ float shadowFactor = a3dResolveSpotShadowOverride(mix(1.0, kind > 0.5 && kind < 1.5 ? a3dPointShadowFactor(v_worldPosition, mappedNormal, lightDirection) : a3dForwardShadowFactor(v_worldPosition, mappedNormal, lightDirection), step(0.5, spotShadowLayer.z)), kind, spotShadowLayer.z, v_worldPosition, mappedNormal, lightDirection);
1780
2121
  shaded += a3dPbrDirectLight(mappedNormal, viewDirection, lightDirection, colorIntensity.rgb, colorIntensity.a * attenuation * shadowFactor, materialBase, u_metallic, u_roughness, u_specularFactor, u_specularColorFactor);
1781
2122
  }
1782
2123
  float alpha = u_baseColor.a * v_vertexColor.a;
@@ -1794,9 +2135,9 @@ void main() {
1794
2135
  { name: DEFAULT_TEXTURED_PBR_TRANSMISSION_VOLUME_TEXTURES_VARIANT, defines: { A3D_PBR_TRANSMISSION_VOLUME_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true } },
1795
2136
  { name: DEFAULT_TEXTURED_PBR_SPECULAR_SHEEN_ANISOTROPY_TEXTURES_VARIANT, defines: { A3D_PBR_SPECULAR_TEXTURES: true, A3D_PBR_SPECULAR_SHEEN_ANISOTROPY_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true } },
1796
2137
  { name: DEFAULT_TEXTURED_PBR_IRIDESCENCE_TEXTURES_VARIANT, defines: { A3D_PBR_IRIDESCENCE_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true } },
1797
- { name: DEFAULT_TEXTURED_PBR_CLEARCOAT_TRANSMISSION_VOLUME_TEXTURES_VARIANT, defines: { A3D_PBR_CLEARCOAT_TEXTURES: true, A3D_PBR_TRANSMISSION_VOLUME_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true } },
2138
+ { name: DEFAULT_TEXTURED_PBR_CLEARCOAT_TRANSMISSION_VOLUME_TEXTURES_VARIANT, defines: { A3D_PBR_CLEARCOAT_TEXTURES: true, A3D_PBR_TRANSMISSION_VOLUME_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true, A3D_PBR_NO_SPOT_SHADOW: true } },
1798
2139
  { name: DEFAULT_TEXTURED_PBR_CLEARCOAT_SPECULAR_TEXTURES_VARIANT, defines: { A3D_PBR_CLEARCOAT_TEXTURES: true, A3D_PBR_SPECULAR_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true } },
1799
- { name: DEFAULT_TEXTURED_PBR_SPECULAR_SHEEN_ANISOTROPY_IRIDESCENCE_TEXTURES_VARIANT, defines: { A3D_PBR_SPECULAR_TEXTURES: true, A3D_PBR_SPECULAR_SHEEN_ANISOTROPY_TEXTURES: true, A3D_PBR_IRIDESCENCE_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true } }
2140
+ { name: DEFAULT_TEXTURED_PBR_SPECULAR_SHEEN_ANISOTROPY_IRIDESCENCE_TEXTURES_VARIANT, defines: { A3D_PBR_SPECULAR_TEXTURES: true, A3D_PBR_SPECULAR_SHEEN_ANISOTROPY_TEXTURES: true, A3D_PBR_IRIDESCENCE_TEXTURES: true, A3D_PBR_DISABLE_TRANSMISSION_BACKDROP: true, A3D_PBR_DISABLE_CLUSTERED_LIGHTING: true, A3D_PBR_NO_SPOT_SHADOW: true } },
1800
2141
  ],
1801
2142
  vertex: `#version 300 es
1802
2143
  // ${DEFAULT_TEXTURED_PBR_SHADER_MARKER}
@@ -1807,9 +2148,17 @@ layout(location = 2) in vec2 a_uv;
1807
2148
  layout(location = 3) in vec4 a_tangent;
1808
2149
  layout(location = 4) in vec4 a_color;
1809
2150
  layout(location = 7) in vec2 a_uv1;
2151
+ layout(location = 8) in vec4 a_instanceMatrix0;
2152
+ layout(location = 9) in vec4 a_instanceMatrix1;
2153
+ layout(location = 10) in vec4 a_instanceMatrix2;
2154
+ layout(location = 11) in vec4 a_instanceMatrix3;
2155
+ layout(location = 12) in vec4 a_instanceColor;
1810
2156
  uniform mat4 u_modelViewProjection;
1811
2157
  uniform mat4 u_modelMatrix;
1812
2158
  uniform mat4 u_normalMatrix;
2159
+ uniform mat4 u_instanceMatrices[64];
2160
+ uniform float u_instanceCount;
2161
+ uniform float u_instanceAttributeMode;
1813
2162
  out vec3 v_normal;
1814
2163
  out vec4 v_tangent;
1815
2164
  out vec3 v_worldPosition;
@@ -1819,11 +2168,27 @@ out vec4 v_vertexColor;
1819
2168
  void main() {
1820
2169
  v_uv = a_uv;
1821
2170
  v_uv1 = a_uv1;
1822
- v_normal = mat3(u_normalMatrix) * a_normal;
1823
- v_tangent = vec4(mat3(u_normalMatrix) * a_tangent.xyz, a_tangent.w);
1824
- v_worldPosition = (u_modelMatrix * vec4(a_position, 1.0)).xyz;
1825
- v_vertexColor = a_color;
1826
- gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
2171
+ // P2 instanced-GLB path (muse3jsparity-PRD): the u_instanceCount branch is
2172
+ // taken only for items carrying instanceTransforms (applyInstanceBinding
2173
+ // stamps a count >= 1); every other draw keeps the legacy math bit-exact.
2174
+ // Mirrors the instanced-PBR vertex convention (MVP carries the base model).
2175
+ if (u_instanceCount > 0.5) {
2176
+ int instanceIndex = clamp(gl_InstanceID, 0, max(int(u_instanceCount) - 1, 0));
2177
+ mat4 attributeMatrix = mat4(a_instanceMatrix0, a_instanceMatrix1, a_instanceMatrix2, a_instanceMatrix3);
2178
+ mat4 instanceMatrix = u_instanceAttributeMode > 0.5 ? attributeMatrix : u_instanceMatrices[instanceIndex];
2179
+ vec4 worldPosition = u_modelMatrix * instanceMatrix * vec4(a_position, 1.0);
2180
+ v_normal = mat3(u_normalMatrix) * transpose(inverse(mat3(instanceMatrix))) * a_normal;
2181
+ v_tangent = vec4(mat3(u_normalMatrix) * (mat3(instanceMatrix) * a_tangent.xyz), a_tangent.w);
2182
+ v_worldPosition = worldPosition.xyz;
2183
+ v_vertexColor = a_color * a_instanceColor;
2184
+ gl_Position = u_modelViewProjection * instanceMatrix * vec4(a_position, 1.0);
2185
+ } else {
2186
+ v_normal = mat3(u_normalMatrix) * a_normal;
2187
+ v_tangent = vec4(mat3(u_normalMatrix) * a_tangent.xyz, a_tangent.w);
2188
+ v_worldPosition = (u_modelMatrix * vec4(a_position, 1.0)).xyz;
2189
+ v_vertexColor = a_color;
2190
+ gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
2191
+ }
1827
2192
  }
1828
2193
  `,
1829
2194
  fragment: `#version 300 es
@@ -1837,6 +2202,7 @@ uniform float u_metallic;
1837
2202
  uniform float u_roughness;
1838
2203
  uniform vec3 u_environmentColor;
1839
2204
  uniform float u_environmentIntensity;
2205
+ uniform float u_materialEnvironmentIntensity;
1840
2206
  uniform vec3 u_environmentSkyColor;
1841
2207
  uniform vec3 u_environmentHorizonColor;
1842
2208
  uniform vec3 u_environmentGroundColor;
@@ -1921,6 +2287,26 @@ uniform float u_pointShadowSlopeBias;
1921
2287
  uniform vec2 u_pointShadowTexelSize;
1922
2288
  uniform float u_pointShadowPcfSampleCount;
1923
2289
  uniform vec4 u_pointShadowPcfSamples[32];
2290
+ // B1 spot path (muse3jsparity-PRD): the two A3D_PBR_NO_SPOT_SHADOW variants
2291
+ // (clearcoat-transmission-volume, specular-sheen-anisotropy-iridescence) sit at
2292
+ // the 16-sampler WebGL2 minimum without it, so they sample spot lights through
2293
+ // the legacy directional factor while every other lit program takes the spot
2294
+ // override. ForwardPass binds spot uniforms only when reflection declares them.
2295
+ #ifndef A3D_PBR_NO_SPOT_SHADOW
2296
+ uniform sampler2D u_spotShadowMapTexture;
2297
+ uniform float u_spotShadowMapEnabled;
2298
+ uniform vec3 u_spotShadowLightPosition;
2299
+ uniform vec3 u_spotShadowLightDirection;
2300
+ uniform mat4 u_spotShadowMatrix;
2301
+ uniform vec2 u_spotShadowCone;
2302
+ uniform float u_spotShadowRange;
2303
+ uniform float u_spotShadowStrength;
2304
+ uniform float u_spotShadowBias;
2305
+ uniform float u_spotShadowSlopeBias;
2306
+ uniform vec2 u_spotShadowTexelSize;
2307
+ uniform float u_spotShadowPcfSampleCount;
2308
+ uniform vec4 u_spotShadowPcfSamples[32];
2309
+ #endif
1924
2310
  uniform sampler2D u_baseColorTexture;
1925
2311
  uniform float u_baseColorTextureEnabled;
1926
2312
  uniform vec2 u_baseColorTextureOffset;
@@ -2076,6 +2462,9 @@ uniform float u_environmentFogNear;
2076
2462
  uniform float u_environmentFogFar;
2077
2463
  uniform float u_environmentFogDensity;
2078
2464
  uniform float u_environmentFogHeightFalloff;
2465
+ uniform float u_volumetricIntensity;
2466
+ uniform vec3 u_volumetricLightDirection;
2467
+ uniform vec3 u_volumetricLightColor;
2079
2468
  uniform float u_environmentFogHeightReference;
2080
2469
  uniform float u_environmentFogMaxOpacity;
2081
2470
  in vec3 v_normal;
@@ -2110,10 +2499,16 @@ vec2 a3dTexturedPbrWrapUv(vec2 uv, vec2 wrapMode) {
2110
2499
  a3dTexturedPbrWrapCoordinate(uv.y, wrapMode.y)
2111
2500
  );
2112
2501
  }
2502
+ vec3 a3dTexturedPbrEncodeLinearToSrgb(vec3 linear) {
2503
+ vec3 clamped = max(linear, vec3(0.0));
2504
+ vec3 low = clamped * 12.92;
2505
+ vec3 high = 1.055 * pow(clamped, vec3(1.0 / 2.4)) - 0.055;
2506
+ return mix(low, high, step(vec3(0.0031308), clamped));
2507
+ }
2113
2508
  vec3 a3dTexturedPbrEncodeOutput(vec3 linearColor) {
2114
2509
  vec3 color = max(linearColor, vec3(0.0));
2115
2510
  vec3 filmic = clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));
2116
- vec3 srgb = pow(filmic, vec3(1.0 / 2.2));
2511
+ vec3 srgb = a3dTexturedPbrEncodeLinearToSrgb(filmic);
2117
2512
  return mix(color, srgb, step(0.5, u_outputColorSpace));
2118
2513
  }
2119
2514
  vec3 a3dTexturedPbrDecodeSrgb(vec3 encodedColor) {
@@ -2220,6 +2615,52 @@ float a3dTexturedPbrPointShadowFactor(vec3 worldPosition, vec3 normal, vec3 ligh
2220
2615
  float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
2221
2616
  return mix(1.0, 1.0 - occlusion, clamp(u_pointShadowStrength, 0.0, 1.0));
2222
2617
  }
2618
+ #ifndef A3D_PBR_NO_SPOT_SHADOW
2619
+ float a3dTexturedPbrSpotShadowFactor(vec3 worldPosition, vec3 normal, vec3 lightDirection) {
2620
+ if (u_spotShadowMapEnabled < 0.5) return 1.0;
2621
+ vec3 toFragment = worldPosition - u_spotShadowLightPosition;
2622
+ float distanceToLight = max(length(toFragment), 0.0001);
2623
+ if (distanceToLight > u_spotShadowRange) return 1.0;
2624
+ vec3 coneAxis = u_spotShadowLightDirection / max(length(u_spotShadowLightDirection), 0.0001);
2625
+ if (dot(toFragment / distanceToLight, coneAxis) < cos(u_spotShadowCone.x)) return 1.0;
2626
+ vec4 lightPosition = u_spotShadowMatrix * vec4(worldPosition, 1.0);
2627
+ vec3 projected = lightPosition.xyz / max(lightPosition.w, 0.0001);
2628
+ vec2 uv = projected.xy * 0.5 + 0.5;
2629
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 1.0;
2630
+ vec3 receiverNormal = normalize(normal);
2631
+ vec3 receiverLightDirection = lightDirection / max(length(lightDirection), 0.0001);
2632
+ float normalDotLight = clamp(abs(dot(receiverNormal, receiverLightDirection)), 0.0, 1.0);
2633
+ // Shared bias-table policy: the same per-sample, tangent-scaled slope bias as the
2634
+ // directional and point-cube paths. A centre-only bias under-compensates outer PCF
2635
+ // taps on sloped receivers, so each tap scales by its own texel distance.
2636
+ float slopeTangent = min(sqrt(max(1.0 - normalDotLight * normalDotLight, 0.0)) / max(normalDotLight, 0.05), 8.0);
2637
+ float slopeTexelBias = slopeTangent * u_spotShadowSlopeBias * max(u_spotShadowTexelSize.x, u_spotShadowTexelSize.y);
2638
+ float projectedDepth = projected.z * 0.5 + 0.5;
2639
+ float shadowed = 0.0;
2640
+ float totalWeight = 0.0;
2641
+ int sampleCount = clamp(int(u_spotShadowPcfSampleCount), 1, 32);
2642
+ for (int i = 0; i < 32; ++i) {
2643
+ if (i >= sampleCount) break;
2644
+ vec4 sampleData = u_spotShadowPcfSamples[i];
2645
+ float weight = max(sampleData.z, 0.0);
2646
+ vec2 offset = sampleData.xy * u_spotShadowTexelSize;
2647
+ float storedDepth = texture(u_spotShadowMapTexture, uv + offset).r;
2648
+ float sampleTexelDistance = max(1.0, length(sampleData.xy));
2649
+ float receiverDepth = projectedDepth - u_spotShadowBias - slopeTexelBias * sampleTexelDistance;
2650
+ shadowed += (receiverDepth > storedDepth ? 1.0 : 0.0) * weight;
2651
+ totalWeight += weight;
2652
+ }
2653
+ float occlusion = totalWeight > 0.0 ? shadowed / totalWeight : 0.0;
2654
+ return mix(1.0, 1.0 - occlusion, clamp(u_spotShadowStrength, 0.0, 1.0));
2655
+ }
2656
+ float a3dTexturedPbrResolveSpotShadowOverride(float baseFactor, float kind, float shadowFlag, vec3 worldPosition, vec3 normal, vec3 lightDirection) {
2657
+ // Spot-kind lights (kind == 2) sample the perspective spot map only when it is
2658
+ // bound. Every gate term is 0 when no spot map is present, so mix() returns
2659
+ // baseFactor exactly and scenes without spot lights render unchanged.
2660
+ float spotGate = step(1.5, kind) * (1.0 - step(2.5, kind)) * step(0.5, u_spotShadowMapEnabled) * step(0.5, shadowFlag);
2661
+ return mix(baseFactor, a3dTexturedPbrSpotShadowFactor(worldPosition, normal, lightDirection), spotGate);
2662
+ }
2663
+ #endif
2223
2664
  vec2 a3dTexturedPbrEnvironmentUv(vec3 direction, float rotation) {
2224
2665
  vec3 d = normalize(direction);
2225
2666
  float u = atan(d.z, d.x) / 6.28318530718 + 0.5 + rotation;
@@ -2268,7 +2709,7 @@ vec3 a3dTexturedPbrEnvironmentDiffuseInput(vec3 normal) {
2268
2709
  float sampledEnvironmentWeight = step(0.0001, u_environmentMapTextureEnabled * u_environmentMapTextureIntensity);
2269
2710
  float diffuseEnvironmentLod = max(u_environmentMapTextureMipCount - 1.0, 0.0);
2270
2711
  vec3 sampledDiffuse = a3dTexturedPbrDecodeEnvironmentSample(a3dTexturedPbrEnvironmentSampleRaw(normal, diffuseEnvironmentLod));
2271
- return mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity, sampledEnvironmentWeight);
2712
+ return mix(environmentDiffuse, ambientEnvironment + sampledDiffuse * u_environmentMapTextureIntensity * u_materialEnvironmentIntensity, sampledEnvironmentWeight);
2272
2713
  }
2273
2714
  vec3 a3dTexturedPbrEnvironmentSpecularInput(vec3 normal, vec3 viewDirection, float roughness) {
2274
2715
  float materialEnvironmentSpecularScale = clamp(u_materialEnvironmentSpecularScale, 0.0, 1.0);
@@ -2297,7 +2738,7 @@ vec3 a3dTexturedPbrEnvironmentSpecularInput(vec3 normal, vec3 viewDirection, flo
2297
2738
  float environmentLod = clampedRoughness * max(u_environmentMapTextureMipCount - 1.0, 0.0);
2298
2739
  vec3 sampledSpecular = a3dTexturedPbrBoundHdrSpecularRadiance(a3dTexturedPbrDecodeEnvironmentSample(a3dTexturedPbrEnvironmentSampleRaw(reflectionDirection, environmentLod)));
2299
2740
  sampledSpecular = a3dTexturedPbrClampSampledSpecularEdgeEnergy(sampledSpecular, nDotV, clampedRoughness);
2300
- sampledSpecular *= u_environmentMapTextureSpecularIntensity * sampledEnvironmentWeight * mix(1.1, 0.65, clampedRoughness);
2741
+ sampledSpecular *= u_environmentMapTextureSpecularIntensity * u_materialEnvironmentIntensity * sampledEnvironmentWeight * mix(1.1, 0.65, clampedRoughness);
2301
2742
  float proceduralSpecularScale = materialFiniteSpecularScale;
2302
2743
  float sampledSpecularScale = materialEnvironmentSpecularScale;
2303
2744
  return proceduralSpecular * proceduralSpecularScale + sampledSpecular * sampledSpecularScale;
@@ -2720,6 +3161,9 @@ void main() {
2720
3161
  attenuation *= smoothstep(outer, inner, cone);
2721
3162
  }
2722
3163
  float shadowFactor = mix(1.0, kind > 0.5 && kind < 1.5 ? a3dTexturedPbrPointShadowFactor(v_worldPosition, mappedNormal, lightDirection) : a3dTexturedPbrShadowFactor(v_worldPosition, mappedNormal, lightDirection), step(0.5, spotShadowLayer.z));
3164
+ #ifndef A3D_PBR_NO_SPOT_SHADOW
3165
+ shadowFactor = a3dTexturedPbrResolveSpotShadowOverride(shadowFactor, kind, spotShadowLayer.z, v_worldPosition, mappedNormal, lightDirection);
3166
+ #endif
2723
3167
  float directLightIntensity = colorIntensity.a
2724
3168
  * attenuation
2725
3169
  * shadowFactor