@aura3d/rendering 1.3.3 → 1.4.1

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 (314) hide show
  1. package/dist/ForwardPass.js +7 -1
  2. package/dist/ForwardPass.js.map +1 -1
  3. package/dist/MorphTarget.d.ts +1 -0
  4. package/dist/MorphTarget.d.ts.map +1 -1
  5. package/dist/MorphTarget.js.map +1 -1
  6. package/dist/Renderer.js +7 -1
  7. package/dist/Renderer.js.map +1 -1
  8. package/package.json +38 -4
  9. package/src/ArchitecturalLightingFixtures.ts +0 -241
  10. package/src/ArchitecturalMaterialCatalog.ts +0 -130
  11. package/src/ArchitecturalMeasurementFixtures.ts +0 -155
  12. package/src/BRDFLut.ts +0 -47
  13. package/src/CameraFraming.ts +0 -200
  14. package/src/CanonicalSceneFixtures.ts +0 -95
  15. package/src/CascadedShadowMaps.ts +0 -449
  16. package/src/ColorManagement.ts +0 -127
  17. package/src/CullingFixtures.ts +0 -280
  18. package/src/DecalGeometry.ts +0 -26
  19. package/src/DepthPass.ts +0 -110
  20. package/src/EnvironmentBackgroundPass.ts +0 -194
  21. package/src/EnvironmentBackgroundResources.ts +0 -309
  22. package/src/EnvironmentLighting.ts +0 -96
  23. package/src/EnvironmentMapResources.ts +0 -1026
  24. package/src/EnvironmentPipeline.ts +0 -69
  25. package/src/EnvironmentPlatform.ts +0 -1306
  26. package/src/EnvironmentPreset.ts +0 -155
  27. package/src/Exposure.ts +0 -66
  28. package/src/ExternalParityRenderPreset.ts +0 -725
  29. package/src/ForwardPass.ts +0 -1663
  30. package/src/FrameVisualMetrics.ts +0 -301
  31. package/src/Geometry.ts +0 -461
  32. package/src/GeometryPrimitives.ts +0 -2
  33. package/src/HDRRenderPipeline.ts +0 -94
  34. package/src/IBL.ts +0 -86
  35. package/src/IndexBuffer.ts +0 -57
  36. package/src/InstancedPBRMaterial.ts +0 -186
  37. package/src/InstancedUnlitMaterial.ts +0 -60
  38. package/src/Instancing.ts +0 -19
  39. package/src/LightCollector.ts +0 -82
  40. package/src/LightUniforms.ts +0 -47
  41. package/src/LightingDebug.ts +0 -80
  42. package/src/LightingDefaults.ts +0 -79
  43. package/src/LightingRig.ts +0 -301
  44. package/src/LineGeometry.ts +0 -2
  45. package/src/Material.ts +0 -209
  46. package/src/MaterialBinding.ts +0 -131
  47. package/src/MaterialInstance.ts +0 -59
  48. package/src/MaterialPresets.ts +0 -272
  49. package/src/MorphTarget.ts +0 -123
  50. package/src/MorphTargetPlan.ts +0 -178
  51. package/src/MorphUnlitMaterial.ts +0 -60
  52. package/src/NormalMappedPBRMaterial.ts +0 -188
  53. package/src/OceanFixtures.ts +0 -318
  54. package/src/PBRLightingDefaults.ts +0 -12
  55. package/src/PBRMaterial.ts +0 -442
  56. package/src/PMREM.ts +0 -53
  57. package/src/PbrReference.ts +0 -681
  58. package/src/PostProcessPass.ts +0 -2507
  59. package/src/ProceduralTextureFixtures.ts +0 -393
  60. package/src/ProductTurntableFixtures.ts +0 -815
  61. package/src/Raycaster.ts +0 -21
  62. package/src/ReflectionProbe.ts +0 -13
  63. package/src/ReflectionSurfaces.ts +0 -219
  64. package/src/RenderBackend.ts +0 -43
  65. package/src/RenderDebugViews.ts +0 -138
  66. package/src/RenderDevice.ts +0 -836
  67. package/src/RenderGraph.ts +0 -194
  68. package/src/RenderPass.ts +0 -27
  69. package/src/RenderPipeline.ts +0 -101
  70. package/src/RenderQueue.ts +0 -12
  71. package/src/RenderState.ts +0 -12
  72. package/src/Renderer.ts +0 -2626
  73. package/src/RendererDebugOverlay.ts +0 -80
  74. package/src/RendererFeatureGates.ts +0 -150
  75. package/src/RendererPostprocessPlan.ts +0 -279
  76. package/src/RendererTiming.ts +0 -251
  77. package/src/RendererVisualPipelineReport.ts +0 -836
  78. package/src/ResourceLifecycle.ts +0 -21
  79. package/src/Sampler.ts +0 -37
  80. package/src/SceneOptimization.ts +0 -518
  81. package/src/ShaderChunks.ts +0 -393
  82. package/src/ShaderLibrary.ts +0 -2766
  83. package/src/ShaderModule.ts +0 -36
  84. package/src/ShaderPreprocessor.ts +0 -260
  85. package/src/ShaderReflection.ts +0 -201
  86. package/src/ShadowMap.ts +0 -237
  87. package/src/ShadowPass.ts +0 -198
  88. package/src/ShadowProjection.ts +0 -115
  89. package/src/SkinnedLitMaterial.ts +0 -397
  90. package/src/SkinnedUnlitMaterial.ts +0 -63
  91. package/src/SkinningBounds.ts +0 -131
  92. package/src/SpaceEnvironmentFixtures.ts +0 -200
  93. package/src/SpriteGeometry.ts +0 -21
  94. package/src/StereoCameraRig.ts +0 -127
  95. package/src/StereoEffects.ts +0 -274
  96. package/src/TerrainFixtures.ts +0 -180
  97. package/src/Texture.ts +0 -307
  98. package/src/TextureBinding.ts +0 -91
  99. package/src/TexturedPBRMaterial.ts +0 -1112
  100. package/src/TexturedUnlitMaterial.ts +0 -67
  101. package/src/ToneMapping.ts +0 -105
  102. package/src/UniformBinder.ts +0 -16
  103. package/src/UniformLayout.ts +0 -129
  104. package/src/UnlitMaterial.ts +0 -75
  105. package/src/VegetationFixtures.ts +0 -314
  106. package/src/VertexBuffer.ts +0 -120
  107. package/src/VertexFormat.ts +0 -171
  108. package/src/VoxelWorldFixtures.ts +0 -280
  109. package/src/WeatherFixtures.ts +0 -223
  110. package/src/WebGL2Device.ts +0 -2280
  111. package/src/WebGL2StateCache.ts +0 -330
  112. package/src/WebGPUDevice.ts +0 -2877
  113. package/src/WebGPURenderToTextureProof.ts +0 -139
  114. package/src/advanced-runtime/AdvancedRenderer.ts +0 -73
  115. package/src/advanced-runtime/index.ts +0 -9
  116. package/src/animation/AnimationMaterialStyle.ts +0 -32
  117. package/src/animation/AnimationRenderPreset.ts +0 -45
  118. package/src/animation/AnimationToonMaterial.ts +0 -387
  119. package/src/animation/AnimationVisualQuality.ts +0 -71
  120. package/src/animation/applyAnimationRenderPreset.ts +0 -180
  121. package/src/animation/index.ts +0 -5
  122. package/src/cinematic/BloomPass.ts +0 -26
  123. package/src/cinematic/CinematicDepthComposition.ts +0 -30
  124. package/src/cinematic/CinematicEvidence.ts +0 -123
  125. package/src/cinematic/CinematicLightingRig.ts +0 -144
  126. package/src/cinematic/CinematicMaterialPresets.ts +0 -130
  127. package/src/cinematic/CinematicPostProcess.ts +0 -83
  128. package/src/cinematic/DepthHazePass.ts +0 -30
  129. package/src/cinematic/EmissivePracticalLightSystem.ts +0 -45
  130. package/src/cinematic/FilmGrainPass.ts +0 -24
  131. package/src/cinematic/FogVolumeSystem.ts +0 -30
  132. package/src/cinematic/GlowCardSystem.ts +0 -44
  133. package/src/cinematic/RainParticleSystem.ts +0 -151
  134. package/src/cinematic/VignettePass.ts +0 -25
  135. package/src/cinematic/WetReflectionApproximation.ts +0 -27
  136. package/src/cinematic/index.ts +0 -14
  137. package/src/effects/CollisionModule.ts +0 -64
  138. package/src/effects/ColorModule.ts +0 -60
  139. package/src/effects/ForceModule.ts +0 -25
  140. package/src/effects/GPUParticleBackend.ts +0 -569
  141. package/src/effects/Particle.ts +0 -108
  142. package/src/effects/ParticleDiagnostics.ts +0 -224
  143. package/src/effects/ParticleEffectPresets.ts +0 -147
  144. package/src/effects/ParticleEmitter.ts +0 -326
  145. package/src/effects/ParticleModule.ts +0 -26
  146. package/src/effects/ParticleRenderPass.ts +0 -117
  147. package/src/effects/ParticleRenderer.ts +0 -132
  148. package/src/effects/ParticleSystem.ts +0 -333
  149. package/src/effects/SizeModule.ts +0 -56
  150. package/src/effects/TrailModule.ts +0 -72
  151. package/src/effects/VelocityModule.ts +0 -75
  152. package/src/effects/index.ts +0 -15
  153. package/src/index.ts +0 -925
  154. package/src/materials/AlphaSorting.ts +0 -30
  155. package/src/materials/MaterialExtensions.ts +0 -42
  156. package/src/materials/PhysicalMaterial.ts +0 -133
  157. package/src/materials/TransmissionPass.ts +0 -51
  158. package/src/performance/BVH.ts +0 -18
  159. package/src/performance/Batcher.ts +0 -2
  160. package/src/performance/FrustumCuller.ts +0 -9
  161. package/src/performance/LOD.ts +0 -20
  162. package/src/performance/Octree.ts +0 -10
  163. package/src/performance/RenderItemSorting.ts +0 -170
  164. package/src/performance/RendererStats.ts +0 -43
  165. package/src/performance/ResourceBudget.ts +0 -44
  166. package/src/postprocess/BloomPass.ts +0 -28
  167. package/src/postprocess/CinematicDiagnostics.ts +0 -642
  168. package/src/postprocess/ColorGradingPass.ts +0 -24
  169. package/src/postprocess/DepthOfFieldPass.ts +0 -12
  170. package/src/postprocess/EffectComposer.ts +0 -349
  171. package/src/postprocess/SSAOPass.ts +0 -20
  172. package/src/postprocess/index.ts +0 -34
  173. package/src/production-runtime/AnimationControlsPipeline.ts +0 -77
  174. package/src/production-runtime/PBRHDRPipeline.ts +0 -381
  175. package/src/production-runtime/ProductionEffectsPipeline.ts +0 -85
  176. package/src/production-runtime/ProductionRendererTypes.ts +0 -136
  177. package/src/production-runtime/ProductionRuntimeRenderer.ts +0 -172
  178. package/src/production-runtime/ProductionWebGL2Renderer.ts +0 -313
  179. package/src/production-runtime/ProductionWebGPURenderer.ts +0 -396
  180. package/src/production-runtime/TransmissionBackdropCapture.ts +0 -130
  181. package/src/production-runtime/animation/MorphTargetRenderer.ts +0 -2
  182. package/src/production-runtime/animation/SkinningRenderer.ts +0 -2
  183. package/src/production-runtime/backends/RendererBackend.ts +0 -17
  184. package/src/production-runtime/backends/WebGL2RendererBackend.ts +0 -30
  185. package/src/production-runtime/backends/WebGPURendererBackend.ts +0 -24
  186. package/src/production-runtime/backends/webgl2/WebGL2Buffer.ts +0 -2
  187. package/src/production-runtime/backends/webgl2/WebGL2Capabilities.ts +0 -2
  188. package/src/production-runtime/backends/webgl2/WebGL2RenderTarget.ts +0 -2
  189. package/src/production-runtime/backends/webgl2/WebGL2Shader.ts +0 -2
  190. package/src/production-runtime/backends/webgl2/WebGL2StateCache.ts +0 -1
  191. package/src/production-runtime/backends/webgl2/WebGL2Texture.ts +0 -2
  192. package/src/production-runtime/backends/webgpu/WebGPUBuffer.ts +0 -2
  193. package/src/production-runtime/backends/webgpu/WebGPUCapabilities.ts +0 -2
  194. package/src/production-runtime/backends/webgpu/WebGPURenderTarget.ts +0 -2
  195. package/src/production-runtime/backends/webgpu/WebGPUShader.ts +0 -2
  196. package/src/production-runtime/backends/webgpu/WebGPUTexture.ts +0 -2
  197. package/src/production-runtime/color/ColorManagement.ts +0 -3
  198. package/src/production-runtime/color/ToneMapping.ts +0 -7
  199. package/src/production-runtime/diagnostics/FrameCapture.ts +0 -2
  200. package/src/production-runtime/diagnostics/GPUCapabilities.ts +0 -2
  201. package/src/production-runtime/diagnostics/RendererStats.ts +0 -2
  202. package/src/production-runtime/environment/EnvironmentMap.ts +0 -1
  203. package/src/production-runtime/environment/HDRLoader.ts +0 -57
  204. package/src/production-runtime/environment/PMREMGenerator.ts +0 -713
  205. package/src/production-runtime/framegraph/FrameGraph.ts +0 -18
  206. package/src/production-runtime/framegraph/RenderPass.ts +0 -15
  207. package/src/production-runtime/geometry/ProjectedDecalGeometry.ts +0 -439
  208. package/src/production-runtime/index.ts +0 -127
  209. package/src/production-runtime/lights/LightManager.ts +0 -7
  210. package/src/production-runtime/lights/ShadowMapRenderer.ts +0 -5
  211. package/src/production-runtime/materials/GLTFMaterialAdapter.ts +0 -6
  212. package/src/production-runtime/materials/GLTFPBRMaterialAdapter.ts +0 -2
  213. package/src/production-runtime/materials/MaterialCompiler.ts +0 -7
  214. package/src/production-runtime/materials/MaterialTextureBindings.ts +0 -5
  215. package/src/production-runtime/materials/PBRMaterial.ts +0 -2
  216. package/src/production-runtime/materials/PBRShaderFeatures.ts +0 -2
  217. package/src/production-runtime/passes/ContactShadowPass.ts +0 -184
  218. package/src/production-runtime/passes/DepthPrepass.ts +0 -7
  219. package/src/production-runtime/passes/OpaquePass.ts +0 -7
  220. package/src/production-runtime/passes/ShadowPass.ts +0 -7
  221. package/src/production-runtime/passes/SkyboxPass.ts +0 -7
  222. package/src/production-runtime/passes/ToneMappingPass.ts +0 -7
  223. package/src/production-runtime/passes/TransparentPass.ts +0 -7
  224. package/src/production-runtime/postprocess/BloomPass.ts +0 -51
  225. package/src/production-runtime/postprocess/ColorGradingPass.ts +0 -49
  226. package/src/production-runtime/postprocess/DOFPass.ts +0 -47
  227. package/src/production-runtime/postprocess/FXAAPass.ts +0 -53
  228. package/src/production-runtime/postprocess/ProductionEffectComposer.ts +0 -78
  229. package/src/production-runtime/postprocess/ProductionPostProcessTypes.ts +0 -174
  230. package/src/production-runtime/postprocess/SSAOPass.ts +0 -45
  231. package/src/production-runtime/resources/GPUBuffer.ts +0 -2
  232. package/src/production-runtime/resources/GPUTexture.ts +0 -2
  233. package/src/production-runtime/resources/RenderTarget.ts +0 -3
  234. package/src/production-runtime/resources/ResourceCache.ts +0 -7
  235. package/src/production-runtime/scene/Camera.ts +0 -1
  236. package/src/production-runtime/scene/Lights.ts +0 -2
  237. package/src/production-runtime/scene/RenderableMesh.ts +0 -2
  238. package/src/production-runtime/scene/RenderablePrimitive.ts +0 -1
  239. package/src/production-runtime/scene/RenderableScene.ts +0 -4
  240. package/src/production-runtime/shaders/ShaderProgramLibrary.ts +0 -7
  241. package/src/production-runtime/shaders/chunks/brdf.glsl +0 -2
  242. package/src/production-runtime/shaders/chunks/ibl.glsl +0 -3
  243. package/src/production-runtime/shaders/chunks/pbr.frag.glsl +0 -47
  244. package/src/production-runtime/shaders/chunks/pbr.vert.glsl +0 -8
  245. package/src/production-runtime/shaders/chunks/shadows.glsl +0 -2
  246. package/src/production-runtime/shaders/wgsl/pbr.wgsl +0 -3
  247. package/src/production-runtime/shaders/wgsl/postprocess.wgsl +0 -2
  248. package/src/production-runtime/shaders/wgsl/skybox.wgsl +0 -2
  249. package/src/shaders/depth.frag.glsl +0 -5
  250. package/src/shaders/depth.vert.glsl +0 -8
  251. package/src/shaders/pbr-direct.frag.glsl +0 -687
  252. package/src/shaders/pbr-direct.vert.glsl +0 -18
  253. package/src/shaders/unlit.frag.glsl +0 -18
  254. package/src/shaders/unlit.vert.glsl +0 -13
  255. package/src/shadows/CascadedShadowPipeline.ts +0 -53
  256. package/src/shadows/ContactShadows.ts +0 -106
  257. package/src/shadows/ShadowDebugViews.ts +0 -46
  258. package/src/threejs-compatibility/InstancingSystem.ts +0 -17
  259. package/src/threejs-compatibility/LightingSystem.ts +0 -22
  260. package/src/threejs-compatibility/MaterialSystem.ts +0 -9
  261. package/src/threejs-compatibility/RenderTargetSystem.ts +0 -35
  262. package/src/threejs-compatibility/RendererDiagnostics.ts +0 -61
  263. package/src/threejs-compatibility/SceneRenderer.ts +0 -34
  264. package/src/threejs-compatibility/ShadowSystem.ts +0 -19
  265. package/src/threejs-compatibility/TextureSystem.ts +0 -19
  266. package/src/threejs-compatibility/ThreeCompatRenderer.ts +0 -90
  267. package/src/threejs-compatibility/TransparencySystem.ts +0 -19
  268. package/src/threejs-compatibility/index.ts +0 -29
  269. package/src/threejs-compatibility/performance/BVH.ts +0 -14
  270. package/src/threejs-compatibility/performance/FrustumCulling.ts +0 -10
  271. package/src/threejs-compatibility/performance/Instancing.ts +0 -4
  272. package/src/threejs-compatibility/performance/LODSystem.ts +0 -16
  273. package/src/threejs-compatibility/performance/OcclusionCulling.ts +0 -6
  274. package/src/threejs-compatibility/performance/RaycastAcceleration.ts +0 -12
  275. package/src/threejs-compatibility/performance/RendererProfiler.ts +0 -19
  276. package/src/threejs-compatibility/performance/TextureStreaming.ts +0 -4
  277. package/src/threejs-compatibility/performance/index.ts +0 -12
  278. package/src/threejs-compatibility/postprocess/BloomPass.ts +0 -16
  279. package/src/threejs-compatibility/postprocess/ColorGradingPass.ts +0 -16
  280. package/src/threejs-compatibility/postprocess/DepthOfFieldPass.ts +0 -21
  281. package/src/threejs-compatibility/postprocess/EffectComposer.ts +0 -14
  282. package/src/threejs-compatibility/postprocess/FXAAPass.ts +0 -15
  283. package/src/threejs-compatibility/postprocess/MotionBlurPass.ts +0 -20
  284. package/src/threejs-compatibility/postprocess/OutlinePass.ts +0 -21
  285. package/src/threejs-compatibility/postprocess/PostProcessTypes.ts +0 -169
  286. package/src/threejs-compatibility/postprocess/RenderPass.ts +0 -9
  287. package/src/threejs-compatibility/postprocess/SMAAPass.ts +0 -15
  288. package/src/threejs-compatibility/postprocess/SSAOPass.ts +0 -21
  289. package/src/threejs-compatibility/postprocess/ShaderPass.ts +0 -7
  290. package/src/threejs-compatibility/postprocess/TAAPass.ts +0 -15
  291. package/src/threejs-compatibility/postprocess/VignettePass.ts +0 -16
  292. package/src/threejs-compatibility/postprocess/index.ts +0 -15
  293. package/src/threejs-compatibility/shaders/NodeMaterial.ts +0 -21
  294. package/src/threejs-compatibility/shaders/RawShaderMaterial.ts +0 -6
  295. package/src/threejs-compatibility/shaders/ShaderDiagnostics.ts +0 -17
  296. package/src/threejs-compatibility/shaders/ShaderMaterial.ts +0 -18
  297. package/src/threejs-compatibility/shaders/ThreeCompatShaderChunks.ts +0 -6
  298. package/src/threejs-compatibility/shaders/Uniforms.ts +0 -18
  299. package/src/threejs-compatibility/shaders/index.ts +0 -9
  300. package/src/threejs-compatibility/vfx/GPUPointCloud.ts +0 -7
  301. package/src/threejs-compatibility/vfx/LineRenderer.ts +0 -13
  302. package/src/threejs-compatibility/vfx/ParticleSystem.ts +0 -23
  303. package/src/threejs-compatibility/vfx/SpriteSystem.ts +0 -14
  304. package/src/threejs-compatibility/vfx/TrailRenderer.ts +0 -8
  305. package/src/threejs-compatibility/vfx/VFXDiagnostics.ts +0 -23
  306. package/src/threejs-compatibility/vfx/index.ts +0 -9
  307. package/src/threejs-example-parity/index.ts +0 -205
  308. package/src/webgpu/WebGPUBuffer.ts +0 -1
  309. package/src/webgpu/WebGPUCompute.ts +0 -2
  310. package/src/webgpu/WebGPUPipelineCache.ts +0 -15
  311. package/src/webgpu/WebGPUPostProcess.ts +0 -2
  312. package/src/webgpu/WebGPUTexture.ts +0 -2
  313. package/tsconfig.json +0 -26
  314. package/vitest.config.ts +0 -20
@@ -1,1663 +0,0 @@
1
- import { Geometry } from "./Geometry";
2
- import { type CollectedLight } from "./LightCollector";
3
- import { LightUniforms } from "./LightUniforms";
4
- import { Material, type RenderState } from "./Material";
5
- import { MaterialBinding } from "./MaterialBinding";
6
- import { MaterialInstance } from "./MaterialInstance";
7
- import { applyMorphTargets, type MorphTargetDelta } from "./MorphTarget";
8
- import { type DrawCommand, type InstanceVertexAttribute, type RenderBuffer, type RenderDevice, RenderDeviceError, type RenderShaderProgram, type UniformValue } from "./RenderDevice";
9
- import { RenderPipeline } from "./RenderPipeline";
10
- import { BaseRenderPass, type RenderPassContext } from "./RenderPass";
11
- import { ShaderModule } from "./ShaderModule";
12
- import { createDefaultShaderLibrary, type ShaderLibrary } from "./ShaderLibrary";
13
- import { createShadowFilterKernel, type ShadowFilterKernel } from "./ShadowMap";
14
- import { TextureBinding } from "./TextureBinding";
15
- import { UnlitMaterial } from "./UnlitMaterial";
16
- import { sortRenderQueueItems } from "./performance/RenderItemSorting";
17
-
18
- export interface RenderItem {
19
- readonly geometry: Geometry;
20
- readonly material?: RenderMaterial;
21
- readonly label?: string;
22
- readonly drawRange?: RenderItemDrawRange;
23
- readonly includeInAutoFrame?: boolean;
24
- readonly modelMatrix?: Float32Array | readonly number[];
25
- readonly normalMatrix?: Float32Array | readonly number[];
26
- readonly modelViewProjectionMatrix?: Float32Array | readonly number[];
27
- readonly skinning?: SkinningPaletteBinding;
28
- readonly morphTargets?: readonly MorphTargetDelta[];
29
- readonly morphWeights?: readonly number[];
30
- readonly instanceTransforms?: Float32Array | readonly number[];
31
- readonly instanceColors?: Float32Array | readonly number[];
32
- readonly instanceAttributes?: readonly RenderItemInstanceAttribute[];
33
- readonly boundingBoxCenter?: readonly [number, number, number];
34
- }
35
-
36
- export interface RenderItemDrawRange {
37
- readonly start: number;
38
- readonly count: number;
39
- }
40
-
41
- export interface RenderItemInstanceAttribute {
42
- readonly shaderName: string;
43
- readonly components: 1 | 2 | 3 | 4;
44
- readonly data: Float32Array | readonly number[];
45
- readonly normalized?: boolean;
46
- readonly divisor?: number;
47
- }
48
-
49
- export type RenderMaterial = Material | MaterialInstance;
50
-
51
- export interface SkinningPaletteBinding {
52
- readonly jointCount: number;
53
- readonly matrices: Float32Array;
54
- }
55
-
56
- export const MAX_GPU_MORPH_VERTICES = 64;
57
- export const MAX_GPU_MORPH_TARGETS = 4;
58
- export const MAX_GPU_INSTANCES = 64;
59
-
60
- export interface ForwardPassOptions {
61
- readonly items: readonly RenderItem[];
62
- readonly lights?: readonly CollectedLight[];
63
- readonly environmentLighting?: EnvironmentLightingOptions;
64
- readonly environmentFog?: ForwardEnvironmentFogOptions | false;
65
- readonly inputColorResource?: string;
66
- readonly shadowMap?: ForwardShadowMapOptions;
67
- readonly cameraPosition?: readonly [number, number, number];
68
- readonly outputColorSpace?: "linear" | "srgb";
69
- readonly shaderLibrary?: ShaderLibrary;
70
- }
71
-
72
- export interface EnvironmentLightingOptions {
73
- readonly color: readonly [number, number, number];
74
- readonly intensity: number;
75
- readonly proceduralMap?: ProceduralEnvironmentMapLightingOptions;
76
- readonly environmentMapTexture?: TextureBinding;
77
- readonly environmentCubeMapTexture?: TextureBinding;
78
- readonly environmentMapIntensity?: number;
79
- readonly environmentMapSpecularIntensity?: number;
80
- readonly environmentMapRotation?: number;
81
- readonly environmentMapMipCount?: number;
82
- readonly environmentMapEncoding?: "srgb" | "rgbe" | "linear";
83
- readonly environmentBrdfLutTexture?: TextureBinding;
84
- }
85
-
86
- export interface ProceduralEnvironmentMapLightingOptions {
87
- readonly skyColor: readonly [number, number, number];
88
- readonly horizonColor: readonly [number, number, number];
89
- readonly groundColor: readonly [number, number, number];
90
- readonly specularColor: readonly [number, number, number];
91
- readonly intensity: number;
92
- readonly specularIntensity: number;
93
- }
94
-
95
- export type ForwardEnvironmentFogMode = "linear" | "exponential" | "exponential-squared";
96
-
97
- export interface ForwardEnvironmentFogOptions {
98
- readonly mode: ForwardEnvironmentFogMode;
99
- readonly color: readonly [number, number, number];
100
- readonly near: number;
101
- readonly far: number;
102
- readonly density: number;
103
- readonly heightFalloff?: number;
104
- readonly heightReference?: number;
105
- readonly maxOpacity?: number;
106
- }
107
-
108
- export interface ForwardShadowMapOptions {
109
- readonly texture: TextureBinding;
110
- readonly lightMatrix: Float32Array | readonly number[];
111
- readonly strength?: number;
112
- readonly bias?: number;
113
- readonly slopeBias?: number;
114
- readonly texelSize?: readonly [number, number];
115
- readonly filterKernel?: ShadowFilterKernel;
116
- readonly pointLight?: ForwardPointShadowMapOptions;
117
- }
118
-
119
- export interface ForwardPointShadowMapOptions {
120
- readonly texture: TextureBinding;
121
- readonly lightPosition: readonly [number, number, number];
122
- readonly range: number;
123
- readonly faceMatrices: Float32Array | readonly number[];
124
- readonly faceRects: Float32Array | readonly number[];
125
- readonly strength?: number;
126
- readonly bias?: number;
127
- readonly slopeBias?: number;
128
- readonly texelSize?: readonly [number, number];
129
- readonly filterKernel?: ShadowFilterKernel;
130
- }
131
-
132
- type Mat4 = [
133
- number, number, number, number,
134
- number, number, number, number,
135
- number, number, number, number,
136
- number, number, number, number
137
- ];
138
-
139
- const MAX_FORWARD_SHADOW_PCF_SAMPLES = 32;
140
- const DEFAULT_FORWARD_SHADOW_FILTER_KERNEL = createShadowFilterKernel({ filter: "pcf", pcfRadius: 1, pcfSamples: 9 });
141
- const INSTANCE_MATRIX_ATTRIBUTE_NAMES = [
142
- "a_instanceMatrix0",
143
- "a_instanceMatrix1",
144
- "a_instanceMatrix2",
145
- "a_instanceMatrix3"
146
- ] as const;
147
-
148
- export class ForwardPass extends BaseRenderPass {
149
- private static readonly shaderCaches = new WeakMap<RenderDevice, WeakMap<ShaderLibrary, Map<string, ShaderModule>>>();
150
- private readonly materialBinding = new MaterialBinding();
151
- private readonly shaderLibrary: ShaderLibrary;
152
- private readonly skinningPaletteUploads = new SkinningPaletteUploadManager();
153
-
154
- constructor(private readonly options: ForwardPassOptions) {
155
- super("forward", options.inputColorResource ? [options.inputColorResource] : [], ["color"]);
156
- this.shaderLibrary = options.shaderLibrary ?? createDefaultShaderLibrary();
157
- }
158
-
159
- execute(context: RenderPassContext): void {
160
- this.skinningPaletteUploads.beginFrame();
161
- for (const item of sortForwardRenderItems(this.options.items, this.options.cameraPosition)) {
162
- this.drawItem(context.device, item);
163
- }
164
- }
165
-
166
- private drawItem(device: RenderDevice, item: RenderItem): void {
167
- const material = item.material ?? new UnlitMaterial();
168
- const baseMaterial = getBaseMaterial(material);
169
- this.applyLightUniforms(material);
170
- const shader = this.getShader(baseMaterial, device);
171
- if (item.instanceTransforms && baseMaterial.renderState.cullMode !== "none" && instancedItemNeedsPerInstanceCullState(item)) {
172
- for (const expanded of expandInstancedRenderItem(item)) {
173
- this.drawItem(device, expanded);
174
- }
175
- return;
176
- }
177
- if (item.instanceTransforms && !supportsInstanceAttributes(shader) && instanceTransformCount(item) > MAX_GPU_INSTANCES) {
178
- for (const batch of splitInstanceTransforms(item.instanceTransforms)) {
179
- this.drawItem(device, { ...item, instanceTransforms: batch });
180
- }
181
- return;
182
- }
183
- if (item.instanceTransforms && !supportsInstanceAttributes(shader) && !supportsInstanceUniforms(shader)) {
184
- for (const expanded of expandInstancedRenderItem(item)) {
185
- this.drawItem(device, expanded);
186
- }
187
- return;
188
- }
189
- const binding = this.materialBinding.bind(material, shader);
190
- const uniforms = new Map<string, UniformValue>(binding.uniforms);
191
- applyEnvironmentLightingUniforms(this.options.environmentLighting, item, shader, uniforms);
192
- applyEnvironmentFogUniforms(this.options.environmentFog, item, shader, uniforms);
193
- applyForwardShadowMapUniforms(this.options.shadowMap, item, shader, uniforms);
194
- applyOutputColorSpaceUniform(this.options.outputColorSpace ?? "srgb", item, shader, uniforms);
195
- applyCameraUniforms(this.options.cameraPosition, item, shader, uniforms);
196
- applyAlphaCutoffUniform(item, shader, uniforms);
197
- applyTransformUniforms(item, shader, uniforms);
198
- if (item.skinning) {
199
- this.skinningPaletteUploads.bind(item, item.skinning, baseMaterial, shader, uniforms);
200
- }
201
- const instanceBinding = item.instanceTransforms ? applyInstanceBinding(device, item, shader, uniforms) : { count: 1 };
202
- const gpuMorph = item.morphTargets || item.morphWeights ? applyGpuMorphUniforms(item, shader, uniforms) : false;
203
- const geometry = gpuMorph ? item.geometry : resolveRenderGeometry(item);
204
- validateMaterialGeometryContract(item, baseMaterial, geometry);
205
- try {
206
- const vertexBuffer = geometry.vertexBuffer.upload(device);
207
- const indexBuffer = geometry.indexBuffer?.upload(device);
208
- const drawRange = resolveDrawRange(geometry, item.drawRange);
209
- const pipeline = new RenderPipeline({
210
- label: item.label ?? baseMaterial.name,
211
- shader,
212
- vertexFormat: geometry.vertexBuffer.format,
213
- topology: geometry.topology,
214
- renderState: renderStateForItem(baseMaterial.renderState, item),
215
- requiredAttributes: baseMaterial.requiredAttributes
216
- });
217
- const command: DrawCommand = pipeline.createDrawCommand({
218
- label: item.label,
219
- vertexBuffer,
220
- vertexCount: indexBuffer !== undefined ? geometry.vertexBuffer.vertexCount : drawRange.count,
221
- ...(indexBuffer === undefined && drawRange.start > 0 ? { firstVertex: drawRange.start } : {}),
222
- uniforms,
223
- ...(item.instanceTransforms ? { instanceCount: instanceBinding.count } : {}),
224
- ...(instanceBinding.attributes ? { instanceAttributes: instanceBinding.attributes } : {})
225
- });
226
- if (indexBuffer !== undefined) {
227
- Object.assign(command, {
228
- indexBuffer,
229
- indexType: geometry.indexBuffer?.type,
230
- indexCount: drawRange.count,
231
- ...(drawRange.start > 0 ? { firstIndex: drawRange.start } : {})
232
- });
233
- }
234
- device.draw(command);
235
- } finally {
236
- for (const buffer of instanceBinding.buffers ?? []) buffer.dispose();
237
- if (geometry !== item.geometry) {
238
- geometry.dispose();
239
- }
240
- }
241
- }
242
-
243
- private applyLightUniforms(material: RenderMaterial): void {
244
- const baseMaterial = getBaseMaterial(material);
245
- if (!baseMaterial.requiredUniforms.includes("u_lightCount") || !baseMaterial.requiredUniforms.includes("u_lightData")) {
246
- return;
247
- }
248
- const packed = LightUniforms.pack(this.options.lights ?? []);
249
- if (material instanceof MaterialInstance) {
250
- material.setOverride("u_lightCount", packed.lightCount);
251
- material.setOverride("u_lightData", packed.data);
252
- } else {
253
- material.setParameter("u_lightCount", packed.lightCount);
254
- material.setParameter("u_lightData", packed.data);
255
- }
256
- }
257
-
258
- private getShader(material: Material, device: RenderDevice): RenderShaderProgram {
259
- const cacheKey = shaderCacheKey(material);
260
- const shaderCache = getForwardPassShaderCache(device, this.shaderLibrary);
261
- let module = shaderCache.get(cacheKey);
262
- if (!module) {
263
- module = material.shaderVariant
264
- ? ShaderModule.fromLibraryVariant(this.shaderLibrary, material.shaderKey, material.shaderVariant)
265
- : ShaderModule.fromLibrary(this.shaderLibrary, material.shaderKey);
266
- shaderCache.set(cacheKey, module);
267
- }
268
- return module.compile(device);
269
- }
270
- }
271
-
272
- class SkinningPaletteUploadManager {
273
- private static readonly validatedGeometryJointCounts = new WeakMap<Geometry, Set<number>>();
274
- private submissions = 0;
275
- private jointsUploaded = 0;
276
- private maxJointCount = 0;
277
-
278
- beginFrame(): void {
279
- this.submissions = 0;
280
- this.jointsUploaded = 0;
281
- this.maxJointCount = 0;
282
- }
283
-
284
- bind(
285
- item: RenderItem,
286
- skinning: SkinningPaletteBinding,
287
- material: Material,
288
- shader: RenderShaderProgram,
289
- uniforms: Map<string, UniformValue>
290
- ): void {
291
- applySkinningUniforms(skinning, material, shader, uniforms);
292
- const validatedJointCounts = SkinningPaletteUploadManager.validatedGeometryJointCounts.get(item.geometry) ?? new Set<number>();
293
- if (!validatedJointCounts.has(skinning.jointCount)) {
294
- validateSkinningGeometryContract(item, skinning);
295
- validatedJointCounts.add(skinning.jointCount);
296
- SkinningPaletteUploadManager.validatedGeometryJointCounts.set(item.geometry, validatedJointCounts);
297
- }
298
- this.submissions += 1;
299
- this.jointsUploaded += skinning.jointCount;
300
- this.maxJointCount = Math.max(this.maxJointCount, skinning.jointCount);
301
- }
302
- }
303
-
304
- function getForwardPassShaderCache(device: RenderDevice, library: ShaderLibrary): Map<string, ShaderModule> {
305
- let libraryCaches = ForwardPass["shaderCaches"].get(device);
306
- if (!libraryCaches) {
307
- libraryCaches = new WeakMap<ShaderLibrary, Map<string, ShaderModule>>();
308
- ForwardPass["shaderCaches"].set(device, libraryCaches);
309
- }
310
- let shaderCache = libraryCaches.get(library);
311
- if (!shaderCache) {
312
- shaderCache = new Map<string, ShaderModule>();
313
- libraryCaches.set(library, shaderCache);
314
- }
315
- return shaderCache;
316
- }
317
-
318
- function applyOutputColorSpaceUniform(
319
- outputColorSpace: "linear" | "srgb",
320
- item: RenderItem,
321
- shader: RenderShaderProgram,
322
- uniforms: Map<string, UniformValue>
323
- ): void {
324
- if (!shader.reflection.uniforms.has("u_outputColorSpace")) return;
325
- if (outputColorSpace !== "linear" && outputColorSpace !== "srgb") {
326
- throw new RenderDeviceError("Forward pass outputColorSpace must be linear or srgb", "FORWARD_OUTPUT_COLOR_SPACE_CONTRACT", {
327
- label: item.label,
328
- outputColorSpace
329
- });
330
- }
331
- uniforms.set("u_outputColorSpace", outputColorSpace === "srgb" ? 1 : 0);
332
- }
333
-
334
- function applyEnvironmentFogUniforms(
335
- fog: ForwardEnvironmentFogOptions | false | undefined,
336
- item: RenderItem,
337
- shader: RenderShaderProgram,
338
- uniforms: Map<string, UniformValue>
339
- ): void {
340
- const requiredUniforms = [
341
- "u_environmentFogEnabled",
342
- "u_environmentFogMode",
343
- "u_environmentFogColor",
344
- "u_environmentFogNear",
345
- "u_environmentFogFar",
346
- "u_environmentFogDensity",
347
- "u_environmentFogHeightFalloff",
348
- "u_environmentFogHeightReference",
349
- "u_environmentFogMaxOpacity"
350
- ];
351
- if (!requiredUniforms.every((uniform) => shader.reflection.uniforms.has(uniform))) {
352
- return;
353
- }
354
- if (!fog) {
355
- uniforms.set("u_environmentFogEnabled", 0);
356
- uniforms.set("u_environmentFogMode", 1);
357
- uniforms.set("u_environmentFogColor", [0, 0, 0]);
358
- uniforms.set("u_environmentFogNear", 0);
359
- uniforms.set("u_environmentFogFar", 1);
360
- uniforms.set("u_environmentFogDensity", 0);
361
- uniforms.set("u_environmentFogHeightFalloff", 0);
362
- uniforms.set("u_environmentFogHeightReference", 0);
363
- uniforms.set("u_environmentFogMaxOpacity", 1);
364
- return;
365
- }
366
- const color = Array.from(fog.color);
367
- const heightFalloff = fog.heightFalloff ?? 0;
368
- const heightReference = fog.heightReference ?? 0;
369
- const maxOpacity = fog.maxOpacity ?? 1;
370
- if (color.length !== 3 || !color.every((component) => Number.isFinite(component) && component >= 0 && component <= 1)) {
371
- throw new RenderDeviceError("Forward environment fog color must contain three finite linear RGB values in [0, 1]", "FORWARD_ENVIRONMENT_FOG_CONTRACT", {
372
- label: item.label,
373
- color
374
- });
375
- }
376
- if (!Number.isFinite(fog.near) || !Number.isFinite(fog.far) || fog.far <= fog.near) {
377
- throw new RenderDeviceError("Forward environment fog requires finite far greater than near", "FORWARD_ENVIRONMENT_FOG_CONTRACT", {
378
- label: item.label,
379
- near: fog.near,
380
- far: fog.far
381
- });
382
- }
383
- if (!Number.isFinite(fog.density) || fog.density < 0) {
384
- throw new RenderDeviceError("Forward environment fog density must be finite and non-negative", "FORWARD_ENVIRONMENT_FOG_CONTRACT", {
385
- label: item.label,
386
- density: fog.density
387
- });
388
- }
389
- if (!Number.isFinite(heightFalloff) || heightFalloff < 0) {
390
- throw new RenderDeviceError("Forward environment fog heightFalloff must be finite and non-negative", "FORWARD_ENVIRONMENT_FOG_CONTRACT", {
391
- label: item.label,
392
- heightFalloff
393
- });
394
- }
395
- if (!Number.isFinite(heightReference)) {
396
- throw new RenderDeviceError("Forward environment fog heightReference must be finite", "FORWARD_ENVIRONMENT_FOG_CONTRACT", {
397
- label: item.label,
398
- heightReference
399
- });
400
- }
401
- if (!Number.isFinite(maxOpacity) || maxOpacity < 0 || maxOpacity > 1) {
402
- throw new RenderDeviceError("Forward environment fog maxOpacity must be finite in [0, 1]", "FORWARD_ENVIRONMENT_FOG_CONTRACT", {
403
- label: item.label,
404
- maxOpacity
405
- });
406
- }
407
- uniforms.set("u_environmentFogEnabled", 1);
408
- uniforms.set("u_environmentFogMode", fogModeUniform(fog.mode, item.label));
409
- uniforms.set("u_environmentFogColor", color);
410
- uniforms.set("u_environmentFogNear", fog.near);
411
- uniforms.set("u_environmentFogFar", fog.far);
412
- uniforms.set("u_environmentFogDensity", fog.density);
413
- uniforms.set("u_environmentFogHeightFalloff", heightFalloff);
414
- uniforms.set("u_environmentFogHeightReference", heightReference);
415
- uniforms.set("u_environmentFogMaxOpacity", maxOpacity);
416
- }
417
-
418
- function fogModeUniform(mode: ForwardEnvironmentFogMode, label: string | undefined): number {
419
- switch (mode) {
420
- case "linear":
421
- return 1;
422
- case "exponential":
423
- return 2;
424
- case "exponential-squared":
425
- return 3;
426
- default:
427
- throw new RenderDeviceError("Forward environment fog mode must be linear, exponential, or exponential-squared", "FORWARD_ENVIRONMENT_FOG_CONTRACT", {
428
- label,
429
- mode
430
- });
431
- }
432
- }
433
-
434
- function validateMaterialGeometryContract(item: RenderItem, material: Material, geometry: Geometry): void {
435
- const missing = material.requiredAttributes.filter((attribute) => !geometryHasAttribute(geometry, attribute));
436
- if (missing.length === 0) {
437
- return;
438
- }
439
- throw new RenderDeviceError("Render item geometry is missing attributes required by its material", "RENDER_ITEM_GEOMETRY_MATERIAL_CONTRACT", {
440
- label: item.label,
441
- material: material.name,
442
- topology: geometry.topology,
443
- vertexFormat: geometry.vertexBuffer.format.attributes.map((attribute) => attribute.shaderName),
444
- missingAttributes: missing
445
- });
446
- }
447
-
448
- function resolveDrawRange(geometry: Geometry, range: RenderItemDrawRange | undefined): { readonly start: number; readonly count: number } {
449
- const available = geometry.indexBuffer?.count ?? geometry.vertexBuffer.vertexCount;
450
- if (!range) {
451
- return { start: 0, count: available };
452
- }
453
- if (!Number.isInteger(range.start) || range.start < 0 || !Number.isInteger(range.count) || range.count <= 0) {
454
- throw new RenderDeviceError("Render item drawRange must contain non-negative integer start and positive integer count", "RENDER_ITEM_DRAW_RANGE_INVALID", {
455
- start: range.start,
456
- count: range.count,
457
- available
458
- });
459
- }
460
- if (range.start + range.count > available) {
461
- throw new RenderDeviceError("Render item drawRange exceeds geometry draw count", "RENDER_ITEM_DRAW_RANGE_INVALID", {
462
- start: range.start,
463
- count: range.count,
464
- available
465
- });
466
- }
467
- return range;
468
- }
469
-
470
- function geometryHasAttribute(geometry: Geometry, attribute: string): boolean {
471
- return geometry.vertexBuffer.format.attributes.some((candidate) => candidate.semantic === attribute || candidate.shaderName === attribute);
472
- }
473
-
474
- function shaderCacheKey(material: Material): string {
475
- return material.shaderVariant ? `${material.shaderKey}:${material.shaderVariant}` : material.shaderKey;
476
- }
477
-
478
- function supportsInstanceUniforms(shader: RenderShaderProgram): boolean {
479
- return shader.reflection.uniforms.has("u_instanceMatrices") && shader.reflection.uniforms.has("u_instanceCount");
480
- }
481
-
482
- function supportsInstanceAttributes(shader: RenderShaderProgram): boolean {
483
- return shader.reflection.uniforms.has("u_instanceAttributeMode") &&
484
- shader.reflection.uniforms.has("u_instanceCount") &&
485
- INSTANCE_MATRIX_ATTRIBUTE_NAMES.every((name) => shader.reflection.attributes.has(name));
486
- }
487
-
488
- function renderStateForItem(renderState: RenderState, item: RenderItem): RenderState {
489
- if (renderState.cullMode === "none") return renderState;
490
- const modelMatrix = toMat4Values(item.modelMatrix ?? identityMatrix(), "modelMatrix", item.label);
491
- if (!hasNegativeHandedness(modelMatrix)) return renderState;
492
- return {
493
- ...renderState,
494
- cullMode: renderState.cullMode === "back" ? "front" : "back"
495
- };
496
- }
497
-
498
- function instancedItemNeedsPerInstanceCullState(item: RenderItem): boolean {
499
- const source = validateInstanceTransformSource(item);
500
- const baseModel = toMat4Values(item.modelMatrix ?? identityMatrix(), "modelMatrix", item.label);
501
- const handedness = new Set<boolean>();
502
- for (let offset = 0; offset < source.length; offset += 16) {
503
- const instanceMatrix = mat4FromArrayLike(source, offset);
504
- handedness.add(hasNegativeHandedness(multiplyMat4(baseModel, instanceMatrix)));
505
- }
506
- return handedness.has(true);
507
- }
508
-
509
- function expandInstancedRenderItem(item: RenderItem): readonly RenderItem[] {
510
- const source = validateInstanceTransformSource(item);
511
- const baseModel = toMat4Values(item.modelMatrix ?? identityMatrix(), "modelMatrix", item.label);
512
- const baseModelViewProjection = toMat4Values(item.modelViewProjectionMatrix ?? baseModel, "modelViewProjectionMatrix", item.label);
513
- const { instanceTransforms: _instanceTransforms, normalMatrix: _normalMatrix, ...baseItem } = item;
514
- const expanded: RenderItem[] = [];
515
- for (let offset = 0; offset < source.length; offset += 16) {
516
- const index = offset / 16;
517
- const instanceMatrix = mat4FromArrayLike(source, offset);
518
- const modelMatrix = multiplyMat4(baseModel, instanceMatrix);
519
- expanded.push({
520
- ...baseItem,
521
- label: item.label ? `${item.label}#instance-${index}` : undefined,
522
- modelMatrix: new Float32Array(modelMatrix),
523
- modelViewProjectionMatrix: new Float32Array(multiplyMat4(baseModelViewProjection, instanceMatrix)),
524
- normalMatrix: new Float32Array(normalMatrixFromModel(modelMatrix))
525
- });
526
- }
527
- return expanded;
528
- }
529
-
530
- function multiplyMat4(left: Mat4, right: Mat4): Mat4 {
531
- const out = new Array(16).fill(0) as Mat4;
532
- for (let column = 0; column < 4; column += 1) {
533
- for (let row = 0; row < 4; row += 1) {
534
- out[column * 4 + row] =
535
- left[0 * 4 + row] * right[column * 4 + 0] +
536
- left[1 * 4 + row] * right[column * 4 + 1] +
537
- left[2 * 4 + row] * right[column * 4 + 2] +
538
- left[3 * 4 + row] * right[column * 4 + 3];
539
- }
540
- }
541
- return out;
542
- }
543
-
544
- function normalMatrixFromModel(modelMatrix: Mat4): Mat4 {
545
- const matrix = transposeMat4(invertMat4(modelMatrix));
546
- return [
547
- matrix[0], matrix[1], matrix[2], 0,
548
- matrix[4], matrix[5], matrix[6], 0,
549
- matrix[8], matrix[9], matrix[10], 0,
550
- 0, 0, 0, 1
551
- ];
552
- }
553
-
554
- function hasNegativeHandedness(matrix: Mat4): boolean {
555
- const determinant =
556
- matrix[0] * (matrix[5] * matrix[10] - matrix[9] * matrix[6]) -
557
- matrix[4] * (matrix[1] * matrix[10] - matrix[9] * matrix[2]) +
558
- matrix[8] * (matrix[1] * matrix[6] - matrix[5] * matrix[2]);
559
- return Number.isFinite(determinant) && determinant < -1e-8;
560
- }
561
-
562
- function transposeMat4(matrix: Mat4): Mat4 {
563
- return [
564
- matrix[0], matrix[4], matrix[8], matrix[12],
565
- matrix[1], matrix[5], matrix[9], matrix[13],
566
- matrix[2], matrix[6], matrix[10], matrix[14],
567
- matrix[3], matrix[7], matrix[11], matrix[15]
568
- ];
569
- }
570
-
571
- function invertMat4(matrix: Mat4): Mat4 {
572
- const [
573
- a00, a01, a02, a03,
574
- a10, a11, a12, a13,
575
- a20, a21, a22, a23,
576
- a30, a31, a32, a33
577
- ] = matrix;
578
- const b00 = a00 * a11 - a01 * a10;
579
- const b01 = a00 * a12 - a02 * a10;
580
- const b02 = a00 * a13 - a03 * a10;
581
- const b03 = a01 * a12 - a02 * a11;
582
- const b04 = a01 * a13 - a03 * a11;
583
- const b05 = a02 * a13 - a03 * a12;
584
- const b06 = a20 * a31 - a21 * a30;
585
- const b07 = a20 * a32 - a22 * a30;
586
- const b08 = a20 * a33 - a23 * a30;
587
- const b09 = a21 * a32 - a22 * a31;
588
- const b10 = a21 * a33 - a23 * a31;
589
- const b11 = a22 * a33 - a23 * a32;
590
- const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
591
- if (Math.abs(det) <= 1e-12 || !Number.isFinite(det)) {
592
- throw new RenderDeviceError("Instance transform fallback requires invertible model matrices", "INSTANCING_CONTRACT");
593
- }
594
- const invDet = 1 / det;
595
- return [
596
- (a11 * b11 - a12 * b10 + a13 * b09) * invDet,
597
- (a02 * b10 - a01 * b11 - a03 * b09) * invDet,
598
- (a31 * b05 - a32 * b04 + a33 * b03) * invDet,
599
- (a22 * b04 - a21 * b05 - a23 * b03) * invDet,
600
- (a12 * b08 - a10 * b11 - a13 * b07) * invDet,
601
- (a00 * b11 - a02 * b08 + a03 * b07) * invDet,
602
- (a32 * b02 - a30 * b05 - a33 * b01) * invDet,
603
- (a20 * b05 - a22 * b02 + a23 * b01) * invDet,
604
- (a10 * b10 - a11 * b08 + a13 * b06) * invDet,
605
- (a01 * b08 - a00 * b10 - a03 * b06) * invDet,
606
- (a30 * b04 - a31 * b02 + a33 * b00) * invDet,
607
- (a21 * b02 - a20 * b04 - a23 * b00) * invDet,
608
- (a11 * b07 - a10 * b09 - a12 * b06) * invDet,
609
- (a00 * b09 - a01 * b07 + a02 * b06) * invDet,
610
- (a31 * b01 - a30 * b03 - a32 * b00) * invDet,
611
- (a20 * b03 - a21 * b01 + a22 * b00) * invDet
612
- ];
613
- }
614
-
615
- function applyForwardShadowMapUniforms(
616
- shadowMap: ForwardShadowMapOptions | undefined,
617
- item: RenderItem,
618
- shader: RenderShaderProgram,
619
- uniforms: Map<string, UniformValue>
620
- ): void {
621
- const requiredUniforms = [
622
- "u_shadowMapTexture",
623
- "u_shadowMapEnabled",
624
- "u_shadowMapMatrix",
625
- "u_shadowMapStrength",
626
- "u_shadowMapBias",
627
- "u_shadowMapSlopeBias",
628
- "u_shadowMapTexelSize",
629
- "u_shadowPcfSampleCount",
630
- "u_shadowPcfSamples"
631
- ];
632
- if (!requiredUniforms.every((uniform) => shader.reflection.uniforms.has(uniform))) {
633
- return;
634
- }
635
- applyForwardPointShadowMapUniforms(shadowMap?.pointLight, item, shader, uniforms);
636
- if (!shadowMap) {
637
- uniforms.set("u_shadowMapTexture", new TextureBinding({ name: "u_shadowMapTexture", required: false }));
638
- uniforms.set("u_shadowMapEnabled", 0);
639
- uniforms.set("u_shadowMapMatrix", new Float32Array(identityMatrix()));
640
- uniforms.set("u_shadowMapStrength", 0);
641
- uniforms.set("u_shadowMapBias", 0);
642
- uniforms.set("u_shadowMapSlopeBias", 0);
643
- uniforms.set("u_shadowMapTexelSize", [1, 1]);
644
- uniforms.set("u_shadowPcfSampleCount", 1);
645
- uniforms.set("u_shadowPcfSamples", new Float32Array(MAX_FORWARD_SHADOW_PCF_SAMPLES * 4));
646
- return;
647
- }
648
- const validation = shadowMap.texture.validate();
649
- if (!validation.ok) {
650
- throw new RenderDeviceError("Forward shadow-map texture binding validation failed", "FORWARD_SHADOW_MAP_CONTRACT", {
651
- label: item.label,
652
- diagnostics: validation.diagnostics
653
- });
654
- }
655
- const strength = shadowMap.strength ?? 0.65;
656
- const bias = shadowMap.bias ?? 0.001;
657
- const slopeBias = shadowMap.slopeBias ?? 1;
658
- const texelSize = shadowMap.texelSize ?? [
659
- 1 / Math.max(1, shadowMap.texture.texture?.width ?? 1),
660
- 1 / Math.max(1, shadowMap.texture.texture?.height ?? 1)
661
- ];
662
- if (!Number.isFinite(strength) || strength < 0 || strength > 1) {
663
- throw new RenderDeviceError("Forward shadow-map strength must be finite in [0, 1]", "FORWARD_SHADOW_MAP_CONTRACT", {
664
- label: item.label,
665
- strength
666
- });
667
- }
668
- if (!Number.isFinite(bias) || bias < 0) {
669
- throw new RenderDeviceError("Forward shadow-map bias must be finite and non-negative", "FORWARD_SHADOW_MAP_CONTRACT", {
670
- label: item.label,
671
- bias
672
- });
673
- }
674
- if (!Number.isFinite(slopeBias) || slopeBias < 0) {
675
- throw new RenderDeviceError("Forward shadow-map slopeBias must be finite and non-negative", "FORWARD_SHADOW_MAP_CONTRACT", {
676
- label: item.label,
677
- slopeBias
678
- });
679
- }
680
- if (texelSize.length !== 2 || !texelSize.every((value) => Number.isFinite(value) && value > 0)) {
681
- throw new RenderDeviceError("Forward shadow-map texelSize must contain two finite positive values", "FORWARD_SHADOW_MAP_CONTRACT", {
682
- label: item.label,
683
- texelSize: Array.from(texelSize)
684
- });
685
- }
686
- const filterKernel = shadowMap.filterKernel ?? DEFAULT_FORWARD_SHADOW_FILTER_KERNEL;
687
- const pcfSamples = packForwardShadowPcfSamples(filterKernel, item.label);
688
- uniforms.set("u_shadowMapTexture", shadowMap.texture);
689
- uniforms.set("u_shadowMapEnabled", 1);
690
- uniforms.set("u_shadowMapMatrix", toMat4Uniform(shadowMap.lightMatrix, "shadowMap.lightMatrix", item.label));
691
- uniforms.set("u_shadowMapStrength", strength);
692
- uniforms.set("u_shadowMapBias", bias);
693
- uniforms.set("u_shadowMapSlopeBias", slopeBias);
694
- uniforms.set("u_shadowMapTexelSize", texelSize);
695
- uniforms.set("u_shadowPcfSampleCount", filterKernel.samples.length);
696
- uniforms.set("u_shadowPcfSamples", pcfSamples);
697
- }
698
-
699
- function applyForwardPointShadowMapUniforms(
700
- pointShadowMap: ForwardPointShadowMapOptions | undefined,
701
- item: RenderItem,
702
- shader: RenderShaderProgram,
703
- uniforms: Map<string, UniformValue>
704
- ): void {
705
- const requiredUniforms = [
706
- "u_pointShadowMapTexture",
707
- "u_pointShadowMapEnabled",
708
- "u_pointShadowLightPosition",
709
- "u_pointShadowRange",
710
- "u_pointShadowFaceMatrices",
711
- "u_pointShadowFaceRects",
712
- "u_pointShadowStrength",
713
- "u_pointShadowBias",
714
- "u_pointShadowSlopeBias",
715
- "u_pointShadowTexelSize",
716
- "u_pointShadowPcfSampleCount",
717
- "u_pointShadowPcfSamples"
718
- ];
719
- if (!requiredUniforms.every((uniform) => shader.reflection.uniforms.has(uniform))) {
720
- return;
721
- }
722
- if (!pointShadowMap) {
723
- uniforms.set("u_pointShadowMapTexture", new TextureBinding({ name: "u_pointShadowMapTexture", required: false }));
724
- uniforms.set("u_pointShadowMapEnabled", 0);
725
- uniforms.set("u_pointShadowLightPosition", [0, 0, 0]);
726
- uniforms.set("u_pointShadowRange", 1);
727
- uniforms.set("u_pointShadowFaceMatrices", new Float32Array(6 * 16));
728
- uniforms.set("u_pointShadowFaceRects", new Float32Array(6 * 4));
729
- uniforms.set("u_pointShadowStrength", 0);
730
- uniforms.set("u_pointShadowBias", 0);
731
- uniforms.set("u_pointShadowSlopeBias", 0);
732
- uniforms.set("u_pointShadowTexelSize", [1, 1]);
733
- uniforms.set("u_pointShadowPcfSampleCount", 1);
734
- uniforms.set("u_pointShadowPcfSamples", new Float32Array(MAX_FORWARD_SHADOW_PCF_SAMPLES * 4));
735
- return;
736
- }
737
- const validation = pointShadowMap.texture.validate();
738
- if (!validation.ok) {
739
- throw new RenderDeviceError("Forward point-shadow atlas texture binding validation failed", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
740
- label: item.label,
741
- diagnostics: validation.diagnostics
742
- });
743
- }
744
- if (pointShadowMap.lightPosition.length !== 3 || !isFiniteArrayLike(pointShadowMap.lightPosition)) {
745
- throw new RenderDeviceError("Forward point-shadow lightPosition must contain three finite values", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
746
- label: item.label,
747
- lightPosition: pointShadowMap.lightPosition
748
- });
749
- }
750
- if (!Number.isFinite(pointShadowMap.range) || pointShadowMap.range <= 0) {
751
- throw new RenderDeviceError("Forward point-shadow range must be finite and positive", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
752
- label: item.label,
753
- range: pointShadowMap.range
754
- });
755
- }
756
- const faceMatrices = toFloat32Array(pointShadowMap.faceMatrices, 6 * 16, "pointShadowMap.faceMatrices", item.label);
757
- const faceRects = toFloat32Array(pointShadowMap.faceRects, 6 * 4, "pointShadowMap.faceRects", item.label);
758
- validatePointShadowFaceRects(faceRects, item.label);
759
- const strength = pointShadowMap.strength ?? 0.65;
760
- const bias = pointShadowMap.bias ?? 0.001;
761
- const slopeBias = pointShadowMap.slopeBias ?? 1;
762
- const texelSize = pointShadowMap.texelSize ?? [
763
- 1 / Math.max(1, pointShadowMap.texture.texture?.width ?? 1),
764
- 1 / Math.max(1, pointShadowMap.texture.texture?.height ?? 1)
765
- ];
766
- if (!Number.isFinite(strength) || strength < 0 || strength > 1) {
767
- throw new RenderDeviceError("Forward point-shadow strength must be finite in [0, 1]", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
768
- label: item.label,
769
- strength
770
- });
771
- }
772
- if (!Number.isFinite(bias) || bias < 0) {
773
- throw new RenderDeviceError("Forward point-shadow bias must be finite and non-negative", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
774
- label: item.label,
775
- bias
776
- });
777
- }
778
- if (!Number.isFinite(slopeBias) || slopeBias < 0) {
779
- throw new RenderDeviceError("Forward point-shadow slopeBias must be finite and non-negative", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
780
- label: item.label,
781
- slopeBias
782
- });
783
- }
784
- if (texelSize.length !== 2 || !texelSize.every((value) => Number.isFinite(value) && value > 0)) {
785
- throw new RenderDeviceError("Forward point-shadow texelSize must contain two finite positive values", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
786
- label: item.label,
787
- texelSize: Array.from(texelSize)
788
- });
789
- }
790
- const filterKernel = pointShadowMap.filterKernel ?? DEFAULT_FORWARD_SHADOW_FILTER_KERNEL;
791
- uniforms.set("u_pointShadowMapTexture", pointShadowMap.texture);
792
- uniforms.set("u_pointShadowMapEnabled", 1);
793
- uniforms.set("u_pointShadowLightPosition", pointShadowMap.lightPosition);
794
- uniforms.set("u_pointShadowRange", pointShadowMap.range);
795
- uniforms.set("u_pointShadowFaceMatrices", faceMatrices);
796
- uniforms.set("u_pointShadowFaceRects", faceRects);
797
- uniforms.set("u_pointShadowStrength", strength);
798
- uniforms.set("u_pointShadowBias", bias);
799
- uniforms.set("u_pointShadowSlopeBias", slopeBias);
800
- uniforms.set("u_pointShadowTexelSize", texelSize);
801
- uniforms.set("u_pointShadowPcfSampleCount", filterKernel.samples.length);
802
- uniforms.set("u_pointShadowPcfSamples", packForwardShadowPcfSamples(filterKernel, item.label));
803
- }
804
-
805
- function toFloat32Array(values: Float32Array | readonly number[], expectedLength: number, name: string, label: string | undefined): Float32Array {
806
- const source = values instanceof Float32Array ? values : new Float32Array(values);
807
- if (source.length !== expectedLength || !isFiniteArrayLike(source)) {
808
- throw new RenderDeviceError(`${name} must contain ${expectedLength} finite values`, "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
809
- label,
810
- length: source.length,
811
- expectedLength
812
- });
813
- }
814
- return source;
815
- }
816
-
817
- function validatePointShadowFaceRects(faceRects: Float32Array, label: string | undefined): void {
818
- for (let offset = 0; offset < faceRects.length; offset += 4) {
819
- const rect = [faceRects[offset], faceRects[offset + 1], faceRects[offset + 2], faceRects[offset + 3]];
820
- if (!rect.every((value) => Number.isFinite(value) && value >= 0 && value <= 1) || rect[2]! <= 0 || rect[3]! <= 0) {
821
- throw new RenderDeviceError("Forward point-shadow face rects must be normalized atlas rectangles", "FORWARD_POINT_SHADOW_MAP_CONTRACT", {
822
- label,
823
- face: offset / 4,
824
- rect
825
- });
826
- }
827
- }
828
- }
829
-
830
- function packForwardShadowPcfSamples(filterKernel: ShadowFilterKernel, label: string | undefined): Float32Array {
831
- if (filterKernel.samples.length < 1 || filterKernel.samples.length > MAX_FORWARD_SHADOW_PCF_SAMPLES) {
832
- throw new RenderDeviceError("Forward shadow-map PCF kernel must contain 1 to 32 samples", "FORWARD_SHADOW_MAP_CONTRACT", {
833
- label,
834
- samples: filterKernel.samples.length
835
- });
836
- }
837
- const packed = new Float32Array(MAX_FORWARD_SHADOW_PCF_SAMPLES * 4);
838
- let weightSum = 0;
839
- for (const [index, sample] of filterKernel.samples.entries()) {
840
- if (![sample.x, sample.y, sample.weight].every(Number.isFinite) || sample.weight < 0) {
841
- throw new RenderDeviceError("Forward shadow-map PCF samples must contain finite offsets and non-negative weights", "FORWARD_SHADOW_MAP_CONTRACT", {
842
- label,
843
- sample
844
- });
845
- }
846
- const offset = index * 4;
847
- packed[offset] = sample.x;
848
- packed[offset + 1] = sample.y;
849
- packed[offset + 2] = sample.weight;
850
- weightSum += sample.weight;
851
- }
852
- if (weightSum <= 0) {
853
- throw new RenderDeviceError("Forward shadow-map PCF sample weights must sum to a positive value", "FORWARD_SHADOW_MAP_CONTRACT", {
854
- label,
855
- weightSum
856
- });
857
- }
858
- return packed;
859
- }
860
-
861
- function applyEnvironmentLightingUniforms(
862
- environment: EnvironmentLightingOptions | undefined,
863
- item: RenderItem,
864
- shader: RenderShaderProgram,
865
- uniforms: Map<string, UniformValue>
866
- ): void {
867
- if (!environment) {
868
- clearProceduralEnvironmentMapUniforms(shader, uniforms);
869
- clearSampledEnvironmentMapUniforms(shader, uniforms);
870
- clearEnvironmentBrdfLutUniforms(shader, uniforms);
871
- return;
872
- }
873
- if (!shader.reflection.uniforms.has("u_environmentColor") || !shader.reflection.uniforms.has("u_environmentIntensity")) {
874
- return;
875
- }
876
- const color = environment.color;
877
- if (color.length !== 3 || !isFiniteColor(color)) {
878
- throw new RenderDeviceError("Environment lighting color must contain three finite values in [0, 1]", "ENVIRONMENT_LIGHTING_CONTRACT", {
879
- label: item.label,
880
- color
881
- });
882
- }
883
- if (!Number.isFinite(environment.intensity) || environment.intensity < 0) {
884
- throw new RenderDeviceError("Environment lighting intensity must be finite and non-negative", "ENVIRONMENT_LIGHTING_CONTRACT", {
885
- label: item.label,
886
- intensity: environment.intensity
887
- });
888
- }
889
- uniforms.set("u_environmentColor", color);
890
- uniforms.set("u_environmentIntensity", environment.intensity);
891
- applyProceduralEnvironmentMapUniforms(environment.proceduralMap, item, shader, uniforms);
892
- applySampledEnvironmentMapUniforms(environment, item, shader, uniforms);
893
- }
894
-
895
- function applyProceduralEnvironmentMapUniforms(
896
- proceduralMap: ProceduralEnvironmentMapLightingOptions | undefined,
897
- item: RenderItem,
898
- shader: RenderShaderProgram,
899
- uniforms: Map<string, UniformValue>
900
- ): void {
901
- if (!proceduralMap) {
902
- clearProceduralEnvironmentMapUniforms(shader, uniforms);
903
- return;
904
- }
905
- const requiredUniforms = [
906
- "u_environmentSkyColor",
907
- "u_environmentHorizonColor",
908
- "u_environmentGroundColor",
909
- "u_environmentSpecularColor",
910
- "u_environmentMapIntensity",
911
- "u_environmentSpecularIntensity"
912
- ];
913
- if (!requiredUniforms.every((uniform) => shader.reflection.uniforms.has(uniform))) {
914
- return;
915
- }
916
- const skyColor = toEnvironmentColor(proceduralMap.skyColor, "proceduralMap.skyColor", item.label);
917
- const horizonColor = toEnvironmentColor(proceduralMap.horizonColor, "proceduralMap.horizonColor", item.label);
918
- const groundColor = toEnvironmentColor(proceduralMap.groundColor, "proceduralMap.groundColor", item.label);
919
- const specularColor = toEnvironmentColor(proceduralMap.specularColor, "proceduralMap.specularColor", item.label);
920
- if (!Number.isFinite(proceduralMap.intensity) || proceduralMap.intensity < 0) {
921
- throw new RenderDeviceError("Procedural environment map intensity must be finite and non-negative", "ENVIRONMENT_LIGHTING_CONTRACT", {
922
- label: item.label,
923
- intensity: proceduralMap.intensity
924
- });
925
- }
926
- if (!Number.isFinite(proceduralMap.specularIntensity) || proceduralMap.specularIntensity < 0) {
927
- throw new RenderDeviceError("Procedural environment map specularIntensity must be finite and non-negative", "ENVIRONMENT_LIGHTING_CONTRACT", {
928
- label: item.label,
929
- specularIntensity: proceduralMap.specularIntensity
930
- });
931
- }
932
- uniforms.set("u_environmentSkyColor", skyColor);
933
- uniforms.set("u_environmentHorizonColor", horizonColor);
934
- uniforms.set("u_environmentGroundColor", groundColor);
935
- uniforms.set("u_environmentSpecularColor", specularColor);
936
- uniforms.set("u_environmentMapIntensity", proceduralMap.intensity);
937
- uniforms.set("u_environmentSpecularIntensity", proceduralMap.specularIntensity);
938
- }
939
-
940
- function clearProceduralEnvironmentMapUniforms(
941
- shader: RenderShaderProgram,
942
- uniforms: Map<string, UniformValue>
943
- ): void {
944
- if (shader.reflection.uniforms.has("u_environmentMapIntensity")) {
945
- uniforms.set("u_environmentMapIntensity", 0);
946
- }
947
- if (shader.reflection.uniforms.has("u_environmentSpecularIntensity")) {
948
- uniforms.set("u_environmentSpecularIntensity", 0);
949
- }
950
- }
951
-
952
- function applySampledEnvironmentMapUniforms(
953
- environment: EnvironmentLightingOptions,
954
- item: RenderItem,
955
- shader: RenderShaderProgram,
956
- uniforms: Map<string, UniformValue>
957
- ): void {
958
- const environmentMapTexture = environment.environmentMapTexture;
959
- const environmentCubeMapTexture = environment.environmentCubeMapTexture;
960
- if (!environmentMapTexture && !environmentCubeMapTexture) {
961
- clearSampledEnvironmentMapUniforms(shader, uniforms);
962
- clearEnvironmentBrdfLutUniforms(shader, uniforms);
963
- return;
964
- }
965
- const requiredUniforms = [
966
- "u_environmentMapTexture",
967
- "u_environmentMapTextureEnabled",
968
- "u_environmentMapTextureIntensity",
969
- "u_environmentMapTextureSpecularIntensity",
970
- "u_environmentMapTextureRotation",
971
- "u_environmentMapTextureMipCount"
972
- ];
973
- if (!requiredUniforms.every((uniform) => shader.reflection.uniforms.has(uniform))) {
974
- return;
975
- }
976
- if (environmentMapTexture) {
977
- validateEnvironmentTextureBinding("Environment map texture", environmentMapTexture, "2d", item);
978
- }
979
- if (environmentCubeMapTexture) {
980
- validateEnvironmentTextureBinding("Environment cube map texture", environmentCubeMapTexture, "cube", item);
981
- }
982
- const intensity = environment.environmentMapIntensity ?? 1;
983
- const specularIntensity = environment.environmentMapSpecularIntensity ?? 0.5;
984
- const rotation = environment.environmentMapRotation ?? 0;
985
- const mipCount = environment.environmentMapMipCount ?? 1;
986
- const encoding = environment.environmentMapEncoding ?? "srgb";
987
- if (!Number.isFinite(intensity) || intensity < 0) {
988
- throw new RenderDeviceError("Sampled environment map intensity must be finite and non-negative", "ENVIRONMENT_LIGHTING_CONTRACT", {
989
- label: item.label,
990
- intensity
991
- });
992
- }
993
- if (!Number.isFinite(specularIntensity) || specularIntensity < 0) {
994
- throw new RenderDeviceError("Sampled environment map specular intensity must be finite and non-negative", "ENVIRONMENT_LIGHTING_CONTRACT", {
995
- label: item.label,
996
- specularIntensity
997
- });
998
- }
999
- if (!Number.isFinite(rotation)) {
1000
- throw new RenderDeviceError("Sampled environment map rotation must be finite", "ENVIRONMENT_LIGHTING_CONTRACT", {
1001
- label: item.label,
1002
- rotation
1003
- });
1004
- }
1005
- if (!Number.isInteger(mipCount) || mipCount < 1) {
1006
- throw new RenderDeviceError("Sampled environment map mip count must be a positive integer", "ENVIRONMENT_LIGHTING_CONTRACT", {
1007
- label: item.label,
1008
- mipCount
1009
- });
1010
- }
1011
- if (encoding !== "srgb" && encoding !== "rgbe" && encoding !== "linear") {
1012
- throw new RenderDeviceError("Sampled environment map encoding must be srgb, rgbe, or linear", "ENVIRONMENT_LIGHTING_CONTRACT", {
1013
- label: item.label,
1014
- encoding
1015
- });
1016
- }
1017
- uniforms.set(
1018
- "u_environmentMapTexture",
1019
- environmentMapTexture ?? new TextureBinding({ name: "u_environmentMapTexture", required: false, expectedDimension: "2d" })
1020
- );
1021
- if (shader.reflection.uniforms.has("u_environmentCubeMapTexture")) {
1022
- uniforms.set(
1023
- "u_environmentCubeMapTexture",
1024
- environmentCubeMapTexture ?? new TextureBinding({ name: "u_environmentCubeMapTexture", required: false, expectedDimension: "cube" })
1025
- );
1026
- }
1027
- uniforms.set("u_environmentMapTextureEnabled", 1);
1028
- if (shader.reflection.uniforms.has("u_environmentCubeMapTextureEnabled")) {
1029
- uniforms.set("u_environmentCubeMapTextureEnabled", environmentCubeMapTexture ? 1 : 0);
1030
- }
1031
- uniforms.set("u_environmentMapTextureIntensity", intensity);
1032
- uniforms.set("u_environmentMapTextureSpecularIntensity", specularIntensity);
1033
- uniforms.set("u_environmentMapTextureRotation", rotation);
1034
- uniforms.set("u_environmentMapTextureMipCount", mipCount);
1035
- if (shader.reflection.uniforms.has("u_environmentMapTextureEncoding")) {
1036
- uniforms.set("u_environmentMapTextureEncoding", encoding === "rgbe" ? 1 : encoding === "linear" ? 2 : 0);
1037
- }
1038
- applyEnvironmentBrdfLutUniforms(environment, item, shader, uniforms);
1039
- }
1040
-
1041
- function clearSampledEnvironmentMapUniforms(
1042
- shader: RenderShaderProgram,
1043
- uniforms: Map<string, UniformValue>
1044
- ): void {
1045
- if (shader.reflection.uniforms.has("u_environmentMapTexture")) {
1046
- uniforms.set("u_environmentMapTexture", new TextureBinding({ name: "u_environmentMapTexture", required: false, expectedDimension: "2d" }));
1047
- }
1048
- if (shader.reflection.uniforms.has("u_environmentCubeMapTexture")) {
1049
- uniforms.set("u_environmentCubeMapTexture", new TextureBinding({ name: "u_environmentCubeMapTexture", required: false, expectedDimension: "cube" }));
1050
- }
1051
- if (shader.reflection.uniforms.has("u_environmentMapTextureEnabled")) {
1052
- uniforms.set("u_environmentMapTextureEnabled", 0);
1053
- }
1054
- if (shader.reflection.uniforms.has("u_environmentCubeMapTextureEnabled")) {
1055
- uniforms.set("u_environmentCubeMapTextureEnabled", 0);
1056
- }
1057
- if (shader.reflection.uniforms.has("u_environmentMapTextureIntensity")) {
1058
- uniforms.set("u_environmentMapTextureIntensity", 0);
1059
- }
1060
- if (shader.reflection.uniforms.has("u_environmentMapTextureSpecularIntensity")) {
1061
- uniforms.set("u_environmentMapTextureSpecularIntensity", 0);
1062
- }
1063
- if (shader.reflection.uniforms.has("u_environmentMapTextureEncoding")) {
1064
- uniforms.set("u_environmentMapTextureEncoding", 0);
1065
- }
1066
- }
1067
-
1068
- function validateEnvironmentTextureBinding(
1069
- label: string,
1070
- binding: TextureBinding,
1071
- expectedDimension: "2d" | "cube",
1072
- item: RenderItem
1073
- ): void {
1074
- const validation = binding.validate();
1075
- if (!validation.ok) {
1076
- throw new RenderDeviceError(`${label} binding validation failed`, "ENVIRONMENT_LIGHTING_CONTRACT", {
1077
- label: item.label,
1078
- diagnostics: validation.diagnostics
1079
- });
1080
- }
1081
- if (binding.texture?.dimension !== expectedDimension) {
1082
- throw new RenderDeviceError(`${label} must bind a ${expectedDimension} texture`, "ENVIRONMENT_LIGHTING_CONTRACT", {
1083
- label: item.label,
1084
- texture: binding.name,
1085
- expectedDimension,
1086
- actualDimension: binding.texture?.dimension ?? null
1087
- });
1088
- }
1089
- }
1090
-
1091
- function applyEnvironmentBrdfLutUniforms(
1092
- environment: EnvironmentLightingOptions,
1093
- item: RenderItem,
1094
- shader: RenderShaderProgram,
1095
- uniforms: Map<string, UniformValue>
1096
- ): void {
1097
- if (!environment.environmentBrdfLutTexture) {
1098
- clearEnvironmentBrdfLutUniforms(shader, uniforms);
1099
- return;
1100
- }
1101
- if (!shader.reflection.uniforms.has("u_environmentBrdfLutTexture") || !shader.reflection.uniforms.has("u_environmentBrdfLutEnabled")) {
1102
- return;
1103
- }
1104
- const validation = environment.environmentBrdfLutTexture.validate();
1105
- if (!validation.ok) {
1106
- throw new RenderDeviceError("Environment BRDF LUT texture binding validation failed", "ENVIRONMENT_LIGHTING_CONTRACT", {
1107
- label: item.label,
1108
- diagnostics: validation.diagnostics
1109
- });
1110
- }
1111
- uniforms.set("u_environmentBrdfLutTexture", environment.environmentBrdfLutTexture);
1112
- uniforms.set("u_environmentBrdfLutEnabled", 1);
1113
- }
1114
-
1115
- function clearEnvironmentBrdfLutUniforms(
1116
- shader: RenderShaderProgram,
1117
- uniforms: Map<string, UniformValue>
1118
- ): void {
1119
- if (shader.reflection.uniforms.has("u_environmentBrdfLutTexture")) {
1120
- uniforms.set("u_environmentBrdfLutTexture", new TextureBinding({ name: "u_environmentBrdfLutTexture", required: false }));
1121
- }
1122
- if (shader.reflection.uniforms.has("u_environmentBrdfLutEnabled")) {
1123
- uniforms.set("u_environmentBrdfLutEnabled", 0);
1124
- }
1125
- }
1126
-
1127
- function toEnvironmentColor(value: readonly number[], field: string, label?: string): readonly number[] {
1128
- if (value.length !== 3 || !isFiniteColor(value)) {
1129
- throw new RenderDeviceError("Procedural environment map colors must contain three finite values in [0, 1]", "ENVIRONMENT_LIGHTING_CONTRACT", {
1130
- label,
1131
- field,
1132
- color: value
1133
- });
1134
- }
1135
- return value;
1136
- }
1137
-
1138
- function applyCameraUniforms(
1139
- cameraPosition: readonly [number, number, number] | undefined,
1140
- item: RenderItem,
1141
- shader: RenderShaderProgram,
1142
- uniforms: Map<string, UniformValue>
1143
- ): void {
1144
- if (!shader.reflection.uniforms.has("u_cameraPosition")) return;
1145
- const position = cameraPosition ?? [0, 0, 1];
1146
- if (position.length !== 3 || position.some((value) => !Number.isFinite(value))) {
1147
- throw new RenderDeviceError("Forward pass camera position must be a finite vec3", "FORWARD_CAMERA_CONTRACT", {
1148
- label: item.label,
1149
- cameraPosition: [...position]
1150
- });
1151
- }
1152
- uniforms.set("u_cameraPosition", [...position]);
1153
- }
1154
-
1155
- function applyAlphaCutoffUniform(
1156
- item: RenderItem,
1157
- shader: RenderShaderProgram,
1158
- uniforms: Map<string, UniformValue>
1159
- ): void {
1160
- if (!shader.reflection.uniforms.has("u_alphaCutoff") || uniforms.has("u_alphaCutoff")) return;
1161
- uniforms.set("u_alphaCutoff", 0);
1162
- }
1163
-
1164
- function applyTransformUniforms(
1165
- item: RenderItem,
1166
- shader: RenderShaderProgram,
1167
- uniforms: Map<string, UniformValue>
1168
- ): void {
1169
- const modelMatrix = shader.reflection.uniforms.has("u_modelMatrix") || shader.reflection.uniforms.has("u_normalMatrix")
1170
- ? toMat4Uniform(item.modelMatrix ?? identityMatrix(), "modelMatrix", item.label)
1171
- : undefined;
1172
- if (item.modelViewProjectionMatrix && shader.reflection.uniforms.has("u_modelViewProjection")) {
1173
- uniforms.set("u_modelViewProjection", toMat4Uniform(item.modelViewProjectionMatrix, "modelViewProjectionMatrix", item.label));
1174
- }
1175
- if (modelMatrix && shader.reflection.uniforms.has("u_modelMatrix")) {
1176
- uniforms.set("u_modelMatrix", modelMatrix);
1177
- }
1178
- if (shader.reflection.uniforms.has("u_normalMatrix")) {
1179
- uniforms.set(
1180
- "u_normalMatrix",
1181
- item.normalMatrix
1182
- ? toMat4Uniform(item.normalMatrix, "normalMatrix", item.label)
1183
- : new Float32Array(normalMatrixFromModel(toMat4Values(modelMatrix ?? identityMatrix(), "modelMatrix", item.label)))
1184
- );
1185
- }
1186
- }
1187
-
1188
- function toMat4Uniform(value: Float32Array | readonly number[], field: string, label?: string): Float32Array {
1189
- if (value.length !== 16 || !isFiniteArrayLike(value)) {
1190
- throw new RenderDeviceError("Render item transform uniforms must be finite mat4 values", "RENDER_ITEM_TRANSFORM_CONTRACT", {
1191
- label,
1192
- field,
1193
- scalars: value.length
1194
- });
1195
- }
1196
- return value instanceof Float32Array ? value : new Float32Array(value);
1197
- }
1198
-
1199
- function toMat4Values(value: Float32Array | readonly number[], field: string, label?: string): Mat4 {
1200
- if (value.length !== 16 || !isFiniteArrayLike(value)) {
1201
- throw new RenderDeviceError("Render item transform uniforms must be finite mat4 values", "RENDER_ITEM_TRANSFORM_CONTRACT", {
1202
- label,
1203
- field,
1204
- scalars: value.length
1205
- });
1206
- }
1207
- return mat4FromArrayLike(value, 0);
1208
- }
1209
-
1210
- function identityMatrix(): readonly number[] {
1211
- return [
1212
- 1, 0, 0, 0,
1213
- 0, 1, 0, 0,
1214
- 0, 0, 1, 0,
1215
- 0, 0, 0, 1
1216
- ];
1217
- }
1218
-
1219
- function getBaseMaterial(material: RenderMaterial): Material {
1220
- return material instanceof MaterialInstance ? material.baseMaterial : material;
1221
- }
1222
-
1223
- function sortForwardRenderItems(
1224
- items: readonly RenderItem[],
1225
- cameraPosition: readonly [number, number, number] | undefined
1226
- ): readonly RenderItem[] {
1227
- return sortRenderQueueItems(items.map((item) => ({
1228
- item,
1229
- bucket: isTransparentRenderItem(item) ? "transparent" : isTransmissionRenderItem(item) ? "transmission" : "opaque",
1230
- depth: cameraPosition ? distanceSquaredFromCamera(item, cameraPosition) : 0,
1231
- pipelineKey: renderItemPipelineKey(item),
1232
- batchKey: renderItemPipelineKey(item),
1233
- instanceCount: item.instanceTransforms ? instanceTransformCount(item) : 1
1234
- }))).items;
1235
- }
1236
-
1237
- function isTransparentRenderItem(item: RenderItem): boolean {
1238
- const material = item.material ?? new UnlitMaterial();
1239
- return getBaseMaterial(material).renderState.blend;
1240
- }
1241
-
1242
- function materialNumericParameter(material: Material, name: string, fallback: number): number {
1243
- const value = material.getParameter(name);
1244
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1245
- }
1246
-
1247
- function isTransmissionRenderItem(item: RenderItem): boolean {
1248
- const material = item.material ?? new UnlitMaterial();
1249
- const baseMaterial = getBaseMaterial(material);
1250
- if (baseMaterial.renderState.blend) return false;
1251
- const transmissionFactor = materialNumericParameter(baseMaterial, "u_transmissionFactor", 0);
1252
- const diffuseTransmissionFactor = materialNumericParameter(baseMaterial, "u_diffuseTransmissionFactor", 0);
1253
- const volumeThicknessFactor = materialNumericParameter(baseMaterial, "u_volumeThicknessFactor", 0);
1254
- return transmissionFactor > 0.001 || diffuseTransmissionFactor > 0.001 || volumeThicknessFactor > 0.001;
1255
- }
1256
-
1257
- function distanceSquaredFromCamera(item: RenderItem, cameraPosition: readonly [number, number, number]): number {
1258
- const center = item.boundingBoxCenter;
1259
- const tx = center?.[0] ?? item.modelMatrix?.[12] ?? 0;
1260
- const ty = center?.[1] ?? item.modelMatrix?.[13] ?? 0;
1261
- const tz = center?.[2] ?? item.modelMatrix?.[14] ?? 0;
1262
- const x = tx - cameraPosition[0];
1263
- const y = ty - cameraPosition[1];
1264
- const z = tz - cameraPosition[2];
1265
- return x * x + y * y + z * z;
1266
- }
1267
-
1268
- function renderItemPipelineKey(item: RenderItem): string {
1269
- const material = item.material ?? new UnlitMaterial();
1270
- const baseMaterial = getBaseMaterial(material);
1271
- const state = baseMaterial.renderState;
1272
- return `${baseMaterial.name}|${state.depthTest ? "dt" : "ndt"}|${state.depthWrite ? "dw" : "ndw"}|${state.cullMode}|${state.blend ? "blend" : "opaque"}`;
1273
- }
1274
-
1275
- function instanceTransformCount(item: RenderItem): number {
1276
- return validateInstanceTransformSource(item).length / 16;
1277
- }
1278
-
1279
- function splitInstanceTransforms(transforms: Float32Array | readonly number[]): Float32Array[] {
1280
- const source = transforms instanceof Float32Array ? transforms : new Float32Array(transforms);
1281
- const batches: Float32Array[] = [];
1282
- for (let offset = 0; offset < source.length; offset += MAX_GPU_INSTANCES * 16) {
1283
- batches.push(source.subarray(offset, Math.min(source.length, offset + MAX_GPU_INSTANCES * 16)));
1284
- }
1285
- return batches;
1286
- }
1287
-
1288
- function applyInstanceUniforms(
1289
- item: RenderItem,
1290
- shader: RenderShaderProgram,
1291
- uniforms: Map<string, UniformValue>
1292
- ): number {
1293
- if (!shader.reflection.uniforms.has("u_instanceMatrices") || !shader.reflection.uniforms.has("u_instanceCount")) {
1294
- throw new RenderDeviceError("Instanced render items require a shader with instance matrix uniforms", "INSTANCING_SHADER_CONTRACT", {
1295
- label: item.label
1296
- });
1297
- }
1298
- const source = validateInstanceTransformSource(item);
1299
- const instanceCount = source.length / 16;
1300
- if (instanceCount > MAX_GPU_INSTANCES) {
1301
- throw new RenderDeviceError("Instanced shader path exceeds the supported uniform instance count", "GPU_INSTANCE_LIMIT", {
1302
- label: item.label,
1303
- instanceCount,
1304
- maxInstances: MAX_GPU_INSTANCES
1305
- });
1306
- }
1307
- const packed = new Float32Array(MAX_GPU_INSTANCES * 16);
1308
- packed.set(source);
1309
- uniforms.set("u_instanceMatrices", packed);
1310
- uniforms.set("u_instanceCount", instanceCount);
1311
- uniforms.set("u_instanceAttributeMode", 0);
1312
- return instanceCount;
1313
- }
1314
-
1315
- function applyInstanceBinding(
1316
- device: RenderDevice,
1317
- item: RenderItem,
1318
- shader: RenderShaderProgram,
1319
- uniforms: Map<string, UniformValue>
1320
- ): { readonly count: number; readonly attributes?: readonly InstanceVertexAttribute[]; readonly buffers?: readonly RenderBuffer[] } {
1321
- const source = validateInstanceTransformSource(item);
1322
- const instanceCount = source.length / 16;
1323
- const extraBindings = createExtraInstanceAttributeBindings(device, item, instanceCount);
1324
- if (supportsInstanceAttributes(shader) && instanceCount > MAX_GPU_INSTANCES) {
1325
- const data = source instanceof Float32Array ? source : new Float32Array(source);
1326
- const buffer = device.createBuffer("vertex", data.byteLength, data);
1327
- uniforms.set("u_instanceCount", instanceCount);
1328
- uniforms.set("u_instanceAttributeMode", 1);
1329
- return {
1330
- count: instanceCount,
1331
- buffers: [buffer, ...extraBindings.buffers],
1332
- attributes: [
1333
- ...INSTANCE_MATRIX_ATTRIBUTE_NAMES.map((shaderName, column) => ({
1334
- buffer,
1335
- shaderName,
1336
- components: 4 as const,
1337
- offset: column * 16,
1338
- stride: 64,
1339
- divisor: 1
1340
- })),
1341
- ...extraBindings.attributes
1342
- ]
1343
- };
1344
- }
1345
- return {
1346
- count: applyInstanceUniforms(item, shader, uniforms),
1347
- ...(extraBindings.attributes.length > 0 ? { attributes: extraBindings.attributes } : {}),
1348
- ...(extraBindings.buffers.length > 0 ? { buffers: extraBindings.buffers } : {})
1349
- };
1350
- }
1351
-
1352
- function createExtraInstanceAttributeBindings(
1353
- device: RenderDevice,
1354
- item: RenderItem,
1355
- instanceCount: number
1356
- ): { readonly attributes: readonly InstanceVertexAttribute[]; readonly buffers: readonly RenderBuffer[] } {
1357
- const descriptors: RenderItemInstanceAttribute[] = [];
1358
- if (item.instanceColors) {
1359
- descriptors.push({
1360
- shaderName: "a_instanceColor",
1361
- components: 4,
1362
- data: item.instanceColors
1363
- });
1364
- }
1365
- if (item.instanceAttributes) descriptors.push(...item.instanceAttributes);
1366
- if (descriptors.length === 0) return { attributes: [], buffers: [] };
1367
-
1368
- const attributes: InstanceVertexAttribute[] = [];
1369
- const buffers: RenderBuffer[] = [];
1370
- for (const descriptor of descriptors) {
1371
- validateInstanceAttributeDescriptor(descriptor, instanceCount, item.label);
1372
- const data = descriptor.data instanceof Float32Array ? descriptor.data : new Float32Array(descriptor.data);
1373
- const stride = descriptor.components * 4;
1374
- const buffer = device.createBuffer("vertex", data.byteLength, data);
1375
- buffers.push(buffer);
1376
- attributes.push({
1377
- buffer,
1378
- shaderName: descriptor.shaderName,
1379
- components: descriptor.components,
1380
- offset: 0,
1381
- stride,
1382
- normalized: descriptor.normalized ?? false,
1383
- divisor: descriptor.divisor ?? 1
1384
- });
1385
- }
1386
- return { attributes, buffers };
1387
- }
1388
-
1389
- function validateInstanceAttributeDescriptor(descriptor: RenderItemInstanceAttribute, instanceCount: number, label: string | undefined): void {
1390
- if (!descriptor.shaderName.trim()) {
1391
- throw new RenderDeviceError("Instance attribute shaderName is required", "INSTANCE_ATTRIBUTE_CONTRACT", { label });
1392
- }
1393
- if (![1, 2, 3, 4].includes(descriptor.components)) {
1394
- throw new RenderDeviceError("Instance attribute components must be 1, 2, 3, or 4", "INSTANCE_ATTRIBUTE_CONTRACT", {
1395
- label,
1396
- shaderName: descriptor.shaderName,
1397
- components: descriptor.components
1398
- });
1399
- }
1400
- const data = descriptor.data instanceof Float32Array ? descriptor.data : new Float32Array(descriptor.data);
1401
- if (data.length !== instanceCount * descriptor.components || !Array.from(data).every(Number.isFinite)) {
1402
- throw new RenderDeviceError("Instance attribute data must contain finite values for every instance", "INSTANCE_ATTRIBUTE_CONTRACT", {
1403
- label,
1404
- shaderName: descriptor.shaderName,
1405
- components: descriptor.components,
1406
- dataLength: data.length,
1407
- expectedLength: instanceCount * descriptor.components
1408
- });
1409
- }
1410
- }
1411
-
1412
- function resolveRenderGeometry(item: RenderItem): Geometry {
1413
- if (item.morphTargets === undefined && item.morphWeights === undefined) {
1414
- return item.geometry;
1415
- }
1416
- if (!item.morphTargets || !item.morphWeights) {
1417
- throw new RenderDeviceError("Morph render items require both morphTargets and morphWeights", "MORPH_TARGET_CONTRACT", {
1418
- label: item.label,
1419
- targetCount: item.morphTargets?.length ?? 0,
1420
- weightCount: item.morphWeights?.length ?? 0
1421
- });
1422
- }
1423
- return applyMorphTargets(item.geometry, item.morphTargets, item.morphWeights);
1424
- }
1425
-
1426
- function applyGpuMorphUniforms(
1427
- item: RenderItem,
1428
- shader: RenderShaderProgram,
1429
- uniforms: Map<string, UniformValue>
1430
- ): boolean {
1431
- if (
1432
- !shader.reflection.uniforms.has("u_morphPositionDeltas") ||
1433
- !shader.reflection.uniforms.has("u_morphWeights") ||
1434
- !shader.reflection.uniforms.has("u_morphTargetCount")
1435
- ) {
1436
- return false;
1437
- }
1438
- if (!item.morphTargets || !item.morphWeights) {
1439
- throw new RenderDeviceError("Morph render items require both morphTargets and morphWeights", "MORPH_TARGET_CONTRACT", {
1440
- label: item.label,
1441
- targetCount: item.morphTargets?.length ?? 0,
1442
- weightCount: item.morphWeights?.length ?? 0
1443
- });
1444
- }
1445
- if (item.morphTargets.length !== item.morphWeights.length) {
1446
- throw new RenderDeviceError("Morph target count must match morph weight count", "MORPH_TARGET_CONTRACT", {
1447
- label: item.label,
1448
- targetCount: item.morphTargets.length,
1449
- weightCount: item.morphWeights.length
1450
- });
1451
- }
1452
- // Counts beyond the uniform fast-path capacity are not an error: fall back to the CPU morph
1453
- // (resolveRenderGeometry -> applyMorphTargets), which is unlimited and morphs normals + tangents
1454
- // so lighting follows the deformation. The texture-backed GPU plan (createMorphTargetPlan) packs
1455
- // the same data for the texture path; see MorphTargetPlan.ts.
1456
- if (item.morphTargets.length > MAX_GPU_MORPH_TARGETS || item.geometry.vertexBuffer.vertexCount > MAX_GPU_MORPH_VERTICES) {
1457
- return false;
1458
- }
1459
- const packed = new Float32Array(MAX_GPU_MORPH_TARGETS * MAX_GPU_MORPH_VERTICES * 4);
1460
- const packedNormals = new Float32Array(MAX_GPU_MORPH_TARGETS * MAX_GPU_MORPH_VERTICES * 4);
1461
- const weights = new Float32Array(MAX_GPU_MORPH_TARGETS);
1462
- for (let targetIndex = 0; targetIndex < item.morphTargets.length; targetIndex += 1) {
1463
- const target = item.morphTargets[targetIndex]!;
1464
- if (!target.positions || target.positions.length < item.geometry.vertexBuffer.vertexCount) {
1465
- throw new RenderDeviceError("GPU morph shader path requires position deltas for every source vertex", "GPU_MORPH_TARGET_CONTRACT", {
1466
- label: item.label,
1467
- targetIndex,
1468
- vertexCount: item.geometry.vertexBuffer.vertexCount,
1469
- deltaCount: target.positions?.length ?? 0
1470
- });
1471
- }
1472
- const weight = item.morphWeights[targetIndex] ?? 0;
1473
- if (!Number.isFinite(weight)) {
1474
- throw new RenderDeviceError("GPU morph weights must be finite", "GPU_MORPH_TARGET_CONTRACT", {
1475
- label: item.label,
1476
- targetIndex,
1477
- weight
1478
- });
1479
- }
1480
- weights[targetIndex] = weight;
1481
- for (let vertex = 0; vertex < item.geometry.vertexBuffer.vertexCount; vertex += 1) {
1482
- const delta = target.positions[vertex]!;
1483
- if (delta.length !== 3 || !Number.isFinite(delta[0]) || !Number.isFinite(delta[1]) || !Number.isFinite(delta[2])) {
1484
- throw new RenderDeviceError("GPU morph position deltas must be finite vec3 values", "GPU_MORPH_TARGET_CONTRACT", {
1485
- label: item.label,
1486
- targetIndex,
1487
- vertex
1488
- });
1489
- }
1490
- const offset = (targetIndex * MAX_GPU_MORPH_VERTICES + vertex) * 4;
1491
- packed[offset] = delta[0];
1492
- packed[offset + 1] = delta[1];
1493
- packed[offset + 2] = delta[2];
1494
- const normal = target.normals?.[vertex];
1495
- if (normal && normal.length === 3) {
1496
- packedNormals[offset] = normal[0];
1497
- packedNormals[offset + 1] = normal[1];
1498
- packedNormals[offset + 2] = normal[2];
1499
- }
1500
- }
1501
- }
1502
- uniforms.set("u_morphPositionDeltas", packed);
1503
- uniforms.set("u_morphWeights", weights);
1504
- uniforms.set("u_morphTargetCount", item.morphTargets.length);
1505
- // Normal deltas are uploaded only when the bound shader declares the uniform (lit morph variants);
1506
- // the default unlit morph shader ignores them.
1507
- if (shader.reflection.uniforms.has("u_morphNormalDeltas")) {
1508
- uniforms.set("u_morphNormalDeltas", packedNormals);
1509
- }
1510
- return true;
1511
- }
1512
-
1513
- function applySkinningUniforms(
1514
- skinning: SkinningPaletteBinding,
1515
- material: Material,
1516
- shader: RenderShaderProgram,
1517
- uniforms: Map<string, UniformValue>
1518
- ): void {
1519
- if (!shader.reflection.uniforms.has("u_jointMatrices") || !shader.reflection.uniforms.has("u_jointCount")) {
1520
- throw new RenderDeviceError("Skinned render item requires a shader with joint palette uniforms", "SKINNING_SHADER_CONTRACT", {
1521
- material: material.name
1522
- });
1523
- }
1524
- if (!Number.isInteger(skinning.jointCount) || skinning.jointCount <= 0 || skinning.jointCount > 96) {
1525
- throw new RenderDeviceError("Skinning jointCount must be an integer in [1, 96]", "INVALID_SKINNING_PALETTE", {
1526
- jointCount: skinning.jointCount
1527
- });
1528
- }
1529
- if (skinning.matrices.length !== skinning.jointCount * 16) {
1530
- throw new RenderDeviceError("Skinning matrix palette length must equal jointCount * 16", "INVALID_SKINNING_PALETTE", {
1531
- jointCount: skinning.jointCount,
1532
- matrixScalars: skinning.matrices.length
1533
- });
1534
- }
1535
- if (!isFiniteArrayLike(skinning.matrices)) {
1536
- throw new RenderDeviceError("Skinning matrix palette must contain finite values", "INVALID_SKINNING_PALETTE", {
1537
- jointCount: skinning.jointCount
1538
- });
1539
- }
1540
- uniforms.set("u_jointCount", skinning.jointCount);
1541
- uniforms.set("u_jointMatrices", skinning.matrices);
1542
- }
1543
-
1544
- function validateSkinningGeometryContract(item: RenderItem, skinning: SkinningPaletteBinding): void {
1545
- const format = item.geometry.vertexBuffer.format;
1546
- if (!format.hasAttribute("joints") || !format.hasAttribute("weights")) {
1547
- throw new RenderDeviceError("Skinned render item geometry must include joints and weights attributes", "SKINNING_GEOMETRY_CONTRACT", {
1548
- label: item.label,
1549
- jointCount: skinning.jointCount,
1550
- vertexFormat: format.attributes.map((attribute) => attribute.shaderName),
1551
- missingAttributes: [
1552
- ...(format.hasAttribute("joints") ? [] : ["a_joints"]),
1553
- ...(format.hasAttribute("weights") ? [] : ["a_weights"])
1554
- ]
1555
- });
1556
- }
1557
-
1558
- const jointsAttribute = format.getAttribute("joints");
1559
- const weightsAttribute = format.getAttribute("weights");
1560
- if (jointsAttribute.components !== 4 || weightsAttribute.components !== 4) {
1561
- throw new RenderDeviceError("Skinned render item geometry must use four joint and four weight influences per vertex", "SKINNING_GEOMETRY_CONTRACT", {
1562
- label: item.label,
1563
- jointCount: skinning.jointCount,
1564
- jointComponents: jointsAttribute.components,
1565
- weightComponents: weightsAttribute.components
1566
- });
1567
- }
1568
-
1569
- for (let vertex = 0; vertex < item.geometry.vertexBuffer.vertexCount; vertex += 1) {
1570
- const joints = item.geometry.vertexBuffer.getAttribute(vertex, "joints");
1571
- const weights = item.geometry.vertexBuffer.getAttribute(vertex, "weights");
1572
- let weightSum = 0;
1573
- for (let influence = 0; influence < 4; influence += 1) {
1574
- const joint = joints[influence] ?? 0;
1575
- const weight = weights[influence] ?? 0;
1576
- if (!Number.isFinite(weight) || weight < 0) {
1577
- throw new RenderDeviceError("Skinned render item weights must be finite non-negative values", "SKINNING_GEOMETRY_CONTRACT", {
1578
- label: item.label,
1579
- jointCount: skinning.jointCount,
1580
- vertex,
1581
- influence,
1582
- weight
1583
- });
1584
- }
1585
- if (!Number.isFinite(joint) || !Number.isInteger(joint) || joint < 0) {
1586
- throw new RenderDeviceError("Skinned render item joints must be finite non-negative integer indices", "SKINNING_GEOMETRY_CONTRACT", {
1587
- label: item.label,
1588
- jointCount: skinning.jointCount,
1589
- vertex,
1590
- influence,
1591
- joint
1592
- });
1593
- }
1594
- if (weight > 0 && joint >= skinning.jointCount) {
1595
- throw new RenderDeviceError("Skinned render item joint indices must be within the uploaded skinning palette", "SKINNING_GEOMETRY_CONTRACT", {
1596
- label: item.label,
1597
- jointCount: skinning.jointCount,
1598
- vertex,
1599
- influence,
1600
- joint,
1601
- weight
1602
- });
1603
- }
1604
- weightSum += weight;
1605
- }
1606
- if (weightSum <= 0) {
1607
- throw new RenderDeviceError("Skinned render item weights must sum to a positive value", "SKINNING_GEOMETRY_CONTRACT", {
1608
- label: item.label,
1609
- jointCount: skinning.jointCount,
1610
- vertex,
1611
- weightSum
1612
- });
1613
- }
1614
- if (Math.abs(weightSum - 1) > 0.02) {
1615
- throw new RenderDeviceError("Skinned render item weights must be normalized before GPU skinning", "SKINNING_GEOMETRY_CONTRACT", {
1616
- label: item.label,
1617
- jointCount: skinning.jointCount,
1618
- vertex,
1619
- weightSum
1620
- });
1621
- }
1622
- }
1623
- }
1624
-
1625
- function validateInstanceTransformSource(item: RenderItem): Float32Array | readonly number[] {
1626
- const source = item.instanceTransforms ?? [];
1627
- if (source.length === 0 || source.length % 16 !== 0) {
1628
- throw new RenderDeviceError("Instance transforms must contain one or more mat4 values", "INSTANCING_CONTRACT", {
1629
- label: item.label,
1630
- scalars: source.length
1631
- });
1632
- }
1633
- if (!isFiniteArrayLike(source)) {
1634
- throw new RenderDeviceError("Instance transforms must contain finite mat4 values", "INSTANCING_CONTRACT", {
1635
- label: item.label
1636
- });
1637
- }
1638
- return source;
1639
- }
1640
-
1641
- function isFiniteArrayLike(values: ArrayLike<number>): boolean {
1642
- for (let index = 0; index < values.length; index += 1) {
1643
- if (!Number.isFinite(values[index])) return false;
1644
- }
1645
- return true;
1646
- }
1647
-
1648
- function isFiniteColor(values: ArrayLike<number>): boolean {
1649
- for (let index = 0; index < values.length; index += 1) {
1650
- const channel = values[index];
1651
- if (!Number.isFinite(channel) || channel < 0 || channel > 1) return false;
1652
- }
1653
- return true;
1654
- }
1655
-
1656
- function mat4FromArrayLike(values: ArrayLike<number>, offset: number): Mat4 {
1657
- return [
1658
- values[offset]!, values[offset + 1]!, values[offset + 2]!, values[offset + 3]!,
1659
- values[offset + 4]!, values[offset + 5]!, values[offset + 6]!, values[offset + 7]!,
1660
- values[offset + 8]!, values[offset + 9]!, values[offset + 10]!, values[offset + 11]!,
1661
- values[offset + 12]!, values[offset + 13]!, values[offset + 14]!, values[offset + 15]!
1662
- ];
1663
- }