Pārlūkot izejas kodu

Updated examples builds.

Mr.doob 2 gadi atpakaļ
vecāks
revīzija
65597f5c08

+ 50 - 0
examples/js/exporters/USDZExporter.js

@@ -42,6 +42,10 @@
 
 					}
 
+				} else if ( object.isCamera ) {
+
+					output += buildCamera( object );
+
 				}
 
 			} );
@@ -508,6 +512,52 @@ ${samplers.join( '\n' )}
 
 	}
 
+	function buildCamera( camera ) {
+
+		const name = camera.name ? camera.name : 'Camera_' + camera.id;
+		const transform = buildMatrix( camera.matrixWorld );
+		if ( camera.matrixWorld.determinant() < 0 ) {
+
+			console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', camera );
+
+		}
+
+		if ( camera.isOrthographicCamera ) {
+
+			return `def Camera "${name}"
+		{
+			matrix4d xformOp:transform = ${transform}
+			uniform token[] xformOpOrder = ["xformOp:transform"]
+	
+			float2 clippingRange = (${camera.near}, ${camera.far})
+			float horizontalAperture = ${( Math.abs( camera.left ) + Math.abs( camera.right ) ) * 10}
+			float verticalAperture = ${( Math.abs( camera.top ) + Math.abs( camera.bottom ) ) * 10}
+			token projection = "orthographic"
+		}
+	
+	`;
+
+		} else {
+
+			return `def Camera "${name}"
+		{
+			matrix4d xformOp:transform = ${transform}
+			uniform token[] xformOpOrder = ["xformOp:transform"]
+	
+			float2 clippingRange = (${camera.near}, ${camera.far})
+			float focalLength = ${camera.getFocalLength()}
+			float focusDistance = ${camera.focus}
+			float horizontalAperture = ${camera.getFilmWidth()}
+			token projection = "perspective"
+			float verticalAperture = ${camera.getFilmHeight()}
+		}
+	
+	`;
+
+		}
+
+	}
+
 	THREE.USDZExporter = USDZExporter;
 
 } )();

+ 1 - 1
examples/js/loaders/GLTFLoader.js

@@ -2730,7 +2730,7 @@
 			const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) {
 
 				texture.flipY = false;
-				if ( textureDef.name ) texture.name = textureDef.name;
+				texture.name = textureDef.name || sourceDef.name || '';
 				const samplers = json.samplers || {};
 				const sampler = samplers[ textureDef.sampler ] || {};
 				texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter;

+ 392 - 0
examples/js/loaders/MaterialXLoader.js

@@ -0,0 +1,392 @@
+( function () {
+const colorSpaceLib = {
+  THREE.mx_srgb_texture_to_lin_rec709
+};
+class MtlXElement {
+  constructor(name, nodeFunc, params = null) {
+    this.name = name;
+    this.nodeFunc = nodeFunc;
+    this.params = params;
+  }
+}
+
+// Ref: https://github.com/mrdoob/three.js/issues/24674
+
+const MtlXElements = [
+// << Math >>
+new MtlXElement('add', THREE.add, ['in1', 'in2']), new MtlXElement('subtract', THREE.sub, ['in1', 'in2']), new MtlXElement('multiply', THREE.mul, ['in1', 'in2']), new MtlXElement('divide', THREE.div, ['in1', 'in2']), new MtlXElement('modulo', THREE.mod, ['in1', 'in2']), new MtlXElement('absval', THREE.abs, ['in1', 'in2']), new MtlXElement('sign', THREE.sign, ['in1', 'in2']), new MtlXElement('floor', THREE.floor, ['in1', 'in2']), new MtlXElement('ceil', THREE.ceil, ['in1', 'in2']), new MtlXElement('round', THREE.round, ['in1', 'in2']), new MtlXElement('power', THREE.pow, ['in1', 'in2']), new MtlXElement('sin', THREE.sin, ['in']), new MtlXElement('cos', THREE.cos, ['in']), new MtlXElement('tan', THREE.tan, ['in']), new MtlXElement('asin', THREE.asin, ['in']), new MtlXElement('acos', THREE.acos, ['in']), new MtlXElement('atan2', THREE.atan2, ['in1', 'in2']), new MtlXElement('sqrt', THREE.sqrt, ['in']),
+//new MtlXElement( 'ln', ... ),
+new MtlXElement('exp', THREE.exp, ['in']), new MtlXElement('clamp', THREE.clamp, ['in', 'low', 'high']), new MtlXElement('min', THREE.min, ['in1', 'in2']), new MtlXElement('max', THREE.max, ['in1', 'in2']), new MtlXElement('normalize', THREE.normalize, ['in']), new MtlXElement('magnitude', THREE.length, ['in1', 'in2']), new MtlXElement('dotproduct', THREE.dot, ['in1', 'in2']), new MtlXElement('crossproduct', THREE.cross, ['in']),
+//new MtlXElement( 'transformpoint', ... ),
+//new MtlXElement( 'transformvector', ... ),
+//new MtlXElement( 'transformnormal', ... ),
+//new MtlXElement( 'transformmatrix', ... ),
+new MtlXElement('normalmap', THREE.normalMap, ['in', 'scale']),
+//new MtlXElement( 'transpose', ... ),
+//new MtlXElement( 'determinant', ... ),
+//new MtlXElement( 'invertmatrix', ... ),
+//new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ),
+//new MtlXElement( 'rotate3d', ... ),
+//new MtlXElement( 'arrayappend', ... ),
+//new MtlXElement( 'dot', ... ),
+
+// << Adjustment >>
+new MtlXElement('remap', THREE.remap, ['in', 'inlow', 'inhigh', 'outlow', 'outhigh']), new MtlXElement('smoothstep', THREE.smoothstep, ['in', 'low', 'high']),
+//new MtlXElement( 'curveadjust', ... ),
+//new MtlXElement( 'curvelookup', ... ),
+new MtlXElement('luminance', THREE.luminance, ['in', 'lumacoeffs']), new MtlXElement('rgbtohsv', THREE.mx_rgbtohsv, ['in']), new MtlXElement('hsvtorgb', THREE.mx_hsvtorgb, ['in']),
+// << Mix >>
+new MtlXElement('mix', THREE.mix, ['bg', 'fg', 'mix']),
+// << Channel >>
+new MtlXElement('combine2', THREE.vec2, ['in1', 'in2']), new MtlXElement('combine3', THREE.vec3, ['in1', 'in2', 'in3']), new MtlXElement('combine4', THREE.vec4, ['in1', 'in2', 'in3', 'in4']),
+// << Procedural >>
+new MtlXElement('ramplr', THREE.mx_ramplr, ['valuel', 'valuer', 'texcoord']), new MtlXElement('ramptb', THREE.mx_ramptb, ['valuet', 'valueb', 'texcoord']), new MtlXElement('splitlr', THREE.mx_splitlr, ['valuel', 'valuer', 'texcoord']), new MtlXElement('splittb', THREE.mx_splittb, ['valuet', 'valueb', 'texcoord']), new MtlXElement('noise2d', THREE.mx_noise_float, ['texcoord', 'amplitude', 'pivot']), new MtlXElement('noise3d', THREE.mx_noise_float, ['texcoord', 'amplitude', 'pivot']), new MtlXElement('fractal3d', THREE.mx_fractal_noise_float, ['position', 'octaves', 'lacunarity', 'diminish', 'amplitude']), new MtlXElement('cellnoise2d', THREE.mx_cell_noise_float, ['texcoord']), new MtlXElement('cellnoise3d', THREE.mx_cell_noise_float, ['texcoord']), new MtlXElement('worleynoise2d', THREE.mx_worley_noise_float, ['texcoord', 'jitter']), new MtlXElement('worleynoise3d', THREE.mx_worley_noise_float, ['texcoord', 'jitter']),
+// << Supplemental >>
+//new MtlXElement( 'tiledimage', ... ),
+//new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ),
+//new MtlXElement( 'ramp4', ... ),
+//new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ),
+new MtlXElement('safepower', THREE.mx_safepower, ['in1', 'in2']), new MtlXElement('contrast', THREE.mx_contrast, ['in', 'amount', 'pivot']),
+//new MtlXElement( 'hsvadjust', ... ),
+new MtlXElement('saturate', THREE.saturation, ['in', 'amount'])
+//new MtlXElement( 'extract', ... ),
+//new MtlXElement( 'separate2', ... ),
+//new MtlXElement( 'separate3', ... ),
+//new MtlXElement( 'separate4', ... )
+];
+
+const MtlXLibrary = {};
+MtlXElements.forEach(element => MtlXLibrary[element.name] = element);
+class MaterialXLoader extends THREE.Loader {
+  constructor(manager) {
+    super(manager);
+  }
+  load(url, onLoad, onProgress, onError) {
+    new THREE.FileLoader(this.manager).setPath(this.path).load(url, async text => {
+      try {
+        onLoad(this.parse(text));
+      } catch (e) {
+        onError(e);
+      }
+    }, onProgress, onError);
+    return this;
+  }
+  parse(text) {
+    return new MaterialX(this.manager, this.path).parse(text);
+  }
+}
+class MaterialXNode {
+  constructor(materialX, nodeXML, nodePath = '') {
+    this.materialX = materialX;
+    this.nodeXML = nodeXML;
+    this.nodePath = nodePath ? nodePath + '/' + this.name : this.name;
+    this.parent = null;
+    this.node = null;
+    this.children = [];
+  }
+  get element() {
+    return this.nodeXML.nodeName;
+  }
+  get nodeGraph() {
+    return this.getAttribute('nodegraph');
+  }
+  get nodeName() {
+    return this.getAttribute('nodename');
+  }
+  get interfaceName() {
+    return this.getAttribute('interfacename');
+  }
+  get output() {
+    return this.getAttribute('output');
+  }
+  get name() {
+    return this.getAttribute('name');
+  }
+  get type() {
+    return this.getAttribute('type');
+  }
+  get value() {
+    return this.getAttribute('value');
+  }
+  getNodeGraph() {
+    let nodeX = this;
+    while (nodeX !== null) {
+      if (nodeX.element === 'nodegraph') {
+        break;
+      }
+      nodeX = nodeX.parent;
+    }
+    return nodeX;
+  }
+  getRoot() {
+    let nodeX = this;
+    while (nodeX.parent !== null) {
+      nodeX = nodeX.parent;
+    }
+    return nodeX;
+  }
+  get referencePath() {
+    let referencePath = null;
+    if (this.nodeGraph !== null && this.output !== null) {
+      referencePath = this.nodeGraph + '/' + this.output;
+    } else if (this.nodeName !== null || this.interfaceName !== null) {
+      referencePath = this.getNodeGraph().nodePath + '/' + (this.nodeName || this.interfaceName);
+    }
+    return referencePath;
+  }
+  get hasReference() {
+    return this.referencePath !== null;
+  }
+  get isConst() {
+    return this.element === 'input' && this.value !== null && this.type !== 'filename';
+  }
+  getColorSpaceNode() {
+    const csSource = this.getAttribute('colorspace');
+    const csTarget = this.getRoot().getAttribute('colorspace');
+    const nodeName = `mx_${csSource}_to_${csTarget}`;
+    return colorSpaceLib[nodeName];
+  }
+  getTexture() {
+    const filePrefix = this.getRecursiveAttribute('fileprefix') || '';
+    const THREE.texture = this.materialX.textureLoader.load(filePrefix + this.value);
+    THREE.texture.wrapS = THREE.texture.wrapT = THREE.RepeatWrapping;
+    THREE.texture.flipY = false;
+    return THREE.texture;
+  }
+  getClassFromType(type) {
+    let nodeClass = null;
+    if (type === 'integer') nodeClass = THREE.int;else if (type === 'float') nodeClass = THREE.float;else if (type === 'vector2') nodeClass = THREE.vec2;else if (type === 'vector3') nodeClass = THREE.vec3;else if (type === 'vector4' || type === 'color4') nodeClass = THREE.vec4;else if (type === 'color3') nodeClass = THREE.color;else if (type === 'boolean') nodeClass = THREE.bool;
+    return nodeClass;
+  }
+  getNode() {
+    let node = this.node;
+    if (node !== null) {
+      return node;
+    }
+
+    //
+
+    const type = this.type;
+    if (this.isConst) {
+      const nodeClass = this.getClassFromType(type);
+      node = nodeClass(...this.getVector());
+    } else if (this.hasReference) {
+      node = this.materialX.getMaterialXNode(this.referencePath).getNode();
+    } else {
+      const element = this.element;
+      if (element === 'convert') {
+        const nodeClass = this.getClassFromType(type);
+        node = nodeClass(this.getNodeByName('in'));
+      } else if (element === 'constant') {
+        node = this.getNodeByName('value');
+      } else if (element === 'position') {
+        node = THREE.positionLocal;
+      } else if (element === 'tiledimage') {
+        const file = this.getChildByName('file');
+        const textureFile = file.getTexture();
+        const uvTiling = THREE.mx_transform_uv(...this.getNodesByNames(['uvtiling', 'uvoffset']));
+        node = THREE.texture(textureFile, uvTiling);
+        const colorSpaceNode = file.getColorSpaceNode();
+        if (colorSpaceNode) {
+          node = colorSpaceNode(node);
+        }
+      } else if (element === 'image') {
+        const file = this.getChildByName('file');
+        const uvNode = this.getNodeByName('texcoord');
+        const textureFile = file.getTexture();
+        node = THREE.texture(textureFile, uvNode);
+        const colorSpaceNode = file.getColorSpaceNode();
+        if (colorSpaceNode) {
+          node = colorSpaceNode(node);
+        }
+      } else if (MtlXLibrary[element] !== undefined) {
+        const nodeElement = MtlXLibrary[element];
+        node = nodeElement.nodeFunc(...this.getNodesByNames(...nodeElement.params));
+      }
+    }
+
+    //
+
+    if (node === null) {
+      console.warn(`THREE.MaterialXLoader: Unexpected node ${new XMLSerializer().serializeToString(this.nodeXML)}.`);
+      node = THREE.float(0);
+    }
+
+    //
+
+    const nodeToTypeClass = this.getClassFromType(type);
+    if (nodeToTypeClass !== null) {
+      node = nodeToTypeClass(node);
+    }
+    node.name = this.name;
+    this.node = node;
+    return node;
+  }
+  getChildByName(name) {
+    for (const input of this.children) {
+      if (input.name === name) {
+        return input;
+      }
+    }
+  }
+  getNodes() {
+    const nodes = {};
+    for (const input of this.children) {
+      const node = input.getNode();
+      nodes[node.name] = node;
+    }
+    return nodes;
+  }
+  getNodeByName(name) {
+    return this.getChildByName(name)?.getNode();
+  }
+  getNodesByNames(...names) {
+    const nodes = [];
+    for (const name of names) {
+      const node = this.getNodeByName(name);
+      if (node) nodes.push(node);
+    }
+    return nodes;
+  }
+  getValue() {
+    return this.value.trim();
+  }
+  getVector() {
+    const vector = [];
+    for (const val of this.getValue().split(/[,|\s]/)) {
+      if (val !== '') {
+        vector.push(Number(val.trim()));
+      }
+    }
+    return vector;
+  }
+  getAttribute(name) {
+    return this.nodeXML.getAttribute(name);
+  }
+  getRecursiveAttribute(name) {
+    let attribute = this.nodeXML.getAttribute(name);
+    if (attribute === null && this.parent !== null) {
+      attribute = this.parent.getRecursiveAttribute(name);
+    }
+    return attribute;
+  }
+  setStandardSurfaceToGltfPBR(material) {
+    const inputs = this.getNodes();
+
+    //
+
+    let colorNode = null;
+    if (inputs.base && inputs.base_color) colorNode = THREE.mul(inputs.base, inputs.base_color);else if (inputs.base) colorNode = inputs.base;else if (inputs.base_color) colorNode = inputs.base_color;
+
+    //
+
+    let roughnessNode = null;
+    if (inputs.specular_roughness) roughnessNode = inputs.specular_roughness;
+
+    //
+
+    let metalnessNode = null;
+    if (inputs.metalness) metalnessNode = inputs.metalness;
+
+    //
+
+    let clearcoatNode = null;
+    let clearcoatRoughnessNode = null;
+    if (inputs.coat) clearcoatNode = inputs.coat;
+    if (inputs.coat_roughness) clearcoatRoughnessNode = inputs.coat_roughness;
+    if (inputs.coat_color) {
+      colorNode = colorNode ? THREE.mul(colorNode, inputs.coat_color) : colorNode;
+    }
+
+    //
+
+    material.colorNode = colorNode || THREE.color(0.8, 0.8, 0.8);
+    material.roughnessNode = roughnessNode || THREE.float(0.2);
+    material.metalnessNode = metalnessNode || THREE.float(0);
+    material.clearcoatNode = clearcoatNode || THREE.float(0);
+    material.clearcoatRoughnessNode = clearcoatRoughnessNode || THREE.float(0);
+  }
+
+  /*setGltfPBR( material ) {
+  		const inputs = this.getNodes();
+  		console.log( inputs );
+  	}*/
+
+  setMaterial(material) {
+    const element = this.element;
+    if (element === 'gltf_pbr') {
+
+      //this.setGltfPBR( material );
+    } else if (element === 'standard_surface') {
+      this.setStandardSurfaceToGltfPBR(material);
+    }
+  }
+  toMaterial() {
+    const material = new THREE.MeshPhysicalNodeMaterial();
+    material.name = this.name;
+    for (const nodeX of this.children) {
+      const shaderProperties = this.materialX.getMaterialXNode(nodeX.nodeName);
+      shaderProperties.setMaterial(material);
+    }
+    return material;
+  }
+  toMaterials() {
+    const materials = {};
+    for (const nodeX of this.children) {
+      if (nodeX.element === 'surfacematerial') {
+        const material = nodeX.toMaterial();
+        materials[material.name] = material;
+      }
+    }
+    return materials;
+  }
+  THREE.add(materialXNode) {
+    materialXNode.parent = this;
+    this.children.push(materialXNode);
+  }
+}
+class MaterialX {
+  constructor(manager, path) {
+    this.manager = manager;
+    this.path = path;
+    this.resourcePath = '';
+    this.nodesXLib = new Map();
+    //this.nodesXRefLib = new WeakMap();
+
+    this.textureLoader = new THREE.TextureLoader(manager);
+  }
+  addMaterialXNode(materialXNode) {
+    this.nodesXLib.set(materialXNode.nodePath, materialXNode);
+  }
+
+  /*getMaterialXNodeFromXML( xmlNode ) {
+       return this.nodesXRefLib.get( xmlNode );
+   }*/
+
+  getMaterialXNode(...names) {
+    return this.nodesXLib.get(names.join('/'));
+  }
+  parseNode(nodeXML, nodePath = '') {
+    const materialXNode = new MaterialXNode(this, nodeXML, nodePath);
+    if (materialXNode.nodePath) this.addMaterialXNode(materialXNode);
+    for (const childNodeXML of nodeXML.children) {
+      const childMXNode = this.parseNode(childNodeXML, materialXNode.nodePath);
+      materialXNode.add(childMXNode);
+    }
+    return materialXNode;
+  }
+  parse(text) {
+    const rootXML = new DOMParser().parseFromString(text, 'application/xml').documentElement;
+    this.textureLoader.setPath(this.path);
+
+    //
+
+    const materials = this.parseNode(rootXML).toMaterials();
+    return {
+      materials
+    };
+  }
+}
+
+THREE.MaterialXLoader = MaterialXLoader;
+} )();

+ 5 - 5
examples/js/postprocessing/CubeTexturePass.js

@@ -2,7 +2,7 @@
 
 	class CubeTexturePass extends THREE.Pass {
 
-		constructor( camera, envMap, opacity = 1 ) {
+		constructor( camera, tCube, opacity = 1 ) {
 
 			super();
 			this.camera = camera;
@@ -19,11 +19,11 @@
 			Object.defineProperty( this.cubeMesh.material, 'envMap', {
 				get: function () {
 
-					return this.uniforms.envMap.value;
+					return this.uniforms.tCube.value;
 
 				}
 			} );
-			this.envMap = envMap;
+			this.tCube = tCube;
 			this.opacity = opacity;
 			this.cubeScene = new THREE.Scene();
 			this.cubeCamera = new THREE.PerspectiveCamera();
@@ -36,8 +36,8 @@
 			renderer.autoClear = false;
 			this.cubeCamera.projectionMatrix.copy( this.camera.projectionMatrix );
 			this.cubeCamera.quaternion.setFromRotationMatrix( this.camera.matrixWorld );
-			this.cubeMesh.material.uniforms.envMap.value = this.envMap;
-			this.cubeMesh.material.uniforms.flipEnvMap.value = this.envMap.isCubeTexture && this.envMap.isRenderTargetTexture === false ? - 1 : 1;
+			this.cubeMesh.material.uniforms.tCube.value = this.tCube;
+			this.cubeMesh.material.uniforms.tFlip.value = this.tCube.isCubeTexture && this.tCube.isRenderTargetTexture === false ? - 1 : 1;
 			this.cubeMesh.material.uniforms.opacity.value = this.opacity;
 			this.cubeMesh.material.transparent = this.opacity < 1.0;
 			renderer.setRenderTarget( this.renderToScreen ? null : readBuffer );

+ 126 - 0
examples/js/shaders/VelocityShader.js

@@ -0,0 +1,126 @@
+( function () {
+
+	/**
+ * Mesh Velocity Shader @bhouston
+ */
+
+	const VelocityShader = {
+		uniforms: THREE.UniformsUtils.merge( [ THREE.UniformsLib.common, THREE.UniformsLib.displacementmap, {
+			modelMatrixPrev: {
+				value: new THREE.Matrix4()
+			},
+			currentProjectionViewMatrix: {
+				value: new THREE.Matrix4()
+			},
+			previousProjectionViewMatrix: {
+				value: new THREE.Matrix4()
+			}
+		} ] ),
+		vertexShader: /* glsl */`
+#define NORMAL
+
+#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
+
+	varying vec3 vViewPosition;
+
+#endif
+
+#include <common>
+#include <packing>
+#include <uv_pars_vertex>
+#include <displacementmap_pars_vertex>
+#include <normal_pars_vertex>
+#include <morphtarget_pars_vertex>
+#include <skinning_pars_vertex>
+#include <logdepthbuf_pars_vertex>
+#include <clipping_planes_pars_vertex>
+
+uniform mat4 previousProjectionViewMatrix;
+uniform mat4 currentProjectionViewMatrix;
+
+uniform mat4 modelMatrixPrev;
+
+varying vec4 clipPositionCurrent;
+varying vec4 clipPositionPrevious;
+
+void main() {
+
+
+	#include <uv_vertex>
+
+	#include <beginnormal_vertex>
+	#include <morphnormal_vertex>
+	#include <skinbase_vertex>
+	#include <skinnormal_vertex>
+	#include <defaultnormal_vertex>
+	#include <normal_vertex>
+
+	#include <begin_vertex>
+	#include <morphtarget_vertex>
+	#include <displacementmap_vertex>
+	#include <morphtarget_vertex>
+	#include <skinning_vertex>
+
+#ifdef USE_SKINNING
+
+	vec4 mvPosition = modelViewMatrix * skinned;
+	clipPositionCurrent  = currentProjectionViewMatrix * modelMatrix * skinned;
+	clipPositionPrevious = previousProjectionViewMatrix * modelMatrixPrev * skinned;
+
+#else
+
+	vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );
+	clipPositionCurrent  = currentProjectionViewMatrix * modelMatrix * vec4( transformed, 1.0 );
+	clipPositionPrevious = previousProjectionViewMatrix * modelMatrixPrev * vec4( transformed, 1.0 );
+
+#endif
+
+	gl_Position = projectionMatrix * mvPosition;
+
+	#include <logdepthbuf_vertex>
+	#include <clipping_planes_vertex>
+}
+`,
+		fragmentShader: /* glsl */`
+#define NORMAL
+
+uniform float opacity;
+
+#include <packing>
+#include <uv_pars_fragment>
+#include <map_pars_fragment>
+#include <alphamap_pars_fragment>
+#include <alphatest_pars_fragment>
+#include <logdepthbuf_pars_fragment>
+#include <clipping_planes_pars_fragment>
+
+varying vec4 clipPositionCurrent;
+varying vec4 clipPositionPrevious;
+
+void main() {
+
+	vec4 diffuseColor = vec4( 1.0 );
+	diffuseColor.a = opacity;
+
+	#include <map_fragment>
+	#include <alphamap_fragment>
+	#include <alphatest_fragment>
+
+	vec2 ndcPositionCurrent  = clipPositionCurrent.xy/clipPositionCurrent.w;
+	vec2 ndcPositionPrevious = clipPositionPrevious.xy/clipPositionPrevious.w;
+	vec2 vel = ( ndcPositionCurrent - ndcPositionPrevious ) * 0.5;
+	vel = vel * 0.5 + 0.5;
+	vec2 v1 = packDepthToRG(vel.x);
+	vec2 v2 = packDepthToRG(vel.y);
+	gl_FragColor = vec4(v1.x, v1.y, v2.x, v2.y);
+
+	#include <logdepthbuf_fragment>
+
+}
+
+`
+	};
+
+	THREE.VelocityShader = VelocityShader;
+
+} )();

+ 23 - 0
examples/js/utils/BufferGeometryUtils.js

@@ -323,6 +323,28 @@
 
 	}
 
+	/**
+ * @param {BufferAttribute}
+ * @return {BufferAttribute}
+ */
+	function deepCloneAttribute( attribute ) {
+
+		if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) {
+
+			return deinterleaveAttribute( attribute );
+
+		}
+
+		if ( attribute.isInstancedBufferAttribute ) {
+
+			return new THREE.InstancedBufferAttribute().copy( attribute );
+
+		}
+
+		return new THREE.BufferAttribute().copy( attribute );
+
+	}
+
 	/**
  * @param {Array<BufferAttribute>} attributes
  * @return {Array<InterleavedBufferAttribute>}
@@ -1022,6 +1044,7 @@
 	THREE.BufferGeometryUtils.computeMikkTSpaceTangents = computeMikkTSpaceTangents;
 	THREE.BufferGeometryUtils.computeMorphedAttributes = computeMorphedAttributes;
 	THREE.BufferGeometryUtils.computeTangents = computeTangents;
+	THREE.BufferGeometryUtils.deepCloneAttribute = deepCloneAttribute;
 	THREE.BufferGeometryUtils.deinterleaveAttribute = deinterleaveAttribute;
 	THREE.BufferGeometryUtils.deinterleaveGeometry = deinterleaveGeometry;
 	THREE.BufferGeometryUtils.estimateBytesUsed = estimateBytesUsed;

+ 108 - 0
examples/js/utils/SceneUtils.js

@@ -1,5 +1,7 @@
 ( function () {
 
+	const _color = /*@__PURE__*/new THREE.Color();
+	const _matrix = /*@__PURE__*/new THREE.Matrix4();
 	function createMeshesFromInstancedMesh( instancedMesh ) {
 
 		const group = new THREE.Group();
@@ -98,9 +100,115 @@
 
 	}
 
+	function reduceVertices( object, func, initialValue ) {
+
+		let value = initialValue;
+		const vertex = new THREE.Vector3();
+		object.updateWorldMatrix( true, true );
+		object.traverseVisible( child => {
+
+			const {
+				geometry
+			} = child;
+			if ( geometry !== undefined ) {
+
+				const {
+					position
+				} = geometry.attributes;
+				if ( position !== undefined ) {
+
+					for ( let i = 0, l = position.count; i < l; i ++ ) {
+
+						vertex.fromBufferAttribute( position, i );
+						if ( child.isSkinnedMesh ) {
+
+							child.boneTransform( i, vertex );
+
+						} else {
+
+							vertex.applyMatrix4( child.matrixWorld );
+
+						}
+
+						value = func( value, vertex );
+
+					}
+
+				}
+
+			}
+
+		} );
+		return value;
+
+	}
+
+	/**
+ * @param {InstancedMesh}
+ * @param {function(int, int):int}
+ */
+	function sortInstancedMesh( mesh, compareFn ) {
+
+		// store copy of instanced attributes for lookups
+
+		const instanceMatrixRef = THREE.deepCloneAttribute( mesh.instanceMatrix );
+		const instanceColorRef = mesh.instanceColor ? THREE.deepCloneAttribute( mesh.instanceColor ) : null;
+		const attributeRefs = new Map();
+		for ( const name in mesh.geometry.attributes ) {
+
+			const attribute = mesh.geometry.attributes[ name ];
+			if ( attribute.isInstancedBufferAttribute ) {
+
+				attributeRefs.set( attribute, THREE.deepCloneAttribute( attribute ) );
+
+			}
+
+		}
+
+		// compute sort order
+
+		const tokens = [];
+		for ( let i = 0; i < mesh.count; i ++ ) tokens.push( i );
+		tokens.sort( compareFn );
+
+		// apply sort order
+
+		for ( let i = 0; i < tokens.length; i ++ ) {
+
+			const refIndex = tokens[ i ];
+			_matrix.fromArray( instanceMatrixRef.array, refIndex * mesh.instanceMatrix.itemSize );
+			_matrix.toArray( mesh.instanceMatrix.array, i * mesh.instanceMatrix.itemSize );
+			if ( mesh.instanceColor ) {
+
+				_color.fromArray( instanceColorRef.array, refIndex * mesh.instanceColor.itemSize );
+				_color.toArray( mesh.instanceColor.array, i * mesh.instanceColor.itemSize );
+
+			}
+
+			for ( const name in mesh.geometry.attributes ) {
+
+				const attribute = mesh.geometry.attributes[ name ];
+				if ( attribute.isInstancedBufferAttribute ) {
+
+					const attributeRef = attributeRefs.get( attribute );
+					attribute.setX( i, attributeRef.getX( refIndex ) );
+					if ( attribute.itemSize > 1 ) attribute.setY( i, attributeRef.getY( refIndex ) );
+					if ( attribute.itemSize > 2 ) attribute.setZ( i, attributeRef.getZ( refIndex ) );
+					if ( attribute.itemSize > 3 ) attribute.setW( i, attributeRef.getW( refIndex ) );
+
+				}
+
+			}
+
+		}
+
+	}
+
 	THREE.SceneUtils = {};
 	THREE.SceneUtils.createMeshesFromInstancedMesh = createMeshesFromInstancedMesh;
 	THREE.SceneUtils.createMeshesFromMultiMaterialMesh = createMeshesFromMultiMaterialMesh;
 	THREE.SceneUtils.createMultiMaterialObject = createMultiMaterialObject;
+	THREE.SceneUtils.reduceVertices = reduceVertices;
+	THREE.SceneUtils.sortInstancedMesh = sortInstancedMesh;
 
 } )();