Interactive geometry replacement

Howdy,

I’m trying to use Optix to render a particle physics simulation and need to replace the particle GeometryGroup before each frame that Optix renders.

Here’s the code I’m using to add the new set of particles before each render.

optix::Group scene;
optix::GeometryGroup particleGeometry = nullptr;
float radius = .1f;

void addParticles ()
{
	if (particleGeometry)
		scene->removeChild(particleGeometry);

	scene->getAcceleration()->markDirty();
	
	particleGeometry = context->createGeometryGroup();
	particleGeometry->setAcceleration(context->createAcceleration("Trbvh"));
	
	for (auto position : particles)
	{
		Optix::Geometry particle = context->createGeometry();
		particle->setPrimitiveCount(1u);

		float4 sphereData = make_float4(position.x(), position.y(), position.z(), radius);
		particle["sphere"]->setFloat(sphereData);
		
		particle->setBoundingBoxProgram(sphereBoundsRtn->program);
		particle->setIntersectionProgram(sphereIntersectRtn->program);
		
		GeometryInstance inst = context->createGeometryInstance();
		inst->setGeometry(particle);
		inst->setMaterialCount(1);
		inst->setMaterial(0, particleMaterial);
		particleGeometry->addChild(inst);
	}
        scene->addChild(particleGeometry);
}

The particles render ok, but there’s an unexpected “stuttering” where some frames take quite a bit longer to render than others. Here’s a video that show what’s happening https://youtu.be/n-3t2vmIxaQ

And here’s some debugging output while rendering that shows the inconsistent render times

“Adding 50 particles”
“ActiveOptix::addParticles took 0.003296 seconds”
“ActiveOptix::raytraceScene took 0.093654 seconds”
“Adding 50 particles”
“ActiveOptix::addParticles took 0.005625 seconds”
“ActiveOptix::raytraceScene took 0.092935 seconds”
“Adding 50 particles”
“ActiveOptix::addParticles took 0.001944 seconds”
“ActiveOptix::raytraceScene took 0.092323 seconds”
“Adding 50 particles”
“ActiveOptix::addParticles took 0.004346 seconds”
“ActiveOptix::raytraceScene took 1.54243 seconds”
“Adding 50 particles”
“ActiveOptix::addParticles took 0.005745 seconds”
“ActiveOptix::raytraceScene took 0.107306 seconds”

Any ideas why the render times might vary by so much? Is there a better approach to accomplish what I’m trying to do here?

Thanks!

That is actually the worst performing scene setup you could have chosen. Just don’t do this!

I’ll answer why that is bad inside comments to your code excerpts:

optix::Group scene;
optix::GeometryGroup particleGeometry = nullptr;
float radius = .1f;

void addParticles ()
{
	if (particleGeometry)
		scene->removeChild(particleGeometry); // PERF: No need to remove the existing GeometryGroup, you could simply just update the existing buffer at the Geometry with the particle data.

	scene->getAcceleration()->markDirty(); // OK: But the scene's root node acceleration structure would only need to be refit if you didn't change the topology, which you do below and which gets you an expensive rebuild.
	
	particleGeometry = context->createGeometryGroup(); // BAD: See above, there should be only one for all particles kept. Same for the GeometryInstance. You only need to update a buffer on the Geometry node in this function.
	particleGeometry->setAcceleration(context->createAcceleration("Trbvh")); // MEM: Check if this acceleration structure is freed. PLace that somewhere outside this function.
	
	for (auto position : particles) // PERF: This is absolutely the slowest method to setup your particle geometry! DON'T DO THAT! 
	{
		Optix::Geometry particle = context->createGeometry(); // PERF: Try to combine identical geometric primtives into one Geometry node! See OptiX Programming Chapter 12 third paragraph.
		particle->setPrimitiveCount(1u); // PERF: Worst case scenario for performance is to put each primitive into its own Geometry node. Put them all into one Buffer instead.

		float4 sphereData = make_float4(position.x(), position.y(), position.z(), radius);
		particle["sphere"]->setFloat(sphereData); // PERF: Your primitive data inside one Buffer assigned to a single Geometry node for all particles should be that float4 type.
		
		particle->setBoundingBoxProgram(sphereBoundsRtn->program); // OK: Needed per Geometry node, but you should use only one Geometry for all particle primitives in that scene. Do this once only.
		particle->setIntersectionProgram(sphereIntersectRtn->program);
		
		GeometryInstance inst = context->createGeometryInstance(); // PERF: This shouldn't be created per Particle either. Same as with GeometryGroup and Geometry in this node hierarchy. Do this once only.
		inst->setGeometry(particle);
		inst->setMaterialCount(1);
		inst->setMaterial(0, particleMaterial);
		particleGeometry->addChild(inst); // This looks confusing because of the variable naming, particleGeometryGroup would be clearer.
	}
}

“Your primitive data inside one Buffer assigned to a single Geometry node for all particles should be that float4 type”

That’s what I was missing. I had originally tried to use just one Geometry mode but wasn’t quite sure how to forge all the particle data into one node.

Works even better than I had hoped now. Thanks for the help!.
https://youtu.be/j8VPkw8YKVU

Awesome performance! How many particles were that?

Yeah, the code you used initially was probably inspired by the OptiX ambient occlusion example which has only one sphere inside the scene and didn’t need to use a buffer for that.
But all examples using triangle meshes put their data into buffers at the Geometry node. Updating their vertex attributes for an animation would work exactly the same way. One version of this fluid simulation ray tracing demo was doing exactly that: https://youtu.be/h5mRRElXy-w?t=115

Try to set your root node acceleration property “refit” to “1”, so that if the topology of the bounding volumes underneath doesn’t change, it will only refit instead of rebuild.

The initial code also seemed to assume that if you destoy the root node of a sub-tree the whole sub-tree is destroyed as well. That is NOT the case! The C++ wrappers are a flat wrapper around the C-API only and ref-count just their own handles. They do not implement a scene graph manager. Means in your previous code, all the newly created OptiX objects possibly leaked. You must call destroy() on these to really delete the OptiX objects.

Also make sure to not name the buffer holding the sphere primitives “vertex_buffer”. That name unfortunately has a predefined meaning in the acceleration properties. See here:
https://devtalk.nvidia.com/default/topic/994092/-invalid-vertex-buffer-size-when-using-rtcontextlaunchprogressive2d/?offset=3
https://devtalk.nvidia.com/default/topic/1022634/acceleration-exceptions-issues/

That’s just 20,000 particles but I’ve tried 200,000 and the performance is just about the same.

“One version of this fluid simulation ray tracing demo was doing exactly that”
Cool! I hope to get to that point as soon as I get OpenVDB hooked up.

Thanks again for all the help!

I used the same approach for the particle-to_mesh output from OpenVDB and it works great … except OpenVDB can’t generate the meshes fast enough for real time rendering. Still pretty amazing considering how much work is being done under the hood. :)

https://youtu.be/z5fCht0ubMM