Selaa lähdekoodia

Merge branch 'dev' into e2e

# Conflicts:
#	examples/screenshots/webgpu_equirectangular.jpg
Mr.doob 1 vuosi sitten
vanhempi
commit
4e7b7c38f4
76 muutettua tiedostoa jossa 1140 lisäystä ja 1040 poistoa
  1. 1 1
      .github/workflows/ci.yml
  2. 1 1
      .github/workflows/read-size.yml
  3. 2 1
      build/three.cjs
  4. 2 1
      build/three.module.js
  5. 0 0
      build/three.module.min.js
  6. 64 759
      build/three.webgpu.js
  7. 0 0
      build/three.webgpu.min.js
  8. 1 0
      examples/files.json
  9. 76 3
      examples/jsm/loaders/VTKLoader.js
  10. 2 2
      examples/jsm/misc/GPUComputationRenderer.js
  11. 9 3
      examples/jsm/transpiler/TSLEncoder.js
  12. BIN
      examples/screenshots/webgl_test_wide_gamut.jpg
  13. BIN
      examples/screenshots/webgpu_equirectangular.jpg
  14. BIN
      examples/screenshots/webgpu_materials_basic.jpg
  15. BIN
      examples/screenshots/webgpu_materials_lightmap.jpg
  16. 26 6
      examples/webgl_materials_channels.html
  17. 2 2
      examples/webgl_materials_matcap.html
  18. 45 14
      examples/webgl_test_wide_gamut.html
  19. 3 2
      examples/webgpu_backdrop.html
  20. 3 2
      examples/webgpu_backdrop_area.html
  21. 1 1
      examples/webgpu_compute_particles_snow.html
  22. 2 2
      examples/webgpu_cubemap_adjustments.html
  23. 2 2
      examples/webgpu_cubemap_mix.html
  24. 1 0
      examples/webgpu_equirectangular.html
  25. 169 0
      examples/webgpu_materials_basic.html
  26. 2 2
      examples/webgpu_materials_matcap.html
  27. 3 2
      examples/webgpu_portal.html
  28. 12 7
      examples/webgpu_postprocessing_3dlut.html
  29. 3 3
      examples/webgpu_shadertoy.html
  30. 3 2
      examples/webgpu_skinning.html
  31. 2 0
      src/constants.js
  32. 3 1
      src/nodes/Nodes.js
  33. 1 1
      src/nodes/accessors/BatchNode.js
  34. 21 5
      src/nodes/accessors/CubeTextureNode.js
  35. 18 0
      src/nodes/accessors/MaterialNode.js
  36. 4 0
      src/nodes/accessors/ReflectVectorNode.js
  37. 34 12
      src/nodes/accessors/TextureNode.js
  38. 1 3
      src/nodes/core/LightingModel.js
  39. 14 24
      src/nodes/core/NodeBuilder.js
  40. 0 9
      src/nodes/display/AfterImageNode.js
  41. 2 1
      src/nodes/display/GaussianBlurNode.js
  42. 17 11
      src/nodes/display/PassNode.js
  43. 56 0
      src/nodes/display/RenderOutputNode.js
  44. 78 0
      src/nodes/functions/BasicLightingModel.js
  45. 5 3
      src/nodes/functions/PhongLightingModel.js
  46. 8 0
      src/nodes/functions/PhysicalLightingModel.js
  47. 3 1
      src/nodes/functions/ToonLightingModel.js
  48. 1 4
      src/nodes/lighting/AONode.js
  49. 26 0
      src/nodes/lighting/BasicEnvironmentNode.js
  50. 29 0
      src/nodes/lighting/BasicLightMapNode.js
  51. 2 0
      src/nodes/lighting/LightingNode.js
  52. 1 3
      src/nodes/lighting/LightsNode.js
  53. 33 2
      src/nodes/materials/MeshBasicNodeMaterial.js
  54. 9 0
      src/nodes/materials/MeshLambertNodeMaterial.js
  55. 8 0
      src/nodes/materials/MeshPhongNodeMaterial.js
  56. 9 0
      src/nodes/materials/MeshStandardNodeMaterial.js
  57. 25 10
      src/nodes/materials/NodeMaterial.js
  58. 8 2
      src/nodes/utils/MaxMipLevelNode.js
  59. 28 4
      src/nodes/utils/RTTNode.js
  60. 2 2
      src/objects/BatchedMesh.js
  61. 2 8
      src/renderers/WebGLRenderer.js
  62. 54 7
      src/renderers/common/PostProcessing.js
  63. 22 4
      src/renderers/common/RenderObject.js
  64. 28 13
      src/renderers/common/Renderer.js
  65. 17 14
      src/renderers/common/nodes/Nodes.js
  66. 45 13
      src/renderers/shaders/ShaderChunk/packing.glsl.js
  67. 8 0
      src/renderers/shaders/ShaderLib/depth.glsl.js
  68. 6 0
      src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js
  69. 7 7
      src/renderers/webgl/WebGLUniformsGroups.js
  70. 5 19
      src/renderers/webgpu/WebGPUBackend.js
  71. 26 4
      src/renderers/webgpu/nodes/WGSLNodeBuilder.js
  72. 6 0
      src/renderers/webgpu/utils/WebGPUBindingUtils.js
  73. 1 16
      src/renderers/webgpu/utils/WebGPUPipelineUtils.js
  74. 5 16
      src/renderers/webgpu/utils/WebGPUTextureUtils.js
  75. 24 3
      src/renderers/webgpu/utils/WebGPUUtils.js
  76. 1 0
      test/e2e/puppeteer.js

+ 1 - 1
.github/workflows/ci.yml

@@ -89,7 +89,7 @@ jobs:
       - name: === E2E testing ===
         run: npm run test-e2e
       - name: Upload output screenshots
-        uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
+        uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
         if: always()
         with:
           name: Output screenshots-${{ matrix.os }}-${{ matrix.CI }}

+ 1 - 1
.github/workflows/read-size.yml

@@ -46,7 +46,7 @@ jobs:
           # write the output in a json file to upload it as artifact
           node -pe "JSON.stringify({ filesize: $FILESIZE, gzip: $FILESIZE_GZIP, treeshaken: $TREESHAKEN, treeshakenGzip: $TREESHAKEN_GZIP, pr: $PR })" > sizes.json
       - name: Upload artifact
-        uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
+        uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
         with:
           name: sizes
           path: sizes.json

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2 - 1
build/three.cjs


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2 - 1
build/three.module.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
build/three.module.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 64 - 759
build/three.webgpu.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
build/three.webgpu.min.js


+ 1 - 0
examples/files.json

@@ -344,6 +344,7 @@
 		"webgpu_loader_gltf_transmission",
 		"webgpu_loader_materialx",
 		"webgpu_materials",
+		"webgpu_materials_basic",
 		"webgpu_materials_displacementmap",
 		"webgpu_materials_lightmap",
 		"webgpu_materials_matcap",

+ 76 - 3
examples/jsm/loaders/VTKLoader.js

@@ -447,7 +447,6 @@ class VTKLoader extends Loader {
 
 							} else {
 
-
 								indices[ indicesIndex ++ ] = strip[ j ];
 								indices[ indicesIndex ++ ] = strip[ j + 1 ];
 								indices[ indicesIndex ++ ] = strip[ j + 2 ];
@@ -620,7 +619,17 @@ class VTKLoader extends Loader {
 
 							const tmp = xmlToJson( item );
 
-							if ( tmp !== '' ) obj[ nodeName ] = tmp;
+							if ( tmp !== '' ) {
+
+								if ( Array.isArray( tmp[ '#text' ] ) ) {
+
+									tmp[ '#text' ] = tmp[ '#text' ][ 0 ];
+
+								}
+
+								obj[ nodeName ] = tmp;
+
+							}
 
 						} else {
 
@@ -633,7 +642,17 @@ class VTKLoader extends Loader {
 
 							const tmp = xmlToJson( item );
 
-							if ( tmp !== '' ) obj[ nodeName ].push( tmp );
+							if ( tmp !== '' ) {
+
+								if ( Array.isArray( tmp[ '#text' ] ) ) {
+
+									tmp[ '#text' ] = tmp[ '#text' ][ 0 ];
+
+								}
+
+								obj[ nodeName ].push( tmp );
+
+							}
 
 						}
 
@@ -894,6 +913,60 @@ class VTKLoader extends Loader {
 			let normals = [];
 			let indices = [];
 
+			if ( json.AppendedData ) {
+
+				const appendedData = json.AppendedData[ '#text' ].slice( 1 );
+				const piece = json.PolyData.Piece;
+
+				const sections = [ 'PointData', 'CellData', 'Points', 'Verts', 'Lines', 'Strips', 'Polys' ];
+				let sectionIndex = 0;
+
+				const offsets = sections.map( s => {
+
+					const sect = piece[ s ];
+
+					if ( sect && sect.DataArray ) {
+
+						const arr = Array.isArray( sect.DataArray ) ? sect.DataArray : [ sect.DataArray ];
+
+						return arr.map( a => a.attributes.offset );
+
+					}
+
+					return [];
+
+				} ).flat();
+
+				for ( const sect of sections ) {
+
+					const section = piece[ sect ];
+
+					if ( section && section.DataArray ) {
+
+						if ( Array.isArray( section.DataArray ) ) {
+
+							for ( const sectionEle of section.DataArray ) {
+
+								sectionEle[ '#text' ] = appendedData.slice( offsets[ sectionIndex ], offsets[ sectionIndex + 1 ] );
+								sectionEle.attributes.format = 'binary';
+								sectionIndex ++;
+
+							}
+
+						} else {
+
+							section.DataArray[ '#text' ] = appendedData.slice( offsets[ sectionIndex ], offsets[ sectionIndex + 1 ] );
+							section.DataArray.attributes.format = 'binary';
+							sectionIndex ++;
+
+						}
+
+					}
+
+				}
+
+			}
+
 			if ( json.PolyData ) {
 
 				const piece = json.PolyData.Piece;

+ 2 - 2
examples/jsm/misc/GPUComputationRenderer.js

@@ -46,8 +46,8 @@ import { FullScreenQuad } from '../postprocessing/Pass.js';
  * // and fill in here the texture data...
  *
  * // Add texture variables
- * const velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, pos0 );
- * const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, vel0 );
+ * const velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, vel0 );
+ * const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, pos0 );
  *
  * // Add variable dependencies
  * gpuCompute.setVariableDependencies( velVar, [ velVar, posVar ] );

+ 9 - 3
examples/jsm/transpiler/TSLEncoder.js

@@ -501,7 +501,9 @@ ${ this.tab }} )`;
 
 		this.addImport( 'overloadingFn' );
 
-		return `export const ${ name } = /*#__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
+		const prefix = this.iife === false ? 'export ' : '';
+
+		return `${ prefix }const ${ name } = /*#__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
 
 	}
 
@@ -582,7 +584,9 @@ ${ this.tab }} )`;
 
 		}
 
-		let funcStr = `export const ${ fnName } = /*#__PURE__*/ tslFn( (${ paramsStr }) => {
+		const prefix = this.iife === false ? 'export ' : '';
+
+		let funcStr = `${ prefix }const ${ fnName } = /*#__PURE__*/ tslFn( (${ paramsStr }) => {
 
 ${ bodyStr }
 
@@ -684,6 +688,7 @@ ${ this.tab }} )`;
 		}
 
 		const imports = [ ...this.imports ];
+		const exports = [ ...this.global ];
 
 		let header = '// Three.js Transpiler r' + THREE.REVISION + '\n\n';
 		let footer = '';
@@ -693,12 +698,13 @@ ${ this.tab }} )`;
 			header += '( function ( TSL, uniforms ) {\n\n';
 
 			header += imports.length > 0 ? '\tconst { ' + imports.join( ', ' ) + ' } = TSL;\n' : '';
+			footer += exports.length > 0 ? '\treturn { ' + exports.join( ', ' ) + ' };\n' : '';
 
 			footer += '\n} );';
 
 		} else {
 
-			header += imports.length > 0 ? 'import { ' + imports.join( ', ' ) + ' } from \'three/nodes\';\n' : '';
+			header += imports.length > 0 ? 'import { ' + imports.join( ', ' ) + ' } from \'three/tsl\';\n' : '';
 
 		}
 

BIN
examples/screenshots/webgl_test_wide_gamut.jpg


BIN
examples/screenshots/webgpu_equirectangular.jpg


BIN
examples/screenshots/webgpu_materials_basic.jpg


BIN
examples/screenshots/webgpu_materials_lightmap.jpg


+ 26 - 6
examples/webgl_materials_channels.html

@@ -9,8 +9,8 @@
 	<body>
 
 		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - <span id="description">Normal, Velocity, Depth, DepthRGBA, DepthRGBAUnpacked, Materials</span><br/>
-			by <a href="https://Clara.io">Ben Houston</a>. ninja head from <a href="https://gpuopen.com/archive/gamescgi/amd-gpu-meshmapper/" target="_blank" rel="noopener">AMD GPU MeshMapper</a>
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - <span id="description">Normal, Velocity, and Depth Materials</span><br/>
+			ninja head from <a href="https://gpuopen.com/archive/gamescgi/amd-gpu-meshmapper/" target="_blank" rel="noopener">AMD GPU MeshMapper</a>
 		</div>
 
 		<script type="importmap">
@@ -53,7 +53,7 @@
 			let cameraOrtho, cameraPerspective;
 			let controlsOrtho, controlsPerspective;
 
-			let mesh, materialStandard, materialDepthBasic, materialDepthRGBA, materialNormal, materialVelocity;
+			let mesh, materialStandard, materialDepthBasic, materialDepthRGBA, materialDepthRGB, materialDepthRG, materialNormal, materialVelocity;
 
 			const SCALE = 2.436143; // from original model
 			const BIAS = - 0.428408; // from original model
@@ -90,13 +90,11 @@
 				controlsPerspective = new OrbitControls( cameraPerspective, renderer.domElement );
 				controlsPerspective.minDistance = 1000;
 				controlsPerspective.maxDistance = 2400;
-				controlsPerspective.enablePan = false;
 				controlsPerspective.enableDamping = true;
 
 				controlsOrtho = new OrbitControls( cameraOrtho, renderer.domElement );
 				controlsOrtho.minZoom = 0.5;
 				controlsOrtho.maxZoom = 1.5;
-				controlsOrtho.enablePan = false;
 				controlsOrtho.enableDamping = true;
 
 				// lights
@@ -165,6 +163,26 @@
 					side: THREE.DoubleSide
 				} );
 
+				materialDepthRGB = new THREE.MeshDepthMaterial( {
+					depthPacking: THREE.RGBDepthPacking,
+
+					displacementMap: displacementMap,
+					displacementScale: SCALE,
+					displacementBias: BIAS,
+
+					side: THREE.DoubleSide
+				} );
+
+				materialDepthRG = new THREE.MeshDepthMaterial( {
+					depthPacking: THREE.RGDepthPacking,
+
+					displacementMap: displacementMap,
+					displacementScale: SCALE,
+					displacementBias: BIAS,
+
+					side: THREE.DoubleSide
+				} );
+
 				materialNormal = new THREE.MeshNormalMaterial( {
 					displacementMap: displacementMap,
 					displacementScale: SCALE,
@@ -213,7 +231,7 @@
 				//
 
 				const gui = new GUI();
-				gui.add( params, 'material', [ 'standard', 'normal', 'velocity', 'depthBasic', 'depthRGBA' ] );
+				gui.add( params, 'material', [ 'standard', 'normal', 'velocity', 'depthBasic', 'depthRGBA', 'depthRGB', 'depthRG' ] );
 				gui.add( params, 'camera', [ 'perspective', 'ortho' ] );
 				gui.add( params, 'side', [ 'front', 'back', 'double' ] );
 
@@ -263,6 +281,8 @@
 						case 'standard': material = materialStandard; break;
 						case 'depthBasic': material = materialDepthBasic; break;
 						case 'depthRGBA': material = materialDepthRGBA; break;
+						case 'depthRGB': material = materialDepthRGB; break;
+						case 'depthRG': material = materialDepthRG; break;
 						case 'normal': material = materialNormal; break;
 						case 'velocity': material = materialVelocity; break;
 

+ 2 - 2
examples/webgl_materials_matcap.html

@@ -86,7 +86,7 @@
 
 					mesh.material = new THREE.MeshMatcapMaterial( {
 
-						color: new THREE.Color().setHex( API.color ).convertSRGBToLinear(),
+						color: new THREE.Color().setHex( API.color ),
 						matcap: matcap,
 						normalMap: normalmap
 
@@ -103,7 +103,7 @@
 					.listen()
 					.onChange( function () {
 
-						mesh.material.color.set( API.color ).convertSRGBToLinear();
+						mesh.material.color.set( API.color );
 						render();
 
 					} );

+ 45 - 14
examples/webgl_test_wide_gamut.html

@@ -13,18 +13,39 @@
 				height: 100%;
 			}
 
+			/* based on https://github.com/Paul-Browne/image-comparison-slider */
+
 			.slider {
-				position: absolute;
-				cursor: ew-resize;
+			    position: absolute;
+			    width: 200px;
+			    height: 100%;
+			    top: 0;
+			    left: 0;
+			    z-index: 1;
+			}
+
+			.slider:before,
+			.slider:after {
+			    position: absolute;
+			    left: 50%;
+			    content: "";
+			    background: #fff;
+			    cursor: grab;
+			}
 
-				width: 40px;
-				height: 40px;
-				background-color: #F32196;
-				opacity: 0.7;
-				border-radius: 50%;
+			.slider:before {
+			    top: 0;
+			    transform: translateX(-50%);
+			    width: 1px;
+			    height: 100%;
+			}
 
-				top: calc(50% - 20px);
-				left: calc(50% - 20px);
+			.slider:after {
+			    top: 50%;
+			    transform: translate(-50%, -50%);
+			    width: 5px;
+			    height: 33%;
+			    border-radius: 5px;
 			}
 
 			.label {
@@ -75,6 +96,8 @@
 
 			let sliderPos = window.innerWidth / 2;
 
+			const slider = document.querySelector( '.slider' );
+
 			const isP3Context = WebGL.isColorSpaceAvailable( THREE.DisplayP3ColorSpace );
 
 			if ( isP3Context ) {
@@ -135,8 +158,6 @@
 
 			function initSlider() {
 
-				const slider = document.querySelector( '.slider' );
-
 				function onPointerDown() {
 
 					if ( event.isPrimary === false ) return;
@@ -157,17 +178,25 @@
 
 					if ( event.isPrimary === false ) return;
 
-					sliderPos = Math.max( 0, Math.min( window.innerWidth, e.pageX ) );
-
-					slider.style.left = sliderPos - ( slider.offsetWidth / 2 ) + 'px';
+					updateSlider( e.pageX );
 
 				}
 
+				updateSlider( sliderPos );
+
 				slider.style.touchAction = 'none'; // disable touch scroll
 				slider.addEventListener( 'pointerdown', onPointerDown );
 
 			}
 
+			function updateSlider( offset ) {
+
+				sliderPos = Math.max( 10, Math.min( window.innerWidth - 10, offset ) );
+
+				slider.style.left = sliderPos - ( slider.offsetWidth / 2 ) + 'px';
+
+			}
+
 			function onWindowResize() {
 
 				camera.aspect = window.innerWidth / window.innerHeight;
@@ -178,6 +207,8 @@
 				THREE.TextureUtils.contain( sceneL.background, window.innerWidth / window.innerHeight );
 				THREE.TextureUtils.contain( sceneR.background, window.innerWidth / window.innerHeight );
 
+				updateSlider( sliderPos );
+
 			}
 
 			function onGamutChange( { matches } ) {

+ 3 - 2
examples/webgpu_backdrop.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { float, vec3, color, toneMapping, viewportSharedTexture, viewportTopLeft, checker, uv, timerLocal, oscSine, output } from 'three/tsl';
+			import { float, vec3, color, viewportSharedTexture, viewportTopLeft, checker, uv, timerLocal, oscSine, output } from 'three/tsl';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
@@ -121,7 +121,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .3 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.3;
 				document.body.appendChild( renderer.domElement );
 
 				const controls = new OrbitControls( camera, renderer.domElement );

+ 3 - 2
examples/webgpu_backdrop_area.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { color, linearDepth, toneMapping, viewportLinearDepth, viewportSharedTexture, viewportMipTexture, viewportTopLeft, checker, uv, modelScale } from 'three/tsl';
+			import { color, linearDepth, viewportLinearDepth, viewportSharedTexture, viewportMipTexture, viewportTopLeft, checker, uv, modelScale } from 'three/tsl';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
@@ -118,7 +118,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .2 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.2;
 				document.body.appendChild( renderer.domElement );
 
 				const controls = new OrbitControls( camera, renderer.domElement );

+ 1 - 1
examples/webgpu_compute_particles_snow.html

@@ -298,7 +298,7 @@
 				const vignet = viewportTopLeft.distance( .5 ).mul( 1.35 ).clamp().oneMinus();
 
 				const teapotTreePass = pass( teapotTree, camera ).getTextureNode();
-				const teapotTreePassBlurred = teapotTreePass.gaussianBlur( 3 );
+				const teapotTreePassBlurred = teapotTreePass.gaussianBlur( vec2( 1 ), 3 );
 				teapotTreePassBlurred.resolution = new THREE.Vector2( .2, .2 );
 
 				const scenePassColorBlurred = scenePassColor.gaussianBlur();

+ 2 - 2
examples/webgpu_cubemap_adjustments.html

@@ -27,7 +27,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { uniform, mix, pmremTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector, toneMapping } from 'three/tsl';
+			import { uniform, mix, pmremTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector } from 'three/tsl';
 
 			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
 
@@ -135,7 +135,7 @@
 				renderer = new THREE.WebGPURenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, 1 );
+				renderer.toneMapping = THREE.LinearToneMapping;
 				renderer.setAnimationLoop( render );
 				container.appendChild( renderer.domElement );
 

+ 2 - 2
examples/webgpu_cubemap_mix.html

@@ -27,7 +27,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { mix, oscSine, timerLocal, pmremTexture, float, toneMapping } from 'three/tsl';
+			import { mix, oscSine, timerLocal, pmremTexture, float } from 'three/tsl';
 
 			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
 
@@ -80,7 +80,7 @@
 
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, 1 );
+				renderer.toneMapping = THREE.LinearToneMapping;
 				renderer.setAnimationLoop( render );
 				container.appendChild( renderer.domElement );
 

+ 1 - 0
examples/webgpu_equirectangular.html

@@ -45,6 +45,7 @@
 				camera.position.set( 1, 0, 0 );
 
 				const equirectTexture = new THREE.TextureLoader().load( 'textures/2294472375_24a3b8ef46_o.jpg' );
+				equirectTexture.colorSpace = THREE.SRGBColorSpace;
 
 				scene = new THREE.Scene();
 				scene.backgroundNode = texture( equirectTexture, equirectUV(), 0 );

+ 169 - 0
examples/webgpu_materials_basic.html

@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - material - basic</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/tsl": "../build/three.webgpu.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let camera, scene, renderer;
+
+			const spheres = [];
+
+			let mouseX = 0;
+			let mouseY = 0;
+
+			let windowHalfX = window.innerWidth / 2;
+			let windowHalfY = window.innerHeight / 2;
+
+			const params = {
+				color: '#ffffff',
+				mapping: THREE.CubeReflectionMapping,
+				refractionRatio: 0.98,
+				transparent: false,
+				opacity: 1
+			};
+
+			const mappings = { ReflectionMapping: THREE.CubeReflectionMapping, RefractionMapping: THREE.CubeRefractionMapping };
+
+			document.addEventListener( 'mousemove', onDocumentMouseMove );
+
+			init();
+
+			function init() {
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 100 );
+				camera.position.z = 3;
+
+				const path = './textures/cube/pisa/';
+				const format = '.png';
+				const urls = [
+					path + 'px' + format, path + 'nx' + format,
+					path + 'py' + format, path + 'ny' + format,
+					path + 'pz' + format, path + 'nz' + format
+				];
+
+				const textureCube = new THREE.CubeTextureLoader().load( urls );
+
+				scene = new THREE.Scene();
+				scene.background = textureCube;
+
+				const geometry = new THREE.SphereGeometry( 0.1, 32, 16 );
+				const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } );
+
+				for ( let i = 0; i < 500; i ++ ) {
+
+					const mesh = new THREE.Mesh( geometry, material );
+
+					mesh.position.x = Math.random() * 10 - 5;
+					mesh.position.y = Math.random() * 10 - 5;
+					mesh.position.z = Math.random() * 10 - 5;
+
+					mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
+
+					scene.add( mesh );
+
+					spheres.push( mesh );
+
+				}
+
+				//
+
+				renderer = new THREE.WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				const gui = new GUI( { width: 300 } );
+
+				gui.addColor( params, 'color' ).onChange( ( value ) => material.color.set( value ) );
+				gui.add( params, 'mapping', mappings ).onChange( ( value ) => {
+
+					textureCube.mapping = value;
+					material.needsUpdate = true;
+			
+				} );
+				gui.add( params, 'refractionRatio' ).min( 0.0 ).max( 1.0 ).step( 0.01 ).onChange( ( value ) => material.refractionRatio = value );
+				gui.add( params, 'transparent' ).onChange( ( value ) => {
+			
+					material.transparent = value;
+					material.needsUpdate = true;
+
+				} );
+				gui.add( params, 'opacity' ).min( 0.0 ).max( 1.0 ).step( 0.01 ).onChange( ( value ) => material.opacity = value );
+				gui.open();
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = ( event.clientX - windowHalfX ) / 100;
+				mouseY = ( event.clientY - windowHalfY ) / 100;
+
+			}
+
+			//
+
+			function animate() {
+
+				const timer = 0.0001 * Date.now();
+
+				camera.position.x += ( mouseX - camera.position.x ) * .05;
+				camera.position.y += ( - mouseY - camera.position.y ) * .05;
+
+				camera.lookAt( scene.position );
+
+				for ( let i = 0, il = spheres.length; i < il; i ++ ) {
+
+					const sphere = spheres[ i ];
+
+					sphere.position.x = 5 * Math.cos( timer + i );
+					sphere.position.y = 5 * Math.sin( timer + i * 1.1 );
+
+				}
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 2 - 2
examples/webgpu_materials_matcap.html

@@ -86,7 +86,7 @@
 
 					mesh.material = new THREE.MeshMatcapNodeMaterial( {
 
-						color: new THREE.Color().setHex( API.color ).convertSRGBToLinear(),
+						color: new THREE.Color().setHex( API.color ),
 						matcap: matcap,
 						normalMap: normalmap
 
@@ -103,7 +103,7 @@
 					.listen()
 					.onChange( function () {
 
-						mesh.material.color.set( API.color ).convertSRGBToLinear();
+						mesh.material.color.set( API.color );
 						render();
 
 					} );

+ 3 - 2
examples/webgpu_portal.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { pass, color, mx_worley_noise_float, timerLocal, viewportTopLeft, vec2, uv, normalWorld, mx_fractal_noise_vec3, toneMapping } from 'three/tsl';
+			import { pass, color, mx_worley_noise_float, timerLocal, viewportTopLeft, vec2, uv, normalWorld, mx_fractal_noise_vec3 } from 'three/tsl';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
@@ -143,7 +143,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .15 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.15;
 				document.body.appendChild( renderer.domElement );
 
 				//

+ 12 - 7
examples/webgpu_postprocessing_3dlut.html

@@ -27,8 +27,9 @@
 		</script>
 
 		<script type="module">
+
 			import * as THREE from 'three';
-			import { pass, texture3D, uniform } from 'three/tsl';
+			import { pass, texture3D, uniform, renderOutput } from 'three/tsl';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
@@ -126,18 +127,22 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMapping = THREE.NoToneMapping;
-				renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
 				container.appendChild( renderer.domElement );
 
-				// postprocessing
+				// post processing
 
 				postProcessing = new THREE.PostProcessing( renderer );
 
-				const scenePass = pass( scene, camera );
-				const scenePassColor = scenePass.getTextureNode();
+				// ignore default output color transform ( toneMapping and outputColorSpace )
+				// use renderOutput() for control the sequence
+
+				postProcessing.outputColorTransform = false;
 
-				const outputPass = scenePassColor.toneMapping( THREE.ACESFilmicToneMapping ).linearTosRGB();
+				// scene pass
+
+				const scenePass = pass( scene, camera );
+				const outputPass = renderOutput( scenePass );
 
 				lutPass = outputPass.lut3D();
 				lutPass.lutNode = texture3D( lutMap[ params.lut ] );

+ 3 - 3
examples/webgpu_shadertoy.html

@@ -169,7 +169,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { oscSine, timerLocal } from 'three/tsl';
+			import * as TSL from 'three/tsl';
 
 			import Transpiler from 'three/addons/transpiler/Transpiler.js';
 			import ShaderToyDecoder from 'three/addons/transpiler/ShaderToyDecoder.js';
@@ -203,7 +203,7 @@
 
 					const jsCode = this.transpile( glsl, true );
 
-					const { mainImage } = eval( jsCode )( THREE );
+					const { mainImage } = eval( jsCode )( TSL );
 
 					this.mainImage = mainImage;
 
@@ -258,7 +258,7 @@
 				const geometry = new THREE.PlaneGeometry( 2, 2 );
 
 				const material = new THREE.MeshBasicNodeMaterial();
-				material.colorNode = oscSine( timerLocal( .3 ) ).mix( shaderToy1Node, shaderToy2Node );
+				material.colorNode = TSL.oscSine( TSL.timerLocal( .3 ) ).mix( shaderToy1Node, shaderToy2Node );
 
 				const quad = new THREE.Mesh( geometry, material );
 				scene.add( quad );

+ 3 - 2
examples/webgpu_skinning.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { toneMapping, color, viewportTopLeft } from 'three/tsl';
+			import { color, viewportTopLeft } from 'three/tsl';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
@@ -75,7 +75,8 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
-				renderer.toneMappingNode = toneMapping( THREE.LinearToneMapping, .4 );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 0.4;
 				document.body.appendChild( renderer.domElement );
 
 				window.addEventListener( 'resize', onWindowResize );

+ 2 - 0
src/constants.js

@@ -154,6 +154,8 @@ export const TriangleStripDrawMode = 1;
 export const TriangleFanDrawMode = 2;
 export const BasicDepthPacking = 3200;
 export const RGBADepthPacking = 3201;
+export const RGBDepthPacking = 3202;
+export const RGDepthPacking = 3203;
 export const TangentSpaceNormalMap = 0;
 export const ObjectSpaceNormalMap = 1;
 

+ 3 - 1
src/nodes/Nodes.js

@@ -86,7 +86,7 @@ export { default as VertexColorNode, vertexColor } from './accessors/VertexColor
 export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js';
 export { default as InstanceNode, instance } from './accessors/InstanceNode.js';
 export { default as BatchNode, batch } from './accessors/BatchNode.js';
-export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecular, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth, materialAnisotropy, materialAnisotropyVector, materialDispersion } from './accessors/MaterialNode.js';
+export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecular, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth, materialAnisotropy, materialAnisotropyVector, materialDispersion, materialLightMap, materialAOMap } from './accessors/MaterialNode.js';
 export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js';
 export { default as RendererReferenceNode, rendererReference } from './accessors/RendererReferenceNode.js';
 export { default as MorphNode, morphReference } from './accessors/MorphNode.js';
@@ -133,6 +133,7 @@ export { default as DotScreenNode, dotScreen } from './display/DotScreenNode.js'
 export { default as RGBShiftNode, rgbShift } from './display/RGBShiftNode.js';
 export { default as FilmNode, film } from './display/FilmNode.js';
 export { default as Lut3DNode, lut3D } from './display/Lut3DNode.js';
+export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js';
 
 export { default as PassNode, pass, texturePass, depthPass } from './display/PassNode.js';
 
@@ -168,6 +169,7 @@ export { default as LightingNode /* @TODO: lighting (abstract), light */ } from
 export { default as LightingContextNode, lightingContext } from './lighting/LightingContextNode.js';
 export { default as HemisphereLightNode } from './lighting/HemisphereLightNode.js';
 export { default as EnvironmentNode } from './lighting/EnvironmentNode.js';
+export { default as BasicEnvironmentNode } from './lighting/BasicEnvironmentNode.js';
 export { default as IrradianceNode } from './lighting/IrradianceNode.js';
 export { default as AONode } from './lighting/AONode.js';
 export { default as AnalyticLightNode } from './lighting/AnalyticLightNode.js';

+ 1 - 1
src/nodes/accessors/BatchNode.js

@@ -45,7 +45,7 @@ class BatchNode extends Node {
 			const size = textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 );
 			const x = int( id ).remainder( int( size ) );
 			const y = int( id ).div( int( size ) );
-			return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ), null, 'uvec4' ).x;
+			return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x;
 
 		} ).setLayout( {
 			name: 'getIndirectIndex',

+ 21 - 5
src/nodes/accessors/CubeTextureNode.js

@@ -1,15 +1,15 @@
 import TextureNode from './TextureNode.js';
-import { reflectVector } from './ReflectVectorNode.js';
+import { reflectVector, refractVector } from './ReflectVectorNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeElement, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
 
-import { WebGPUCoordinateSystem } from '../../constants.js';
+import { CubeReflectionMapping, CubeRefractionMapping, WebGPUCoordinateSystem } from '../../constants.js';
 
 class CubeTextureNode extends TextureNode {
 
-	constructor( value, uvNode = null, levelNode = null ) {
+	constructor( value, uvNode = null, levelNode = null, biasNode = null ) {
 
-		super( value, uvNode, levelNode );
+		super( value, uvNode, levelNode, biasNode );
 
 		this.isCubeTextureNode = true;
 
@@ -23,7 +23,23 @@ class CubeTextureNode extends TextureNode {
 
 	getDefaultUV() {
 
-		return reflectVector;
+		const texture = this.value;
+
+		if ( texture.mapping === CubeReflectionMapping ) {
+
+			return reflectVector;
+
+		} else if ( texture.mapping === CubeRefractionMapping ) {
+
+			return refractVector;
+
+		} else {
+
+			console.error( 'THREE.CubeTextureNode: Mapping "%s" not supported.', texture.mapping );
+
+			return vec3( 0, 0, 0 );
+
+		}
 
 	}
 

+ 18 - 0
src/nodes/accessors/MaterialNode.js

@@ -319,6 +319,18 @@ class MaterialNode extends Node {
 
 			node = this.getFloat( scope );
 
+		} else if ( scope === MaterialNode.REFRACTION_RATIO ) {
+
+			node = this.getFloat( scope );
+
+		} else if ( scope === MaterialNode.LIGHT_MAP ) {
+
+			node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) );
+
+		} else if ( scope === MaterialNode.AO_MAP ) {
+
+			node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 );
+
 		} else {
 
 			const outputType = this.getNodeType( builder );
@@ -368,6 +380,9 @@ MaterialNode.LINE_WIDTH = 'linewidth';
 MaterialNode.LINE_DASH_OFFSET = 'dashOffset';
 MaterialNode.POINT_WIDTH = 'pointWidth';
 MaterialNode.DISPERSION = 'dispersion';
+MaterialNode.LIGHT_MAP = 'light';
+MaterialNode.AO_MAP = 'ao';
+MaterialNode.REFRACTION_RATIO = 'refractionRatio';
 
 export default MaterialNode;
 
@@ -408,6 +423,9 @@ export const materialLineWidth = nodeImmutable( MaterialNode, MaterialNode.LINE_
 export const materialLineDashOffset = nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET );
 export const materialPointWidth = nodeImmutable( MaterialNode, MaterialNode.POINT_WIDTH );
 export const materialDispersion = nodeImmutable( MaterialNode, MaterialNode.DISPERSION );
+export const materialLightMap = nodeImmutable( MaterialNode, MaterialNode.LIGHT_MAP );
+export const materialAOMap = nodeImmutable( MaterialNode, MaterialNode.AO_MAP );
+export const materialRefractionRatio = nodeImmutable( MaterialNode, MaterialNode.REFRACTION_RATIO );
 export const materialAnisotropyVector = uniform( new Vector2() ).onReference( function ( frame ) {
 
 	return frame.material;

+ 4 - 0
src/nodes/accessors/ReflectVectorNode.js

@@ -1,6 +1,10 @@
 import { cameraViewMatrix } from './CameraNode.js';
 import { transformedNormalView } from './NormalNode.js';
 import { positionViewDirection } from './PositionNode.js';
+import { materialRefractionRatio } from './MaterialNode.js';
 
 export const reflectView = /*#__PURE__*/ positionViewDirection.negate().reflect( transformedNormalView );
+export const refractView = /*#__PURE__*/ positionViewDirection.negate().refract( transformedNormalView, materialRefractionRatio );
+
 export const reflectVector = /*#__PURE__*/ reflectView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );
+export const refractVector = /*#__PURE__*/ refractView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' );

+ 34 - 12
src/nodes/accessors/TextureNode.js

@@ -12,7 +12,7 @@ import { IntType, UnsignedIntType } from '../../constants.js';
 
 class TextureNode extends UniformNode {
 
-	constructor( value, uvNode = null, levelNode = null ) {
+	constructor( value, uvNode = null, levelNode = null, biasNode = null ) {
 
 		super( value );
 
@@ -20,6 +20,7 @@ class TextureNode extends UniformNode {
 
 		this.uvNode = uvNode;
 		this.levelNode = levelNode;
+		this.biasNode = biasNode;
 		this.compareNode = null;
 		this.depthNode = null;
 		this.gradNode = null;
@@ -31,6 +32,7 @@ class TextureNode extends UniformNode {
 		this.referenceNode = null;
 
 		this._value = value;
+		this._matrixUniform = null;
 
 		this.setUpdateMatrix( uvNode === null );
 
@@ -100,9 +102,10 @@ class TextureNode extends UniformNode {
 
 	getTransformedUV( uvNode ) {
 
-		const texture = this.value;
+		if ( this._matrixUniform === null ) this._matrixUniform = uniform( this.value.matrix );
+
+		return this._matrixUniform.mul( vec3( uvNode, 1 ) ).xy;
 
-		return uniform( texture.matrix ).mul( vec3( uvNode, 1 ) ).xy;
 
 	}
 
@@ -168,6 +171,7 @@ class TextureNode extends UniformNode {
 
 		properties.uvNode = uvNode;
 		properties.levelNode = levelNode;
+		properties.biasNode = this.biasNode;
 		properties.compareNode = this.compareNode;
 		properties.gradNode = this.gradNode;
 		properties.depthNode = this.depthNode;
@@ -180,7 +184,7 @@ class TextureNode extends UniformNode {
 
 	}
 
-	generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet, gradSnippet ) {
+	generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet ) {
 
 		const texture = this.value;
 
@@ -190,6 +194,10 @@ class TextureNode extends UniformNode {
 
 			snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet );
 
+		} else if ( biasSnippet ) {
+
+			snippet = builder.generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet );
+
 		} else if ( gradSnippet ) {
 
 			snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet );
@@ -242,10 +250,11 @@ class TextureNode extends UniformNode {
 
 			if ( propertyName === undefined ) {
 
-				const { uvNode, levelNode, compareNode, depthNode, gradNode } = properties;
+				const { uvNode, levelNode, biasNode, compareNode, depthNode, gradNode } = properties;
 
 				const uvSnippet = this.generateUV( builder, uvNode );
 				const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null;
+				const biasSnippet = biasNode ? biasNode.build( builder, 'float' ) : null;
 				const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
 				const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null;
 				const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null;
@@ -254,7 +263,7 @@ class TextureNode extends UniformNode {
 
 				propertyName = builder.getPropertyName( nodeVar );
 
-				const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet, gradSnippet );
+				const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet );
 
 				builder.addLineFlowCode( `${propertyName} = ${snippet}` );
 
@@ -297,17 +306,17 @@ class TextureNode extends UniformNode {
 	uv( uvNode ) {
 
 		const textureNode = this.clone();
-		textureNode.uvNode = uvNode;
+		textureNode.uvNode = nodeObject( uvNode );
 		textureNode.referenceNode = this;
 
 		return nodeObject( textureNode );
 
 	}
 
-	blur( levelNode ) {
+	blur( amountNode ) {
 
 		const textureNode = this.clone();
-		textureNode.levelNode = levelNode.mul( maxMipLevel( textureNode ) );
+		textureNode.biasNode = nodeObject( amountNode ).mul( maxMipLevel( textureNode ) );
 		textureNode.referenceNode = this;
 
 		return nodeObject( textureNode );
@@ -317,10 +326,10 @@ class TextureNode extends UniformNode {
 	level( levelNode ) {
 
 		const textureNode = this.clone();
-		textureNode.levelNode = levelNode;
+		textureNode.levelNode = nodeObject( levelNode );
 		textureNode.referenceNode = this;
 
-		return textureNode;
+		return nodeObject( textureNode );
 
 	}
 
@@ -330,6 +339,16 @@ class TextureNode extends UniformNode {
 
 	}
 
+	bias( biasNode ) {
+
+		const textureNode = this.clone();
+		textureNode.biasNode = nodeObject( biasNode );
+		textureNode.referenceNode = this;
+
+		return nodeObject( textureNode );
+
+	}
+
 	compare( compareNode ) {
 
 		const textureNode = this.clone();
@@ -382,6 +401,9 @@ class TextureNode extends UniformNode {
 	update() {
 
 		const texture = this.value;
+		const matrixUniform = this._matrixUniform;
+
+		if ( matrixUniform !== null ) matrixUniform.value = texture.matrix;
 
 		if ( texture.matrixAutoUpdate === true ) {
 
@@ -393,7 +415,7 @@ class TextureNode extends UniformNode {
 
 	clone() {
 
-		const newNode = new this.constructor( this.value, this.uvNode, this.levelNode );
+		const newNode = new this.constructor( this.value, this.uvNode, this.levelNode, this.biasNode );
 		newNode.sampler = this.sampler;
 
 		return newNode;

+ 1 - 3
src/nodes/core/LightingModel.js

@@ -8,9 +8,7 @@ class LightingModel {
 
 	directRectArea( /*input, stack, builder*/ ) {}
 
-	indirectDiffuse( /*input, stack, builder*/ ) { }
-
-	indirectSpecular( /*input, stack, builder*/ ) { }
+	indirect( /*input, stack, builder*/ ) { }
 
 	ambientOcclusion( /*input, stack, builder*/ ) { }
 

+ 14 - 24
src/nodes/core/NodeBuilder.js

@@ -173,7 +173,7 @@ class NodeBuilder {
 
 		const bindGroupsCache = this.getBingGroupsCache();
 
-		// cache individual uniforms group
+		//
 
 		const bindingsArray = [];
 
@@ -181,30 +181,9 @@ class NodeBuilder {
 
 		for ( const binding of bindings ) {
 
-			if ( binding.groupNode.shared === true ) {
-
-				// nodes is the chainmap key
-				const nodes = binding.getNodes();
-
-				let sharedBinding = bindGroupsCache.get( nodes );
-
-				if ( sharedBinding === undefined ) {
-
-					bindGroupsCache.set( nodes, binding );
-
-					sharedBinding = binding;
-
-				}
-
-				bindingsArray.push( sharedBinding );
-
-			} else {
+			bindingsArray.push( binding );
 
-				bindingsArray.push( binding );
-
-				sharedGroup = false;
-
-			}
+			sharedGroup = sharedGroup && binding.groupNode.shared !== true;
 
 		}
 
@@ -416,6 +395,17 @@ class NodeBuilder {
 
 	}
 
+	getSharedContext() {
+
+		const context = { ...this.context };
+
+		delete context.keywords;
+		delete context.material;
+
+		return this.context;
+
+	}
+
 	setCache( cache ) {
 
 		this.cache = cache;

+ 0 - 9
src/nodes/display/AfterImageNode.js

@@ -9,7 +9,6 @@ import { sign, max } from '../math/MathNode.js';
 import QuadMesh from '../../renderers/common/QuadMesh.js';
 
 import { Vector2 } from '../../math/Vector2.js';
-import { NoToneMapping } from '../../constants.js';
 import { RenderTarget } from '../../core/RenderTarget.js';
 
 const _size = new Vector2();
@@ -67,17 +66,12 @@ class AfterImageNode extends TempNode {
 
 		this.setSize( _size.x, _size.y );
 
-
-		const currentToneMapping = renderer.toneMapping;
-		const currentToneMappingNode = renderer.toneMappingNode;
 		const currentRenderTarget = renderer.getRenderTarget();
 		const currentTexture = textureNode.value;
 
 		this.textureNodeOld.value = this._oldRT.texture;
 
 		// comp
-		renderer.toneMapping = NoToneMapping;
-		renderer.toneMappingNode = null;
 		renderer.setRenderTarget( this._compRT );
 		quadMeshComp.render( renderer );
 
@@ -86,9 +80,6 @@ class AfterImageNode extends TempNode {
 		this._oldRT = this._compRT;
 		this._compRT = temp;
 
-
-		renderer.toneMapping = currentToneMapping;
-		renderer.toneMappingNode = currentToneMappingNode;
 		renderer.setRenderTarget( currentRenderTarget );
 		textureNode.value = currentTexture;
 

+ 2 - 1
src/nodes/display/GaussianBlurNode.js

@@ -155,7 +155,8 @@ class GaussianBlurNode extends TempNode {
 		//
 
 		const material = this._material || ( this._material = builder.createNodeMaterial() );
-		material.fragmentNode = blur();
+		material.fragmentNode = blur().context( builder.getSharedContext() );
+		material.needsUpdate = true;
 
 		//
 

+ 17 - 11
src/nodes/display/PassNode.js

@@ -6,7 +6,7 @@ import { nodeObject } from '../shadernode/ShaderNode.js';
 import { uniform } from '../core/UniformNode.js';
 import { viewZToOrthographicDepth, perspectiveDepthToViewZ } from './ViewportDepthNode.js';
 
-import { HalfFloatType, NoToneMapping/*, FloatType*/ } from '../../constants.js';
+import { HalfFloatType/*, FloatType*/ } from '../../constants.js';
 import { Vector2 } from '../../math/Vector2.js';
 import { DepthTexture } from '../../textures/DepthTexture.js';
 import { RenderTarget } from '../../core/RenderTarget.js';
@@ -43,13 +43,14 @@ class PassTextureNode extends TextureNode {
 
 class PassNode extends TempNode {
 
-	constructor( scope, scene, camera ) {
+	constructor( scope, scene, camera, options = {} ) {
 
 		super( 'vec4' );
 
 		this.scope = scope;
 		this.scene = scene;
 		this.camera = camera;
+		this.options = options;
 
 		this._pixelRatio = 1;
 		this._width = 1;
@@ -60,7 +61,7 @@ class PassNode extends TempNode {
 		//depthTexture.type = FloatType;
 		depthTexture.name = 'PostProcessingDepth';
 
-		const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
+		const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options } );
 		renderTarget.texture.name = 'PostProcessing';
 		renderTarget.depthTexture = depthTexture;
 
@@ -130,7 +131,18 @@ class PassNode extends TempNode {
 
 	}
 
-	setup() {
+	setup( { renderer } ) {
+
+		this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples;
+
+		// Disable MSAA for WebGL backend for now
+		if ( renderer.backend.isWebGLBackend === true ) {
+
+			this.renderTarget.samples = 0;
+
+		}
+
+		this.renderTarget.depthTexture.isMultisampleRenderTargetTexture = this.renderTarget.samples > 1;
 
 		return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode();
 
@@ -147,21 +159,15 @@ class PassNode extends TempNode {
 
 		this.setSize( size.width, size.height );
 
-		const currentToneMapping = renderer.toneMapping;
-		const currentToneMappingNode = renderer.toneMappingNode;
 		const currentRenderTarget = renderer.getRenderTarget();
 
 		this._cameraNear.value = camera.near;
 		this._cameraFar.value = camera.far;
 
-		renderer.toneMapping = NoToneMapping;
-		renderer.toneMappingNode = null;
 		renderer.setRenderTarget( this.renderTarget );
 
 		renderer.render( scene, camera );
 
-		renderer.toneMapping = currentToneMapping;
-		renderer.toneMappingNode = currentToneMappingNode;
 		renderer.setRenderTarget( currentRenderTarget );
 
 	}
@@ -200,7 +206,7 @@ PassNode.DEPTH = 'depth';
 
 export default PassNode;
 
-export const pass = ( scene, camera ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera ) );
+export const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) );
 export const texturePass = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) );
 export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) );
 

+ 56 - 0
src/nodes/display/RenderOutputNode.js

@@ -0,0 +1,56 @@
+import TempNode from '../core/TempNode.js';
+import { addNodeClass } from '../core/Node.js';
+import { addNodeElement, nodeObject } from '../shadernode/ShaderNode.js';
+
+import { SRGBColorSpace, NoToneMapping } from '../../constants.js';
+
+class RenderOutputNode extends TempNode {
+
+	constructor( colorNode, toneMapping, outputColorSpace ) {
+
+		super( 'vec4' );
+
+		this.colorNode = colorNode;
+		this.toneMapping = toneMapping;
+		this.outputColorSpace = outputColorSpace;
+
+		this.isRenderOutput = true;
+
+	}
+
+	setup( { context } ) {
+
+		let outputNode = this.colorNode || context.color;
+
+		// tone mapping
+
+		const toneMapping = this.toneMapping !== null ? this.toneMapping : context.toneMapping;
+		const outputColorSpace = this.outputColorSpace !== null ? this.outputColorSpace : context.outputColorSpace;
+
+		if ( toneMapping !== NoToneMapping ) {
+
+			outputNode = outputNode.toneMapping( toneMapping );
+
+		}
+
+		// output color space
+
+		if ( outputColorSpace === SRGBColorSpace ) {
+
+			outputNode = outputNode.linearToColorSpace( outputColorSpace );
+
+		}
+
+		return outputNode;
+
+	}
+
+}
+
+export default RenderOutputNode;
+
+export const renderOutput = ( color, toneMapping = null, outputColorSpace = null ) => nodeObject( new RenderOutputNode( nodeObject( color ), toneMapping, outputColorSpace ) );
+
+addNodeElement( 'renderOutput', renderOutput );
+
+addNodeClass( 'RenderOutputNode', RenderOutputNode );

+ 78 - 0
src/nodes/functions/BasicLightingModel.js

@@ -0,0 +1,78 @@
+import LightingModel from '../core/LightingModel.js';
+import { diffuseColor } from '../core/PropertyNode.js';
+import { MultiplyOperation, MixOperation, AddOperation } from '../../constants.js';
+import { materialSpecularStrength, materialReflectivity } from '../accessors/MaterialNode.js';
+import { mix } from '../math/MathNode.js';
+import { vec4 } from '../shadernode/ShaderNode.js';
+
+class BasicLightingModel extends LightingModel {
+
+	constructor() {
+
+		super();
+
+	}
+
+	indirect( context, stack, builder ) {
+
+		const ambientOcclusion = context.ambientOcclusion;
+		const reflectedLight = context.reflectedLight;
+		const irradianceLightMap = builder.context.irradianceLightMap;
+
+		reflectedLight.indirectDiffuse.assign( vec4( 0.0 ) );
+
+		// accumulation (baked indirect lighting only)
+
+		if ( irradianceLightMap ) {
+
+			reflectedLight.indirectDiffuse.addAssign( irradianceLightMap );
+
+		} else {
+
+			reflectedLight.indirectDiffuse.addAssign( vec4( 1.0, 1.0, 1.0, 0.0 ) );
+
+		}
+
+		// modulation
+
+		reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
+
+		reflectedLight.indirectDiffuse.mulAssign( diffuseColor.rgb );
+
+	}
+
+	finish( context, stack, builder ) {
+
+		const material = builder.material;
+		const outgoingLight = context.outgoingLight;
+		const envNode = builder.context.environment;
+
+		if ( envNode ) {
+
+			switch ( material.combine ) {
+
+				case MultiplyOperation:
+					outgoingLight.rgb.assign( mix( outgoingLight.rgb, outgoingLight.rgb.mul( envNode.rgb ), materialSpecularStrength.mul( materialReflectivity ) ) );
+					break;
+
+				case MixOperation:
+					outgoingLight.rgb.assign( mix( outgoingLight.rgb, envNode.rgb, materialSpecularStrength.mul( materialReflectivity ) ) );
+					break;
+
+				case AddOperation:
+					outgoingLight.rgb.addAssign( envNode.rgb.mul( materialSpecularStrength.mul( materialReflectivity ) ) );
+					break;
+
+				default:
+					console.warn( 'THREE.BasicLightingModel: Unsupported .combine value:', material.combine );
+					break;
+
+			}
+
+		}
+
+	}
+
+}
+
+export default BasicLightingModel;

+ 5 - 3
src/nodes/functions/PhongLightingModel.js

@@ -1,4 +1,4 @@
-import LightingModel from '../core/LightingModel.js';
+import BasicLightingModel from './BasicLightingModel.js';
 import F_Schlick from './BSDF/F_Schlick.js';
 import BRDF_Lambert from './BSDF/BRDF_Lambert.js';
 import { diffuseColor } from '../core/PropertyNode.js';
@@ -31,7 +31,7 @@ const BRDF_BlinnPhong = tslFn( ( { lightDirection } ) => {
 
 } );
 
-class PhongLightingModel extends LightingModel {
+class PhongLightingModel extends BasicLightingModel {
 
 	constructor( specular = true ) {
 
@@ -56,10 +56,12 @@ class PhongLightingModel extends LightingModel {
 
 	}
 
-	indirectDiffuse( { irradiance, reflectedLight } ) {
+	indirect( { ambientOcclusion, irradiance, reflectedLight } ) {
 
 		reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );
 
+		reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
+
 	}
 
 }

+ 8 - 0
src/nodes/functions/PhysicalLightingModel.js

@@ -507,6 +507,14 @@ class PhysicalLightingModel extends LightingModel {
 
 	}
 
+	indirect( context, stack, builder ) {
+
+		this.indirectDiffuse( context, stack, builder );
+		this.indirectSpecular( context, stack, builder );
+		this.ambientOcclusion( context, stack, builder );
+
+	}
+
 	indirectDiffuse( { irradiance, reflectedLight } ) {
 
 		reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );

+ 3 - 1
src/nodes/functions/ToonLightingModel.js

@@ -38,10 +38,12 @@ class ToonLightingModel extends LightingModel {
 
 	}
 
-	indirectDiffuse( { irradiance, reflectedLight } ) {
+	indirect( { ambientOcclusion, irradiance, reflectedLight } ) {
 
 		reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );
 
+		reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
+
 	}
 
 }

+ 1 - 4
src/nodes/lighting/AONode.js

@@ -13,10 +13,7 @@ class AONode extends LightingNode {
 
 	setup( builder ) {
 
-		const aoIntensity = 1;
-		const aoNode = this.aoNode.x.sub( 1.0 ).mul( aoIntensity ).add( 1.0 );
-
-		builder.context.ambientOcclusion.mulAssign( aoNode );
+		builder.context.ambientOcclusion.mulAssign( this.aoNode );
 
 	}
 

+ 26 - 0
src/nodes/lighting/BasicEnvironmentNode.js

@@ -0,0 +1,26 @@
+import LightingNode from './LightingNode.js';
+import { addNodeClass } from '../core/Node.js';
+
+class BasicEnvironmentNode extends LightingNode {
+
+	constructor( envNode = null ) {
+
+		super();
+
+		this.envNode = envNode;
+
+	}
+
+	setup( builder ) {
+
+		// environment property is used in the finish() method of BasicLightingModel
+
+		builder.context.environment = this.envNode;
+
+	}
+
+}
+
+export default BasicEnvironmentNode;
+
+addNodeClass( 'BasicEnvironmentNode', BasicEnvironmentNode );

+ 29 - 0
src/nodes/lighting/BasicLightMapNode.js

@@ -0,0 +1,29 @@
+import LightingNode from './LightingNode.js';
+import { addNodeClass } from '../core/Node.js';
+import { float } from '../shadernode/ShaderNode.js';
+
+class BasicLightMapNode extends LightingNode {
+
+	constructor( lightMapNode = null ) {
+
+		super();
+
+		this.lightMapNode = lightMapNode;
+
+	}
+
+	setup( builder ) {
+
+		// irradianceLightMap property is used in the indirectDiffuse() method of BasicLightingModel
+
+		const RECIPROCAL_PI = float( 1 / Math.PI );
+
+		builder.context.irradianceLightMap = this.lightMapNode.mul( RECIPROCAL_PI );
+
+	}
+
+}
+
+export default BasicLightMapNode;
+
+addNodeClass( 'BasicLightMapNode', BasicLightMapNode );

+ 2 - 0
src/nodes/lighting/LightingNode.js

@@ -6,6 +6,8 @@ class LightingNode extends Node {
 
 		super( 'vec3' );
 
+		this.isLightingNode = true;
+
 	}
 
 	generate( /*builder*/ ) {

+ 1 - 3
src/nodes/lighting/LightsNode.js

@@ -99,9 +99,7 @@ class LightsNode extends Node {
 
 			//
 
-			lightingModel.indirectDiffuse( context, stack, builder );
-			lightingModel.indirectSpecular( context, stack, builder );
-			lightingModel.ambientOcclusion( context, stack, builder );
+			lightingModel.indirect( context, stack, builder );
 
 			//
 

+ 33 - 2
src/nodes/materials/MeshBasicNodeMaterial.js

@@ -1,6 +1,9 @@
 import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js';
-
+import { materialLightMap } from '../accessors/MaterialNode.js';
 import { MeshBasicMaterial } from '../../materials/MeshBasicMaterial.js';
+import BasicEnvironmentNode from '../lighting/BasicEnvironmentNode.js';
+import BasicLightMapNode from '../lighting/BasicLightMapNode.js';
+import BasicLightingModel from '../functions/BasicLightingModel.js';
 
 const defaultValues = new MeshBasicMaterial();
 
@@ -12,7 +15,7 @@ class MeshBasicNodeMaterial extends NodeMaterial {
 
 		this.isMeshBasicNodeMaterial = true;
 
-		this.lights = false;
+		this.lights = true;
 		//this.normals = false; @TODO: normals usage by context
 
 		this.setDefaultValues( defaultValues );
@@ -21,6 +24,34 @@ class MeshBasicNodeMaterial extends NodeMaterial {
 
 	}
 
+	setupEnvironment( builder ) {
+
+		const envNode = super.setupEnvironment( builder );
+
+		return envNode ? new BasicEnvironmentNode( envNode ) : null;
+
+	}
+
+	setupLightMap( builder ) {
+
+		let node = null;
+
+		if ( builder.material.lightMap ) {
+
+			node = new BasicLightMapNode( materialLightMap );
+
+		}
+
+		return node;
+
+	}
+
+	setupLightingModel() {
+
+		return new BasicLightingModel();
+
+	}
+
 }
 
 export default MeshBasicNodeMaterial;

+ 9 - 0
src/nodes/materials/MeshLambertNodeMaterial.js

@@ -1,4 +1,5 @@
 import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js';
+import BasicEnvironmentNode from '../lighting/BasicEnvironmentNode.js';
 import PhongLightingModel from '../functions/PhongLightingModel.js';
 
 import { MeshLambertMaterial } from '../../materials/MeshLambertMaterial.js';
@@ -21,6 +22,14 @@ class MeshLambertNodeMaterial extends NodeMaterial {
 
 	}
 
+	setupEnvironment( builder ) {
+
+		const envNode = super.setupEnvironment( builder );
+
+		return envNode ? new BasicEnvironmentNode( envNode ) : null;
+
+	}
+
 	setupLightingModel( /*builder*/ ) {
 
 		return new PhongLightingModel( false ); // ( specular ) -> force lambert

+ 8 - 0
src/nodes/materials/MeshPhongNodeMaterial.js

@@ -2,6 +2,7 @@ import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js';
 import { shininess, specularColor } from '../core/PropertyNode.js';
 import { materialShininess, materialSpecular } from '../accessors/MaterialNode.js';
 import { float } from '../shadernode/ShaderNode.js';
+import BasicEnvironmentNode from '../lighting/BasicEnvironmentNode.js';
 import PhongLightingModel from '../functions/PhongLightingModel.js';
 
 import { MeshPhongMaterial } from '../../materials/MeshPhongMaterial.js';
@@ -27,6 +28,13 @@ class MeshPhongNodeMaterial extends NodeMaterial {
 
 	}
 
+	setupEnvironment( builder ) {
+
+		const envNode = super.setupEnvironment( builder );
+
+		return envNode ? new BasicEnvironmentNode( envNode ) : null;
+
+	}
 	setupLightingModel( /*builder*/ ) {
 
 		return new PhongLightingModel();

+ 9 - 0
src/nodes/materials/MeshStandardNodeMaterial.js

@@ -4,6 +4,7 @@ import { mix } from '../math/MathNode.js';
 import { materialRoughness, materialMetalness } from '../accessors/MaterialNode.js';
 import getRoughness from '../functions/material/getRoughness.js';
 import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
+import EnvironmentNode from '../lighting/EnvironmentNode.js';
 import { float, vec3, vec4 } from '../shadernode/ShaderNode.js';
 
 import { MeshStandardMaterial } from '../../materials/MeshStandardMaterial.js';
@@ -29,6 +30,14 @@ class MeshStandardNodeMaterial extends NodeMaterial {
 
 	}
 
+	setupEnvironment( builder ) {
+
+		const envNode = super.setupEnvironment( builder );
+
+		return envNode ? new EnvironmentNode( envNode ) : null;
+
+	}
+
 	setupLightingModel( /*builder*/ ) {
 
 		return new PhysicalLightingModel();

+ 25 - 10
src/nodes/materials/NodeMaterial.js

@@ -4,7 +4,7 @@ import { NormalBlending } from '../../constants.js';
 import { getNodeChildren, getCacheKey } from '../core/NodeUtils.js';
 import { attribute } from '../core/AttributeNode.js';
 import { output, diffuseColor, varyingProperty } from '../core/PropertyNode.js';
-import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal } from '../accessors/MaterialNode.js';
+import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal, materialLightMap, materialAOMap } from '../accessors/MaterialNode.js';
 import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js';
 import { transformedNormalView, normalLocal } from '../accessors/NormalNode.js';
 import { instance } from '../accessors/InstanceNode.js';
@@ -20,7 +20,6 @@ import { mix } from '../math/MathNode.js';
 import { float, vec3, vec4 } from '../shadernode/ShaderNode.js';
 import AONode from '../lighting/AONode.js';
 import { lightingContext } from '../lighting/LightingContextNode.js';
-import EnvironmentNode from '../lighting/EnvironmentNode.js';
 import IrradianceNode from '../lighting/IrradianceNode.js';
 import { depth } from '../display/ViewportDepthNode.js';
 import { cameraLogDepth } from '../accessors/CameraNode.js';
@@ -330,7 +329,7 @@ class NodeMaterial extends Material {
 
 	}
 
-	getEnvNode( builder ) {
+	setupEnvironment( builder ) {
 
 		let node = null;
 
@@ -352,29 +351,45 @@ class NodeMaterial extends Material {
 
 	}
 
+	setupLightMap( builder ) {
+
+		let node = null;
+
+		if ( builder.material.lightMap ) {
+
+			node = new IrradianceNode( materialLightMap );
+
+		}
+
+		return node;
+
+	}
+
 	setupLights( builder ) {
 
-		const envNode = this.getEnvNode( builder );
+		const materialLightsNode = [];
 
 		//
 
-		const materialLightsNode = [];
+		const envNode = this.setupEnvironment( builder );
 
-		if ( envNode ) {
+		if ( envNode && envNode.isLightingNode ) {
 
-			materialLightsNode.push( new EnvironmentNode( envNode ) );
+			materialLightsNode.push( envNode );
 
 		}
 
-		if ( builder.material.lightMap ) {
+		const lightMapNode = this.setupLightMap( builder );
+
+		if ( lightMapNode && lightMapNode.isLightingNode ) {
 
-			materialLightsNode.push( new IrradianceNode( materialReference( 'lightMap', 'texture' ) ) );
+			materialLightsNode.push( lightMapNode );
 
 		}
 
 		if ( this.aoNode !== null || builder.material.aoMap ) {
 
-			const aoNode = this.aoNode !== null ? this.aoNode : texture( builder.material.aoMap );
+			const aoNode = this.aoNode !== null ? this.aoNode : materialAOMap;
 
 			materialLightsNode.push( new AONode( aoNode ) );
 

+ 8 - 2
src/nodes/utils/MaxMipLevelNode.js

@@ -9,15 +9,21 @@ class MaxMipLevelNode extends UniformNode {
 
 		super( 0 );
 
-		this.textureNode = textureNode;
+		this._textureNode = textureNode;
 
 		this.updateType = NodeUpdateType.FRAME;
 
 	}
 
+	get textureNode() {
+
+		return this._textureNode;
+
+	}
+
 	get texture() {
 
-		return this.textureNode.value;
+		return this._textureNode.value;
 
 	}
 

+ 28 - 4
src/nodes/utils/RTTNode.js

@@ -10,7 +10,6 @@ import { RenderTarget } from '../../core/RenderTarget.js';
 import { Vector2 } from '../../math/Vector2.js';
 import { HalfFloatType } from '../../constants.js';
 
-const _quadMesh = new QuadMesh( new NodeMaterial() );
 const _size = new Vector2();
 
 class RTTNode extends TextureNode {
@@ -32,6 +31,9 @@ class RTTNode extends TextureNode {
 
 		this.updateMap = new WeakMap();
 
+		this._rttNode = null;
+		this._quadMesh = new QuadMesh( new NodeMaterial() );
+
 		this.updateBeforeType = NodeUpdateType.RENDER;
 
 	}
@@ -42,17 +44,37 @@ class RTTNode extends TextureNode {
 
 	}
 
+	setup( builder ) {
+
+		this._rttNode = this.node.context( builder.getSharedContext() );
+		this._quadMesh.material.needsUpdate = true;
+
+		return super.setup( builder );
+
+	}
+
 	setSize( width, height ) {
 
 		this.width = width;
 		this.height = height;
 
-		this.renderTarget.setSize( width, height );
+		const effectiveWidth = width * this.pixelRatio;
+		const effectiveHeight = height * this.pixelRatio;
+
+		this.renderTarget.setSize( effectiveWidth, effectiveHeight );
 
 		this.textureNeedsUpdate = true;
 
 	}
 
+	setPixelRatio( pixelRatio ) {
+
+		this.pixelRatio = pixelRatio;
+
+		this.setSize( this.width, this.height );
+
+	}
+
 	updateBefore( { renderer } ) {
 
 		if ( this.textureNeedsUpdate === false && this.autoUpdate === false ) return;
@@ -63,6 +85,8 @@ class RTTNode extends TextureNode {
 
 		if ( this.autoSize === true ) {
 
+			this.pixelRatio = renderer.getPixelRatio();
+
 			const size = renderer.getSize( _size );
 
 			this.setSize( size.width, size.height );
@@ -71,7 +95,7 @@ class RTTNode extends TextureNode {
 
 		//
 
-		_quadMesh.material.fragmentNode = this.node;
+		this._quadMesh.material.fragmentNode = this._rttNode;
 
 		//
 
@@ -79,7 +103,7 @@ class RTTNode extends TextureNode {
 
 		renderer.setRenderTarget( this.renderTarget );
 
-		_quadMesh.render( renderer );
+		this._quadMesh.render( renderer );
 
 		renderer.setRenderTarget( currentRenderTarget );
 

+ 2 - 2
src/objects/BatchedMesh.js

@@ -927,12 +927,12 @@ class BatchedMesh extends Mesh {
 		this._multiDrawStarts = source._multiDrawStarts.slice();
 
 		this._matricesTexture = source._matricesTexture.clone();
-		this._matricesTexture.image.data = this._matricesTexture.image.slice();
+		this._matricesTexture.image.data = this._matricesTexture.image.data.slice();
 
 		if ( this._colorsTexture !== null ) {
 
 			this._colorsTexture = source._colorsTexture.clone();
-			this._colorsTexture.image.data = this._colorsTexture.image.slice();
+			this._colorsTexture.image.data = this._colorsTexture.image.data.slice();
 
 		}
 

+ 2 - 8
src/renderers/WebGLRenderer.js

@@ -1490,15 +1490,9 @@ class WebGLRenderer {
 			_currentClearAlpha = _this.getClearAlpha();
 			if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 );
 
-			if ( _renderBackground ) {
-
-				background.render( scene );
-
-			} else {
-
-				_this.clear();
+			_this.clear();
 
-			}
+			if ( _renderBackground ) background.render( scene );
 
 			// Turn off the features which can affect the frag color for opaque objects pass.
 			// Otherwise they are applied twice in opaque objects pass and transmission objects pass.

+ 54 - 7
src/renderers/common/PostProcessing.js

@@ -1,4 +1,5 @@
-import { vec4, NodeMaterial } from '../../nodes/Nodes.js';
+import { vec4, renderOutput, NodeMaterial } from '../../nodes/Nodes.js';
+import { LinearSRGBColorSpace, NoToneMapping } from '../../constants.js';
 import QuadMesh from '../../renderers/common/QuadMesh.js';
 
 const quadMesh = new QuadMesh( new NodeMaterial() );
@@ -10,27 +11,73 @@ class PostProcessing {
 		this.renderer = renderer;
 		this.outputNode = outputNode;
 
+		this.outputColorTransform = true;
+
+		this.needsUpdate = true;
+
 	}
 
 	render() {
 
-		quadMesh.material.fragmentNode = this.outputNode;
+		this.update();
+
+		const renderer = this.renderer;
+
+		const toneMapping = renderer.toneMapping;
+		const outputColorSpace = renderer.outputColorSpace;
+
+		renderer.toneMapping = NoToneMapping;
+		renderer.outputColorSpace = LinearSRGBColorSpace;
+
+		//
 
 		quadMesh.render( this.renderer );
 
+		//
+
+		renderer.toneMapping = toneMapping;
+		renderer.outputColorSpace = outputColorSpace;
+
 	}
 
-	renderAsync() {
+	update() {
+
+		if ( this.needsUpdate === true ) {
+
+			const renderer = this.renderer;
 
-		quadMesh.material.fragmentNode = this.outputNode;
+			const toneMapping = renderer.toneMapping;
+			const outputColorSpace = renderer.outputColorSpace;
 
-		return quadMesh.renderAsync( this.renderer );
+			quadMesh.material.fragmentNode = this.outputColorTransform === true ? renderOutput( this.outputNode, toneMapping, outputColorSpace ) : this.outputNode.context( { toneMapping, outputColorSpace } );
+			quadMesh.material.needsUpdate = true;
+
+			this.needsUpdate = false;
+
+		}
 
 	}
 
-	set needsUpdate( value ) {
+	async renderAsync() {
+
+		this.update();
+
+		const renderer = this.renderer;
+
+		const toneMapping = renderer.toneMapping;
+		const outputColorSpace = renderer.outputColorSpace;
+
+		renderer.toneMapping = NoToneMapping;
+		renderer.outputColorSpace = LinearSRGBColorSpace;
+
+		//
+
+		await quadMesh.renderAsync( this.renderer );
+
+		//
 
-		quadMesh.material.needsUpdate = value;
+		renderer.toneMapping = toneMapping;
+		renderer.outputColorSpace = outputColorSpace;
 
 	}
 

+ 22 - 4
src/renderers/common/RenderObject.js

@@ -193,18 +193,36 @@ export default class RenderObject {
 
 			if ( /^(is[A-Z]|_)|^(visible|version|uuid|name|opacity|userData)$/.test( property ) ) continue;
 
-			let value = material[ property ];
+			const value = material[ property ];
+
+			let valueKey;
 
 			if ( value !== null ) {
 
 				const type = typeof value;
 
-				if ( type === 'number' ) value = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc
-				else if ( type === 'object' ) value = '{}';
+				if ( type === 'number' ) valueKey = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc
+				else if ( type === 'object' ) {
+
+					valueKey = '{';
+
+					if ( value.isTexture ) {
+
+						valueKey += value.mapping;
+
+					}
+
+					valueKey += '}';
+
+				}
+
+			} else {
+
+				valueKey = String( value );
 
 			}
 
-			cacheKey += /*property + ':' +*/ value + ',';
+			cacheKey += /*property + ':' +*/ valueKey + ',';
 
 		}
 

+ 28 - 13
src/renderers/common/Renderer.js

@@ -32,7 +32,6 @@ const _screen = new Vector4();
 const _frustum = new Frustum();
 const _projScreenMatrix = new Matrix4();
 const _vector3 = new Vector3();
-const _quad = new QuadMesh( new NodeMaterial() );
 
 class Renderer {
 
@@ -44,15 +43,18 @@ class Renderer {
 
 		const {
 			logarithmicDepthBuffer = false,
-			alpha = true
+			alpha = true,
+			antialias = false,
+			samples = 0
 		} = parameters;
 
 		// public
-
 		this.domElement = backend.getDomElement();
 
 		this.backend = backend;
 
+		this.samples = samples || ( antialias === true ) ? 4 : 0;
+
 		this.autoClear = true;
 		this.autoClearColor = true;
 		this.autoClearDepth = true;
@@ -76,10 +78,6 @@ class Renderer {
 
 		this.info = new Info();
 
-		// nodes
-
-		this.toneMappingNode = null;
-
 		// internals
 
 		this._pixelRatio = 1;
@@ -103,6 +101,8 @@ class Renderer {
 		this._textures = null;
 		this._background = null;
 
+		this._quad = new QuadMesh( new NodeMaterial() );
+
 		this._currentRenderContext = null;
 
 		this._opaqueSort = null;
@@ -439,7 +439,7 @@ class Renderer {
 
 		const { currentColorSpace } = this;
 
-		const useToneMapping = this._renderTarget === null && ( this.toneMapping !== NoToneMapping || this.toneMappingNode !== null );
+		const useToneMapping = this._renderTarget === null && ( this.toneMapping !== NoToneMapping );
 		const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace;
 
 		if ( useToneMapping === false && useColorSpace === false ) return null;
@@ -460,7 +460,7 @@ class Renderer {
 				generateMipmaps: false,
 				minFilter: LinearFilter,
 				magFilter: LinearFilter,
-				samples: this.backend.parameters.antialias ? 4 : 0
+				samples: this.samples
 			} );
 
 			frameBufferTarget.isPostProcessingRenderTarget = true;
@@ -683,9 +683,16 @@ class Renderer {
 
 			this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel );
 
-			_quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
+			const quad = this._quad;
+
+			if ( this._nodes.hasOutputChange( renderTarget.texture ) ) {
+
+				quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
+				quad.material.needsUpdate = true;
 
-			this._renderScene( _quad, _quad.camera, false );
+			}
+
+			this._renderScene( quad, quad.camera, false );
 
 		}
 
@@ -966,8 +973,16 @@ class Renderer {
 			// If a color space transform or tone mapping is required,
 			// the clear operation clears the intermediate renderTarget texture, but does not update the screen canvas.
 
-			_quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
-			this._renderScene( _quad, _quad.camera, false );
+			const quad = this._quad;
+
+			if ( this._nodes.hasOutputChange( renderTarget.texture ) ) {
+
+				quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture );
+				quad.material.needsUpdate = true;
+
+			}
+
+			this._renderScene( quad, quad.camera, false );
 
 		}
 

+ 17 - 14
src/renderers/common/nodes/Nodes.js

@@ -1,9 +1,11 @@
 import DataMap from '../DataMap.js';
 import ChainMap from '../ChainMap.js';
 import NodeBuilderState from './NodeBuilderState.js';
-import { NodeFrame, vec4, objectGroup, renderGroup, frameGroup, cubeTexture, texture, rangeFog, densityFog, reference, viewportBottomLeft, normalWorld, pmremTexture, viewportTopLeft } from '../../../nodes/Nodes.js';
+import { NodeFrame, objectGroup, renderGroup, frameGroup, cubeTexture, texture, rangeFog, densityFog, reference, viewportBottomLeft, normalWorld, pmremTexture, viewportTopLeft } from '../../../nodes/Nodes.js';
 
-import { EquirectangularReflectionMapping, EquirectangularRefractionMapping, NoToneMapping, SRGBColorSpace } from '../../../constants.js';
+import { EquirectangularReflectionMapping, EquirectangularRefractionMapping } from '../../../constants.js';
+
+const outputNodeMap = new WeakMap();
 
 class Nodes extends DataMap {
 
@@ -391,29 +393,30 @@ class Nodes extends DataMap {
 
 	}
 
-	getOutputNode( outputTexture ) {
+	getOutputCacheKey() {
 
-		let output = texture( outputTexture, viewportTopLeft );
+		const renderer = this.renderer;
 
-		if ( this.isToneMappingState ) {
+		return renderer.toneMapping + ',' + renderer.currentColorSpace;
 
-			if ( this.renderer.toneMappingNode ) {
+	}
 
-				output = vec4( this.renderer.toneMappingNode.context( { color: output.rgb } ), output.a );
+	hasOutputChange( outputTarget ) {
 
-			} else if ( this.renderer.toneMapping !== NoToneMapping ) {
+		const cacheKey = outputNodeMap.get( outputTarget );
 
-				output = output.toneMapping( this.renderer.toneMapping );
+		return cacheKey !== this.getOutputCacheKey();
 
-			}
+	}
 
-		}
+	getOutputNode( outputTexture ) {
 
-		if ( this.renderer.currentColorSpace === SRGBColorSpace ) {
+		const renderer = this.renderer;
+		const cacheKey = this.getOutputCacheKey();
 
-			output = output.linearToColorSpace( this.renderer.currentColorSpace );
+		const output = texture( outputTexture, viewportTopLeft ).renderOutput( renderer.toneMapping, renderer.currentColorSpace );
 
-		}
+		outputNodeMap.set( outputTexture, cacheKey );
 
 		return output;
 

+ 45 - 13
src/renderers/shaders/ShaderChunk/packing.glsl.js

@@ -9,36 +9,68 @@ vec3 unpackRGBToNormal( const in vec3 rgb ) {
 
 const float PackUpscale = 256. / 255.; // fraction -> 0..1 (including 1)
 const float UnpackDownscale = 255. / 256.; // 0..1 -> fraction (excluding 1)
+const float ShiftRight8 = 1. / 256.;
+const float Inv255 = 1. / 255.;
 
-const vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );
-const vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );
+const vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );
 
-const float ShiftRight8 = 1. / 256.;
+const vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );
+const vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );
+const vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );
 
 vec4 packDepthToRGBA( const in float v ) {
-	vec4 r = vec4( fract( v * PackFactors ), v );
-	r.yzw -= r.xyz * ShiftRight8; // tidy overflow
-	return r * PackUpscale;
+	if( v <= 0.0 )
+		return vec4( 0., 0., 0., 0. );
+	if( v >= 1.0 )
+		return vec4( 1., 1., 1., 1. );
+	float vuf;
+	float af = modf( v * PackFactors.a, vuf );
+	float bf = modf( vuf * ShiftRight8, vuf );
+	float gf = modf( vuf * ShiftRight8, vuf );
+	return vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );
+}
+
+vec3 packDepthToRGB( const in float v ) {
+	if( v <= 0.0 )
+		return vec3( 0., 0., 0. );
+	if( v >= 1.0 )
+		return vec3( 1., 1., 1. );
+	float vuf;
+	float bf = modf( v * PackFactors.b, vuf );
+	float gf = modf( vuf * ShiftRight8, vuf );
+	// the 0.9999 tweak is unimportant, very tiny empirical improvement
+	// return vec3( vuf * Inv255, gf * PackUpscale, bf * 0.9999 );
+	return vec3( vuf * Inv255, gf * PackUpscale, bf );
+}
+
+vec2 packDepthToRG( const in float v ) {
+	if( v <= 0.0 )
+		return vec2( 0., 0. );
+	if( v >= 1.0 )
+		return vec2( 1., 1. );
+	float vuf;
+	float gf = modf( v * 256., vuf );
+	return vec2( vuf * Inv255, gf );
 }
 
 float unpackRGBAToDepth( const in vec4 v ) {
-	return dot( v, UnpackFactors );
+	return dot( v, UnpackFactors4 );
 }
 
-vec2 packDepthToRG( in highp float v ) {
-	return packDepthToRGBA( v ).yx;
+float unpackRGBToDepth( const in vec3 v ) {
+	return dot( v, UnpackFactors3 );
 }
 
-float unpackRGToDepth( const in highp vec2 v ) {
-	return unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );
+float unpackRGToDepth( const in vec2 v ) {
+	return v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;
 }
 
-vec4 pack2HalfToRGBA( vec2 v ) {
+vec4 pack2HalfToRGBA( const in vec2 v ) {
 	vec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );
 	return vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );
 }
 
-vec2 unpackRGBATo2Half( vec4 v ) {
+vec2 unpackRGBATo2Half( const in vec4 v ) {
 	return vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );
 }
 

+ 8 - 0
src/renderers/shaders/ShaderLib/depth.glsl.js

@@ -91,6 +91,14 @@ void main() {
 
 		gl_FragColor = packDepthToRGBA( fragCoordZ );
 
+	#elif DEPTH_PACKING == 3202
+
+		gl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );
+
+	#elif DEPTH_PACKING == 3203
+
+		gl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );
+
 	#endif
 
 }

+ 6 - 0
src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js

@@ -301,6 +301,12 @@ ${ flowData.code }
 
 	}
 
+	generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) {
+
+		return `texture( ${ textureProperty }, ${ uvSnippet }, ${ biasSnippet } )`;
+
+	}
+
 	generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet ) {
 
 		return `textureGrad( ${ textureProperty }, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`;

+ 7 - 7
src/renderers/webgl/WebGLUniformsGroups.js

@@ -240,27 +240,27 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) {
 
 					const info = getUniformSize( value );
 
-					// Calculate the chunk offset
-					const chunkOffsetUniform = offset % chunkSize;
+					const chunkOffset = offset % chunkSize; // offset in the current chunk
+					const chunkPadding = chunkOffset % info.boundary; // required padding to match boundary
+					const chunkStart = chunkOffset + chunkPadding; // the start position in the current chunk for the data
+
+					offset += chunkPadding;
 
 					// Check for chunk overflow
-					if ( chunkOffsetUniform !== 0 && ( chunkSize - chunkOffsetUniform ) < info.boundary ) {
+					if ( chunkStart !== 0 && ( chunkSize - chunkStart ) < info.storage ) {
 
 						// Add padding and adjust offset
-						offset += ( chunkSize - chunkOffsetUniform );
+						offset += ( chunkSize - chunkStart );
 
 					}
 
 					// the following two properties will be used for partial buffer updates
-
 					uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT );
 					uniform.__offset = offset;
 
-
 					// Update the global offset
 					offset += info.storage;
 
-
 				}
 
 			}

+ 5 - 19
src/renderers/webgpu/WebGPUBackend.js

@@ -28,18 +28,6 @@ class WebGPUBackend extends Backend {
 		// some parameters require default values other than "undefined"
 		this.parameters.alpha = ( parameters.alpha === undefined ) ? true : parameters.alpha;
 
-		this.parameters.antialias = ( parameters.antialias === true );
-
-		if ( this.parameters.antialias === true ) {
-
-			this.parameters.sampleCount = ( parameters.sampleCount === undefined ) ? 4 : parameters.sampleCount;
-
-		} else {
-
-			this.parameters.sampleCount = 1;
-
-		}
-
 		this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;
 
 		this.trackTimestamp = ( parameters.trackTimestamp === true );
@@ -153,8 +141,6 @@ class WebGPUBackend extends Backend {
 
 		let descriptor = this.defaultRenderPassdescriptor;
 
-		const antialias = this.parameters.antialias;
-
 		if ( descriptor === null ) {
 
 			const renderer = this.renderer;
@@ -170,7 +156,7 @@ class WebGPUBackend extends Backend {
 
 			const colorAttachment = descriptor.colorAttachments[ 0 ];
 
-			if ( antialias === true ) {
+			if ( this.renderer.samples > 0 ) {
 
 				colorAttachment.view = this.colorBuffer.createView();
 
@@ -186,7 +172,7 @@ class WebGPUBackend extends Backend {
 
 		const colorAttachment = descriptor.colorAttachments[ 0 ];
 
-		if ( antialias === true ) {
+		if ( this.renderer.samples > 0 ) {
 
 			colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
 
@@ -269,7 +255,7 @@ class WebGPUBackend extends Backend {
 			const depthTextureData = this.get( renderContext.depthTexture );
 
 			const depthStencilAttachment = {
-				view: depthTextureData.texture.createView(),
+				view: depthTextureData.texture.createView()
 			};
 
 			descriptor = {
@@ -976,7 +962,7 @@ class WebGPUBackend extends Backend {
 
 		const utils = this.utils;
 
-		const sampleCount = utils.getSampleCount( renderObject.context );
+		const sampleCount = utils.getSampleCountRenderContext( renderObject.context );
 		const colorSpace = utils.getCurrentColorSpace( renderObject.context );
 		const colorFormat = utils.getCurrentColorFormat( renderObject.context );
 		const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
@@ -1041,7 +1027,7 @@ class WebGPUBackend extends Backend {
 			material.stencilFail, material.stencilZFail, material.stencilZPass,
 			material.stencilFuncMask, material.stencilWriteMask,
 			material.side,
-			utils.getSampleCount( renderContext ),
+			utils.getSampleCountRenderContext( renderContext ),
 			utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
 			utils.getPrimitiveTopology( object, material ),
 			renderObject.clippingContextVersion

+ 26 - 4
src/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -241,7 +241,7 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 		this._include( 'repeatWrapping' );
 
-		const dimension = `textureDimensions( ${ textureProperty }, 0 )`;
+		const dimension = texture.isMultisampleRenderTargetTexture === true ? `textureDimensions( ${ textureProperty } )` : `textureDimensions( ${ textureProperty }, 0 )`;
 
 		return `textureLoad( ${ textureProperty }, threejs_repeatWrapping( ${ uvSnippet }, ${ dimension } ), i32( ${ levelSnippet } ) )`;
 
@@ -269,7 +269,7 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 	isUnfilterable( texture ) {
 
-		return this.getComponentTypeFromTexture( texture ) !== 'float' || ( texture.isDataTexture === true && texture.type === FloatType );
+		return this.getComponentTypeFromTexture( texture ) !== 'float' || ( texture.isDataTexture === true && texture.type === FloatType ) || texture.isMultisampleRenderTargetTexture === true;
 
 	}
 
@@ -342,6 +342,20 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 	}
 
+	generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet, shaderStage = this.shaderStage ) {
+
+		if ( shaderStage === 'fragment' ) {
+
+			return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ biasSnippet } )`;
+
+		} else {
+
+			console.error( `WebGPURenderer: THREE.TextureNode.biasNode does not support ${ shaderStage } shader.` );
+
+		}
+
+	}
+
 	getPropertyName( node, shaderStage = this.shaderStage ) {
 
 		if ( node.isNodeVarying === true && node.needsInterpolation === true ) {
@@ -850,6 +864,14 @@ ${ flowData.code }
 
 				let textureType;
 
+				let multisampled = '';
+
+				if ( texture.isMultisampleRenderTargetTexture === true ) {
+
+					multisampled = '_multisampled';
+
+				}
+
 				if ( texture.isCubeTexture === true ) {
 
 					textureType = 'texture_cube<f32>';
@@ -860,7 +882,7 @@ ${ flowData.code }
 
 				} else if ( texture.isDepthTexture === true ) {
 
-					textureType = 'texture_depth_2d';
+					textureType = `texture_depth${multisampled}_2d`;
 
 				} else if ( texture.isVideoTexture === true ) {
 
@@ -881,7 +903,7 @@ ${ flowData.code }
 
 					const componentPrefix = this.getComponentTypeFromTexture( texture ).charAt( 0 );
 
-					textureType = `texture_2d<${ componentPrefix }32>`;
+					textureType = `texture${multisampled}_2d<${ componentPrefix }32>`;
 
 				}
 

+ 6 - 0
src/renderers/webgpu/utils/WebGPUBindingUtils.js

@@ -71,6 +71,12 @@ class WebGPUBindingUtils {
 
 				const texture = {}; // GPUTextureBindingLayout
 
+				if ( binding.texture.isMultisampleRenderTargetTexture === true ) {
+
+					texture.multisampled = true;
+
+				}
+
 				if ( binding.texture.isDepthTexture ) {
 
 					texture.sampleType = GPUTextureSampleType.Depth;

+ 1 - 16
src/renderers/webgpu/utils/WebGPUPipelineUtils.js

@@ -25,22 +25,7 @@ class WebGPUPipelineUtils {
 
 	_getSampleCount( renderObjectContext ) {
 
-		let sampleCount = this.backend.utils.getSampleCount( renderObjectContext );
-
-		if ( sampleCount > 1 ) {
-
-			// WebGPU only supports power-of-two sample counts and 2 is not a valid value
-			sampleCount = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );
-
-			if ( sampleCount === 2 ) {
-
-				sampleCount = 4;
-
-			}
-
-		}
-
-		return sampleCount;
+		return this.backend.utils.getSampleCountRenderContext( renderObjectContext );
 
 	}
 

+ 5 - 16
src/renderers/webgpu/utils/WebGPUTextureUtils.js

@@ -119,20 +119,9 @@ class WebGPUTextureUtils {
 
 		let sampleCount = options.sampleCount !== undefined ? options.sampleCount : 1;
 
-		if ( sampleCount > 1 ) {
+		sampleCount = backend.utils.getSampleCount( sampleCount );
 
-			// WebGPU only supports power-of-two sample counts and 2 is not a valid value
-			sampleCount = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );
-
-			if ( sampleCount === 2 ) {
-
-				sampleCount = 4;
-
-			}
-
-		}
-
-		const primarySampleCount = texture.isRenderTargetTexture ? 1 : sampleCount;
+		const primarySampleCount = texture.isRenderTargetTexture && ! texture.isMultisampleRenderTargetTexture ? 1 : sampleCount;
 
 		let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;
 
@@ -190,7 +179,7 @@ class WebGPUTextureUtils {
 
 		}
 
-		if ( texture.isRenderTargetTexture && sampleCount > 1 ) {
+		if ( texture.isRenderTargetTexture && sampleCount > 1 && ! texture.isMultisampleRenderTargetTexture ) {
 
 			const msaaTextureDescriptorGPU = Object.assign( {}, textureDescriptorGPU );
 
@@ -263,7 +252,7 @@ class WebGPUTextureUtils {
 				height: height,
 				depthOrArrayLayers: 1
 			},
-			sampleCount: backend.parameters.sampleCount,
+			sampleCount: backend.utils.getSampleCount( backend.renderer.samples ),
 			format: GPUTextureFormat.BGRA8Unorm,
 			usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
 		} );
@@ -312,7 +301,7 @@ class WebGPUTextureUtils {
 		depthTexture.image.width = width;
 		depthTexture.image.height = height;
 
-		this.createTexture( depthTexture, { sampleCount: backend.parameters.sampleCount, width, height } );
+		this.createTexture( depthTexture, { sampleCount: backend.utils.getSampleCount( backend.renderer.samples ), width, height } );
 
 		return backend.get( depthTexture ).texture;
 

+ 24 - 3
src/renderers/webgpu/utils/WebGPUUtils.js

@@ -76,15 +76,36 @@ class WebGPUUtils {
 
 	}
 
-	getSampleCount( renderContext ) {
+	getSampleCount( sampleCount ) {
+
+		let count = 1;
+
+		if ( sampleCount > 1 ) {
+
+			// WebGPU only supports power-of-two sample counts and 2 is not a valid value
+			count = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );
+
+			if ( count === 2 ) {
+
+				count = 4;
+
+			}
+
+		}
+
+		return count;
+
+	}
+
+	getSampleCountRenderContext( renderContext ) {
 
 		if ( renderContext.textures !== null ) {
 
-			return renderContext.sampleCount;
+			return this.getSampleCount( renderContext.sampleCount );
 
 		}
 
-		return this.backend.parameters.sampleCount;
+		return this.getSampleCount( this.backend.renderer.samples );
 
 	}
 

+ 1 - 0
test/e2e/puppeteer.js

@@ -103,6 +103,7 @@ const exceptionList = [
 	'webgl_tiled_forward',
 	'webgl_points_dynamic',
 	'webgpu_multisampled_renderbuffers',
+	'webgl_test_wide_gamut',
 
 	// TODO: implement determinism for setTimeout and setInterval
 	// could it fix some examples from above?

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä