Shader with tex coords passed as attributes

85 views
Skip to first unread message

EdwardP

unread,
Feb 6, 2013, 12:25:54 PM2/6/13
to g3d-...@googlegroups.com
Chaps,

I've been trying to move my particle system over to shaders (which I am trying to fathom as I go along) but I've reached a complete standstill and would really appreciate some advice. I have reduced my code to the simplest form I can - one textured quad. But I still cannot seem to provide, or get the shader to use, the texture coordinates set as an attribute. If I use a hardwired tex coord in the shader the colour is found correctly, so I think that texture is available and working. However when I try to use gl_TexCoord[0].st I just seem to always get the colour at 0,0 (I think). Unfortunately I can't find many examples in G3D to use a reference - the code I have is largely lifted from Draw::skyBox.

My draw code is:

void ParticleEmitter::Draw()
{
   m_render_device->pushState();
   m_render_device->setProjectionAndCameraMatrix(m_camera->GetProjection(), m_camera->GetCoordinateFrame());
   m_render_device->setObjectToWorldMatrix(CoordinateFrame(GetOrientation().toRotationMatrix(), GetPosition()));
   m_render_device->setCullFace(CullFace::NONE);

   static Args args;
   static shared_ptr<Shader> shader;
   static bool init = true;
   if (init) 
   {
      Shader::Specification shaderSpec;
      shaderSpec[Shader::VERTEX] = Shader::Source(STR(
      void main() 
      {
         gl_Position = g3d_ModelViewProjectionMatrix * gl_Vertex;
      }));

      shaderSpec[Shader::PIXEL] = Shader::Source(STR(
      uniform sampler2D texture;
      void main()
      {
         gl_FragColor = texture2D(texture, gl_TexCoord[0].st);
         //gl_FragColor = texture2D(texture, vec2(0.4, 0.4));
      }));

      shader = Shader::create(shaderSpec);

      args.geometryInput = PrimitiveType::QUADS;

      float s = 5.0f;
      // Used for the failed attempt to use Attribute arrays 
      //static Array<Vector4> positions;
      //positions.append(Vector4(-s, -s, 0, 0)); // bottom left
      //positions.append(Vector4(-s, +s, 0, 0)); // top left           
      //positions.append(Vector4(+s, +s, 0, 0)); // top right        
      //positions.append(Vector4(+s, -s, 0, 0)); // bottom right

      static Array<Point3> point_positions;
      point_positions.append(Point3(-s, -s, 0)); // bottom left
      point_positions.append(Point3(-s, +s, 0)); // top left           
      point_positions.append(Point3(+s, +s, 0)); // top right       
      point_positions.append(Point3(+s, -s, 0)); // bottom right

      static Array<Vector2> tex_coords;
      tex_coords.append(Vector2(0, 0));
      tex_coords.append(Vector2(0, 1));
      tex_coords.append(Vector2(1, 1));
      tex_coords.append(Vector2(1, 0));


      static Array<int> indices;
      indices.append(0, 1, 2, 3);

      // Upload to GPU
      //shared_ptr<VertexBuffer> vbuffer = VertexBuffer::create( (sizeof(Vector4) + sizeof(Vector2) ) * positions.size() + sizeof(int) * indices.size());

      //AttributeArray gpuVertex   = AttributeArray(positions, vbuffer);
      //AttributeArray gpuTexCoord = AttributeArray(tex_coords, vbuffer);
      //IndexStream gpuIndices  = IndexStream(indices, vbuffer);

      //args.setAttributeArray("gl_Vertex", gpuVertex);
      //args.setAttributeArray("gl_MultiTexCoord0", gpuTexCoord);
      //args.setIndexStream(gpuIndices);

      args.setAttributePointer("gl_Vertex", point_positions);
      args.setAttributePointer("gl_TexCoord", tex_coords);
      args.setIndexPointer(indices);
      init = false;
   }
   Texture::Ref particle_texture = Texture::fromFile("data/Textures/bling2.jpg");
   args.setUniform("texture", particle_texture);

   m_render_device->apply(shader, args);
   m_render_device->popState();
}

From the code comments it seemed to me that the I should be *actually* be trying to use AttributeArrays? You can see from the commented out sections above how I tried to do this, but with even less success unfortunately - not only can I not seem to texture the quad, but I also appear to lose the gl_Vertex transformation too so I just get one massive untransformed coloured quad.

If you can spot my stupid mistake or point me to some existing example code/docs, I would be *very* grateful.

Cheers

E

Michael Mara

unread,
Feb 6, 2013, 1:22:32 PM2/6/13
to g3d-...@googlegroups.com
Hello Edward,

There are several things going on here. 
First off, I believe you can get your code working the way you want it to simply by adding:
gl_TexCoord[0] = gl_MultiTexCoord0;
to your vertex shader. (Your fragment shader has no idea what your texture coordinate is since you never set it in the vertex shader.)

Second: you should be using AttributeArrays as you have surmised; cpu pointers for attributes are deprecated in OpenGL and we are only supporting them as a stop gap while we change. I believe your commented out code should work if you make one change: set the 4th coordinate of all your positions to 1, not 0 (or use Point3s, as in your current code). Unprojected points in homogenous coordinates should have a w component of 1, or they will not be transformed correctly by projection matrices (and OpenGL will default the w coordinate to 1 if you don't specify it). I recommend you take a bit of time to learn about homogenous coordinates, since you are diving into shader work.

Third: I recommend completely ditching the OpenGL built-in attributes such as gl_Vertex and gl_Texcoord, and instead using generic attributes. See the pixelShader sample in G3D for more info, but basically all you have to do is declare the attributes in the vertex shader:

attribute vec2 myTexCoord;

and assign them to a "varying" ("out" in GLSL 1.3+) in the vertex shader, and they will be passed as a "varying" ("in" in GLSL 1.3+) to the pixel shader, where you can then use them (attributes are interpolated across primitives, so the pixel in the middle of a square with texcoords (0,0),(0,1),(1,1),(1,0) will have a texcoord value of (0.5, 0.5), as desired. 

Check out G3D9\samples\pixelShader\data-files\phong.pix and G3D9\samples\pixelShader\data-files\phong.vrt for an example (though using positions and normals, not texCoords).

Cheers,
Michael

EdwardP

unread,
Feb 6, 2013, 5:32:00 PM2/6/13
to g3d-...@googlegroups.com
Michael,

Thank you so much!! I followed your advice and cut straight to the AttributeArrays solution - I probably should have known about the w coord really, but there is no doubt I wouldn't have spotted it without your help! I'm passing in the Point3's now and then 'casting' them up to vec4's in the shader so I can multiply them with the Mat4x4 - I hope this is a sensible approach?

In case anyone else is wondering, here is the final version of the code which now renders the textured quad (Michael, please feel free to correct anything that is still a bit dodgy):

void ParticleEmitter::Draw()
{
   m_render_device->pushState();
   m_render_device->setProjectionAndCameraMatrix(m_camera->GetProjection(), m_camera->GetCoordinateFrame());
   m_render_device->setObjectToWorldMatrix(CoordinateFrame(GetOrientation().toRotationMatrix(), GetPosition()));
   m_render_device->setCullFace(CullFace::NONE);

   static Args args;
   static shared_ptr<Shader> shader;
   static bool init = true;
   if (init) 
   {
      Shader::Specification shaderSpec;
      shaderSpec[Shader::VERTEX] = Shader::Source(STR(
      attribute vec3 myVertex;
      attribute vec2 myTexCoord;
      varying vec2 tex_coord;
      void main() 
      {
         gl_Position = g3d_ModelViewProjectionMatrix * vec4(myVertex.x, myVertex.y, myVertex.z, 1.0f);
         tex_coord = myTexCoord;

      }));

      shaderSpec[Shader::PIXEL] = Shader::Source(STR(
      uniform sampler2D texture;
      varying vec2 tex_coord;
      void main()
      {
         gl_FragColor = texture2D(texture, tex_coord);
      }));

      shader = Shader::create(shaderSpec);

      args.geometryInput = PrimitiveType::QUADS;

      float s = 5.0f;
      static Array<Point3> point_positions;
      point_positions.append(Point3(-s, -s, 0)); // bottom left
      point_positions.append(Point3(-s, +s, 0)); // top left           
      point_positions.append(Point3(+s, +s, 0)); // top right       
      point_positions.append(Point3(+s, -s, 0)); // bottom right

      static Array<Vector2> tex_coords;
      tex_coords.append(Vector2(0, 0));
      tex_coords.append(Vector2(0, 1));
      tex_coords.append(Vector2(1, 1));
      tex_coords.append(Vector2(1, 0));

      static Array<int> indices;
      indices.append(0, 1, 2, 3);

      // Upload to GPU
      shared_ptr<VertexBuffer> vbuffer = VertexBuffer::create( (sizeof(Point3) + sizeof(Vector2) ) * point_positions.size() + sizeof(int) * indices.size());

      AttributeArray gpuVertex   = AttributeArray(point_positions, vbuffer);
      AttributeArray gpuTexCoord = AttributeArray(tex_coords, vbuffer);
      IndexStream gpuIndices  = IndexStream(indices, vbuffer);

      args.setAttributeArray("myVertex", gpuVertex);
      args.setAttributeArray("myTexCoord", gpuTexCoord);
      args.setIndexStream(gpuIndices);

      init = false;
   }
   Texture::Ref particle_texture = Texture::fromFile("data/Textures/bling2.jpg");
   args.setUniform("texture", particle_texture);

   m_render_device->apply(shader, args);
   m_render_device->popState();
}

Using this to render my particle list still seems a way off, but things always seem brighter when I have at least something rendering! :)

Thanks again,

E

Morgan McGuire

unread,
Feb 6, 2013, 5:42:17 PM2/6/13
to g3d-...@googlegroups.com
When you get it rendering, consider updating the wiki with the
relevant code or tips.

-m

Prof. Morgan McGuire
Computer Science Department
Williams College
http://cs.williams.edu/~morgan
> --
> You received this message because you are subscribed to the Google Groups
> "G3D Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to g3d-users+...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Michael Mara

unread,
Feb 6, 2013, 5:47:45 PM2/6/13
to g3d-...@googlegroups.com
Great, glad I could be of help! I'll offer three more suggestions:

First: vec4(myVertex, 1.0) is semantically the same as what you wrote, and a bit more concise.
Second: I'd save out your vertex and pixel shaders to files if I were you, and load them using Shader::fromFiles (again, see the samples for examples). Then you can dynamically reload the shaders simply by pressing F5 while running your program. This allows you to quickly debug, write and test shaders.
Third: There are two approaches that have their own strengths and weaknesses for rendering an entire particle list. Either dynamically resize the attribute array with N copies of the square, or (the way I'd do it), keep your attribute arrays as they are, and upload position data for the particles in a texture every frame, and render multiple instances of your quad using args.setNumInstances(), using gl_InstanceID in the shader to compute an index into your texture.

I second Morgan's note about updating the wiki when you get it running; I think it would be generally useful for the rest of our users.

-Michael

EdwardP

unread,
Feb 7, 2013, 5:50:42 AM2/7/13
to g3d-...@googlegroups.com
Thanks for the extra top-tips. Ultimately I would like to end up with a proper instancing solution, but I think I will need to eat this elephant a bite at a time! So I'll try and implement the conceptually simpler first approach this weekend and see how I get on. 

I certainly will update the wiki according to my experiences in due course but you will all need to follow up and correct the guaranteed misconceptions and factual errors ;)

Cheers

E
Message has been deleted

EdwardP

unread,
Feb 12, 2013, 4:50:21 AM2/12/13
to g3d-...@googlegroups.com
Hello all,

I finished the basic implementation of a particle system:


The vid shows two 'flares' with 100 or so particles each, but different tint and particle size settings. I've also used the emitter with static particles to render the galaxy map (using the positions that emerge from the Elite galaxy generation algorithm) in the Navigation screen at the end.

The implementation is Michael's first suggestion - so it has a simple shader that just does the projection transform and the colour tint with most of the work is still being done on the CPU. But... it works as well as I need for now. :) The way I've structured it is that the emitter will be largely the same for all implementations, but that I derive new particle classes for each effect to add to it. All the particles for one emitter have to use the same texture, but can have independent coordinates to allow for animation. 

At the moment I just stack the card with as many quads as are required, so the system will not degrade gracefully - I'm struggling to see how one finds the remaining available memory using OpenGL so that I can batch the calls if necessary?

Anyway, thanks for your help and I hope this is interesting for others.

E
Reply all
Reply to author
Forward
0 new messages