WebGLNodesHandler.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. import {
  2. GLSL3,
  3. UniformsGroup,
  4. Compatibility,
  5. Color,
  6. UniformsLib,
  7. UniformsUtils,
  8. } from 'three';
  9. import {
  10. context,
  11. cubeTexture,
  12. reference,
  13. texture,
  14. fog,
  15. rangeFogFactor,
  16. densityFogFactor,
  17. workingToColorSpace,
  18. } from 'three/tsl';
  19. import {
  20. NodeUtils,
  21. NodeFrame,
  22. Lighting,
  23. InspectorBase,
  24. GLSLNodeBuilder,
  25. BasicNodeLibrary,
  26. WebGLCapabilities,
  27. } from 'three/webgpu';
  28. // Limitations
  29. // - VSM shadows not supported
  30. // - MRT not supported
  31. // - Transmission not supported
  32. // - WebGPU postprocessing stack not supported
  33. // - Storage textures not supported
  34. // - Fog / environment do not automatically update - must call "dispose"
  35. // - instanced mesh geometry cannot be shared
  36. // - Node materials cannot be used with "compile" function
  37. // hash any object parameters that will impact the resulting shader so we can force
  38. // a program update
  39. function getObjectHash( object ) {
  40. return '' + object.receiveShadow;
  41. }
  42. // Mirrors WebGLUniforms.seqWithValue from WebGLRenderer
  43. function generateUniformsList( program, uniforms ) {
  44. const progUniforms = program.getUniforms();
  45. const uniformsList = [];
  46. for ( let i = 0; i < progUniforms.seq.length; i ++ ) {
  47. const u = progUniforms.seq[ i ];
  48. if ( u.id in uniforms ) uniformsList.push( u );
  49. }
  50. return uniformsList;
  51. }
  52. // overrides shadow nodes to use the built in shadow textures
  53. class WebGLNodeBuilder extends GLSLNodeBuilder {
  54. addNode( node ) {
  55. if ( node.isShadowNode ) {
  56. node.setupRenderTarget = shadow => {
  57. return { shadowMap: shadow.map, depthTexture: shadow.map.depthTexture };
  58. };
  59. node.updateBefore = () => {
  60. // no need to rerender shadows since WebGLRenderer is handling it
  61. };
  62. }
  63. super.addNode( node );
  64. }
  65. }
  66. // produce and update reusable nodes for a scene
  67. class SceneContext {
  68. constructor( renderer, scene ) {
  69. // TODO: can / should we update the fog and environment node every frame for recompile?
  70. this.renderer = renderer;
  71. this.scene = scene;
  72. this.lightsNode = renderer.lighting.getNode( scene );
  73. this.fogNode = null;
  74. this.environmentNode = null;
  75. this.prevFog = null;
  76. this.prevEnvironment = null;
  77. }
  78. getCacheKey() {
  79. const { lightsNode, environmentNode, fogNode } = this;
  80. const lightsHash = lightsNode.getCacheKey();
  81. const envHash = environmentNode ? environmentNode.getCacheKey : 0;
  82. const fogHash = fogNode ? fogNode.getCacheKey() : 0;
  83. return NodeUtils.hashArray( [ lightsHash, envHash, fogHash ] );
  84. }
  85. update() {
  86. const { scene, lightsNode } = this;
  87. // update lighting
  88. const sceneLights = [];
  89. scene.traverse( object => {
  90. if ( object.isLight ) {
  91. sceneLights.push( object );
  92. }
  93. } );
  94. lightsNode.setLights( sceneLights );
  95. // update fog
  96. if ( this.prevFog !== scene.fog ) {
  97. this.fogNode = this.getFogNode();
  98. this.prevFog = scene.fog;
  99. }
  100. // update environment
  101. if ( this.prevEnvironment !== scene.environment ) {
  102. this.environmentNode = this.getEnvironmentNode();
  103. this.prevEnvironment = scene.environment;
  104. }
  105. }
  106. getFogNode() {
  107. const { scene } = this;
  108. if ( scene.fog && scene.fog.isFogExp2 ) {
  109. const color = reference( 'color', 'color', scene.fog );
  110. const density = reference( 'density', 'float', scene.fog );
  111. return fog( color, densityFogFactor( density ) );
  112. } else if ( scene.fog && scene.fog.isFog ) {
  113. const color = reference( 'color', 'color', scene.fog );
  114. const near = reference( 'near', 'float', scene.fog );
  115. const far = reference( 'far', 'float', scene.fog );
  116. return fog( color, rangeFogFactor( near, far ) );
  117. } else {
  118. return null;
  119. }
  120. }
  121. getEnvironmentNode() {
  122. const { scene } = this;
  123. if ( scene.environment && scene.environment.isCubeTexture ) {
  124. return cubeTexture( scene.environment );
  125. } else if ( scene.environment && scene.environment.isTexture ) {
  126. return texture( scene.environment );
  127. } else {
  128. return null;
  129. }
  130. }
  131. }
  132. class RendererProxy {
  133. constructor( renderer ) {
  134. const backend = {
  135. isWebGPUBackend: false,
  136. extensions: renderer.extensions,
  137. gl: renderer.getContext(),
  138. capabilities: null,
  139. };
  140. backend.capabilities = new WebGLCapabilities( backend );
  141. this.contextNode = context();
  142. this.inspector = new InspectorBase();
  143. this.library = new BasicNodeLibrary();
  144. this.lighting = new Lighting();
  145. this.backend = backend;
  146. const self = this;
  147. return new Proxy( renderer, {
  148. get( target, property ) {
  149. return Reflect.get( property in self ? self : target, property );
  150. },
  151. set( target, property, value ) {
  152. return Reflect.set( property in self ? self : target, property, value );
  153. }
  154. } );
  155. }
  156. hasInitialized() {
  157. return true;
  158. }
  159. getMRT() {
  160. return null;
  161. }
  162. hasCompatibility( name ) {
  163. if ( name === Compatibility.TEXTURE_COMPARE ) {
  164. return true;
  165. }
  166. return false;
  167. }
  168. getCacheKey() {
  169. return this.toneMapping + this.outputColorSpace;
  170. }
  171. }
  172. /**
  173. * Compatibility loader and builder for TSL Node materials in WebGLRenderer.
  174. */
  175. export class WebGLNodesHandler {
  176. /**
  177. * Constructs a new WebGL node adapter.
  178. */
  179. constructor() {
  180. this.renderer = null;
  181. this.nodeFrame = new NodeFrame();
  182. this.sceneContexts = new WeakMap();
  183. this.programCache = new Map();
  184. this.renderStack = [];
  185. const self = this;
  186. this.onDisposeMaterialCallback = function () {
  187. // dispose of all the uniform groups
  188. const { programCache } = self;
  189. if ( programCache.has( this ) ) {
  190. self.programCache.get( this ).forEach( ( { uniformsGroups } ) => {
  191. uniformsGroups.forEach( u => u.dispose() );
  192. } );
  193. self.programCache.delete( this );
  194. }
  195. this.removeEventListener( 'dispose', self.onDisposeMaterialCallback );
  196. };
  197. this.getOutputCallback = function ( outputNode ) {
  198. // apply tone mapping and color spaces to the output
  199. const { outputColorSpace, toneMapping } = self.renderer;
  200. outputNode = outputNode.toneMapping( toneMapping );
  201. outputNode = workingToColorSpace( outputNode, outputColorSpace );
  202. return outputNode;
  203. };
  204. this.onBeforeRenderCallback = function ( renderer, scene, camera, geometry, object ) {
  205. // update node frame references for update nodes
  206. const { nodeFrame } = self;
  207. nodeFrame.material = this;
  208. nodeFrame.object = object;
  209. // increment "frame" here to force uniform buffers to update for the material, which otherwise only get
  210. // updated once per frame.
  211. renderer.info.render.frame ++;
  212. // update the uniform groups and nodes for the program if they're available before rendering
  213. if ( renderer.properties.has( this ) ) {
  214. const currentProgram = renderer.properties.get( this ).currentProgram;
  215. const programs = self.programCache.get( this );
  216. if ( programs && programs.has( currentProgram ) ) {
  217. // update the nodes for the current object
  218. const { updateNodes } = programs.get( currentProgram );
  219. self.updateNodes( updateNodes );
  220. }
  221. }
  222. const objectHash = getObjectHash( object );
  223. if ( this.prevObjectHash !== objectHash ) {
  224. this.prevObjectHash = objectHash;
  225. this.needsUpdate = true;
  226. }
  227. };
  228. this.customProgramCacheKeyCallback = function () {
  229. const { renderStack, renderer, nodeFrame } = self;
  230. const sceneHash = renderStack[ renderStack.length - 1 ].sceneContext.getCacheKey();
  231. const materialHash = this.constructor.prototype.customProgramCacheKey.call( this );
  232. const rendererHash = renderer.getCacheKey();
  233. return materialHash + sceneHash + rendererHash + getObjectHash( nodeFrame.object );
  234. };
  235. }
  236. setRenderer( renderer ) {
  237. const rendererProxy = new RendererProxy( renderer );
  238. this.nodeFrame.renderer = rendererProxy;
  239. this.renderer = rendererProxy;
  240. }
  241. onUpdateProgram( material, program, materialProperties ) {
  242. const { programCache } = this;
  243. if ( ! programCache.has( material ) ) {
  244. programCache.set( material, new Map() );
  245. }
  246. const programs = programCache.get( material );
  247. if ( ! programs.has( program ) ) {
  248. const builder = material._latestBuilder;
  249. const uniforms = materialProperties.uniforms;
  250. programs.set( program, {
  251. uniformsGroups: this.collectUniformsGroups( builder ),
  252. uniforms: uniforms,
  253. uniformsList: generateUniformsList( program, uniforms ),
  254. updateNodes: builder.updateNodes,
  255. } );
  256. }
  257. const { uniformsGroups, uniforms, uniformsList, updateNodes } = programs.get( program );
  258. material.uniformsGroups = uniformsGroups;
  259. materialProperties.uniforms = uniforms;
  260. materialProperties.uniformsList = uniformsList;
  261. this.updateNodes( updateNodes );
  262. }
  263. renderStart( scene, camera ) {
  264. const { nodeFrame, renderStack, renderer, sceneContexts } = this;
  265. nodeFrame.update();
  266. nodeFrame.camera = camera;
  267. nodeFrame.scene = scene;
  268. nodeFrame.frameId ++;
  269. let sceneContext = sceneContexts.get( scene );
  270. if ( ! sceneContext ) {
  271. sceneContext = new SceneContext( renderer, scene );
  272. sceneContexts.set( scene, sceneContext );
  273. }
  274. sceneContext.update();
  275. renderStack.push( { sceneContext, camera } );
  276. // ensure all node material callbacks are initialized before
  277. // traversal and build
  278. const {
  279. customProgramCacheKeyCallback,
  280. onBeforeRenderCallback,
  281. } = this;
  282. scene.traverse( object => {
  283. if ( object.material && object.material.isNodeMaterial ) {
  284. object.material.customProgramCacheKey = customProgramCacheKeyCallback;
  285. object.material.onBeforeRender = onBeforeRenderCallback;
  286. }
  287. } );
  288. }
  289. renderEnd() {
  290. const { nodeFrame, renderStack } = this;
  291. renderStack.pop();
  292. const frame = renderStack[ renderStack.length - 1 ];
  293. if ( frame ) {
  294. const { camera, sceneContext } = frame;
  295. nodeFrame.camera = camera;
  296. nodeFrame.scene = sceneContext.scene;
  297. }
  298. }
  299. build( material, object, parameters ) {
  300. const {
  301. nodeFrame,
  302. renderer,
  303. getOutputCallback,
  304. onDisposeMaterialCallback,
  305. renderStack,
  306. } = this;
  307. const {
  308. camera,
  309. sceneContext,
  310. } = renderStack[ renderStack.length - 1 ];
  311. const {
  312. fogNode,
  313. environmentNode,
  314. lightsNode,
  315. scene,
  316. } = sceneContext;
  317. // prepare the frame
  318. nodeFrame.material = material;
  319. nodeFrame.object = object;
  320. // create & run the builder
  321. const builder = new WebGLNodeBuilder( object, renderer );
  322. builder.scene = scene;
  323. builder.camera = camera;
  324. builder.material = material;
  325. builder.fogNode = fogNode;
  326. builder.environmentNode = environmentNode;
  327. builder.lightsNode = lightsNode;
  328. builder.context.getOutput = getOutputCallback;
  329. builder.build();
  330. // update the shader parameters and geometry for program creation and rendering
  331. this.updateShaderParameters( builder, parameters );
  332. this.updateGeometryAttributes( builder, object.geometry );
  333. // reset node frame settings to account for any intermediate renders
  334. nodeFrame.material = material;
  335. nodeFrame.object = object;
  336. // set up callbacks for uniforms and node updates
  337. material._latestBuilder = builder;
  338. material.addEventListener( 'dispose', onDisposeMaterialCallback );
  339. this.updateNodes( builder.updateNodes );
  340. }
  341. updateGeometryAttributes( builder, geometry ) {
  342. // TODO: this may cause issues if the material / geometry is used in multiple places
  343. // add instancing attributes
  344. builder.bufferAttributes.forEach( v => {
  345. geometry.setAttribute( v.name, v.node.attribute );
  346. } );
  347. // force WebGLAttributes & WebGLBindingStates to refresh
  348. // could be fixed by running "build" sooner? Or calling "WebGLAttributes" separately for those
  349. // associated with a material?
  350. queueMicrotask( () => geometry.dispose() );
  351. }
  352. updateShaderParameters( builder, parameters ) {
  353. // set up shaders
  354. parameters.isRawShaderMaterial = true;
  355. parameters.glslVersion = GLSL3;
  356. parameters.vertexShader = builder.vertexShader.replace( /#version 300 es/, '' );
  357. parameters.fragmentShader = builder.fragmentShader.replace( /#version 300 es/, '' );
  358. // add uniforms accessed by WebGLRenderer
  359. parameters.uniforms = {
  360. fogColor: { value: new Color() },
  361. fogNear: { value: 0 },
  362. fogFar: { value: 0 },
  363. envMapIntensity: { value: 0 },
  364. ...UniformsUtils.clone( UniformsLib.lights )
  365. };
  366. // init uniforms
  367. const builderUniforms = [ ...builder.uniforms.vertex, ...builder.uniforms.fragment ];
  368. for ( const uniform of builderUniforms ) {
  369. parameters.uniforms[ uniform.name ] = uniform.node;
  370. }
  371. }
  372. collectUniformsGroups( builder ) {
  373. // create UniformsGroups for regular grouped uniforms
  374. const uniformsGroups = [];
  375. for ( const key in builder.uniformGroups ) {
  376. const { uniforms } = builder.uniformGroups[ key ];
  377. const group = new UniformsGroup();
  378. group.name = key;
  379. group.uniforms = uniforms.map( node => node.nodeUniform );
  380. uniformsGroups.push( group );
  381. }
  382. // init uniforms
  383. const builderUniforms = [ ...builder.uniforms.vertex, ...builder.uniforms.fragment ];
  384. for ( const uniform of builderUniforms ) {
  385. if ( uniform.type === 'buffer' ) {
  386. // buffer uniforms are all nested in groups
  387. const group = new UniformsGroup();
  388. group.name = uniform.node.name;
  389. group.uniforms = [ uniform ];
  390. uniformsGroups.push( group );
  391. }
  392. }
  393. return uniformsGroups;
  394. }
  395. updateNodes( updateNodes ) {
  396. // update nodes for render
  397. const { nodeFrame } = this;
  398. nodeFrame.renderId ++;
  399. for ( const node of updateNodes ) {
  400. nodeFrame.updateNode( node );
  401. }
  402. }
  403. }
粤ICP备19079148号