Billboard Shader Issues

Hi there. I am a GLSL newbie and I’m building my first serious project- a GPU-driven particle system using multiple FBOs to do physics processing and some other stuff.

I have had all of it working for nearly two weeks now… on nVidia hardware. But I’m having massive problems with HD-series Radeons.

Problems:

  1. Originally, I wanted to just use GL_POINTS and point-sprites.

This worked just fine on nVidia, but on ATi hardware, it seems to bork. The texture coordinates keep getting lost somehow.

Reading a bit on here, it appears that that this is a problem people are familiar with, so I was hoping maybe somebody knows what fixed-function stuff or GLSL I need to have set up before I submit my display list to the fragment shader to actually have it work as planned, or some magic combination of things I need to do within the vertex shader to pass the correct data to the fragment shader.

  1. The second problem’s more embarrassing, and I hope people don’t think too badly of me for having this problem, but I am a newbie in a lot of ways, and this problem turned out to be a gotcha.

I wrote an alternative workaround to just use quads instead, and billboard them in the final vertex shader, but I can’t seem to get the final rotation to billboard it right. I’ve read a lot of tutorials and looked at a lot of source, and it’s still not working, the matrix I supply doesn’t result in the right behaviors.

Anyhow, I apologize for the wall of text, but I wanted to make sure people understand what I’m trying to do, and where it’s going wrong. Here’s the full source of the vertex shader, both the point version and the quad version. The rest of it has been repeatedly verified to be operating properly- particle birth, death, motion simulation, etc. is all performed elsewhere in the code.

Point version, final vertex shader as of October 22, 2009. Confirmed working properly on my hardware (nVidia 7800GS) and on other nVidia GPUs, but does not pass working texture coordinates to ATi HD:

     		uniform sampler2D posTex;
      		uniform sampler2D dragTex;
		uniform vec3 camPos;
		varying vec2 losPos;
		varying vec2 offset;
		uniform float shaderTime;
		varying float timePercent;

		const float mapSize = ]] .. FloatStr(MaxSizeX) .. [[;
		const float dScale = 1000000.0;

		void main(void)
		{
			#extension GL_ARB_pointsize : enable
			vec2 texCoord = gl_Vertex.xy;
			offset = texCoord;

			vec4 posData = vec4(texture2D(posTex, texCoord));  // Get Color Data from sectors
			vec4 timeData = vec4(texture2D(dragTex, texCoord)); // get time data

			timePercent = ((shaderTime / dScale) - timeData.b) / (timeData.g - timeData.b);

			if (posData.r > 0.5)
			{
				posData.r = float((posData.r - 0.5) * (dScale * -1.0));
			} else {

				posData.r = float(posData.r * dScale);
			}

			if (posData.g > 0.5)
			{
				posData.g = float((posData.g - 0.5) * (dScale * -1.0));
			} else {
				posData.g = float(posData.g * dScale);
			}


			if (posData.b > 0.5)
			{
				posData.b = float((posData.b - 0.5) * (dScale * -1.0));
			} else {
				posData.b = float(posData.b * dScale);
			}

			float size = float(posData.w * dScale);
		
			gl_Vertex.x = posData.r;
			gl_Vertex.y = posData.g;
			gl_Vertex.z = posData.b;

			float myLength = distance(gl_Vertex.xyz, camPos.xyz);			

			gl_PointSize = float((size * 1e4 / myLength) / 2.0);
			if (gl_PointSize > 256.0 || gl_PointSize < 1.0)//SAFEGUARD CODE
			{
				gl_Vertex.xyz = vec3(-100000.0,100000.0,-100000.0);
			}

			gl_Position = ftransform();	
			gl_FrontColor.xy = gl_Vertex.xy;

			losPos = vec2(gl_Vertex.x/mapSize,gl_Vertex.z/mapSize);
			if (losPos.x < 0.0)
			{
				losPos.x = 0.01;
			}
			if (losPos.x < 0.0)
			{
				losPos.x = 0.01;
			}
			if (losPos.y > 1.0)
			{
				losPos.y = 0.99;
			}
			if (losPos.y > 1.0)
			{
				losPos.y = 0.99;
			}		
		}

Quad version, with a display list of GL_QUADS in the XY plane, draws nothing at all on my hardware (but the shader compiles, and it is using the same FBO / GLSL setup to get to this point, which I have confirmed working on ATi):

     		uniform sampler2D posTex;
      		uniform sampler2D dragTex;
		uniform vec3 camPos;
		varying vec2 losPos;
		varying vec2 offset;
		uniform float shaderTime;
		varying float timePercent;

		const float mapSize = ]] .. FloatStr(MaxSizeX) .. [[;
		const float dScale = 1000000.0;

		void main(void)
		{
			offset = gl_MultiTexCoord1.xy;
			gl_TexCoord[0].st = gl_Vertex.xy;

			vec4 posData = vec4(texture2D(posTex, offset));  // Get Color Data from sectors
			vec4 timeData = vec4(texture2D(dragTex, offset)); // get time data

			timePercent = ((shaderTime / dScale) - timeData.b) / (timeData.g - timeData.b);

			if (posData.r > 0.5)
			{
				posData.r = float((posData.r - 0.5) * (dScale * -1.0));
			} else {

				posData.r = float(posData.r * dScale);
			}

			if (posData.g > 0.5)
			{
				posData.g = float((posData.g - 0.5) * (dScale * -1.0));
			} else {
				posData.g = float(posData.g * dScale);
			}


			if (posData.b > 0.5)
			{
				posData.b = float((posData.b - 0.5) * (dScale * -1.0));
			} else {
				posData.b = float(posData.b * dScale);
			}

			float size = float(posData.w * dScale);
			float myLength = distance(gl_Vertex.xyz, camPos.xyz);

			float finalSize = float((size * 1e4 / myLength) / 2.0);

			gl_Position = vec4(posData.xyz,1.0);

			// offset vertex from center of the polygon
			gl_Vertex *= gl_Vertex * finalSize;

			// end
			gl_Position = gl_ProjectionMatrix * gl_Vertex;

			losPos = vec2(gl_Vertex.x/mapSize,gl_Vertex.z/mapSize);
			if (losPos.x < 0.0)
			{
				losPos.x = 0.01;
			}
			if (losPos.x < 0.0)
			{
				losPos.x = 0.01;
			}
			if (losPos.y > 1.0)
			{
				losPos.y = 0.99;
			}
			if (losPos.y > 1.0)
			{
				losPos.y = 0.99;
			}
					
		}

Anyhow, I have posted here as a last resort, hoping somebody can help me out. I know that billboards are considered “simple”, but once I moved to GLSL from fixed-function, I am in over my head.

wow, this is a long shader for some billboarding! You are thinking way too complicated here. You actually dont need the camera position for instance since that allready saved in the opengl matrices. something like this should do the trick:


void main( void )
{
    vec4 wpos = vec4(gl_MultiTexCoord0.xyz, 1.0);
    vec4 epos = gl_ModelViewMatrix * wpos;
    epos.xy += gl_Vertex.xy * gl_MultiTexCoord0.w; 
    gl_Position = gl_ProjectionMatrix * epos;
    gl_TexCoord[0] = gl_Vertex*0.5 + vec4(0.5);
    gl_FrontColor = gl_Color;
}

then for each billboard you save its position and radius in glTexCoord4f(x,y,z,r) and simply draw a quad like this:


	glBegin(GL_QUADS);
	glVertex3f(-1.0, -1.0,0);
        glVertex3f(1.0, -1.0,0);
        glVertex3f(1.0, 1.0,0);
	glVertex3f(-1.0, 1.0,0);
	glEnd();

this is a very simple billboard shader. Google has a lot more of them to offer :slight_smile:

[b]OK, so… let’s say that the quads are already in a display list. The reason why the shader’s so huge, btw, is that it’s unloading a bunch of values from floating-point textures that I’m manipulating with FBO and some other shaders.

You’re basically telling me that if I put the position XYZ into wpos in a vertex shader (substituting for gl_MultiTexCoord0), then store the final transform in gl_MultiTexCoord4, that the quad will get automatically rotated to the correct view?

I mean, if I’m supposed to do something specific with the value of gl_MultiTexCood4, I’m not sure what it is.

[EDIT]

OK, I tested the code as given, substituting posData (my final values for worldspace of a given particle):

vec4 wpos = vec4(posData.xyz, 1.0);
vec4 epos = gl_ModelViewMatrix * wpos;
epos.xy += gl_Vertex.xy * gl_MultiTexCoord0.w; 
gl_Position = gl_ProjectionMatrix * epos;
gl_TexCoord[0] = gl_Vertex*0.5 + vec4(0.5);
gl_FrontColor = gl_Color;

Result: I see quads, and they’re billboarded! Yay!

However, they move around willy-nilly in worldspace when I turn the POV! Boo!

What’s causing that last problem? Camera position in worldspace causes the particles to change apparent direction and position, but more importantly, vector of apparent motion (i.e., it’s all wrong, but they’re all behaving the same way).

The POINTS version behaves correctly, using ftransform(). What’s the difference between these two? There is obviously a key ingredient missing here.[/b]

Struck for future reference. The mistake was mine; the code worked exactly as advertised.

OK! It works! I made a mistake elsewhere in my fixed-function setup! Thanks so much for your help!

Don’t know if you worked on simplifying your shader code, but note that your sequence of code handling ‘posData’ could be expressed as :


posData.rgb = dScale * abs(posData.rgb - vec3(0.5));

thus removing a whole bunch of useless ‘ifs’, among other possible simplifications…

Cheers.