Using InstancedMesh with a Scrolling Grid

I just learned about InstancedMesh and it seems tailor made for the nested scrolling grid that I have been using. Here is an example of the grid.

The grid consists of 3 sets of 16x16 planes (256 in total). Each set uses a different size plane. And each set uses 4 different colors. How can I group these?

Does each size of plane and each color of that plane have to be a different mesh for purposes of InstancedMesh? For example, do I have to use somethin like this?

mesh1R = THREE.InstancedMesh(geoSquareSmall, matRed, 64);
mesh1W = THREE.InstancedMesh(geoSquareSmall,matWhite,64);
mesh1B = THREE.InstancedMesh(geoSquareSmall,matBlue,64);
mesh1G = THREE.InstancedMesh(geoSquareSmall,matGreen,64);
mesh2R = THREE.InstancedMesh(geoSquareMedium, matRed, 64);
mesh2W = THREE.InstancedMesh(geoSquareMedium,matWhite,64);
mesh2B = THREE.InstancedMesh(geoSquareMedium,matBlue,64);
mesh2G = THREE.InstancedMesh(geoSquareMedium,matGreen,64);
mesh3R = THREE.InstancedMesh(geoSquareLarge, matRed, 64);
mesh3W = THREE.InstancedMesh(geoSquareLarge,matWhite,64);
mesh3B = THREE.InstancedMesh(geoSquareLarge,matBlue,64);
mesh3G = THREE.InstancedMesh(geoSquareLarge,matGreen,64);

Or can I group all of the same size meshes together?

I currently create and separate mesh for each plane and store those addresses in an array. I currently position and move these meshes using a custom routine which stores the X (east/west) and Z (north/south) positions in an array (you only need X positions for one row and Z positions for one column). Y (altitude) is the same for all.

One this is defined, do I need to add any commands to the animation loop?

Forgive the basic nature of these questions, but I want to make sure that I am full advantage of the capabilities of this command and not doing something stupid like double-rendering the meshes.

Too late!!!

It looks like I was able to figure this out, except for 1 mystery.

Here is the working example.

For the benefit of others, here is what I did:

Each grid of squares is defined using the same variables. For Grid1 (the inner grid), these are:

let Grd1 = {
	Typ:	1,					// Type of Grid - Inner or Outer
	RCs:	16,				// Rows and Columns - use odd number (for now = divisible by 3)
	Siz:	GrdSiz,				// Size of square
	Stp:	4,					// Steps
	RCi:	0,					// Rows and Columns Index (computed)
	MZV:	[0],				// Ground Z Value
	MXV:	[0],				// Ground X Value
	Nor:	0,					// Max North Square (updated)
	Est:	0,					// Max East Square (updated)
	Num:	0,					// Size of array (computed)
	Ptr:	[0],				// Ground Address
	Shd:	1,					// Shadow enabled
	RCF:	0,					// N/A
	NSA:	0,					// N/A
	EWA:	0,					// N/A
	Mat:	0,					// N/A
	Msh:	0,					// Instanced Mesh
	Dum:	new THREE.Object3D(),	// Dummy Variable
	Vis:	1					// Visibility Flag
}

To initialize the InstancedMesh for each Grid, I used the following code. Changes relating to Instanced Mesh are marked “IM”. Note: this has been corrected to fix the coding errors noted below.):

// Inner Grid
if (Grd.Typ == 1) {
	waves_.WtrNrm.repeat.set(1,1);
	WtrGeo = new THREE.PlaneGeometry(Grd.Siz,Grd.Siz,waves_.GrdSeg,waves_.GrdSeg);
	WtrGeo.rotateX(-Math.PI * 0.5);			
	WtrMat = new THREE.MeshPhysicalMaterial({
		color: WtrCol,
		displacementMap: waves_.WtrDsp,
		normalMap: waves_.WtrNrm,
		envMap: envMap,
		normalScale: new THREE.Vector2(2.5,2.5),
		metalness: 1.0,				// 1 for max reflection
		roughness: 0.7,				// 0 for max reflection
		reflectivity: 0.5,			// 1 for max reflection
		envMapIntensity: 5,			// max reflection suggested = 5
		premultipliedAlpha: true,
	});
	Grd.Msh = new THREE.InstancedMesh(WtrGeo, WtrMat, Grd.RCs*Grd.RCs);		// IM
	// Set Starting Position of Squares
	let n = 0;
	for (let x = 0; x < Grd.RCs; x++) {
		for (let z = 0; z < Grd.RCs; z++) {
			Grd.Dum.position.set(Grd.MXV[x],-Grd0.MPY-WavMax,-Grd.MZV[z]);	// IM
			Grd.Dum.updateMatrix();								// IM
			Grd.Msh.setMatrixAt(n, Grd.Dum.matrix);					// IM
			n++;
		}
	}
	scene.add(Grd.Msh);										// IM
}
// Outer Grids
else {
	waves_.WtrNrm.repeat.set(2,2);
	if (Grd.Typ == 3) waves_.WtrNrm.repeat.set(4,4);
	geometry = new THREE.PlaneGeometry(Grd.Siz,Grd.Siz);
	geometry.rotateX(-Math.PI * 0.5);
	material = new THREE.MeshPhysicalMaterial({
		color: WtrCol,
		normalMap: waves_.WtrNrm,
		envMap: envMap,
		normalScale: new THREE.Vector2(2.5,2.5),
		metalness: 1.0,				// 1 for max reflection
		roughness: 0.7,				// 0 for max reflection
		reflectivity: 0.5,			// 1 for max reflection
		envMapIntensity: 5,			// max reflection suggested = 5
		premultipliedAlpha: true,
	});
	Grd.Msh = new THREE.InstancedMesh(geometry, material, Grd.RCs*Grd.RCs);		// IM
	// Assemble and Set Starting Position of Squares
	let n = 0;
	for (let x = 0; x < Grd.RCs; x++) {
		for (let z = 0; z < Grd.RCs; z++) {
			Grd.Dum.position.set(Grd.MXV[x],-Grd0.MPY-WavMax,-Grd.MZV[z]);	// IM
			Grd.Dum.updateMatrix();								// IM
			Grd.Msh.setMatrixAt(n, Grd.Dum.matrix);					// IM
			n++;
		}
	}
	scene.add(Grd.Msh);										// IM
}

To move the squares within each Grid, I used the following:

for (let x = 0; x < Grd.RCs; x++) {
	for (let z = 0; z < Grd.RCs; z++) {
		Grd.Dum.position.set(Grd.MXV[x],-Grd0.MPY-WavMax,-Grd.MZV[z]);		// IM
		Grd.Dum.position.y = -Grd0.MPY-WavMax;						// IM
		Grd.Dum.visible = true;									// IM
		Grd.Dum.updateMatrix();									// IM
		Grd.Msh.setMatrixAt(n, Grd.Dum.matrix);						// IM
		n++;
	}
}
Grd.Vis = 1;

I used the following command to cause the Grids to update:

if (Grd.Typ>1) Grd.Msh.instanceMatrix.needsUpdate = true;					// IM s/b All???

This works perfectly. All of the grids update, including Grid1 which has moving squares and animated displacement and normal maps.

However, this is a bit of a mystery. I used that command when I first tried using InstancedMesh for only the outer Grids 2 and 3. But when I change it to:

Grd.Msh.instanceMatrix.needsUpdate = true;	

The Grid1 squares display as white spheres!

This is interesting in that this indicates that InstancedMesh works with animated textures, which could be useful for those modeling other kinds of terrain.

It appears to be working faster because I tried using the time savings to increase the segments in each Grid1 square from 256 to 512. Normally, this drops the frame rate below 60fps, but with InstancedMesh, I was able to get a solid 60 fps. (Unfortunately, the change did not yield the anticipated improvement in resolution.)

Any thoughts on why updating the mesh on Grid1 results in spheres? I want to make sure that I have not done something wrong that will be “fixed” later.

This is amazingly strange → squares shown as spheres. Is it possible to set up CodePen to try it?

The initialization of Grid 1 is almost identical to initialization of other grids. Would it be possible to combine them?

While playing with the code, the performance was excellent. At heading 45 deg there is a white spot that does not get closer as the plane flies towards it. Initially I thought it is some ship wreckage, but most likely it is a glitch. The spot is not shown in close up mode.

1 Like

Hi!
In Grd.Typ == 1 you create a plane

WtrGeo = new THREE.PlaneGeometry(Grd.Siz,Grd.Siz,waves_.GrdSeg,waves_.GrdSeg);

and then you instantiate an InstancedMesh like this:

Grd.Msh = new THREE.InstancedMesh(geometry, material, Grd.RCs*Grd.RCs);

What is geometry in this line? I think it has to be WrtGeo, but I may be wrong.
And the same about material.

2 Likes

Good catch! It is apparently one of those spheres that somehow escaped. That is odd because I don’t make Grid1 visible until you go at bit below 5000 feet. (No need for a displacement map until you get to lower altitudes.)

Here is a version where I have made the update apply to Grid1. So if you turn to 45 degrees and descend a bit below 5000 feet, you will see that the sphere has company.

Because one sphere is appearing, I would think that I made some error in the initialization. But you are right, I could use the same code to define and position both I tried that out and it works fine. (Ball still there.)

Interestingly, that little ball at 45 degrees drops below the waves as you descend, which makes sense because once I have designated Grd1 as not visible, I no longer make changes to the height or location of Grid1 squares. But it still shouldn’t be visible in the first place.

Here is the code where I turn Grid1 off:

	// If Grid 1 = First Invisible, Just Turn Off
	if (Grd.Typ == 1 && Grd0.Flg > 0 && Grd.Vis == 1) {
		for (let x = 0; x < Grd.RCs; x++) {
			for (let z = 0; z < Grd.RCs; z++) {
				Grd.Dum.visible = false;
				Grd.Dum.updateMatrix();
				Grd.Msh.setMatrixAt(n, Grd.Dum.matrix);
				n++;
			}
		}
		Grd.Vis = 0;
	}

The Grd0.Flg is set to 1 above 1500 meters. The Grd.Vis flags are all set to 1 at the beginning. So the program should initialize Grid1, but should make it invisible on the first update because Grd0.Flg>0 and Grd.Vis = 1.

I saw the spheres:

They look like geometries, not like some texture rendering issue. Most likely some sphere sneaked into the instance mesh. See @prisoner849’s comment about the geometry of Grid 1.

1 Like

You are absolutely right. The perils of cutting and pasting - since I use a special definition for the geometry and materials of Grid1.

However, the servers aren’t letting me upload the revised example and I have a plane to catch in a couple of hours (who needs sleep?).

It turns out that the original program was probably not even activating Grid1 since, if you slam onto the deck, you will see that there are no waves washing over the plane. Just a flat surface normal map - no displacement map. (Perhaps additional commands are needed after all to update vertices?)

I will take another look at this again tomorrow.

UPDATE (revised 4-15 6am)
To recap:
*The first version (with the single sphere at 45 degrees), incorrectly used geometry and material.
But since it was also incorrectly not updating Grid1, you only saw the initial sphere at 45 degrees, which was not updated.
*The second version updated Grid1, but not the geometry and material references. So you saw all the spheres.

Here is a version which corrects both errors above and “seems” to work correctly. However, as you cruise along at sea level, the displacement map should cause the waves to wash over you. Instead, the surface is flat. The normal map is updating, but the displacement map is not.

SMALL UPDATE (4/17)
I have made the changes to a single 16x16 grid version and everything seems to work. Both the normal and displacement maps are working. So the problem with the 3 grid version above must be another coding error on my part.
One surprise is that the version using InstancedMesh appears to be slower. I will let you judge: (I am using a very old and very slow laptop, so it may not be a good indication.)

  • Here is the original version.
  • Here is the version using InstancedMesh

FINAL NOTES (4/20)
I was wrong. The 4-15 version above is working. If you drop to sea level and move your viewpoint to level with or below the waves, you will see that there are visible waves and that the mesh is being displaced. The waves just weren’t as big as I was expecting.

Also, if there is interest, I can modify my simple CodePen example (similar to the 4/17 example) to incorporate the small changes needed to take advantage of InstancedMesh.

Finally, I will correct my erroneous code above so that viewers won’t make a similar mistake.

1 Like