Problem blending batch instances

Hello, Cinderists!
I’m still pretty new to Cinder and I’m working my way through the wonderful samples folder.

Currently I’m trying to blend multiple simple mesh instances using the instanced teapot sample as the base. I’m updating the instance positions over time, but there seems to be a problem with depth testing or the draw function? I wanted to blend all the instances with “Additive” blending, but I’m getting a weird result.

I was hoping for the instances to blend by depth, but it seems that the “draw call id”(?) for each instance stays the same. Is there an easy way to sort the instance draw calls based on depth?

I’m also adding the code to reproduce the effect. Could someone go through the code and point out what I’ve gotten wrong? There are two different approaches. First one is where the animations is done only in the vertex shader. The second one uses the VBO remapping function. For the remapping to work, you must uncomment it in the cpp and in the vertex shader.

Any help will be appreciated. Thank you.

main cpp file

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
#include "cinder\Utilities.h"
#include <math.h>

using namespace ci;
using namespace ci::app;
using namespace std;

class Blending001App : public App {
  public:
	void setup() override;
	void mouseDown( MouseEvent event ) override;
	void update() override;
	void draw() override;
	void resize();

	static void prepare(Settings *settings);

	std::vector<ci::vec3> positions;
	CameraPersp		mCam;
	gl::BatchRef	mBatch;
	gl::GlslProgRef mShader;
	gl::VboRef		instanceVbo;

	int const instances = 72 * 4;
	float const maxAngle = M_PI * 6.0;
	float const posRadius = 80.0;
	float const circleRadius = 24.0;
};

int window_x = 800;
int window_y = 800;

void Blending001App::setup()
{
	//Shader building
	mShader = gl::GlslProg::create(loadAsset("vert001.vert"), loadAsset("frag001.frag"));
	mShader->uniform("numInstances", instances);
	
	//VBO mesh
	gl::VboMeshRef mesh = gl::VboMesh::create(geom::Circle().radius(circleRadius).subdivisions(32));

	//Calculate instance positions
	for (int i = 0; i < instances; i++)
	{
		float loopRatio = i / (float)instances;
		vec3 radius = glm::vec3(posRadius, 0.0, 0.0);

		glm::mat4 rotationMat(1);
		rotationMat = glm::rotate(rotationMat, loopRatio * maxAngle, glm::vec3(0.0, 0.0, 1.0));

		vec3 instancePos = glm::vec3(rotationMat * vec4(radius, 1.0));

		positions.push_back(instancePos + vec3(0.0, 0.0, loopRatio));
	}

	//Same process as instanced teapots sample
	instanceVbo = gl::Vbo::create(GL_ARRAY_BUFFER, positions.size() * sizeof(vec3), positions.data(), GL_DYNAMIC_DRAW);

	geom::BufferLayout instanceLayout;
	instanceLayout.append(geom::Attrib::CUSTOM_0, 3, 0, 0, 1);

	mesh->appendVbo(instanceLayout, instanceVbo);

	mBatch = gl::Batch::create(mesh, mShader, { {geom::Attrib::CUSTOM_0, "vInstancePosition"} });
	
	gl::enableDepthRead();
	gl::enableDepthWrite();
	
}

void Blending001App::mouseDown( MouseEvent event )
{
}

void Blending001App::resize()
{
	mCam.setPerspective(30.0, getWindowAspectRatio(), 300, 700);
	gl::setMatrices(mCam);
}

void Blending001App::update()
{
	float t = getElapsedSeconds();
	//Update camera position
	vec3 camPos = vec3(30.0 * cos(t), 30.0 * sin(t), 400.0);
	mCam.lookAt(camPos, vec3( 0.0));

	//Shader uniform update
	mShader->uniform("uTime", t);

	// Update positions with mapping
	// Uncomment to work with VERSION 2 in vertex shader

	//auto ptr = (vec3*)instanceVbo->mapReplace();
	//for (auto &pos : positions) {
	//	pos.z = fract(pos.z + 0.002);
	//	*ptr++ = pos;		
	//}
	//instanceVbo->unmap();	
}

void Blending001App::draw()
{
	gl::setMatrices(mCam);

	{
		gl::ScopedBlendAdditive scpBlend;

		gl::clear( Color( 0.0, 0.0, 0.0) ); 

		mBatch->drawInstanced(instances);
	}
}

void Blending001App::prepare(Settings *settings)
{
	settings->setWindowSize(window_x, window_y);
	settings->setFrameRate(60.0f);
	settings->setTitle("Blending App");
}

CINDER_APP( Blending001App, RendererGl, &Blending001App::prepare )

vert001.vert

#version 150

uniform mat4	ciModelViewProjection;
uniform mat3	ciNormalMatrix;
uniform float   uTime;

in vec4		ciPosition;
in vec3		vInstancePosition; // per-instance position variable

out float 		ID;

float pi = 3.14159;

void main( void )
{
	//Pass through to fragment shader
	ID 			= gl_InstanceID;
	//Vertex Animation
 	vec3 anim 	= vInstancePosition;
 	float zPos 	= fract(anim.z + uTime * 0.3) * 200.0;
 	anim.z 		= zPos - 200.0;
 	//VERSION 1 - Vertex shader animation.
	gl_Position	= ciModelViewProjection * ( ciPosition + vec4( anim, 0 ) );

	//VERSION 2 - Remapping VBO in CPP file animation
	//Must uncomment mapping commands in CPP update function to work
	//gl_Position	= ciModelViewProjection * ( ciPosition + vec4(vInstancePosition*vec3(1.0, 1.0, 200.0)-vec3(0.0, 0.0,200.0), 0.0) );
	
}

frag001.frag

#version 150

uniform int 	numInstances;

in float ID;

out vec4 			oColor;

vec3 colorA = vec3(1.0, 0.68, 0.0);
vec3 colorB = vec3(0.05, 0.3, 0.8);

void main( void )
{
	float ratio = ID / float(numInstances);

	vec3 fragCol = mix(colorA, colorB, ratio);
	oColor = vec4( fragCol * 0.05 , 1.0);
}

Hi,

normally when doing additive blending, you simply disable the depth buffer (or depth testing). However, if depth is important for the effect you’re after, you should make sure to render your objects from back to front. As far as I know, there is no way to do this on the GPU (apart from using a compute shader to fill the instance buffer), so you should change the instance positions on the CPU in the update method and then sort them (also on the CPU) before issuing the draw call. This means moving the code of your vertex shader to your C++ code.

-Paul

Hi, @paul.houx !

Thanks for such a fast reply!
I did turn off the depth buffer and got the result I was hoping for. Thanks!

I did include the method of remapping the VBO data which is done on CPU side and was somehow hoping that Cinder magically sorts it under the hood. For the moment I’m satisfied with the result, but I will keep this approach in mind if I’ll need it in the future.

Thanks again.

Hi,

good to hear you succeeded. While Cinder sometimes feels like magic (black or white… I’ll leave that up to you), in the end it’s only a toolbox and toolboxes don’t make assumptions about the stuff you’d like to create. :slight_smile:

-Paul

Hi, Paul.

While we are on the topic of magic, I figured I should ask. :slight_smile:
Is there some magic behind how Cinder is affected by monitor resolution? The problem is that whenever I size any of my Apps window height larger than that of my main monitor, the app window turns black. For off-screen rendering I haven’t reached a limit yet.

I have a 1080p(main) and 2560x1440p monitor setup.

Cinder for me is more than a toolbox. It’s pretty interesting to learn how CG works in such a low-level.

Hi Willow,

Cinder uses both platform-specific code and OpenGL to create a window and render to it. OpenGL is used to talk to the GPU driver. If a window spans more than a single monitor, it could be that the GPU driver needs to perform some magic under the hood to send the correct content to each window. And sometimes, it simply can’t. This is a limitation of the GPU and its driver, not of Cinder.

There are ways to fix that problem. For example, you could try tweaking the monitor setup using the NVIDIA (or AMD) control panel until you find a setting that works. There’s also hardware available that sits between the 2 (or 3) monitors and your PC and acts like one big giant monitor. Lastly, you could create a separate window for each of the monitors and render the content to each window yourself. You may run into synchronization problems, where content on one monitor may seem to show up later than on other monitors, though.

Nice anecdote: while working on a project for Facebook, we had a 3x3 monitor video wall. The monitors were of the highest quality, with expensive glass fiber extenders and very expensive PC’s running the show. But we noticed that content seemed skewed: each row seemed to be shifted horizontally compared to the other rows. This was especially noticeable with horizontally scrolling content, which we had a lot. We checked everything, from hardware timings to application code. In the end, it turned out this was caused by the progressive scan of the monitors. Pixels at the top of each row were drawn slightly sooner than pixels at the bottom. Normally you can’t see this, but in our case it very much was apparent and we tried for 5 days to find a solution. We flipped the middle row of monitors upside down, so that pixels on opposite monitors were drawn at the same time. It required us to flip part of the content in OpenGL as well, which was more challenging than expected due to performance requirements, but it worked. I thought I’d share this story with you. :slight_smile:

-Paul

Hi, Paul

I see that I triggered your troubleshooting mindset. :slight_smile:

Checked out a few different scenarios and the problem persists, but most of the time it’s something related to the main monitor. Will have to figure that out by going through your suggestions. From what I’ve read here and there is that PC manufacturers setup their computers differently so the fix might be different on separate systems? But the NVIDIA control panel seems as a great place to start.

Haha, that’s a good one. Haven’t had the experience, but this field of real-time installations seems as one where you really have to know your tech also.

Not entirely related, but an real life anecdote that cheered me up recently was:
I mostly work with offline renders for VFX. The environment is such that there is no dedicated rendering guru. When an overnight render fails it’s hard to track down the cause of the problem if it’s inconsistent. I had my computer serviced and stress tested, but the crashes once in a while persisted. Some time later I discovered a pattern that the majority of the crashes happen after a certain period when I leave the PC. Aha! Power settings! But I have turned down every possible power management option and it still seems as the cause. For a few days I searched the web on how to dig super deep into the computer and adjust some alien settings. A colleague asked what I was doing. After my whole story, he gave me some pretty good advice: “You can just leave a movie on loop in a movie player while rendering.” Somehow that overrides all of those alien settings. Haven’t had a crash since…

Thanks Paul for the suggestions and the anecdote.

1 Like