question

Clair A avatar image
1 Like"
Clair A asked Clair A commented

Are there specific commands to draw meshes with instance rendering ?

Hi,

Since FlexSim 17.2, support for instanced meshes has been added. For example, the rollers of a conveyor are drawn using instance rendering.

Are there specific commands or parameters to draw meshes using instance rendering ?

Situation: we have a heavy 3D shape representing a storage area with hundreds of boxes. To improve render performance, if possible I would like to draw these hundreds of boxes directly in FlexSim using instanced rendering, just like for the rollers of a conveyor. This is for a VR model so our ultimate goal is to keep 90 frames per second in the headset, even if there is a large amount of boxes in the static scene.

FlexSim 18.0.2
meshinstance rendering
· 1
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

Clair A avatar image Clair A commented ·
0 Likes 0 ·

1 Answer

·
Phil BoBo avatar image
3 Likes"
Phil BoBo answered Clair A commented

Instanced rendering can be done using the Module SDK. To do instanced rendering, you need to add a custom shader and use it in conjunction with an instanced mesh.

Adding a custom shader

You can customize a vertex shader by adding a node to MAIN:/project/exec/globals/shaders/vertex. For an example of this, see MAIN:/project/exec/globals/shaders/vertex/conveyorInstanceTransform.

To use the customized vertex shader within a shader program, you need to create a custom shader program in MAIN:/project/exec/globals/shaders/programs. For an example of this, see MAIN:/project/exec/globals/shaders/programs/animatedConveyor. This program uses the default "animated" vertex shader with the conveyorInstanceTransform vertex shader defining a custom function used in the shader called transformInstancedVert(). This program uses the AUTO fragment shaders so that it works with all the various shadow settings.

After customizing or changing the nodes under MAIN:/project/exec/globals/shaders, you can use the following code to compile and link the shaders:

applicationcommand("refreshshaders");

That will print any shader compile errors to the System Console and set the shader ID values on the nodes.

That command will be called automatically when the application starts for the existing nodes and nodes added by the Module SDK, but you can call it manually from a Script window to test it while you are adding and changing values in those nodes during development.

Using instanced meshes

The InstancedMesh class is defined in mesh.h. You use this class to render instanced meshes. It is a subclass of the IndexedMesh class.

(In these instructions, I will show some code snippets from how the Conveyor Module renders its rollers. These snippets will not be fully-functional code, as I will only show certain pertinent functions and not the entire code. For example, certain variables may be used but the code that defines and sets these variables may be omitted for simplicity.)

The first step is to define the instanced mesh on your class. For example:

class Conveyor
{
	InstancedMesh rollerMesh;
	int instanceTransformID;
}

The next step is to use InstancedMesh.addInstancedVertexAttrib() to tell the mesh about the vertex attribute in your shader that you will use for accessing per-instance data when rendering the mesh. For example:

Conveyor::Conveyor()
{
	instanceTransformID = rollerMesh.addInstancedVertexAttrib("fs_InstanceTransform", 16, GL_FLOAT, 1, false);
}

Then, when building the mesh, you can use addInstancedVertex() and setInstancedVertexAttrib() to add instanced vertex data one at a time, or you can use defineInstancedVertexAttribs() to add all the instanced vertex data at once. For example:

void StraightConveyor::buildRenderMesh()
{
	std::vector<glm::mat4> instanceTransforms;
	glm::mat4 curTransform(1.0f);
	curTransform = glm::translate(curTransform, glm::vec3(0.5 * transPerRoller.x, 0.5 * transPerRoller.y, -0.51 * type->rollerDiameter + 0.5 * transPerRoller.z));
	for (int i = 0; i < numRollers; i++) {
		curTransform = glm::translate(curTransform, glm::vec3(0, yOffset, 0));
		curTransform = glm::scale(curTransform, glm::vec3(1.0, yScale, 1.0));
		curTransform = glm::rotate(curTransform, rollerAngle, glm::vec3(0, 0, 1));
		instanceTransforms.push_back(curTransform);
	}
	if (instanceTransforms.size() > 0)
		rollerMesh.defineInstancedVertexAttribs(instanceTransforms.size(), instanceTransformID, &(instanceTransforms.front()[0][0]));
}

In your onDraw() code, when you want to render the instanced mesh, you need to use your custom shader program and set it back after you are finished rendering. You do this with some application commands:

double ConveyorSystem::onDraw(treenode view)
{
	int lastProgram = useConveyorShader(view);
	drawRenderMode(view);
	revertToStandardShader(lastProgram);
}

int ConveyorSystem::useConveyorShader(treenode view)
{
	int lastProgram = applicationcommand("useshaderprogramauto", "simpleConveyor", "animatedConveyor", view);
	int newProgram = applicationcommand("getshaderprogram");

	if (newProgram) {
		bool shaderChanged = (fs_InstanceTransformID < 0);
		if (fglinfo(FGL_INFO_SHADERTYPE, view) == SHADERTYPE_DEFAULT && !getpickingmode(view)) {
			static int programId = -1;
			if (newProgram != programId) {
				programId = newProgram;
				shaderChanged = true;
			}
		}
		if (shaderChanged) {
			fs_InstanceTransformID = glGetAttribLocation(newProgram, "fs_InstanceTransform");
		}

		if (fs_InstanceTransformID >= 0) {
			glm::mat4 instanceMatrix(1.0);
			glVertexAttrib4fv(fs_InstanceTransformID, &(instanceMatrix[0][0]));
			glVertexAttrib4fv(fs_InstanceTransformID + 1, &(instanceMatrix[1][0]));
			glVertexAttrib4fv(fs_InstanceTransformID + 2, &(instanceMatrix[2][0]));
			glVertexAttrib4fv(fs_InstanceTransformID + 3, &(instanceMatrix[3][0]));
		}
	} else {
		// turn off render mode if we don't have a shader that supports it (e.g., a generic OpenGL context)
		renderMode = 0;
	}

	return lastProgram;
}

void ConveyorSystem::revertToStandardShader(int lastProgram)
{
	applicationcommand("useshaderprogram", lastProgram);
}

In your draw code, you will call InstancedMesh.draw() and pass it a separate IndexedMesh that you defined and built (not shown in this example code). In your case, you would build your box as an IndexedMesh and pass it as a parameter to your storage area of boxes. For the conveyors, it creates an indexed mesh for one roller and passes it to the instanced mesh to draw lots of rollers:

void ConveyorSystem::drawRenderMode(treenode view)
{
	IndexedMesh* mesh = system->getShapeMesh("WhiteRoller");
	if (mesh) {
		rollerMesh.draw(GL_TRIANGLES, mesh);
	}
}

Conclusion

Instanced rendering has many interconnected steps involving using the Module SDK, custom shaders, and the Mesh API. If you follow these steps to build your own module with instanced rendering and run into trouble, you can post a question with your module's source code here on Answers and we may be able to figure out how to help you.

If you are trying to render a static shape of a bunch of boxes that doesn't change as the model is running, then it would be easier to simply load a static shape that is well optimized instead of instanced rendering. Instanced rendering is helpful when what you want to draw is more dynamic, such as rotating conveyor rollers on a customizable conveyor system. If the boxes in your storage area are dynamically added, moved, removed, etc., then yes, instanced rendering will be faster than drawing them one at a time, but keep in mind that it requires a fair amount of effort to do instanced rendering.

· 1
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

Clair A avatar image Clair A commented ·

Thank you very much @phil.bobo, this is very well explained.

0 Likes 0 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.