Explorar o código

WebGLBackend (#26581)

* Move to webgl-legacy

* WebGLBackend: WIP

* ignore normalMap for now

* WIP

* WIP

* update webgpu examples

* restore

* update webgl node examples to webgl-legacy

* update import of playground to webgl-legacy

* Rename GLSLNodeBuilder -> GLSL1NodeBuilder

* Added GLSL ES 3.0

* cleanup

* update example

* update

* add throw
sunag hai 1 ano
pai
achega
9272367d11
Modificáronse 35 ficheiros con 2264 adicións e 82 borrados
  1. 34 1
      examples/jsm/nodes/core/NodeBuilder.js
  2. 6 0
      examples/jsm/renderers/common/Backend.js
  3. 340 0
      examples/jsm/renderers/webgl-legacy/nodes/GLSL1NodeBuilder.js
  4. 0 0
      examples/jsm/renderers/webgl-legacy/nodes/SlotNode.js
  5. 0 0
      examples/jsm/renderers/webgl-legacy/nodes/WebGLNodeBuilder.js
  6. 0 0
      examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js
  7. 607 0
      examples/jsm/renderers/webgl/WebGLBackend.js
  8. 118 17
      examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js
  9. 84 0
      examples/jsm/renderers/webgl/utils/WebGLAttributeUtils.js
  10. 26 0
      examples/jsm/renderers/webgl/utils/WebGLExtensions.js
  11. 529 0
      examples/jsm/renderers/webgl/utils/WebGLState.js
  12. 199 0
      examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js
  13. 242 0
      examples/jsm/renderers/webgl/utils/WebGLUtils.js
  14. 18 2
      examples/jsm/renderers/webgpu/WebGPURenderer.js
  15. 4 35
      examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js
  16. BIN=BIN
      examples/screenshots/webgl_materials_lightmap.jpg
  17. 1 1
      examples/webgl_materials_lightmap.html
  18. 1 1
      examples/webgl_nodes_loader_gltf_iridescence.html
  19. 1 1
      examples/webgl_nodes_loader_gltf_sheen.html
  20. 1 1
      examples/webgl_nodes_loader_gltf_transmission.html
  21. 1 1
      examples/webgl_nodes_loader_materialx.html
  22. 1 1
      examples/webgl_nodes_materials_instance_uniform.html
  23. 1 1
      examples/webgl_nodes_materials_physical_clearcoat.html
  24. 1 1
      examples/webgl_nodes_materials_standard.html
  25. 1 1
      examples/webgl_nodes_materialx_noise.html
  26. 1 1
      examples/webgl_nodes_points.html
  27. 4 2
      examples/webgpu_cubemap_adjustments.html
  28. 4 2
      examples/webgpu_cubemap_mix.html
  29. 4 2
      examples/webgpu_equirectangular.html
  30. 4 2
      examples/webgpu_instance_uniform.html
  31. 4 2
      examples/webgpu_lights_custom.html
  32. 4 2
      examples/webgpu_lights_phong.html
  33. 4 2
      examples/webgpu_lights_selective.html
  34. 18 2
      examples/webgpu_tsl_editor.html
  35. 1 1
      playground/index.html

+ 34 - 1
examples/jsm/nodes/core/NodeBuilder.js

@@ -8,11 +8,18 @@ import NodeCache from './NodeCache.js';
 import { createNodeMaterialFromType } from '../materials/NodeMaterial.js';
 import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js';
 
-import { REVISION, NoColorSpace, LinearEncoding, sRGBEncoding, SRGBColorSpace, Color, Vector2, Vector3, Vector4, Float16BufferAttribute } from 'three';
+import {
+	FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
+	ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
+} from '../../renderers/common/nodes/NodeUniform.js';
+
+import { REVISION, RenderTarget, NoColorSpace, LinearEncoding, sRGBEncoding, SRGBColorSpace, Color, Vector2, Vector3, Vector4, Float16BufferAttribute } from 'three';
 
 import { stack } from './StackNode.js';
 import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
 
+import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js';
+
 const typeFromLength = new Map( [
 	[ 2, 'vec2' ],
 	[ 3, 'vec3' ],
@@ -98,6 +105,18 @@ class NodeBuilder {
 
 	}
 
+	getRenderTarget( width, height, options ) {
+
+		return new RenderTarget( width, height, options );
+
+	}
+
+	getCubeRenderTarget( size, options ) {
+
+		return new CubeRenderTarget( size, options );
+
+	}
+
 	includes( node ) {
 
 		return this.nodes.includes( node );
@@ -968,6 +987,20 @@ class NodeBuilder {
 
 	}
 
+	getNodeUniform( uniformNode, type ) {
+
+		if ( type === 'float' ) return new FloatNodeUniform( uniformNode );
+		if ( type === 'vec2' ) return new Vector2NodeUniform( uniformNode );
+		if ( type === 'vec3' ) return new Vector3NodeUniform( uniformNode );
+		if ( type === 'vec4' ) return new Vector4NodeUniform( uniformNode );
+		if ( type === 'color' ) return new ColorNodeUniform( uniformNode );
+		if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode );
+		if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode );
+
+		throw new Error( `Uniform "${type}" not declared.` );
+
+	}
+
 	createNodeMaterial( type ) {
 
 		return createNodeMaterialFromType( type );

+ 6 - 0
examples/jsm/renderers/common/Backend.js

@@ -133,6 +133,12 @@ class Backend {
 
 	// resource properties
 
+	set( object, value ) {
+
+		this.data.set( object, value );
+
+	}
+
 	get( object ) {
 
 		let map = this.data.get( object );

+ 340 - 0
examples/jsm/renderers/webgl-legacy/nodes/GLSL1NodeBuilder.js

@@ -0,0 +1,340 @@
+import { MathNode, GLSLNodeParser, NodeBuilder, NodeMaterial } from '../../../nodes/Nodes.js';
+
+const glslMethods = {
+	[ MathNode.ATAN2 ]: 'atan'
+};
+
+const precisionLib = {
+	low: 'lowp',
+	medium: 'mediump',
+	high: 'highp'
+};
+
+class GLSL1NodeBuilder extends NodeBuilder {
+
+	constructor( object, renderer, scene = null ) {
+
+		super( object, renderer, new GLSLNodeParser(), scene );
+
+	}
+
+	getMethod( method ) {
+
+		return glslMethods[ method ] || method;
+
+	}
+
+	getTexture( texture, textureProperty, uvSnippet ) {
+
+		if ( texture.isTextureCube ) {
+
+			return `textureCube( ${textureProperty}, ${uvSnippet} )`;
+
+		} else {
+
+			return `texture2D( ${textureProperty}, ${uvSnippet} )`;
+
+		}
+
+	}
+
+	getTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) {
+
+		if ( this.material.extensions !== undefined ) this.material.extensions.shaderTextureLOD = true;
+
+		return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
+
+	}
+
+	getVars( shaderStage ) {
+
+		const snippets = [];
+
+		const vars = this.vars[ shaderStage ];
+
+		for ( const variable of vars ) {
+
+			snippets.push( `${ this.getVar( variable.type, variable.name ) };` );
+
+		}
+
+		return snippets.join( '\n\t' );
+
+	}
+
+	getUniforms( shaderStage ) {
+
+		const uniforms = this.uniforms[ shaderStage ];
+
+		let output = '';
+
+		for ( const uniform of uniforms ) {
+
+			let snippet = null;
+
+			if ( uniform.type === 'texture' ) {
+
+				snippet = `sampler2D ${uniform.name};\n`;
+
+			} else if ( uniform.type === 'cubeTexture' ) {
+
+				snippet = `samplerCube ${uniform.name};\n`;
+
+			} else {
+
+				const vectorType = this.getVectorType( uniform.type );
+
+				snippet = `${vectorType} ${uniform.name};\n`;
+
+			}
+
+			const precision = uniform.node.precision;
+
+			if ( precision !== null ) {
+
+				snippet = 'uniform ' + precisionLib[ precision ] + ' ' + snippet;
+
+			} else {
+
+				snippet = 'uniform ' + snippet;
+
+			}
+
+			output += snippet;
+
+		}
+
+		return output;
+
+	}
+
+	getAttributes( shaderStage ) {
+
+		let snippet = '';
+
+		if ( shaderStage === 'vertex' ) {
+
+			const attributes = this.attributes;
+
+			for ( const attribute of attributes ) {
+
+				snippet += `attribute ${attribute.type} ${attribute.name};\n`;
+
+			}
+
+		}
+
+		return snippet;
+
+	}
+
+	getVaryings( shaderStage ) {
+
+		let snippet = '';
+
+		const varyings = this.varyings;
+
+		if ( shaderStage === 'vertex' ) {
+
+			for ( const varying of varyings ) {
+
+				snippet += `${varying.needsInterpolation ? 'varying' : '/*varying*/'} ${varying.type} ${varying.name};\n`;
+
+			}
+
+		} else if ( shaderStage === 'fragment' ) {
+
+			for ( const varying of varyings ) {
+
+				if ( varying.needsInterpolation ) {
+
+					snippet += `varying ${varying.type} ${varying.name};\n`;
+
+				}
+
+			}
+
+		}
+
+		return snippet;
+
+	}
+
+	getVertexIndex() {
+
+		return 'gl_VertexID';
+
+	}
+
+	getFrontFacing() {
+
+		return 'gl_FrontFacing';
+
+	}
+
+	getFragCoord() {
+
+		return 'gl_FragCoord';
+
+	}
+
+	isFlipY() {
+
+		return true;
+
+	}
+
+	_getGLSLVertexCode( shaderData ) {
+
+		return `${ this.getSignature() }
+
+// uniforms
+${shaderData.uniforms}
+
+// varyings
+${shaderData.varyings}
+
+// attributes
+${shaderData.attributes}
+
+// codes
+${shaderData.codes}
+
+void main() {
+
+	// vars
+	${shaderData.vars}
+
+	// flow
+	${shaderData.flow}
+
+}
+`;
+
+	}
+
+	_getGLSLFragmentCode( shaderData ) {
+
+		return `${ this.getSignature() }
+
+// precision
+precision highp float;
+precision highp int;
+
+// uniforms
+${shaderData.uniforms}
+
+// varyings
+${shaderData.varyings}
+
+// codes
+${shaderData.codes}
+
+void main() {
+
+	// vars
+	${shaderData.vars}
+
+	// flow
+	${shaderData.flow}
+
+}
+`;
+
+	}
+
+	buildCode() {
+
+		const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} };
+
+		for ( const shaderStage in shadersData ) {
+
+			let flow = '// code\n\n';
+			flow += this.flowCode[ shaderStage ];
+
+			const flowNodes = this.flowNodes[ shaderStage ];
+			const mainNode = flowNodes[ flowNodes.length - 1 ];
+
+			for ( const node of flowNodes ) {
+
+				const flowSlotData = this.getFlowData( node/*, shaderStage*/ );
+				const slotName = node.name;
+
+				if ( slotName ) {
+
+					if ( flow.length > 0 ) flow += '\n';
+
+					flow += `\t// flow -> ${ slotName }\n\t`;
+
+				}
+
+				flow += `${ flowSlotData.code }\n\t`;
+
+				if ( node === mainNode && shaderStage !== 'compute' ) {
+
+					flow += '// result\n\t';
+
+					if ( shaderStage === 'vertex' ) {
+
+						flow += 'gl_Position = ';
+
+					} else if ( shaderStage === 'fragment' ) {
+
+						flow += 'gl_FragColor = ';
+
+					}
+
+					flow += `${ flowSlotData.result };`;
+
+				}
+
+			}
+
+			const stageData = shadersData[ shaderStage ];
+
+			stageData.uniforms = this.getUniforms( shaderStage );
+			stageData.attributes = this.getAttributes( shaderStage );
+			stageData.varyings = this.getVaryings( shaderStage );
+			stageData.vars = this.getVars( shaderStage );
+			stageData.codes = this.getCodes( shaderStage );
+			stageData.flow = flow;
+
+		}
+
+		if ( this.material !== null ) {
+
+			this.vertexShader = this._getGLSLVertexCode( shadersData.vertex );
+			this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment );
+
+		} else {
+
+			console.warn( 'GLSLNodeBuilder: compute shaders are not supported.' );
+			//this.computeShader = this._getGLSLComputeCode( shadersData.compute );
+
+		}
+
+	}
+
+	build() {
+
+		// @TODO: Move this code to super.build()
+
+		const { object, material } = this;
+
+		if ( material !== null ) {
+
+			NodeMaterial.fromMaterial( material ).build( this );
+
+		} else {
+
+			this.addFlow( 'compute', object );
+
+		}
+
+		return super.build();
+
+	}
+
+}
+
+export default GLSL1NodeBuilder;

+ 0 - 0
examples/jsm/renderers/webgl/nodes/SlotNode.js → examples/jsm/renderers/webgl-legacy/nodes/SlotNode.js


+ 0 - 0
examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js → examples/jsm/renderers/webgl-legacy/nodes/WebGLNodeBuilder.js


+ 0 - 0
examples/jsm/renderers/webgl/nodes/WebGLNodes.js → examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js


+ 607 - 0
examples/jsm/renderers/webgl/WebGLBackend.js

@@ -0,0 +1,607 @@
+import { WebGLCoordinateSystem } from 'three';
+
+import GLSLNodeBuilder from './nodes/GLSLNodeBuilder.js';
+import Backend from '../common/Backend.js';
+
+import WebGLAttributeUtils from './utils/WebGLAttributeUtils.js';
+import WebGLState from './utils/WebGLState.js';
+import WebGLUtils from './utils/WebGLUtils.js';
+import WebGLTextureUtils from './utils/WebGLTextureUtils.js';
+import WebGLExtensions from './utils/WebGLExtensions.js';
+
+//
+
+class WebGLBackend extends Backend {
+
+	constructor( parameters = {} ) {
+
+		super( parameters );
+
+	}
+
+	async init( renderer ) {
+
+		await super.init( renderer );
+
+		//
+
+		const parameters = this.parameters;
+
+		const glContext = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgl2' );
+
+		this.gl = glContext;
+
+		this.extensions = new WebGLExtensions( this );
+		this.attributeUtils = new WebGLAttributeUtils( this );
+		this.textureUtils = new WebGLTextureUtils( this );
+		this.state = new WebGLState( this );
+		this.utils = new WebGLUtils( this );
+		this.defaultTextures = {};
+
+		this.extensions.get( 'EXT_color_buffer_float' );
+
+	}
+
+	get coordinateSystem() {
+
+		return WebGLCoordinateSystem;
+
+	}
+
+	beginRender( renderContext ) {
+
+		const { gl } = this;
+
+		//
+
+		let clear = 0;
+
+		if ( renderContext.clearColor ) clear |= gl.COLOR_BUFFER_BIT;
+		if ( renderContext.clearDepth ) clear |= gl.DEPTH_BUFFER_BIT;
+		if ( renderContext.clearStencil ) clear |= gl.STENCIL_BUFFER_BIT;
+
+		const clearColor = renderContext.clearColorValue;
+
+		gl.clearColor( clearColor.x, clearColor.y, clearColor.z, clearColor.a );
+		gl.clear( clear );
+
+		//
+
+		if ( renderContext.viewport ) {
+
+			this.updateViewport( renderContext );
+
+		} else {
+
+			gl.viewport( 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight );
+
+		}
+
+	}
+
+	finishRender( /*renderContext*/ ) {
+
+		//console.warn( 'Abstract class.' );
+
+	}
+
+	updateViewport( renderContext ) {
+
+		const gl = this.gl;
+		const { x, y, width, height } = renderContext.viewportValue;
+
+		gl.viewport( x, y, width, height );
+
+	}
+
+	clear( /*renderContext, color, depth, stencil*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	beginCompute( /*computeGroup*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	compute( /*computeGroup, computeNode, bindings, pipeline*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	finishCompute( /*computeGroup*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	draw( renderObject, info ) {
+
+		const { pipeline, material } = renderObject;
+		const { programGPU, vaoGPU } = this.get( pipeline );
+
+		const { gl, state } = this;
+
+		//
+
+		const bindings = renderObject.getBindings();
+
+		for ( const binding of bindings ) {
+
+			const bindingData = this.get( binding );
+			const index = bindingData.index;
+
+			if ( binding.isUniformsGroup ) {
+
+				gl.bindBufferBase( gl.UNIFORM_BUFFER, index, bindingData.bufferGPU );
+
+			} else if ( binding.isSampledTexture ) {
+
+				gl.activeTexture( gl.TEXTURE0 + index );
+				gl.bindTexture( bindingData.glTextureType, bindingData.textureGPU );
+
+			}
+
+		}
+
+		state.setMaterial( material );
+
+		gl.useProgram( programGPU );
+		gl.bindVertexArray( vaoGPU );
+
+		//
+
+		const index = renderObject.getIndex();
+
+		const object = renderObject.object;
+		const geometry = renderObject.geometry;
+		const drawRange = geometry.drawRange;
+		const firstVertex = drawRange.start;
+
+		//
+
+		let mode;
+		if ( object.isPoints ) mode = gl.POINTS;
+		else if ( object.isLine ) mode = gl.LINES;
+		else if ( object.isLineLoop ) mode = gl.LINE_LOOP;
+		else if ( object.isLineSegments ) mode = gl.LINES;
+		else mode = gl.TRIANGLES;
+
+		//
+
+
+		if ( index !== null ) {
+
+			const indexData = this.get( index );
+
+			const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count;
+
+			gl.drawElements( mode, index.count, indexData.type, firstVertex );
+
+			info.update( object, indexCount, 1 );
+
+		} else {
+
+			const positionAttribute = geometry.attributes.position;
+			const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : positionAttribute.count;
+
+
+			gl.drawArrays( mode, 0, vertexCount );
+			//gl.drawArrays( mode, vertexCount, gl.UNSIGNED_SHORT, firstVertex );
+
+			info.update( object, vertexCount, 1 );
+
+		}
+
+
+
+		//
+
+		gl.bindVertexArray( null );
+
+	}
+
+	needsUpdate( renderObject ) {
+
+		return false;
+
+	}
+
+	getCacheKey( renderObject ) {
+
+		return '';
+
+	}
+
+	// textures
+
+	createSampler( /*texture*/ ) {
+
+		//console.warn( 'Abstract class.' );
+
+	}
+
+	destroySampler( /*texture*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	createDefaultTexture( texture ) {
+
+		const { gl, textureUtils, defaultTextures } = this;
+
+		const glTextureType = textureUtils.getGLTextureType( texture );
+
+		let textureGPU = defaultTextures[ glTextureType ];
+
+		if ( textureGPU === undefined ) {
+
+			textureGPU = gl.createTexture();
+
+			gl.bindTexture( glTextureType, textureGPU );
+			gl.texParameteri( glTextureType, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
+			gl.texParameteri( glTextureType, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
+
+			//gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
+
+			defaultTextures[ glTextureType ] = textureGPU;
+
+		}
+
+		this.set( texture, {
+			textureGPU,
+			glTextureType,
+			isDefault: true
+		} );
+
+	}
+
+	createTexture( texture, options ) {
+
+		const { gl, utils, textureUtils } = this;
+		const { levels, width, height } = options;
+
+		const glFormat = utils.convert( texture.format, texture.colorSpace );
+		const glType = utils.convert( texture.type );
+		const glInternalFormat = textureUtils.getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture );
+
+		const textureGPU = gl.createTexture();
+		const glTextureType = textureUtils.getGLTextureType( texture );
+
+		gl.bindTexture( glTextureType, textureGPU );
+
+		gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
+		gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
+		gl.pixelStorei( gl.UNPACK_ALIGNMENT, texture.unpackAlignment );
+		gl.pixelStorei( gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE );
+
+		textureUtils.setTextureParameters( glTextureType, texture );
+
+		gl.bindTexture( glTextureType, textureGPU );
+		gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height );
+
+		this.set( texture, {
+			textureGPU,
+			glTextureType,
+			glFormat,
+			glType,
+			glInternalFormat
+		} );
+
+	}
+
+	updateTexture( texture, options ) {
+
+		const { gl } = this;
+		const { width, height } = options;
+		const { textureGPU, glTextureType, glFormat, glType } = this.get( texture );
+
+		const getImage = ( source ) => {
+
+			if ( source.isDataTexture ) {
+
+				return source.image.data;
+
+			}
+
+			return source;
+
+		};
+
+		gl.bindTexture( glTextureType, textureGPU );
+
+		if ( texture.isCubeTexture ) {
+
+			const images = options.images;
+
+			for ( let i = 0; i < 6; i ++ ) {
+
+				const image = getImage( images[ i ] );
+
+				gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, width, height, glFormat, glType, image );
+
+			}
+
+		} else {
+
+			const image = getImage( options.image );
+
+			gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, image );
+
+		}
+
+	}
+
+	generateMipmaps( texture ) {
+
+		const { gl } = this;
+		const { textureGPU, glTextureType } = this.get( texture );
+
+		gl.bindTexture( glTextureType, textureGPU );
+		gl.generateMipmap( glTextureType );
+
+	}
+
+	destroyTexture( /*texture*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	copyTextureToBuffer( /*texture, x, y, width, height*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	// node builder
+
+	createNodeBuilder( object, renderer, scene = null ) {
+
+		return new GLSLNodeBuilder( object, renderer, scene );
+
+	}
+
+	// program
+
+	createProgram( program ) {
+
+		const gl = this.gl;
+		const { stage, code } = program;
+
+		const shader = stage === 'vertex' ? gl.createShader( gl.VERTEX_SHADER ) : gl.createShader( gl.FRAGMENT_SHADER );
+
+		gl.shaderSource( shader, code );
+		gl.compileShader( shader );
+
+		if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) {
+
+			console.error( 'THREE.WebGLBackend:', gl.getShaderInfoLog( shader ) );
+
+		}
+
+		this.set( program, {
+			shaderGPU: shader
+		} );
+
+	}
+
+	destroyProgram( /*program*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	createRenderPipeline( renderObject ) {
+
+		const gl = this.gl;
+		const pipeline = renderObject.pipeline;
+
+		// Program
+
+		const { fragmentProgram, vertexProgram } = pipeline;
+
+		const programGPU = gl.createProgram();
+		gl.attachShader( programGPU, this.get( fragmentProgram ).shaderGPU );
+		gl.attachShader( programGPU, this.get( vertexProgram ).shaderGPU );
+		gl.linkProgram( programGPU );
+
+		if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) {
+
+			console.error( 'THREE.WebGLBackend:', gl.getProgramInfoLog( programGPU ) );
+
+		}
+
+		gl.useProgram( programGPU );
+
+		// Bindings
+
+		const bindings = renderObject.getBindings();
+
+		for ( const binding of bindings ) {
+
+			const bindingData = this.get( binding );
+			const index = bindingData.index;
+
+			if ( binding.isUniformsGroup ) {
+
+				const location = gl.getUniformBlockIndex( programGPU, binding.name );
+				gl.uniformBlockBinding( programGPU, location, index );
+
+			} else if ( binding.isSampledTexture ) {
+
+				const location = gl.getUniformLocation( programGPU, binding.name );
+				gl.uniform1i( location, index );
+
+			}
+
+		}
+
+		// VAO
+
+		const vaoGPU = gl.createVertexArray();
+
+		const index = renderObject.getIndex();
+		const vertexBuffers = renderObject.getVertexBuffers();
+
+		gl.bindVertexArray( vaoGPU );
+
+		if ( index !== null ) {
+
+			const indexData = this.get( index );
+
+			gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexData.bufferGPU );
+
+		}
+
+		for ( let i = 0; i < vertexBuffers.length; i ++ ) {
+
+			const attribute = vertexBuffers[ i ];
+			const attributeData = this.get( attribute );
+
+			gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU );
+			gl.enableVertexAttribArray( i );
+			gl.vertexAttribPointer( i, attribute.itemSize, attributeData.type, false, 0, 0 );
+
+		}
+
+		gl.bindVertexArray( null );
+
+		//
+
+		this.set( pipeline, {
+			programGPU,
+			vaoGPU
+		} );
+
+	}
+
+	createComputePipeline( /*computePipeline, bindings*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	createBindings( bindings ) {
+
+		this.updateBindings( bindings );
+
+	}
+
+	updateBindings( bindings ) {
+
+		const { gl } = this;
+
+		let groupIndex = 0;
+		let textureIndex = 0;
+
+		for ( const binding of bindings ) {
+
+			if ( binding.isUniformsGroup ) {
+
+				const bufferGPU = gl.createBuffer();
+				const data = binding.buffer;
+
+				gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
+				gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW );
+				gl.bindBufferBase( gl.UNIFORM_BUFFER, groupIndex, bufferGPU );
+
+				this.set( binding, {
+					index: groupIndex ++,
+					bufferGPU
+				} );
+
+			} else if ( binding.isSampledTexture ) {
+
+				const { textureGPU, glTextureType } = this.get( binding.texture );
+
+				this.set( binding, {
+					index: textureIndex ++,
+					textureGPU,
+					glTextureType
+				} );
+
+			}
+
+		}
+
+	}
+
+	updateBinding( binding ) {
+
+		const gl = this.gl;
+
+		if ( binding.isUniformsGroup ) {
+
+			const bindingData = this.get( binding );
+			const bufferGPU = bindingData.bufferGPU;
+			const data = binding.buffer;
+
+			gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
+			gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW );
+
+		}
+
+	}
+
+	// attributes
+
+	createIndexAttribute( attribute ) {
+
+		const gl = this.gl;
+
+		this.attributeUtils.createAttribute( attribute, gl.ELEMENT_ARRAY_BUFFER );
+
+	}
+
+	createAttribute( attribute ) {
+
+		const gl = this.gl;
+
+		this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER );
+
+	}
+
+	createStorageAttribute( /*attribute*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	updateAttribute( /*attribute*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	destroyAttribute( /*attribute*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+	updateSize() {
+
+		//console.warn( 'Abstract class.' );
+
+	}
+
+	hasFeature( name ) {
+
+		return true;
+
+	}
+
+	copyFramebufferToTexture( /*texture, renderContext*/ ) {
+
+		console.warn( 'Abstract class.' );
+
+	}
+
+}
+
+export default WebGLBackend;

+ 118 - 17
examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js

@@ -1,4 +1,7 @@
-import { MathNode, GLSLNodeParser, NodeBuilder, NodeMaterial } from 'three/nodes';
+import { MathNode, GLSLNodeParser, NodeBuilder, NodeMaterial } from '../../../nodes/Nodes.js';
+
+import UniformsGroup from '../../common/UniformsGroup.js';
+import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
 
 const glslMethods = {
 	[ MathNode.ATAN2 ]: 'atan'
@@ -16,6 +19,8 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 		super( object, renderer, new GLSLNodeParser(), scene );
 
+		this.uniformsGroup = {};
+
 	}
 
 	getMethod( method ) {
@@ -32,15 +37,13 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 		} else {
 
-			return `texture2D( ${textureProperty}, ${uvSnippet} )`;
+			return `texture( ${textureProperty}, ${uvSnippet} )`;
 
 		}
 
 	}
 
-	getTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) {
-
-		if ( this.material.extensions !== undefined ) this.material.extensions.shaderTextureLOD = true;
+	getTextureLevel( texture, textureProperty, uvSnippet, biasSnippet ) {
 
 		return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
 
@@ -66,25 +69,29 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 		const uniforms = this.uniforms[ shaderStage ];
 
-		let output = '';
+		const bindingSnippets = [];
+		const groupSnippets = [];
 
 		for ( const uniform of uniforms ) {
 
 			let snippet = null;
+			let group = false;
 
 			if ( uniform.type === 'texture' ) {
 
-				snippet = `sampler2D ${uniform.name};\n`;
+				snippet = `sampler2D ${uniform.name};`;
 
 			} else if ( uniform.type === 'cubeTexture' ) {
 
-				snippet = `samplerCube ${uniform.name};\n`;
+				snippet = `samplerCube ${uniform.name};`;
 
 			} else {
 
 				const vectorType = this.getVectorType( uniform.type );
 
-				snippet = `${vectorType} ${uniform.name};\n`;
+				snippet = `${vectorType} ${uniform.name};`;
+
+				group = true;
 
 			}
 
@@ -92,18 +99,36 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 			if ( precision !== null ) {
 
-				snippet = 'uniform ' + precisionLib[ precision ] + ' ' + snippet;
+				snippet = precisionLib[ precision ] + ' ' + snippet;
+
+			}
+
+			if ( group ) {
+
+				snippet = '\t' + snippet;
+
+				groupSnippets.push( snippet );
 
 			} else {
 
 				snippet = 'uniform ' + snippet;
 
+				bindingSnippets.push( snippet );
+
 			}
 
-			output += snippet;
+		}
+
+		let output = '';
+
+		if ( groupSnippets.length > 0 ) {
+
+			output += this._getGLSLUniformStruct( shaderStage + 'NodeUniforms', groupSnippets.join( '\n' ) ) + '\n';
 
 		}
 
+		output += bindingSnippets.join( '\n' );
+
 		return output;
 
 	}
@@ -116,9 +141,11 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 			const attributes = this.attributes;
 
+			let location = 0;
+
 			for ( const attribute of attributes ) {
 
-				snippet += `attribute ${attribute.type} ${attribute.name};\n`;
+				snippet += `layout( location = ${ location ++ } ) in ${ attribute.type } ${ attribute.name };\n`;
 
 			}
 
@@ -138,7 +165,7 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 			for ( const varying of varyings ) {
 
-				snippet += `${varying.needsInterpolation ? 'varying' : '/*varying*/'} ${varying.type} ${varying.name};\n`;
+				snippet += `${varying.needsInterpolation ? 'out' : '/*out*/'} ${varying.type} ${varying.name};\n`;
 
 			}
 
@@ -148,7 +175,7 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 				if ( varying.needsInterpolation ) {
 
-					snippet += `varying ${varying.type} ${varying.name};\n`;
+					snippet += `in ${varying.type} ${varying.name};\n`;
 
 				}
 
@@ -184,9 +211,24 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 	}
 
+	_getGLSLUniformStruct( name, vars ) {
+
+		return `
+layout( std140 ) uniform ${name} {
+${vars}
+};`;
+
+	}
+
 	_getGLSLVertexCode( shaderData ) {
 
-		return `${ this.getSignature() }
+		return `#version 300 es
+
+${ this.getSignature() }
+
+// precision
+precision highp float;
+precision highp int;
 
 // uniforms
 ${shaderData.uniforms}
@@ -208,6 +250,8 @@ void main() {
 	// flow
 	${shaderData.flow}
 
+	gl_PointSize = 1.0;
+
 }
 `;
 
@@ -215,7 +259,9 @@ void main() {
 
 	_getGLSLFragmentCode( shaderData ) {
 
-		return `${ this.getSignature() }
+		return `#version 300 es
+
+${ this.getSignature() }
 
 // precision
 precision highp float;
@@ -230,6 +276,8 @@ ${shaderData.varyings}
 // codes
 ${shaderData.codes}
 
+layout( location = 0 ) out vec4 fragColor;
+
 void main() {
 
 	// vars
@@ -280,7 +328,7 @@ void main() {
 
 					} else if ( shaderStage === 'fragment' ) {
 
-						flow += 'gl_FragColor = ';
+						flow += 'fragColor = ';
 
 					}
 
@@ -306,6 +354,9 @@ void main() {
 			this.vertexShader = this._getGLSLVertexCode( shadersData.vertex );
 			this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment );
 
+			//console.log( this.vertexShader );
+			//console.log( this.fragmentShader );
+
 		} else {
 
 			console.warn( 'GLSLNodeBuilder: compute shaders are not supported.' );
@@ -315,6 +366,56 @@ void main() {
 
 	}
 
+	getUniformFromNode( node, type, shaderStage, name = null ) {
+
+		const uniformNode = super.getUniformFromNode( node, type, shaderStage, name );
+		const nodeData = this.getDataFromNode( node, shaderStage );
+
+		let uniformGPU = nodeData.uniformGPU;
+
+		if ( uniformGPU === undefined ) {
+
+			if ( type === 'texture' ) {
+
+				uniformGPU = new NodeSampledTexture( uniformNode.name, uniformNode.node );
+
+				this.bindings[ shaderStage ].push( uniformGPU );
+
+			} else if ( type === 'cubeTexture' ) {
+
+				uniformGPU = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node );
+
+				this.bindings[ shaderStage ].push( uniformGPU );
+
+			} else {
+
+				let uniformsGroup = this.uniformsGroup[ shaderStage ];
+
+				if ( uniformsGroup === undefined ) {
+
+					uniformsGroup = new UniformsGroup( shaderStage + 'NodeUniforms' );
+					//uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
+
+					this.uniformsGroup[ shaderStage ] = uniformsGroup;
+
+					this.bindings[ shaderStage ].push( uniformsGroup );
+
+				}
+
+				uniformGPU = this.getNodeUniform( uniformNode, type );
+
+				uniformsGroup.addUniform( uniformGPU );
+
+			}
+
+			nodeData.uniformGPU = uniformGPU;
+
+		}
+
+		return uniformNode;
+
+	}
+
 	build() {
 
 		// @TODO: Move this code to super.build()

+ 84 - 0
examples/jsm/renderers/webgl/utils/WebGLAttributeUtils.js

@@ -0,0 +1,84 @@
+class WebGLAttributeUtils {
+
+	constructor( backend ) {
+
+		this.backend = backend;
+
+	}
+
+	createAttribute( attribute, bufferType ) {
+
+		const backend = this.backend;
+		const { gl } = backend;
+
+		const array = attribute.array;
+		const usage = attribute.usage || gl.STATIC_DRAW;
+
+		const bufferGPU = gl.createBuffer();
+
+		gl.bindBuffer( bufferType, bufferGPU );
+		gl.bufferData( bufferType, array, usage );
+		gl.bindBuffer( bufferType, null );
+
+		//attribute.onUploadCallback();
+
+		let type;
+
+		if ( array instanceof Float32Array ) {
+
+			type = gl.FLOAT;
+
+		} else if ( array instanceof Uint16Array ) {
+
+			if ( attribute.isFloat16BufferAttribute ) {
+
+				type = gl.HALF_FLOAT;
+
+			} else {
+
+				type = gl.UNSIGNED_SHORT;
+
+			}
+
+		} else if ( array instanceof Int16Array ) {
+
+			type = gl.SHORT;
+
+		} else if ( array instanceof Uint32Array ) {
+
+			type = gl.UNSIGNED_INT;
+
+		} else if ( array instanceof Int32Array ) {
+
+			type = gl.INT;
+
+		} else if ( array instanceof Int8Array ) {
+
+			type = gl.BYTE;
+
+		} else if ( array instanceof Uint8Array ) {
+
+			type = gl.UNSIGNED_BYTE;
+
+		} else if ( array instanceof Uint8ClampedArray ) {
+
+			type = gl.UNSIGNED_BYTE;
+
+		} else {
+
+			throw new Error( 'THREE.WebGLBackend: Unsupported buffer data format: ' + array );
+
+		}
+
+		backend.set( attribute, {
+			bufferGPU,
+			type,
+			bytesPerElement: array.BYTES_PER_ELEMENT,
+			version: attribute.version
+		} );
+
+	}
+
+}
+
+export default WebGLAttributeUtils;

+ 26 - 0
examples/jsm/renderers/webgl/utils/WebGLExtensions.js

@@ -0,0 +1,26 @@
+class WebGLExtensions {
+
+	constructor( backend ) {
+
+		this.backend = backend;
+
+		this.gl = this.backend.gl;
+		this.availableExtensions = this.gl.getSupportedExtensions();
+
+	}
+
+	get( name ) {
+
+		return this.gl.getExtension( name );
+
+	}
+
+	has( name ) {
+
+		return this.availableExtensions.includes( name );
+
+	}
+
+}
+
+export default WebGLExtensions;

+ 529 - 0
examples/jsm/renderers/webgl/utils/WebGLState.js

@@ -0,0 +1,529 @@
+import {
+	CullFaceNone, CullFaceBack, CullFaceFront, DoubleSide, BackSide,
+	NormalBlending, NoBlending, CustomBlending, AddEquation,
+	AdditiveBlending, SubtractiveBlending, MultiplyBlending, SubtractEquation, ReverseSubtractEquation,
+	ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor,
+	OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor,
+	NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth
+} from 'three';
+
+let initialized = false, equationToGL, factorToGL;
+
+class WebGLState {
+
+	constructor( backend ) {
+
+		this.backend = backend;
+
+		this.gl = this.backend.gl;
+
+		this.enabled = {};
+		this.currentFlipSided = null;
+		this.currentCullFace = null;
+		this.currentProgram = null;
+		this.currentBlendingEnabled = null;
+		this.currentBlending = null;
+		this.currentBlendSrc = null;
+		this.currentBlendDst = null;
+		this.currentBlendSrcAlpha = null;
+		this.currentBlendDstAlpha = null;
+		this.currentPremultipledAlpha = null;
+		this.currentPolygonOffsetFactor = null;
+		this.currentPolygonOffsetUnits = null;
+		this.currentDepthFunc = null;
+		this.currentDepthMask = null;
+		this.currentStencilFunc = null;
+		this.currentStencilRef = null;
+		this.currentStencilFuncMask = null;
+		this.currentStencilFail = null;
+		this.currentStencilZFail = null;
+		this.currentStencilZPass = null;
+		this.currentStencilMask = null;
+
+		if ( initialized === false ) {
+
+			this._init( this.gl );
+
+			initialized = true;
+
+		}
+
+	}
+
+	_init( gl ) {
+
+		// Store only WebGL constants here.
+
+		equationToGL = {
+			[ AddEquation ]: gl.FUNC_ADD,
+			[ SubtractEquation ]: gl.FUNC_SUBTRACT,
+			[ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT
+		};
+
+		factorToGL = {
+			[ ZeroFactor ]: gl.ZERO,
+			[ OneFactor ]: gl.ONE,
+			[ SrcColorFactor ]: gl.SRC_COLOR,
+			[ SrcAlphaFactor ]: gl.SRC_ALPHA,
+			[ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE,
+			[ DstColorFactor ]: gl.DST_COLOR,
+			[ DstAlphaFactor ]: gl.DST_ALPHA,
+			[ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR,
+			[ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA,
+			[ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR,
+			[ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA
+		};
+
+	}
+
+	enable( id ) {
+
+		const { enabled } = this;
+
+		if ( enabled[ id ] !== true ) {
+
+			this.gl.enable( id );
+			enabled[ id ] = true;
+
+		}
+
+	}
+
+	disable( id ) {
+
+		const { enabled } = this;
+
+		if ( enabled[ id ] !== false ) {
+
+			this.gl.disable( id );
+			enabled[ id ] = false;
+
+		}
+
+	}
+
+	setFlipSided( flipSided ) {
+
+		if ( this.currentFlipSided !== flipSided ) {
+
+			const { gl } = this;
+
+			if ( flipSided ) {
+
+				gl.frontFace( gl.CW );
+
+			} else {
+
+				gl.frontFace( gl.CCW );
+
+			}
+
+			this.currentFlipSided = flipSided;
+
+		}
+
+	}
+
+	setCullFace( cullFace ) {
+
+		const { gl } = this;
+
+		if ( cullFace !== CullFaceNone ) {
+
+			this.enable( gl.CULL_FACE );
+
+			if ( cullFace !== this.currentCullFace ) {
+
+				if ( cullFace === CullFaceBack ) {
+
+					gl.cullFace( gl.BACK );
+
+				} else if ( cullFace === CullFaceFront ) {
+
+					gl.cullFace( gl.FRONT );
+
+				} else {
+
+					gl.cullFace( gl.FRONT_AND_BACK );
+
+				}
+
+			}
+
+		} else {
+
+			this.disable( gl.CULL_FACE );
+
+		}
+
+		this.currentCullFace = cullFace;
+
+	}
+
+	setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
+
+		const { gl } = this;
+
+		if ( blending === NoBlending ) {
+
+			if ( this.currentBlendingEnabled === true ) {
+
+				this.disable( gl.BLEND );
+				this.currentBlendingEnabled = false;
+
+			}
+
+			return;
+
+		}
+
+		if ( this.currentBlendingEnabled === false ) {
+
+			this.enable( gl.BLEND );
+			this.currentBlendingEnabled = true;
+
+		}
+
+		if ( blending !== CustomBlending ) {
+
+			if ( blending !== this.currentBlending || premultipliedAlpha !== this.currentPremultipledAlpha ) {
+
+				if ( this.currentBlendEquation !== AddEquation || this.currentBlendEquationAlpha !== AddEquation ) {
+
+					gl.blendEquation( gl.FUNC_ADD );
+
+					this.currentBlendEquation = AddEquation;
+					this.currentBlendEquationAlpha = AddEquation;
+
+				}
+
+				if ( premultipliedAlpha ) {
+
+					switch ( blending ) {
+
+						case NormalBlending:
+							gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
+							break;
+
+						case AdditiveBlending:
+							gl.blendFunc( gl.ONE, gl.ONE );
+							break;
+
+						case SubtractiveBlending:
+							gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE );
+							break;
+
+						case MultiplyBlending:
+							gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA );
+							break;
+
+						default:
+							console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+							break;
+
+					}
+
+				} else {
+
+					switch ( blending ) {
+
+						case NormalBlending:
+							gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
+							break;
+
+						case AdditiveBlending:
+							gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
+							break;
+
+						case SubtractiveBlending:
+							gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE );
+							break;
+
+						case MultiplyBlending:
+							gl.blendFunc( gl.ZERO, gl.SRC_COLOR );
+							break;
+
+						default:
+							console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+							break;
+
+					}
+
+				}
+
+				this.currentBlendSrc = null;
+				this.currentBlendDst = null;
+				this.currentBlendSrcAlpha = null;
+				this.currentBlendDstAlpha = null;
+
+				this.currentBlending = blending;
+				this.currentPremultipledAlpha = premultipliedAlpha;
+
+			}
+
+			return;
+
+		}
+
+		// custom blending
+
+		blendEquationAlpha = blendEquationAlpha || blendEquation;
+		blendSrcAlpha = blendSrcAlpha || blendSrc;
+		blendDstAlpha = blendDstAlpha || blendDst;
+
+		if ( blendEquation !== this.currentBlendEquation || blendEquationAlpha !== this.currentBlendEquationAlpha ) {
+
+			gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
+
+			this.currentBlendEquation = blendEquation;
+			this.currentBlendEquationAlpha = blendEquationAlpha;
+
+		}
+
+		if ( blendSrc !== this.currentBlendSrc || blendDst !== this.currentBlendDst || blendSrcAlpha !== this.currentBlendSrcAlpha || blendDstAlpha !== this.currentBlendDstAlpha ) {
+
+			gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
+
+			this.currentBlendSrc = blendSrc;
+			this.currentBlendDst = blendDst;
+			this.currentBlendSrcAlpha = blendSrcAlpha;
+			this.currentBlendDstAlpha = blendDstAlpha;
+
+		}
+
+		this.currentBlending = blending;
+		this.currentPremultipledAlpha = false;
+
+	}
+
+	setDepthTest( depthTest ) {
+
+		const { gl } = this;
+
+		if ( depthTest ) {
+
+			this.enable( gl.DEPTH_TEST );
+
+		} else {
+
+			this.disable( gl.DEPTH_TEST );
+
+		}
+
+	}
+
+	setDepthMask( depthMask ) {
+
+		if ( this.currentDepthMask !== depthMask ) {
+
+			this.gl.depthMask( depthMask );
+			this.currentDepthMask = depthMask;
+
+		}
+
+	}
+
+	setDepthFunc( depthFunc ) {
+
+		if ( this.currentDepthFunc !== depthFunc ) {
+
+			const { gl } = this;
+
+			switch ( depthFunc ) {
+
+				case NeverDepth:
+
+					gl.depthFunc( gl.NEVER );
+					break;
+
+				case AlwaysDepth:
+
+					gl.depthFunc( gl.ALWAYS );
+					break;
+
+				case LessDepth:
+
+					gl.depthFunc( gl.LESS );
+					break;
+
+				case LessEqualDepth:
+
+					gl.depthFunc( gl.LEQUAL );
+					break;
+
+				case EqualDepth:
+
+					gl.depthFunc( gl.EQUAL );
+					break;
+
+				case GreaterEqualDepth:
+
+					gl.depthFunc( gl.GEQUAL );
+					break;
+
+				case GreaterDepth:
+
+					gl.depthFunc( gl.GREATER );
+					break;
+
+				case NotEqualDepth:
+
+					gl.depthFunc( gl.NOTEQUAL );
+					break;
+
+				default:
+
+					gl.depthFunc( gl.LEQUAL );
+
+			}
+
+			this.currentDepthFunc = depthFunc;
+
+		}
+
+	}
+
+	setStencilTest( stencilTest ) {
+
+		const { gl } = this;
+
+		if ( stencilTest ) {
+
+			this.enable( gl.STENCIL_TEST );
+
+		} else {
+
+			this.disable( gl.STENCIL_TEST );
+
+		}
+
+	}
+
+	setStencilMask( stencilMask ) {
+
+		if ( this.currentStencilMask !== stencilMask ) {
+
+			this.gl.stencilMask( stencilMask );
+			this.currentStencilMask = stencilMask;
+
+		}
+
+	}
+
+	setStencilFunc( stencilFunc, stencilRef, stencilMask ) {
+
+		if ( this.currentStencilFunc !== stencilFunc ||
+			 this.currentStencilRef !== stencilRef ||
+			 this.currentStencilFuncMask !== stencilMask ) {
+
+			this.gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
+
+			this.currentStencilFunc = stencilFunc;
+			this.currentStencilRef = stencilRef;
+			this.currentStencilFuncMask = stencilMask;
+
+		}
+
+	}
+
+	setStencilOp( stencilFail, stencilZFail, stencilZPass ) {
+
+		if ( this.currentStencilFail !== stencilFail ||
+			 this.currentStencilZFail !== stencilZFail ||
+			 this.currentStencilZPass !== stencilZPass ) {
+
+			this.gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
+
+			this.currentStencilFail = stencilFail;
+			this.currentStencilZFail = stencilZFail;
+			this.currentStencilZPass = stencilZPass;
+
+		}
+
+	}
+
+	setMaterial( material, frontFaceCW ) {
+
+		const { gl } = this;
+
+		material.side === DoubleSide
+			? this.disable( gl.CULL_FACE )
+			: this.enable( gl.CULL_FACE );
+
+		let flipSided = ( material.side === BackSide );
+		if ( frontFaceCW ) flipSided = ! flipSided;
+
+		this.setFlipSided( flipSided );
+
+		( material.blending === NormalBlending && material.transparent === false )
+			? this.setBlending( NoBlending )
+			: this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
+
+		this.setDepthFunc( material.depthFunc );
+		this.setDepthTest( material.depthTest );
+		this.setDepthMask( material.depthWrite );
+		this.setDepthMask( material.colorWrite );
+
+		const stencilWrite = material.stencilWrite;
+		this.setStencilTest( stencilWrite );
+		if ( stencilWrite ) {
+
+			this.setStencilMask( material.stencilWriteMask );
+			this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
+			this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
+
+		}
+
+		this.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+		material.alphaToCoverage === true
+			? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE )
+			: this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE );
+
+	}
+
+	setPolygonOffset( polygonOffset, factor, units ) {
+
+		const { gl } = this;
+
+		if ( polygonOffset ) {
+
+			this.enable( gl.POLYGON_OFFSET_FILL );
+
+			if ( this.currentPolygonOffsetFactor !== factor || this.currentPolygonOffsetUnits !== units ) {
+
+				gl.polygonOffset( factor, units );
+
+				this.currentPolygonOffsetFactor = factor;
+				this.currentPolygonOffsetUnits = units;
+
+			}
+
+		} else {
+
+			this.disable( gl.POLYGON_OFFSET_FILL );
+
+		}
+
+	}
+
+	useProgram( program ) {
+
+		if ( this.currentProgram !== program ) {
+
+			this.gl.useProgram( program );
+
+			this.currentProgram = program;
+
+			return true;
+
+		}
+
+		return false;
+
+	}
+
+
+}
+
+export default WebGLState;

+ 199 - 0
examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js

@@ -0,0 +1,199 @@
+import { LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, RGBAFormat, DepthFormat, DepthStencilFormat, UnsignedShortType, UnsignedIntType, UnsignedInt248Type, FloatType, HalfFloatType, MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, UnsignedByteType, _SRGBAFormat, NoColorSpace, LinearSRGBColorSpace, SRGBColorSpace, NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare } from 'three';
+
+let initialized = false, wrappingToGL, filterToGL, compareToGL;
+
+class WebGLTextureUtils {
+
+	constructor( backend ) {
+
+		this.backend = backend;
+
+		this.gl = backend.gl;
+		this.extensions = backend.extensions;
+
+		if ( initialized === false ) {
+
+			this._init( this.gl );
+
+			initialized = true;
+
+		}
+
+	}
+
+	_init( gl ) {
+
+		// Store only WebGL constants here.
+
+		wrappingToGL = {
+			[ RepeatWrapping ]: gl.REPEAT,
+			[ ClampToEdgeWrapping ]: gl.CLAMP_TO_EDGE,
+			[ MirroredRepeatWrapping ]: gl.MIRRORED_REPEAT
+		};
+
+		filterToGL = {
+			[ NearestFilter ]: gl.NEAREST,
+			[ NearestMipmapNearestFilter ]: gl.NEAREST_MIPMAP_NEAREST,
+			[ NearestMipmapLinearFilter ]: gl.NEAREST_MIPMAP_LINEAR,
+
+			[ LinearFilter ]: gl.LINEAR,
+			[ LinearMipmapNearestFilter ]: gl.LINEAR_MIPMAP_NEAREST,
+			[ LinearMipmapLinearFilter ]: gl.LINEAR_MIPMAP_LINEAR
+		};
+
+		compareToGL = {
+			[ NeverCompare ]: gl.NEVER,
+			[ AlwaysCompare ]: gl.ALWAYS,
+			[ LessCompare ]: gl.LESS,
+			[ LessEqualCompare ]: gl.LEQUAL,
+			[ EqualCompare ]: gl.EQUAL,
+			[ GreaterEqualCompare ]: gl.GEQUAL,
+			[ GreaterCompare ]: gl.GREATER,
+			[ NotEqualCompare ]: gl.NOTEQUAL
+		};
+
+	}
+
+	filterFallback( f ) {
+
+		const { gl } = this;
+
+		if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
+
+			return gl.NEAREST;
+
+		}
+
+		return gl.LINEAR;
+
+	}
+
+	getGLTextureType( texture ) {
+
+		const { gl } = this;
+
+		let glTextureType;
+
+		if ( texture.isCubeTexture === true ) {
+
+			glTextureType = gl.TEXTURE_CUBE_MAP;
+
+		} else {
+
+			glTextureType = gl.TEXTURE_2D;
+
+
+		}
+
+		return glTextureType;
+
+	}
+
+	getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) {
+
+		const { gl, extensions } = this;
+
+		if ( internalFormatName !== null ) {
+
+			if ( gl[ internalFormatName ] !== undefined ) return gl[ internalFormatName ];
+
+			console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
+
+		}
+
+		let internalFormat = glFormat;
+
+		if ( glFormat === gl.RED ) {
+
+			if ( glType === gl.FLOAT ) internalFormat = gl.R32F;
+			if ( glType === gl.HALF_FLOAT ) internalFormat = gl.R16F;
+			if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8;
+
+		}
+
+		if ( glFormat === gl.RED_INTEGER ) {
+
+			if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8UI;
+			if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16UI;
+			if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI;
+			if ( glType === gl.BYTE ) internalFormat = gl.R8I;
+			if ( glType === gl.SHORT ) internalFormat = gl.R16I;
+			if ( glType === gl.INT ) internalFormat = gl.R32I;
+
+		}
+
+		if ( glFormat === gl.RG ) {
+
+			if ( glType === gl.FLOAT ) internalFormat = gl.RG32F;
+			if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RG16F;
+			if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8;
+
+		}
+
+		if ( glFormat === gl.RGBA ) {
+
+			if ( glType === gl.FLOAT ) internalFormat = gl.RGBA32F;
+			if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGBA16F;
+			if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( colorSpace === SRGBColorSpace && forceLinearTransfer === false ) ? gl.SRGB8_ALPHA8 : gl.RGBA8;
+			if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGBA4;
+			if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1;
+
+		}
+
+		if ( internalFormat === gl.R16F || internalFormat === gl.R32F ||
+			internalFormat === gl.RG16F || internalFormat === gl.RG32F ||
+			internalFormat === gl.RGBA16F || internalFormat === gl.RGBA32F ) {
+
+			extensions.get( 'EXT_color_buffer_float' );
+
+		}
+
+		return internalFormat;
+
+	}
+
+	setTextureParameters( textureType, texture ) {
+
+		const { gl, extensions } = this;
+
+		gl.texParameteri( textureType, gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] );
+		gl.texParameteri( textureType, gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] );
+
+		if ( textureType === gl.TEXTURE_3D || textureType === gl.TEXTURE_2D_ARRAY ) {
+
+			gl.texParameteri( textureType, gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] );
+
+		}
+
+		gl.texParameteri( textureType, gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] );
+		gl.texParameteri( textureType, gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] );
+
+		if ( texture.compareFunction ) {
+
+			gl.texParameteri( textureType, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE );
+			gl.texParameteri( textureType, gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] );
+
+		}
+
+		if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
+
+			extension = extensions.get( 'EXT_texture_filter_anisotropic' );
+
+			if ( texture.magFilter === NearestFilter ) return;
+			if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return;
+			if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2
+
+			if ( texture.anisotropy > 1 /*|| properties.get( texture ).__currentAnisotropy*/ ) {
+
+				//gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );
+				//properties.get( texture ).__currentAnisotropy = texture.anisotropy;
+
+			}
+
+		}
+
+	}
+
+}
+
+export default WebGLTextureUtils;

+ 242 - 0
examples/jsm/renderers/webgl/utils/WebGLUtils.js

@@ -0,0 +1,242 @@
+import { RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT5_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT1_Format, RGB_S3TC_DXT1_Format, DepthFormat, DepthStencilFormat, LuminanceAlphaFormat, LuminanceFormat, RedFormat, RGBAFormat, AlphaFormat, RedIntegerFormat, RGFormat, RGIntegerFormat, RGBAIntegerFormat, HalfFloatType, FloatType, UnsignedIntType, IntType, UnsignedShortType, ShortType, ByteType, UnsignedInt248Type, UnsignedShort5551Type, UnsignedShort4444Type, UnsignedByteType, RGBA_BPTC_Format, _SRGBAFormat, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, SRGBColorSpace, NoColorSpace } from 'three';
+
+class WebGLUtils {
+
+	constructor( backend ) {
+
+		this.backend = backend;
+
+		this.gl = this.backend.gl;
+		this.extensions = backend.extensions;
+
+	}
+
+	convert( p, colorSpace = NoColorSpace ) {
+
+		const { gl, extensions } = this;
+
+		let extension;
+
+		if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE;
+		if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4;
+		if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1;
+
+		if ( p === ByteType ) return gl.BYTE;
+		if ( p === ShortType ) return gl.SHORT;
+		if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT;
+		if ( p === IntType ) return gl.INT;
+		if ( p === UnsignedIntType ) return gl.UNSIGNED_INT;
+		if ( p === FloatType ) return gl.FLOAT;
+
+		if ( p === HalfFloatType ) {
+
+			return gl.HALF_FLOAT;
+
+		}
+
+		if ( p === AlphaFormat ) return gl.ALPHA;
+		if ( p === RGBAFormat ) return gl.RGBA;
+		if ( p === LuminanceFormat ) return gl.LUMINANCE;
+		if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA;
+		if ( p === DepthFormat ) return gl.DEPTH_COMPONENT;
+		if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL;
+
+		// WebGL2 formats.
+
+		if ( p === RedFormat ) return gl.RED;
+		if ( p === RedIntegerFormat ) return gl.RED_INTEGER;
+		if ( p === RGFormat ) return gl.RG;
+		if ( p === RGIntegerFormat ) return gl.RG_INTEGER;
+		if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER;
+
+		// S3TC
+
+		if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {
+
+			if ( colorSpace === SRGBColorSpace ) {
+
+				extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' );
+
+				if ( extension !== null ) {
+
+					if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT;
+					if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
+					if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
+					if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+
+				} else {
+
+					return null;
+
+				}
+
+			} else {
+
+				extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
+
+				if ( extension !== null ) {
+
+					if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
+					if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+					if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+					if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+				} else {
+
+					return null;
+
+				}
+
+			}
+
+		}
+
+		// PVRTC
+
+		if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
+
+			extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
+
+			if ( extension !== null ) {
+
+				if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+				if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+				if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+				if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+
+			} else {
+
+				return null;
+
+			}
+
+		}
+
+		// ETC1
+
+		if ( p === RGB_ETC1_Format ) {
+
+			extension = extensions.get( 'WEBGL_compressed_texture_etc1' );
+
+			if ( extension !== null ) {
+
+				return extension.COMPRESSED_RGB_ETC1_WEBGL;
+
+			} else {
+
+				return null;
+
+			}
+
+		}
+
+		// ETC2
+
+		if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
+
+			extension = extensions.get( 'WEBGL_compressed_texture_etc' );
+
+			if ( extension !== null ) {
+
+				if ( p === RGB_ETC2_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2;
+				if ( p === RGBA_ETC2_EAC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC;
+
+			} else {
+
+				return null;
+
+			}
+
+		}
+
+		// ASTC
+
+		if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||
+			p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||
+			p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||
+			p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||
+			p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) {
+
+			extension = extensions.get( 'WEBGL_compressed_texture_astc' );
+
+			if ( extension !== null ) {
+
+				if ( p === RGBA_ASTC_4x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR;
+				if ( p === RGBA_ASTC_5x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR;
+				if ( p === RGBA_ASTC_5x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR;
+				if ( p === RGBA_ASTC_6x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR;
+				if ( p === RGBA_ASTC_6x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR;
+				if ( p === RGBA_ASTC_8x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR;
+				if ( p === RGBA_ASTC_8x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR;
+				if ( p === RGBA_ASTC_8x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR;
+				if ( p === RGBA_ASTC_10x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR;
+				if ( p === RGBA_ASTC_10x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR;
+				if ( p === RGBA_ASTC_10x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR;
+				if ( p === RGBA_ASTC_10x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR;
+				if ( p === RGBA_ASTC_12x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR;
+				if ( p === RGBA_ASTC_12x12_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR;
+
+			} else {
+
+				return null;
+
+			}
+
+		}
+
+		// BPTC
+
+		if ( p === RGBA_BPTC_Format ) {
+
+			extension = extensions.get( 'EXT_texture_compression_bptc' );
+
+			if ( extension !== null ) {
+
+				if ( p === RGBA_BPTC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT;
+
+			} else {
+
+				return null;
+
+			}
+
+		}
+
+		// RGTC
+
+		if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) {
+
+			extension = extensions.get( 'EXT_texture_compression_rgtc' );
+
+			if ( extension !== null ) {
+
+				if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT;
+				if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT;
+				if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT;
+				if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT;
+
+			} else {
+
+				return null;
+
+			}
+
+		}
+
+		//
+
+		if ( p === UnsignedInt248Type ) {
+
+			return gl.UNSIGNED_INT_24_8;
+
+		}
+
+		// if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats)
+
+		return ( gl[ p ] !== undefined ) ? gl[ p ] : null;
+
+	}
+
+}
+
+export default WebGLUtils;

+ 18 - 2
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -1,5 +1,7 @@
 import Renderer from '../common/Renderer.js';
+import WebGLBackend from '../webgl/WebGLBackend.js';
 import WebGPUBackend from './WebGPUBackend.js';
+import WebGPU from '../../capabilities/WebGPU.js';
 /*
 const debugHandler = {
 
@@ -18,9 +20,23 @@ class WebGPURenderer extends Renderer {
 
 	constructor( parameters = {} ) {
 
-		const backend = new WebGPUBackend( parameters );
-		//const backend = new Proxy( new WebGPUBackend( parameters ), debugHandler );
+		let BackendClass;
 
+		if ( WebGPU.isAvailable() ) {
+
+			BackendClass = WebGPUBackend;
+
+		} else {
+
+			BackendClass = WebGLBackend;
+
+			console.warn( 'THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.' );
+
+		}
+
+		const backend = new BackendClass( parameters );
+
+		//super( new Proxy( backend, debugHandler ) );
 		super( backend );
 
 		this.isWebGPURenderer = true;

+ 4 - 35
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -1,10 +1,7 @@
-import { RenderTarget, NoColorSpace, FloatType } from 'three';
+import { NoColorSpace, FloatType } from 'three';
 
 import UniformsGroup from '../../common/UniformsGroup.js';
-import {
-	FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
-	ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
-} from '../../common/nodes/NodeUniform.js';
+
 import NodeSampler from '../../common/nodes/NodeSampler.js';
 import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
 
@@ -12,8 +9,6 @@ import UniformBuffer from '../../common/UniformBuffer.js';
 import StorageBuffer from '../../common/StorageBuffer.js';
 import { getVectorLength, getStrideLength } from '../../common/BufferUtils.js';
 
-import CubeRenderTarget from '../../common/CubeRenderTarget.js';
-
 import { NodeBuilder, CodeNode, NodeMaterial } from '../../../nodes/Nodes.js';
 
 import WGSLNodeParser from './WGSLNodeParser.js';
@@ -367,7 +362,7 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 					for ( const uniformNode of node.nodes ) {
 
-						const uniformNodeGPU = this._getNodeUniform( uniformNode, type );
+						const uniformNodeGPU = this.getNodeUniform( uniformNode, type );
 
 						// fit bounds to buffer
 						uniformNodeGPU.boundary = getVectorLength( uniformNodeGPU.itemSize );
@@ -381,7 +376,7 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 				} else {
 
-					uniformGPU = this._getNodeUniform( uniformNode, type );
+					uniformGPU = this.getNodeUniform( uniformNode, type );
 
 					uniformsGroup.addUniform( uniformGPU );
 
@@ -752,18 +747,6 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 	}
 
-	getRenderTarget( width, height, options ) {
-
-		return new RenderTarget( width, height, options );
-
-	}
-
-	getCubeRenderTarget( size, options ) {
-
-		return new CubeRenderTarget( size, options );
-
-	}
-
 	getMethod( method ) {
 
 		if ( wgslPolyfill[ method ] !== undefined ) {
@@ -794,20 +777,6 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 	}
 
-	_getNodeUniform( uniformNode, type ) {
-
-		if ( type === 'float' ) return new FloatNodeUniform( uniformNode );
-		if ( type === 'vec2' ) return new Vector2NodeUniform( uniformNode );
-		if ( type === 'vec3' ) return new Vector3NodeUniform( uniformNode );
-		if ( type === 'vec4' ) return new Vector4NodeUniform( uniformNode );
-		if ( type === 'color' ) return new ColorNodeUniform( uniformNode );
-		if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode );
-		if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode );
-
-		throw new Error( `Uniform "${type}" not declared.` );
-
-	}
-
 	_getWGSLVertexCode( shaderData ) {
 
 		return `${ this.getSignature() }

BIN=BIN
examples/screenshots/webgl_materials_lightmap.jpg


+ 1 - 1
examples/webgl_materials_lightmap.html

@@ -32,7 +32,7 @@
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
 			import { MeshBasicNodeMaterial, vec4, color, positionLocal, mix } from 'three/nodes';
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			let container, stats;
 			let camera, scene, renderer;

+ 1 - 1
examples/webgl_nodes_loader_gltf_iridescence.html

@@ -33,7 +33,7 @@
 			import * as THREE from 'three';
 			import { NodeMaterial, uv, vec2, checker, float, timerLocal } from 'three/nodes';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

+ 1 - 1
examples/webgl_nodes_loader_gltf_sheen.html

@@ -37,7 +37,7 @@
 			import * as THREE from 'three';
 			import { NodeMaterial, uv, mix, color, checker } from 'three/nodes';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

+ 1 - 1
examples/webgl_nodes_loader_gltf_transmission.html

@@ -33,7 +33,7 @@
 			import * as THREE from 'three';
 			import { NodeMaterial, float, texture } from 'three/nodes';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

+ 1 - 1
examples/webgl_nodes_loader_materialx.html

@@ -40,7 +40,7 @@
 			import { RGBELoader } from './jsm/loaders/RGBELoader.js';
 			import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
 
-			import { nodeFrame } from './jsm/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from './jsm/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			const SAMPLE_PATH = 'https://raw.githubusercontent.com/materialx/MaterialX/main/resources/Materials/Examples/StandardSurface/';
 

+ 1 - 1
examples/webgl_nodes_materials_instance_uniform.html

@@ -34,7 +34,7 @@
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			class InstanceUniformNode extends Node {
 

+ 1 - 1
examples/webgl_nodes_materials_physical_clearcoat.html

@@ -30,7 +30,7 @@
 			import * as THREE from 'three';
 			import { color, float, vec2, texture, normalMap, uv, MeshPhysicalNodeMaterial } from 'three/nodes';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			import Stats from 'three/addons/libs/stats.module.js';
 

+ 1 - 1
examples/webgl_nodes_materials_standard.html

@@ -39,7 +39,7 @@
 			import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
 			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			let container, stats;
 

+ 1 - 1
examples/webgl_nodes_materialx_noise.html

@@ -30,7 +30,7 @@
 			import * as THREE from 'three';
 			import { MeshPhysicalNodeMaterial, normalWorld, timerLocal, mx_noise_vec3, mx_worley_noise_vec3, mx_cell_noise_float, mx_fractal_noise_vec3 } from 'three/nodes';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			import Stats from 'three/addons/libs/stats.module.js';
 

+ 1 - 1
examples/webgl_nodes_points.html

@@ -39,7 +39,7 @@
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			let camera, scene, renderer, stats;
 

+ 4 - 2
examples/webgpu_cubemap_adjustments.html

@@ -34,6 +34,8 @@
 			import { uniform, mix, cubeTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector, toneMapping } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
@@ -49,11 +51,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 

+ 4 - 2
examples/webgpu_cubemap_mix.html

@@ -34,6 +34,8 @@
 			import { mix, oscSine, timerLocal, cubeTexture, float, toneMapping } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
@@ -47,11 +49,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 

+ 4 - 2
examples/webgpu_equirectangular.html

@@ -32,6 +32,8 @@
 			import { texture, equirectUV } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -43,11 +45,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 

+ 4 - 2
examples/webgpu_instance_uniform.html

@@ -30,6 +30,8 @@
 			import { MeshStandardNodeMaterial, NodeUpdateType, Node, nodeObject, uniform, attribute, cubeTexture } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -79,11 +81,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 

+ 4 - 2
examples/webgpu_lights_custom.html

@@ -29,6 +29,8 @@
 			import { color, lights, toneMapping, MeshStandardNodeMaterial, PointsNodeMaterial, LightingModel } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -51,11 +53,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 

+ 4 - 2
examples/webgpu_lights_phong.html

@@ -36,6 +36,8 @@
 			import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			let camera, scene, renderer,
@@ -46,11 +48,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 

+ 4 - 2
examples/webgpu_lights_selective.html

@@ -38,6 +38,8 @@
 			import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			let camera, scene, renderer,
@@ -48,11 +50,11 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
 
 					document.body.appendChild( WebGPU.getErrorMessage() );
 
-					throw new Error( 'No WebGPU support' );
+					throw new Error( 'No WebGPU or WebGL2 support' );
 
 				}
 

+ 18 - 2
examples/webgpu_tsl_editor.html

@@ -60,6 +60,7 @@
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 			import WGSLNodeBuilder from 'three/addons/renderers/webgpu/nodes/WGSLNodeBuilder.js';
 			import GLSLNodeBuilder from 'three/addons/renderers/webgl/nodes/GLSLNodeBuilder.js';
+			import GLSL1NodeBuilder from 'three/addons/renderers/webgl-legacy/nodes/GLSL1NodeBuilder.js';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
@@ -134,6 +135,7 @@ const { texture, uniform, vec2, vec4, uv, oscSine, timerLocal } = TSL;
 const samplerTexture = new THREE.TextureLoader().load( './textures/uv_grid_opengl.jpg' );
 samplerTexture.wrapS = THREE.RepeatWrapping;
 //samplerTexture.wrapT = THREE.RepeatWrapping;
+//samplerTexture.colorSpace = THREE.SRGBColorSpace;
 
 const timer = timerLocal( .5 ); // .5 is speed
 const uv0 = uv();
@@ -183,7 +185,21 @@ output = vec4( finalColor, opacity );
 							mesh.material.outputNode = nodes.output;
 							mesh.material.needsUpdate = true;
 
-							const NodeBuilder = options.output === 'WGSL' ? WGSLNodeBuilder : GLSLNodeBuilder;
+							let NodeBuilder;
+
+							if ( options.output === 'WGSL' ) {
+
+								NodeBuilder = WGSLNodeBuilder;
+
+							} else if ( options.output === 'GLSL ES 3.0' ) {
+
+								NodeBuilder = GLSLNodeBuilder;
+
+							} else {
+
+								NodeBuilder = GLSL1NodeBuilder;
+
+							}
 
 							nodeBuilder = new NodeBuilder( mesh, renderer );
 							nodeBuilder.build();
@@ -221,7 +237,7 @@ output = vec4( finalColor, opacity );
 
 					const gui = new GUI();
 
-					gui.add( options, 'output', [ 'GLSL', 'WGSL' ] ).onChange( build );
+					gui.add( options, 'output', [ 'WGSL', 'GLSL ES 3.0', 'GLSL' ] ).onChange( build );
 					gui.add( options, 'shader', [ 'vertex', 'fragment' ] ).onChange( showCode );
 
 					gui.add( options, 'outputColorSpace', [ THREE.LinearSRGBColorSpace, THREE.SRGBColorSpace ] ).onChange( ( value ) => {

+ 1 - 1
playground/index.html

@@ -70,7 +70,7 @@
 
 			import * as THREE from 'three';
 
-			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';
+			import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';