浏览代码

WebGPURenderer: Improve vertex format support and revisions. (#25924)

* EnvironmentNode: Fix flipY after improve positionWorldDirection

* WebGPUBackground: Fix clip background

* WebGPURenderer: Added update from https://github.com/mrdoob/three.js/pull/25913

* WebGPURenderer: Improve vertex format support.

* Example webgpu_backdrop: Fix space

* Added webgpu_loader_gltf_compressed example

* cleanup
sunag 2 年之前
父节点
当前提交
6a68319102

+ 1 - 0
examples/files.json

@@ -340,6 +340,7 @@
 		"webgpu_lights_phong",
 		"webgpu_lights_phong",
 		"webgpu_lights_selective",
 		"webgpu_lights_selective",
 		"webgpu_loader_gltf",
 		"webgpu_loader_gltf",
+		"webgpu_loader_gltf_compressed",
 		"webgpu_materials",
 		"webgpu_materials",
 		"webgpu_materials_video",
 		"webgpu_materials_video",
 		"webgpu_particles",
 		"webgpu_particles",

+ 8 - 5
examples/jsm/nodes/core/AttributeNode.js

@@ -30,7 +30,7 @@ class AttributeNode extends Node {
 
 
 				const attribute = builder.geometry.getAttribute( attributeName );
 				const attribute = builder.geometry.getAttribute( attributeName );
 
 
-				nodeType = builder.getTypeFromLength( attribute.itemSize );
+				nodeType = builder.getTypeFromAttribute( attribute );
 
 
 			} else {
 			} else {
 
 
@@ -66,23 +66,26 @@ class AttributeNode extends Node {
 
 
 		if ( geometryAttribute === true ) {
 		if ( geometryAttribute === true ) {
 
 
-			const nodeAttribute = builder.getAttribute( attributeName, nodeType );
+			const attribute = builder.geometry.getAttribute( attributeName );
+			const attributeType = builder.getTypeFromAttribute( attribute );
+
+			const nodeAttribute = builder.getAttribute( attributeName, attributeType );
 
 
 			if ( builder.isShaderStage( 'vertex' ) ) {
 			if ( builder.isShaderStage( 'vertex' ) ) {
 
 
-				return nodeAttribute.name;
+				return builder.format( nodeAttribute.name, attributeType, nodeType );
 
 
 			} else {
 			} else {
 
 
 				const nodeVarying = varying( this );
 				const nodeVarying = varying( this );
 
 
-				return nodeVarying.build( builder, nodeAttribute.type );
+				return nodeVarying.build( builder, nodeType );
 
 
 			}
 			}
 
 
 		} else {
 		} else {
 
 
-			console.warn( `Attribute "${ attributeName }" not found.` );
+			console.warn( `AttributeNode: Attribute "${ attributeName }" not found.` );
 
 
 			return builder.getConst( nodeType );
 			return builder.getConst( nodeType );
 
 

+ 50 - 9
examples/jsm/nodes/core/NodeBuilder.js

@@ -7,17 +7,28 @@ import NodeKeywords from './NodeKeywords.js';
 import NodeCache from './NodeCache.js';
 import NodeCache from './NodeCache.js';
 import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js';
 import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js';
 
 
-import { REVISION, NoColorSpace, LinearEncoding, sRGBEncoding, SRGBColorSpace, Color, Vector2, Vector3, Vector4 } from 'three';
+import { REVISION, NoColorSpace, LinearEncoding, sRGBEncoding, SRGBColorSpace, Color, Vector2, Vector3, Vector4, Float16BufferAttribute } from 'three';
 
 
 import { stack } from './StackNode.js';
 import { stack } from './StackNode.js';
 import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
 import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
 
 
-const typeFromLength = new Map();
-typeFromLength.set( 2, 'vec2' );
-typeFromLength.set( 3, 'vec3' );
-typeFromLength.set( 4, 'vec4' );
-typeFromLength.set( 9, 'mat3' );
-typeFromLength.set( 16, 'mat4' );
+const typeFromLength = new Map( [
+	[ 2, 'vec2' ],
+	[ 3, 'vec3' ],
+	[ 4, 'vec4' ],
+	[ 9, 'mat3' ],
+	[ 16, 'mat4' ]
+] );
+
+const typeFromArray = new Map( [
+	[ Int8Array, 'int' ],
+	[ Int16Array, 'int' ],
+	[ Int32Array, 'int' ],
+	[ Uint8Array, 'uint' ],
+	[ Uint16Array, 'uint' ],
+	[ Uint32Array, 'uint' ],
+	[ Float32Array, 'float' ]
+] );
 
 
 const toFloat = ( value ) => {
 const toFloat = ( value ) => {
 
 
@@ -412,12 +423,42 @@ class NodeBuilder {
 	getTypeFromLength( length, componentType = 'float' ) {
 	getTypeFromLength( length, componentType = 'float' ) {
 
 
 		if ( length === 1 ) return componentType;
 		if ( length === 1 ) return componentType;
+
 		const baseType = typeFromLength.get( length );
 		const baseType = typeFromLength.get( length );
 		const prefix = componentType === 'float' ? '' : componentType[ 0 ];
 		const prefix = componentType === 'float' ? '' : componentType[ 0 ];
+
 		return prefix + baseType;
 		return prefix + baseType;
 
 
 	}
 	}
 
 
+	getTypeFromArray( array ) {
+
+		return typeFromArray.get( array.constructor );
+
+	}
+
+	getTypeFromAttribute( attribute ) {
+
+		let dataAttribute = attribute;
+
+		if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data;
+
+		const array = dataAttribute.array;
+		const itemSize = dataAttribute.stride || attribute.itemSize;
+		const normalized = attribute.normalized;
+
+		let arrayType;
+
+		if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) {
+
+			arrayType = this.getTypeFromArray( array );
+
+		}
+
+		return this.getTypeFromLength( itemSize, arrayType );
+
+	}
+
 	getTypeLength( type ) {
 	getTypeLength( type ) {
 
 
 		const vecType = this.getVectorType( type );
 		const vecType = this.getVectorType( type );
@@ -677,7 +718,7 @@ class NodeBuilder {
 
 
 		if ( propertyName !== null ) {
 		if ( propertyName !== null ) {
 
 
-			flowData.code += `${propertyName} = ${flowData.result};\n` + this.tab;
+			flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`;
 
 
 		}
 		}
 
 
@@ -864,7 +905,7 @@ class NodeBuilder {
 
 
 		if ( fromTypeLength > toTypeLength ) {
 		if ( fromTypeLength > toTypeLength ) {
 
 
-			return this.format( `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`, this.getTypeFromLength( toTypeLength ), toType );
+			return this.format( `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType );
 
 
 		}
 		}
 
 

+ 0 - 1
examples/jsm/nodes/lighting/EnvironmentNode.js

@@ -53,7 +53,6 @@ class EnvironmentNode extends LightingNode {
 						// @TODO: Needed PMREM
 						// @TODO: Needed PMREM
 
 
 						radianceTextureUVNode = equirectUV( reflectVec );
 						radianceTextureUVNode = equirectUV( reflectVec );
-						radianceTextureUVNode = vec2( radianceTextureUVNode.x, radianceTextureUVNode.y.oneMinus() );
 
 
 					}
 					}
 
 

+ 11 - 3
examples/jsm/renderers/webgpu/WebGPUAttributes.js

@@ -9,7 +9,7 @@ class WebGPUAttributes {
 
 
 	get( attribute ) {
 	get( attribute ) {
 
 
-		if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+		attribute = this._getAttribute( attribute );
 
 
 		return this.buffers.get( attribute );
 		return this.buffers.get( attribute );
 
 
@@ -17,7 +17,7 @@ class WebGPUAttributes {
 
 
 	remove( attribute ) {
 	remove( attribute ) {
 
 
-		if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+		attribute = this._getAttribute( attribute );
 
 
 		const data = this.buffers.get( attribute );
 		const data = this.buffers.get( attribute );
 
 
@@ -33,7 +33,7 @@ class WebGPUAttributes {
 
 
 	update( attribute, isIndex = false, usage = null ) {
 	update( attribute, isIndex = false, usage = null ) {
 
 
-		if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+		attribute = this._getAttribute( attribute );
 
 
 		let data = this.buffers.get( attribute );
 		let data = this.buffers.get( attribute );
 
 
@@ -115,6 +115,14 @@ class WebGPUAttributes {
 
 
 	}
 	}
 
 
+	_getAttribute( attribute ) {
+
+		if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+
+		return attribute;
+
+	}
+
 	_createBuffer( attribute, usage ) {
 	_createBuffer( attribute, usage ) {
 
 
 		const array = attribute.array;
 		const array = attribute.array;

+ 4 - 1
examples/jsm/renderers/webgpu/WebGPUBackground.js

@@ -69,13 +69,16 @@ class WebGPUBackground {
 				nodeMaterial.side = BackSide;
 				nodeMaterial.side = BackSide;
 				nodeMaterial.depthTest = false;
 				nodeMaterial.depthTest = false;
 				nodeMaterial.depthWrite = false;
 				nodeMaterial.depthWrite = false;
+				nodeMaterial.frustumCulled = false;
 				nodeMaterial.fog = false;
 				nodeMaterial.fog = false;
 
 
 				this.boxMesh = boxMesh = new Mesh( new BoxGeometry( 1, 1, 1 ), nodeMaterial );
 				this.boxMesh = boxMesh = new Mesh( new BoxGeometry( 1, 1, 1 ), nodeMaterial );
 
 
 				boxMesh.onBeforeRender = function ( renderer, scene, camera ) {
 				boxMesh.onBeforeRender = function ( renderer, scene, camera ) {
 
 
-					this.matrixWorld.copyPosition( camera.matrixWorld );
+					const scale = camera.far;
+
+					this.matrixWorld.makeScale( scale, scale, scale ).copyPosition( camera.matrixWorld );
 
 
 				};
 				};
 
 

+ 44 - 104
examples/jsm/renderers/webgpu/WebGPURenderPipeline.js

@@ -1,5 +1,6 @@
-import { GPUIndexFormat, GPUCompareFunction, GPUFrontFace, GPUCullMode, GPUVertexFormat, GPUBlendFactor, GPUBlendOperation, BlendColorFactor, OneMinusBlendColorFactor, GPUColorWriteFlags, GPUStencilOperation, GPUInputStepMode } from './constants.js';
+import { GPUIndexFormat, GPUCompareFunction, GPUFrontFace, GPUCullMode, GPUBlendFactor, GPUBlendOperation, BlendColorFactor, OneMinusBlendColorFactor, GPUColorWriteFlags, GPUStencilOperation, GPUInputStepMode } from './constants.js';
 import {
 import {
+	Float16BufferAttribute,
 	FrontSide, BackSide, DoubleSide,
 	FrontSide, BackSide, DoubleSide,
 	NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth,
 	NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth,
 	NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc,
 	NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc,
@@ -9,6 +10,26 @@ import {
 	ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstAlphaFactor, OneMinusDstAlphaFactor, DstColorFactor, OneMinusDstColorFactor, SrcAlphaSaturateFactor
 	ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstAlphaFactor, OneMinusDstAlphaFactor, DstColorFactor, OneMinusDstColorFactor, SrcAlphaSaturateFactor
 } from 'three';
 } from 'three';
 
 
+const typedArraysToVertexFormatPrefix = new Map( [
+	[ Int8Array, [ 'sint8', 'snorm8' ]],
+	[ Uint8Array, [ 'uint8', 'unorm8' ]],
+	[ Int16Array, [ 'sint16', 'snorm16' ]],
+	[ Uint16Array, [ 'uint16', 'unorm16' ]],
+	[ Int32Array, [ 'sint32', 'snorm32' ]],
+	[ Uint32Array, [ 'uint32', 'unorm32' ]],
+	[ Float32Array, [ 'float32', ]],
+] );
+
+const typedAttributeToVertexFormatPrefix = new Map( [
+	[ Float16BufferAttribute, [ 'float16', ]],
+] );
+
+const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [
+	[ Int32Array, 'sint32' ],
+	[ Uint32Array, 'uint32' ],
+	[ Float32Array, 'float32' ]
+] );
+
 class WebGPURenderPipeline {
 class WebGPURenderPipeline {
 
 
 	constructor( device, utils ) {
 	constructor( device, utils ) {
@@ -561,127 +582,48 @@ class WebGPURenderPipeline {
 
 
 	}
 	}
 
 
-	_getVertexFormat( type, bytesPerElement ) {
-
-		// float
-
-		if ( type === 'float' ) return GPUVertexFormat.Float32;
-
-		if ( type === 'vec2' ) {
-
-			if ( bytesPerElement === 2 ) {
-
-				return GPUVertexFormat.Float16x2;
-
-			} else {
-
-				return GPUVertexFormat.Float32x2;
-
-			}
-
-		}
-
-		if ( type === 'vec3' ) return GPUVertexFormat.Float32x3;
-
-		if ( type === 'vec4' ) {
+	_getVertexFormat( geometryAttribute ) {
 
 
-			if ( bytesPerElement === 2 ) {
+		const { itemSize, normalized } = geometryAttribute;
+		const ArrayType = geometryAttribute.array.constructor;
+		const AttributeType = geometryAttribute.constructor;
 
 
-				return GPUVertexFormat.Float16x4;
+		let format;
 
 
-			} else {
+		if ( itemSize == 1 ) {
 
 
-				return GPUVertexFormat.Float32x4;
+			format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType );
 
 
-			}
-
-		}
-
-		// int
-
-		if ( type === 'int' ) return GPUVertexFormat.Sint32;
-
-		if ( type === 'ivec2' ) {
-
-			if ( bytesPerElement === 1 ) {
-
-				return GPUVertexFormat.Sint8x2;
-
-			} else if ( bytesPerElement === 2 ) {
-
-				return GPUVertexFormat.Sint16x2;
-
-			} else {
-
-				return GPUVertexFormat.Sint32x2;
-
-			}
-
-		}
-
-		if ( type === 'ivec3' ) return GPUVertexFormat.Sint32x3;
-
-		if ( type === 'ivec4' ) {
-
-			if ( bytesPerElement === 1 ) {
-
-				return GPUVertexFormat.Sint8x4;
-
-			} else if ( bytesPerElement === 2 ) {
-
-				return GPUVertexFormat.Sint16x4;
-
-			} else {
-
-				return GPUVertexFormat.Sint32x4;
-
-			}
-
-		}
-
-		// uint
-
-		if ( type === 'uint' ) return GPUVertexFormat.Uint32;
+		} else {
 
 
-		if ( type === 'uvec2' ) {
+			const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType );
+			const prefix = prefixOptions[ normalized ? 1 : 0 ];
 
 
-			if ( bytesPerElement === 1 ) {
+			if ( prefix ) {
 
 
-				return GPUVertexFormat.Uint8x2;
+				const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize;
+				const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4;
+				const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT;
 
 
-			} else if ( bytesPerElement === 2 ) {
+				if ( paddedItemSize % 1 ) {
 
 
-				return GPUVertexFormat.Uint16x2;
+					throw new Error( 'THREE.WebGPURenderer: Bad vertex format item size.' );
 
 
-			} else {
+				}
 
 
-				return GPUVertexFormat.Uint32x2;
+				format = `${prefix}x${paddedItemSize}`;
 
 
 			}
 			}
 
 
 		}
 		}
 
 
-		if ( type === 'uvec3' ) return GPUVertexFormat.Uint32x3;
-
-		if ( type === 'uvec4' ) {
-
-			if ( bytesPerElement === 1 ) {
-
-				return GPUVertexFormat.Uint8x4;
+		if ( ! format ) {
 
 
-			} else if ( bytesPerElement === 2 ) {
-
-				return GPUVertexFormat.Uint16x4;
-
-			} else {
-
-				return GPUVertexFormat.Uint32x4;
-
-			}
+			console.error( 'THREE.WebGPURenderer: Vertex format not supported yet.' );
 
 
 		}
 		}
 
 
-		console.error( 'THREE.WebGPURenderer: Shader variable type not supported yet.', type );
+		return format;
 
 
 	}
 	}
 
 
@@ -693,14 +635,12 @@ class WebGPURenderPipeline {
 		for ( let slot = 0; slot < nodeAttributes.length; slot ++ ) {
 		for ( let slot = 0; slot < nodeAttributes.length; slot ++ ) {
 
 
 			const nodeAttribute = nodeAttributes[ slot ];
 			const nodeAttribute = nodeAttributes[ slot ];
-
 			const name = nodeAttribute.name;
 			const name = nodeAttribute.name;
-			const type = nodeAttribute.type;
 
 
 			const geometryAttribute = geometry.getAttribute( name );
 			const geometryAttribute = geometry.getAttribute( name );
 			const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT;
 			const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT;
 
 
-			const format = this._getVertexFormat( type, bytesPerElement );
+			const format = this._getVertexFormat( geometryAttribute );
 
 
 			let arrayStride = geometryAttribute.itemSize * bytesPerElement;
 			let arrayStride = geometryAttribute.itemSize * bytesPerElement;
 			let offset = 0;
 			let offset = 0;

+ 9 - 4
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -815,14 +815,19 @@ class WebGPURenderer {
 
 
 				if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
 				if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
 
 
+					const geometry = object.geometry;
+					const material = object.material;
+
 					if ( this.sortObjects === true ) {
 					if ( this.sortObjects === true ) {
 
 
-						_vector3.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix );
+						if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
 
 
-					}
+						_vector3
+							.copy( geometry.boundingSphere.center )
+							.applyMatrix4( object.matrixWorld )
+							.applyMatrix4( _projScreenMatrix );
 
 
-					const geometry = object.geometry;
-					const material = object.material;
+					}
 
 
 					if ( Array.isArray( material ) ) {
 					if ( Array.isArray( material ) ) {
 
 

二进制
examples/screenshots/webgpu_loader_gltf_compressed.jpg


+ 1 - 1
examples/webgpu_backdrop.html

@@ -112,7 +112,7 @@
 
 
 				}
 				}
 
 
-				addBackdropSphere( viewportSharedTexture().bgr.hue( oscSine().mul( Math.PI )  ) );
+				addBackdropSphere( viewportSharedTexture().bgr.hue( oscSine().mul( Math.PI ) ) );
 				addBackdropSphere( viewportSharedTexture().rgb.oneMinus() );
 				addBackdropSphere( viewportSharedTexture().rgb.oneMinus() );
 				addBackdropSphere( viewportSharedTexture().rgb.saturation( 0 ) );
 				addBackdropSphere( viewportSharedTexture().rgb.saturation( 0 ) );
 				addBackdropSphere( viewportSharedTexture().rgb.saturation( 10 ), oscSine() );
 				addBackdropSphere( viewportSharedTexture().rgb.saturation( 10 ), oscSine() );

+ 121 - 0
examples/webgpu_loader_gltf_compressed.html

@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js - WebGPU - GLTFloader + compressed</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>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - GLTFLoader + compression extensions
+		</div>
+
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+			import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			let camera, scene, renderer;
+
+			init();
+
+			async function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 20 );
+				camera.position.set( 2, 2, 2 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xEEEEEE );
+
+				//lights
+
+				const light = new THREE.PointLight( 0xffffff );
+				light.power = 1300;
+				camera.add( light );
+				scene.add( camera );
+
+				//renderer
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.ReinhardToneMapping;
+				renderer.toneMappingExposure = 1;
+				document.body.appendChild( renderer.domElement );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 3;
+				controls.maxDistance = 6;
+				controls.update();
+
+				await renderer.init();
+
+				const ktx2Loader = new KTX2Loader()
+					.setTranscoderPath( 'jsm/libs/basis/' )
+					.detectSupport( renderer );
+
+				const loader = new GLTFLoader();
+				loader.setKTX2Loader( ktx2Loader );
+				loader.setMeshoptDecoder( MeshoptDecoder );
+				loader.load( 'models/gltf/coffeemat.glb', function ( gltf ) {
+
+					const gltfScene = gltf.scene;
+					gltfScene.position.y = - .8;
+					gltfScene.scale.setScalar( .01 );
+
+					scene.add( gltfScene );
+
+				} );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>