浏览代码

Add support for thin instances (WIP)

Popov72 5 年之前
父节点
当前提交
8e39b7ae73

+ 48 - 16
loaders/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts

@@ -1,4 +1,4 @@
-import { Vector3, Quaternion } from 'babylonjs/Maths/math.vector';
+import { Vector3, Quaternion, Matrix } from 'babylonjs/Maths/math.vector';
 import { InstancedMesh } from 'babylonjs/Meshes/instancedMesh';
 import { Mesh } from 'babylonjs/Meshes/mesh';
 import { TransformNode } from "babylonjs/Meshes/transformNode";
@@ -10,12 +10,18 @@ import { INode } from "../glTFLoaderInterfaces";
 import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh';
 
 const NAME = "EXT_mesh_gpu_instancing";
+const useThinInstances = true;
 
 interface IEXTMeshGpuInstancing {
     mesh?: number;
     attributes: { [name: string]: number };
 }
 
+const T = new Vector3(0, 0, 0);
+const R = new Quaternion(0, 0, 0, 1);
+const S = new Vector3(1, 1, 1);
+const M = new Matrix();
+
 /**
  * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1691)
  * [Playground Sample](https://playground.babylonjs.com/#QFIGLW#9)
@@ -55,8 +61,10 @@ export class EXT_mesh_gpu_instancing implements IGLTFLoaderExtension {
             }
 
             // Hide the source meshes.
-            for (const babylonMesh of node._primitiveBabylonMeshes) {
-                babylonMesh.isVisible = false;
+            if (!useThinInstances) {
+                for (const babylonMesh of node._primitiveBabylonMeshes) {
+                    babylonMesh.isVisible = false;
+                }
             }
 
             const promises = new Array<Promise<Nullable<Float32Array>>>();
@@ -86,25 +94,49 @@ export class EXT_mesh_gpu_instancing implements IGLTFLoaderExtension {
                 return promise;
             }
 
-            const digitLength = instanceCount.toString().length;
-            for (let i = 0; i < instanceCount; ++i) {
-                for (const babylonMesh of node._primitiveBabylonMeshes!) {
-                    const instanceName = `${babylonMesh.name || babylonMesh.id}_${StringTools.PadNumber(i, digitLength)}`;
-                    const babylonInstancedMesh = (babylonMesh as (InstancedMesh | Mesh)).createInstance(instanceName);
-                    babylonInstancedMesh.setParent(babylonMesh);
+            if (!useThinInstances) {
+                const digitLength = instanceCount.toString().length;
+                for (let i = 0; i < instanceCount; ++i) {
+                    for (const babylonMesh of node._primitiveBabylonMeshes!) {
+                        const instanceName = `${babylonMesh.name || babylonMesh.id}_${StringTools.PadNumber(i, digitLength)}`;
+                        const babylonInstancedMesh = (babylonMesh as (InstancedMesh | Mesh)).createInstance(instanceName);
+                        babylonInstancedMesh.setParent(babylonMesh);
+                    }
                 }
             }
 
             return promise.then((babylonTransformNode) => {
                 return Promise.all(promises).then(([translationBuffer, rotationBuffer, scaleBuffer]) => {
-                    for (const babylonMesh of node._primitiveBabylonMeshes!) {
-                        const babylonInstancedMeshes = babylonMesh.getChildMeshes(true, (node) => (node as AbstractMesh).isAnInstance);
+                    if (!useThinInstances) {
+                        for (const babylonMesh of node._primitiveBabylonMeshes!) {
+                            const babylonInstancedMeshes = babylonMesh.getChildMeshes(true, (node) => (node as AbstractMesh).isAnInstance);
+                            for (let i = 0; i < instanceCount; ++i) {
+                                const babylonInstancedMesh = babylonInstancedMeshes[i];
+                                translationBuffer && Vector3.FromArrayToRef(translationBuffer, i * 3, babylonInstancedMesh.position);
+                                rotationBuffer && Quaternion.FromArrayToRef(rotationBuffer, i * 4, babylonInstancedMesh.rotationQuaternion!);
+                                scaleBuffer && Vector3.FromArrayToRef(scaleBuffer, i * 3, babylonInstancedMesh.scaling);
+                                babylonInstancedMesh.refreshBoundingInfo();
+                            }
+                        }
+                    } else {
+                        const matrices = new Float32Array(instanceCount * 16);
+
+                        T.copyFromFloats(0, 0, 0);
+                        R.copyFromFloats(0, 0, 0, 1);
+                        S.copyFromFloats(1, 1, 1);
+
                         for (let i = 0; i < instanceCount; ++i) {
-                            const babylonInstancedMesh = babylonInstancedMeshes[i];
-                            translationBuffer && Vector3.FromArrayToRef(translationBuffer, i * 3, babylonInstancedMesh.position);
-                            rotationBuffer && Quaternion.FromArrayToRef(rotationBuffer, i * 4, babylonInstancedMesh.rotationQuaternion!);
-                            scaleBuffer && Vector3.FromArrayToRef(scaleBuffer, i * 3, babylonInstancedMesh.scaling);
-                            babylonInstancedMesh.refreshBoundingInfo();
+                            translationBuffer && Vector3.FromArrayToRef(translationBuffer, i * 3, T);
+                            rotationBuffer && Quaternion.FromArrayToRef(rotationBuffer, i * 4, R);
+                            scaleBuffer && Vector3.FromArrayToRef(scaleBuffer, i * 3, S);
+
+                            Matrix.ComposeToRef(S, R, T, M);
+
+                            M.copyToArray(matrices, i * 16);
+                        }
+
+                        for (const babylonMesh of node._primitiveBabylonMeshes!) {
+                            (babylonMesh as Mesh).setThinInstanceBuffer("matrix", matrices);
                         }
                     }
 

+ 2 - 1
materialsLibrary/src/grid/gridMaterial.ts

@@ -24,6 +24,7 @@ class GridMaterialDefines extends MaterialDefines {
     public UV1 = false;
     public UV2 = false;
     public INSTANCES = false;
+    public THIN_INSTANCES = false;
 
     constructor() {
         super();
@@ -216,7 +217,7 @@ export class GridMaterial extends PushMaterial {
         this._activeEffect = effect;
 
         // Matrices
-        if (!defines.INSTANCES) {
+        if (!defines.INSTANCES || defines.THIN_INSTANCE) {
             this.bindOnlyWorldMatrix(world);
         }
         this._activeEffect.setMatrix("view", scene.getViewMatrix());

+ 4 - 1
src/Layers/effectLayer.ts

@@ -522,6 +522,9 @@ export abstract class EffectLayer {
         if (useInstances) {
             defines.push("#define INSTANCES");
             MaterialHelper.PushAttributesForInstances(attribs);
+            if (subMesh.getRenderingMesh().hasThinInstances) {
+                defines.push("#define THIN_INSTANCES");
+            }
         }
 
         this._addCustomEffectDefines(defines);
@@ -685,7 +688,7 @@ export abstract class EffectLayer {
             return;
         }
 
-        var hardwareInstancedRendering = batch.hardwareInstancedRendering[subMesh._id];
+        var hardwareInstancedRendering = batch.hardwareInstancedRendering[subMesh._id] || renderingMesh.hasThinInstances;
 
         this._setEmissiveTextureAndColor(renderingMesh, subMesh, material);
 

+ 6 - 1
src/Lights/Shadows/shadowGenerator.ts

@@ -1042,6 +1042,8 @@ export class ShadowGenerator implements IShadowGenerator {
 
         const world = mesh.getWorldMatrix();
 
+        effect.setMatrix(matriceNames?.world ?? "world", world);
+
         world.multiplyToRef(this.getTransformMatrix(), tmpMatrix);
 
         effect.setMatrix(matriceNames?.worldViewProjection ?? "worldViewProjection", tmpMatrix);
@@ -1075,7 +1077,7 @@ export class ShadowGenerator implements IShadowGenerator {
             return;
         }
 
-        var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
+        var hardwareInstancedRendering = engine.getCaps().instancedArrays && (batch.visibleInstances[subMesh._id] !== null && batch.visibleInstances[subMesh._id] !== undefined || renderingMesh.hasThinInstances);
         if (this.isReady(subMesh, hardwareInstancedRendering, isTransparent)) {
             const shadowDepthWrapper = renderingMesh.material?.shadowDepthWrapper;
 
@@ -1402,6 +1404,9 @@ export class ShadowGenerator implements IShadowGenerator {
             if (useInstances) {
                 defines.push("#define INSTANCES");
                 MaterialHelper.PushAttributesForInstances(attribs);
+                if (subMesh.getRenderingMesh().hasThinInstances) {
+                    defines.push("#define THIN_INSTANCES");
+                }
             }
 
             if (this.customShaderOptions) {

+ 1 - 1
src/Materials/Background/backgroundMaterial.ts

@@ -813,7 +813,7 @@ export class BackgroundMaterial extends PushMaterial {
         MaterialHelper.PrepareDefinesForMisc(mesh, scene, false, this.pointsCloud, this.fogEnabled, this._shouldTurnAlphaTestOn(mesh), defines);
 
         // Values that need to be evaluated on every frame
-        MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances);
+        MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances, null, subMesh.getRenderingMesh().hasThinInstances);
 
         // Attribs
         if (MaterialHelper.PrepareDefinesForAttributes(mesh, defines, false, true, false)) {

+ 11 - 2
src/Materials/Node/Blocks/Vertex/instancesBlock.ts

@@ -8,6 +8,7 @@ import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
 import { NodeMaterialSystemValues } from '../../Enums/nodeMaterialSystemValues';
 import { InputBlock } from '../Input/inputBlock';
 import { _TypeStore } from '../../../../Misc/typeStore';
+import { SubMesh } from '../../../../Meshes/subMesh';
 
 /**
  * Block used to add support for instances
@@ -135,16 +136,21 @@ export class InstancesBlock extends NodeMaterialBlock {
             worldInput.output.connectTo(this.world);
         }
 
-        this.world.define = "!INSTANCES";
+        this.world.define = "!INSTANCES || THIN_INSTANCES";
     }
 
-    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false) {
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false, subMesh?: SubMesh) {
         let changed = false;
         if (defines["INSTANCES"] !== useInstances) {
             defines.setValue("INSTANCES", useInstances);
             changed = true;
         }
 
+        if (subMesh && defines["THIN_INSTANCES"] !== subMesh?.getRenderingMesh().hasInstances) {
+            defines.setValue("THIN_INSTANCES", subMesh?.getRenderingMesh().hasInstances);
+            changed = true;
+        }
+
         if (changed) {
             defines.markAsUnprocessed();
         }
@@ -166,6 +172,9 @@ export class InstancesBlock extends NodeMaterialBlock {
 
         state.compilationString += `#ifdef INSTANCES\r\n`;
         state.compilationString += this._declareOutput(output, state) + ` = mat4(${world0.associatedVariableName}, ${world1.associatedVariableName}, ${world2.associatedVariableName}, ${world3.associatedVariableName});\r\n`;
+        state.compilationString += `#ifdef THIN_INSTANCES\r\n`;
+        state.compilationString += `${output.associatedVariableName} = ${this.world.associatedVariableName} * ${output.associatedVariableName};\r\n`;
+        state.compilationString += `#endif\r\n`;
         state.compilationString += this._declareOutput(instanceID, state) + ` = float(gl_InstanceID);\r\n`;
         state.compilationString += `#else\r\n`;
         state.compilationString += this._declareOutput(output, state) + ` = ${this.world.associatedVariableName};\r\n`;

+ 3 - 3
src/Materials/Node/nodeMaterial.ts

@@ -940,7 +940,7 @@ export class NodeMaterial extends PushMaterial {
         this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_MULTIPLY, onCompiled, onError);
     }
 
-    private _processDefines(mesh: AbstractMesh, defines: NodeMaterialDefines, useInstances = false): Nullable<{
+    private _processDefines(mesh: AbstractMesh, defines: NodeMaterialDefines, useInstances = false, subMesh?: SubMesh): Nullable<{
         lightDisposed: boolean,
         uniformBuffers: string[],
         mergedUniforms: string[],
@@ -955,7 +955,7 @@ export class NodeMaterial extends PushMaterial {
         });
 
         this._sharedData.blocksWithDefines.forEach((b) => {
-            b.prepareDefines(mesh, this, defines, useInstances);
+            b.prepareDefines(mesh, this, defines, useInstances, subMesh);
         });
 
         // Need to recompile?
@@ -1066,7 +1066,7 @@ export class NodeMaterial extends PushMaterial {
             return false;
         }
 
-        const result = this._processDefines(mesh, defines, useInstances);
+        const result = this._processDefines(mesh, defines, useInstances, subMesh);
 
         if (result) {
             let previousEffect = subMesh.effect;

+ 2 - 1
src/Materials/Node/nodeMaterialBlock.ts

@@ -372,8 +372,9 @@ export class NodeMaterialBlock {
      * @param nodeMaterial defines the node material requesting the update
      * @param defines defines the material defines to update
      * @param useInstances specifies that instances should be used
+     * @param subMesh defines which submesh to render
      */
-    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false) {
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false, subMesh?: SubMesh) {
         // Do nothing
     }
 

+ 9 - 7
src/Materials/PBR/pbrBaseMaterial.ts

@@ -153,6 +153,7 @@ export class PBRMaterialDefines extends MaterialDefines
     public HORIZONOCCLUSION = false;
 
     public INSTANCES = false;
+    public THIN_INSTANCES = false;
 
     public NUM_BONE_INFLUENCERS = 0;
     public BonesPerMesh = 0;
@@ -1030,7 +1031,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
         const previousEffect = subMesh.effect;
         const lightDisposed = defines._areLightsDisposed;
-        let effect = this._prepareEffect(mesh, defines, this.onCompiled, this.onError, useInstances);
+        let effect = this._prepareEffect(mesh, defines, this.onCompiled, this.onError, useInstances, null, subMesh.getRenderingMesh().hasThinInstances);
 
         if (effect) {
             if (this._onEffectCreatedObservable) {
@@ -1080,8 +1081,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         return false;
     }
 
-    private _prepareEffect(mesh: AbstractMesh, defines: PBRMaterialDefines, onCompiled: Nullable<(effect: Effect) => void> = null, onError: Nullable<(effect: Effect, errors: string) => void> = null, useInstances: Nullable<boolean> = null, useClipPlane: Nullable<boolean> = null): Nullable<Effect> {
-        this._prepareDefines(mesh, defines, useInstances, useClipPlane);
+    private _prepareEffect(mesh: AbstractMesh, defines: PBRMaterialDefines, onCompiled: Nullable<(effect: Effect) => void> = null, onError: Nullable<(effect: Effect, errors: string) => void> = null,
+                useInstances: Nullable<boolean> = null, useClipPlane: Nullable<boolean> = null, useThinInstances: boolean): Nullable<Effect> {
+        this._prepareDefines(mesh, defines, useInstances, useClipPlane, useThinInstances);
 
         if (!defines.isDirty) {
             return null;
@@ -1278,7 +1280,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         }, engine);
     }
 
-    private _prepareDefines(mesh: AbstractMesh, defines: PBRMaterialDefines, useInstances: Nullable<boolean> = null, useClipPlane: Nullable<boolean> = null): void {
+    private _prepareDefines(mesh: AbstractMesh, defines: PBRMaterialDefines, useInstances: Nullable<boolean> = null, useClipPlane: Nullable<boolean> = null, useThinInstances: boolean = false): void {
         const scene = this.getScene();
         const engine = scene.getEngine();
 
@@ -1567,7 +1569,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         this.sheen.prepareDefines(defines, scene);
 
         // Values that need to be evaluated on every frame
-        MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances ? true : false, useClipPlane);
+        MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances ? true : false, useClipPlane, useThinInstances);
 
         // Attribs
         MaterialHelper.PrepareDefinesForAttributes(mesh, defines, true, true, true, this._transparencyMode !== PBRBaseMaterial.PBRMATERIAL_OPAQUE);
@@ -1584,7 +1586,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         };
 
         const defines = new PBRMaterialDefines();
-        const effect = this._prepareEffect(mesh, defines, undefined, undefined, localOptions.useInstances, localOptions.clipPlane)!;
+        const effect = this._prepareEffect(mesh, defines, undefined, undefined, localOptions.useInstances, localOptions.clipPlane, mesh.hasThinInstances)!;
         if (this._onEffectCreatedObservable) {
             onCreatedEffectParameters.effect = effect;
             onCreatedEffectParameters.subMesh = null;
@@ -1700,7 +1702,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         this._activeEffect = effect;
 
         // Matrices
-        if (!defines.INSTANCES) {
+        if (!defines.INSTANCES || defines.THIN_INSTANCES) {
             this.bindOnlyWorldMatrix(world);
         }
 

+ 8 - 2
src/Materials/materialHelper.ts

@@ -117,8 +117,9 @@ export class MaterialHelper {
      * @param defines specifies the list of active defines
      * @param useInstances defines if instances have to be turned on
      * @param useClipPlane defines if clip plane have to be turned on
+     * @param useInstances defines if thin instances have to be turned on
      */
-    public static PrepareDefinesForFrameBoundValues(scene: Scene, engine: Engine, defines: any, useInstances: boolean, useClipPlane: Nullable<boolean> = null): void {
+    public static PrepareDefinesForFrameBoundValues(scene: Scene, engine: Engine, defines: any, useInstances: boolean, useClipPlane: Nullable<boolean> = null, useThinInstances: boolean): void {
         var changed = false;
         let useClipPlane1 = false;
         let useClipPlane2 = false;
@@ -174,6 +175,11 @@ export class MaterialHelper {
             changed = true;
         }
 
+        if (defines["THIN_INSTANCES"] !== useThinInstances) {
+            defines["THIN_INSTANCES"] = useThinInstances;
+            changed = true;
+        }
+
         if (changed) {
             defines.markAsUnprocessed();
         }
@@ -664,7 +670,7 @@ export class MaterialHelper {
      * @param defines The current MaterialDefines of the effect
      */
     public static PrepareAttributesForInstances(attribs: string[], defines: MaterialDefines): void {
-        if (defines["INSTANCES"]) {
+        if (defines["INSTANCES"] || defines["THIN_INSTANCES"]) {
             this.PushAttributesForInstances(attribs);
         }
     }

+ 3 - 0
src/Materials/shaderMaterial.ts

@@ -525,6 +525,9 @@ export class ShaderMaterial extends Material {
         if (useInstances) {
             defines.push("#define INSTANCES");
             MaterialHelper.PushAttributesForInstances(attribs);
+            if (mesh?.hasThinInstances) {
+                defines.push("#define THIN_INSTANCES");
+            }
         }
 
         // Bones

+ 1 - 0
src/Materials/shadowDepthWrapper.ts

@@ -94,6 +94,7 @@ export class ShadowDepthWrapper {
         const prefix = baseMaterial.getClassName() === "NodeMaterial" ? "u_" : "";
 
         this._matriceNames = {
+            "world": prefix + "world",
             "view": prefix + "view",
             "projection": prefix + "projection",
             "viewProjection": prefix + "viewProjection",

+ 3 - 2
src/Materials/standardMaterial.ts

@@ -83,6 +83,7 @@ export class StandardMaterialDefines extends MaterialDefines implements IImagePr
     public BonesPerMesh = 0;
     public BONETEXTURE = false;
     public INSTANCES = false;
+    public THIN_INSTANCES = false;
     public GLOSSINESS = false;
     public ROUGHNESS = false;
     public EMISSIVEASILLUMINATION = false;
@@ -1035,7 +1036,7 @@ export class StandardMaterial extends PushMaterial {
         MaterialHelper.PrepareDefinesForAttributes(mesh, defines, true, true, true);
 
         // Values that need to be evaluated on every frame
-        MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances);
+        MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances, null, subMesh.getRenderingMesh().hasThinInstances);
 
         // Get correct effect
         if (defines.isDirty) {
@@ -1316,7 +1317,7 @@ export class StandardMaterial extends PushMaterial {
         this._activeEffect = effect;
 
         // Matrices
-        if (!defines.INSTANCES) {
+        if (!defines.INSTANCES || defines.THIN_INSTANCES) {
             this.bindOnlyWorldMatrix(world);
         }
 

+ 7 - 0
src/Meshes/abstractMesh.ts

@@ -1098,6 +1098,13 @@ export class AbstractMesh extends TransformNode implements IDisposable, ICullabl
         return false;
     }
 
+    /**
+     * Gets a boolean indicating if this mesh has thin instances
+     */
+    public get hasThinInstances(): boolean {
+        return false;
+    }
+
     // ================================== Point of View Movement =================================
 
     /**

+ 2 - 1
src/Meshes/index.ts

@@ -18,4 +18,5 @@ export * from "./meshLODLevel";
 export * from "./transformNode";
 export * from "./Builders/index";
 export * from "./dataBuffer";
-export * from "./WebGL/webGLDataBuffer";
+export * from "./WebGL/webGLDataBuffer";
+export * from "./thinInstanceMesh";

+ 33 - 2
src/Meshes/mesh.ts

@@ -275,6 +275,10 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         return this.instances.length > 0;
     }
 
+    public get hasThinInstances(): boolean {
+        return (this._thinInstancedBuffersStorage?.instancesCount ?? 0) > 0;
+    }
+
     // Members
 
     /**
@@ -955,7 +959,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
 
         let engine = this.getEngine();
         let scene = this.getScene();
-        let hardwareInstancedRendering = forceInstanceSupport || engine.getCaps().instancedArrays && this.instances.length > 0;
+        let hardwareInstancedRendering = forceInstanceSupport || engine.getCaps().instancedArrays && (this.instances.length > 0 || this.hasThinInstances);
 
         this.computeWorldMatrix();
 
@@ -1610,6 +1614,20 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
     }
 
     /** @hidden */
+    public _renderWithThinInstances(subMesh: SubMesh, fillMode: number, effect: Effect, engine: Engine) {
+        // Stats
+        const instancesCount = this._thinInstancedBuffersStorage?.instancesCount ?? 0;
+
+        this.getScene()._activeIndices.addCount(subMesh.indexCount * instancesCount, false);
+
+        // Draw
+        this._bind(subMesh, effect, fillMode);
+        this._draw(subMesh, fillMode, instancesCount);
+
+        engine.unbindInstanceAttributes();
+    }
+
+    /** @hidden */
     public _processInstancedBuffers(visibleInstances: InstancedMesh[], renderSelf: boolean) {
         // Do nothing
     }
@@ -1620,6 +1638,11 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         var scene = this.getScene();
         var engine = scene.getEngine();
 
+        if (hardwareInstancedRendering && subMesh.getRenderingMesh().hasThinInstances) {
+            this._renderWithThinInstances(subMesh, fillMode, effect, engine);
+            return this;
+        }
+
         if (hardwareInstancedRendering) {
             this._renderWithInstances(subMesh, fillMode, batch, effect, engine);
         } else {
@@ -1728,7 +1751,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         }
 
         var engine = scene.getEngine();
-        var hardwareInstancedRendering = batch.hardwareInstancedRendering[subMesh._id];
+        var hardwareInstancedRendering = batch.hardwareInstancedRendering[subMesh._id] || subMesh.getRenderingMesh().hasThinInstances;
         let instanceDataStorage = this._instanceDataStorage;
 
         let material = subMesh.getMaterial();
@@ -2269,6 +2292,9 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         // Instances
         this._disposeInstanceSpecificData();
 
+        // Thin instances
+        this._disposeThinInstanceSpecificData();
+
         super.dispose(doNotRecurse, disposeMaterialAndTextures);
     }
 
@@ -2277,6 +2303,11 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         // Do nothing
     }
 
+    /** @hidden */
+    public _disposeThinInstanceSpecificData() {
+        // Do nothing
+    }
+
     /**
      * Modifies the mesh geometry according to a displacement map.
      * A displacement map is a colored image. Each pixel color value (actually a gradient computed from red, green, blue values) will give the displacement to apply to each mesh vertex.

+ 94 - 0
src/Meshes/thinInstanceMesh.ts

@@ -0,0 +1,94 @@
+import { Nullable } from "../types";
+import { Mesh, _InstancesBatch } from "../Meshes/mesh";
+import { VertexBuffer, Buffer } from './buffer';
+//import { SubMesh } from "./subMesh";
+//import { Effect } from "../Materials/effect";
+//import { Engine } from "../Engines/engine";
+
+declare module "./mesh" {
+    export interface Mesh {
+        setThinInstanceBuffer(kind: string, buffer: Float32Array,  stride: number): void;
+
+        /** @hidden */
+        _thinInstancedBuffersStorage: Nullable<{
+            instancesCount: number,
+            matrixBuffer: Nullable<Buffer>,
+            data: {[key: string]: Float32Array},
+            sizes: {[key: string]: number},
+            vertexBuffers: {[key: string]: Nullable<VertexBuffer> | Nullable<Array<VertexBuffer>>},
+            strides: {[key: string]: number},
+        }>;
+    }
+}
+
+Mesh.prototype.setThinInstanceBuffer = function(kind: string, buffer: Float32Array, stride: number = 0): void {
+    if (kind === "matrix") {
+        this.removeVerticesData("world0");
+        this.removeVerticesData("world1");
+        this.removeVerticesData("world2");
+        this.removeVerticesData("world3");
+        stride = 16;
+    } else {
+        this.removeVerticesData(kind);
+    }
+
+    if (!this._thinInstancedBuffersStorage) {
+        this._thinInstancedBuffersStorage = {
+            instancesCount: 0,
+            matrixBuffer: null,
+            data: {},
+            vertexBuffers: {},
+            strides: {},
+            sizes: {},
+        };
+    }
+
+    if (kind === "matrix") {
+        if (this._thinInstancedBuffersStorage.matrixBuffer) {
+            this._thinInstancedBuffersStorage.matrixBuffer.dispose();
+            this._thinInstancedBuffersStorage.matrixBuffer = null;
+        }
+
+        this._thinInstancedBuffersStorage.instancesCount = buffer.length / stride;
+
+        const matrixBuffer = new Buffer(this.getEngine(), buffer, true, stride, false, true);
+
+        this._thinInstancedBuffersStorage.matrixBuffer = matrixBuffer;
+        this._thinInstancedBuffersStorage.vertexBuffers[kind] = [
+            matrixBuffer.createVertexBuffer("world0", 0, 4),
+            matrixBuffer.createVertexBuffer("world1", 4, 4),
+            matrixBuffer.createVertexBuffer("world2", 8, 4),
+            matrixBuffer.createVertexBuffer("world3", 12, 4),
+        ],
+
+        this.setVerticesBuffer((this._thinInstancedBuffersStorage.vertexBuffers[kind] as Array<VertexBuffer>)[0]);
+        this.setVerticesBuffer((this._thinInstancedBuffersStorage.vertexBuffers[kind] as Array<VertexBuffer>)[1]);
+        this.setVerticesBuffer((this._thinInstancedBuffersStorage.vertexBuffers[kind] as Array<VertexBuffer>)[2]);
+        this.setVerticesBuffer((this._thinInstancedBuffersStorage.vertexBuffers[kind] as Array<VertexBuffer>)[3]);
+    } else {
+        this._thinInstancedBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), this._thinInstancedBuffersStorage.data[kind], kind, true, false, stride, true);
+        this.setVerticesBuffer(this._thinInstancedBuffersStorage.vertexBuffers[kind] as VertexBuffer);
+    }
+};
+
+Mesh.prototype._disposeThinInstanceSpecificData = function() {
+    if (this._thinInstancedBuffersStorage?.matrixBuffer) {
+        this._thinInstancedBuffersStorage.matrixBuffer.dispose();
+        this._thinInstancedBuffersStorage.matrixBuffer = null;
+    }
+
+    if (this._thinInstancedBuffersStorage) {
+        for (const kind in this._thinInstancedBuffersStorage) {
+            const vertexBuffer = this._thinInstancedBuffersStorage.vertexBuffers[kind];
+            if (vertexBuffer) {
+                if (Array.isArray(vertexBuffer)) {
+                    vertexBuffer.forEach((vbuffer) => vbuffer.dispose());
+                } else {
+                    vertexBuffer.dispose();
+                }
+            }
+        }
+    }
+
+    this._thinInstancedBuffersStorage = null;
+};

+ 4 - 1
src/PostProcesses/volumetricLightScatteringPostProcess.ts

@@ -206,6 +206,9 @@ export class VolumetricLightScatteringPostProcess extends PostProcess {
         if (useInstances) {
             defines.push("#define INSTANCES");
             MaterialHelper.PushAttributesForInstances(attribs);
+            if (subMesh.getRenderingMesh().hasInstances) {
+                defines.push("#define THIN_INSTANCES");
+            }
         }
 
         // Get correct effect
@@ -320,7 +323,7 @@ export class VolumetricLightScatteringPostProcess extends PostProcess {
                 return;
             }
 
-            var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null);
+            var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null || renderingMesh.hasThinInstances);
 
             if (this._isReady(subMesh, hardwareInstancedRendering)) {
                 var effect: Effect = this._volumetricLightScatteringPass;

+ 4 - 1
src/Rendering/depthRenderer.ts

@@ -118,7 +118,7 @@ export class DepthRenderer {
                 return;
             }
 
-            var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null);
+            var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null || renderingMesh.hasThinInstances);
 
             var camera = this._camera || scene.activeCamera;
             if (this.isReady(subMesh, hardwareInstancedRendering) && camera) {
@@ -237,6 +237,9 @@ export class DepthRenderer {
         if (useInstances) {
             defines.push("#define INSTANCES");
             MaterialHelper.PushAttributesForInstances(attribs);
+            if (subMesh.getRenderingMesh().hasInstances) {
+                defines.push("#define THIN_INSTANCES");
+            }
         }
 
         // None linear depth

+ 4 - 1
src/Rendering/geometryBufferRenderer.ts

@@ -300,6 +300,9 @@ export class GeometryBufferRenderer {
         if (useInstances) {
             defines.push("#define INSTANCES");
             MaterialHelper.PushAttributesForInstances(attribs);
+            if (subMesh.getRenderingMesh().hasInstances) {
+                defines.push("#define THIN_INSTANCES");
+            }
         }
 
         // Setup textures count
@@ -427,7 +430,7 @@ export class GeometryBufferRenderer {
                 return;
             }
 
-            var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null);
+            var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null || renderingMesh.hasThinInstances);
             var world = effectiveMesh.getWorldMatrix();
 
             if (this.isReady(subMesh, hardwareInstancedRendering)) {

+ 4 - 1
src/Rendering/outlineRenderer.ts

@@ -157,7 +157,7 @@ export class OutlineRenderer implements ISceneComponent {
         var scene = this.scene;
         var engine = scene.getEngine();
 
-        var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
+        var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null && batch.visibleInstances[subMesh._id] !== undefined || subMesh.getRenderingMesh().hasThinInstances);
 
         if (!this.isReady(subMesh, hardwareInstancedRendering)) {
             return;
@@ -274,6 +274,9 @@ export class OutlineRenderer implements ISceneComponent {
         if (useInstances) {
             defines.push("#define INSTANCES");
             MaterialHelper.PushAttributesForInstances(attribs);
+            if (subMesh.getRenderingMesh().hasInstances) {
+                defines.push("#define THIN_INSTANCES");
+            }
         }
 
         // Get correct effect

+ 3 - 0
src/Shaders/ShadersInclude/instancesDeclaration.fx

@@ -3,6 +3,9 @@
 	attribute vec4 world1;
 	attribute vec4 world2;
 	attribute vec4 world3;
+    #ifdef THIN_INSTANCES
+        uniform mat4 world;
+    #endif
 #else
 	uniform mat4 world;
 #endif

+ 3 - 0
src/Shaders/ShadersInclude/instancesVertex.fx

@@ -1,5 +1,8 @@
 #ifdef INSTANCES
 	mat4 finalWorld = mat4(world0, world1, world2, world3);
+    #ifdef THIN_INSTANCES
+	    finalWorld = world * finalWorld;
+    #endif
 #else
 	mat4 finalWorld = world;
 #endif

+ 3 - 1
src/scene.ts

@@ -1757,7 +1757,9 @@ export class Scene extends AbstractScene implements IAnimatable {
                 return false;
             }
 
-            let hardwareInstancedRendering = mesh.getClassName() === "InstancedMesh" || mesh.getClassName() === "InstancedLinesMesh" || engine.getCaps().instancedArrays && (<Mesh>mesh).instances.length > 0;
+            let hardwareInstancedRendering = (
+                    (mesh.getClassName() === "InstancedMesh" || mesh.getClassName() === "InstancedLinesMesh") && (<Mesh>mesh).instances.length > 0 || mesh.hasThinInstances
+                ) && engine.getCaps().instancedArrays;
             // Is Ready For Mesh
             for (let step of this._isReadyForMeshStage) {
                 if (!step.action(mesh, hardwareInstancedRendering)) {