Version 4 (modified by robert, 7 years ago)

Converted to osgViewer

Creating Textured Geometry using StateSets

(Wiki editing note: Code needs conversion to osgViewer)
(Wiki editing note: Add link to complete source code at bottom)

Goals

Add a texture to geometry defined by OpenGL drawing primitives introduced in tutorial Basic geometry?.

Background

The previous tutorial introduced viewing scenes that include basic shapes created from OpenGL primitives. This section explains how to add textures to these shapes. To make the code easier to use, we'll put the pyramid code into a function that creates a new geode and returns a pointer to it. The following code is from tutorial Basic geometry.

osg::Geode* createPyramid()
{
   osg::Geode* pyramidGeode = new osg::Geode();
   osg::Geometry* pyramidGeometry = new osg::Geometry();
   pyramidGeode->addDrawable(pyramidGeometry); 

   // Specify the vertices:
   osg::Vec3Array* pyramidVertices = new osg::Vec3Array;
   pyramidVertices->push_back( osg::Vec3(0, 0, 0) ); // front left 
   pyramidVertices->push_back( osg::Vec3(2, 0, 0) ); // front right 
   pyramidVertices->push_back( osg::Vec3(2, 2, 0) ); // back right 
   pyramidVertices->push_back( osg::Vec3( 0,2, 0) ); // back left 
   pyramidVertices->push_back( osg::Vec3( 1, 1,2) ); // peak

   // Associate this set of vertices with the geometry associated with the 
   // geode we added to the scene.
   pyramidGeometry->setVertexArray( pyramidVertices );

   // Create a QUAD primitive for the base by specifying the 
   // vertices from our vertex list that make up this QUAD:
   osg::DrawElementsUInt* pyramidBase = 
      new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
   pyramidBase->push_back(3);
   pyramidBase->push_back(2);
   pyramidBase->push_back(1);
   pyramidBase->push_back(0);

   // Add this primitive to the geometry: pyramidGeometry->addPrimitiveSet(pyramidBase);
   // code to create other faces goes here!
   // (removed to save space, see tutorial two)
   osg::Vec4Array* colors = new osg::Vec4Array;
   colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 0 red
   colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); //index 1 green
   colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); //index 2 blue
   colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); //index 3 white

   osg::TemplateIndexArray
      <unsigned int, osg::Array::UIntArrayType,4,4> *colorIndexArray;
   colorIndexArray = 
      new osg::TemplateIndexArray<unsigned int, osg::Array::UIntArrayType,4,4>;
   colorIndexArray->push_back(0); // vertex 0 assigned color array element 0
   colorIndexArray->push_back(1); // vertex 1 assigned color array element 1
   colorIndexArray->push_back(2); // vertex 2 assigned color array element 2
   colorIndexArray->push_back(3); // vertex 3 assigned color array element 3
   colorIndexArray->push_back(0); // vertex 4 assigned color array element 0

   pyramidGeometry->setColorArray(colors);
   pyramidGeometry->setColorIndices(colorIndexArray);
   pyramidGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

   // Since the mapping from vertices to texture coordinates is 1:1, 
   // we don't need to use an index array to map vertices to texture
   // coordinates. We can do it directly with the 'setTexCoordArray' 
   // method of the Geometry class. 
   // This method takes a variable that is an array of two dimensional
   // vectors (osg::Vec2). This variable needs to have the same
   // number of elements as our Geometry has vertices. Each array element
   // defines the texture coordinate for the cooresponding vertex in the
   // vertex array.
   osg::Vec2Array* texcoords = new osg::Vec2Array(5);
   (*texcoords)[0].set(0.00f,0.0f); // tex coord for vertex 0 
   (*texcoords)[1].set(0.25f,0.0f); // tex coord for vertex 1 
   (*texcoords)[2].set(0.50f,0.0f); // ""
   (*texcoords)[3].set(0.75f,0.0f); // "" 
   (*texcoords)[4].set(0.50f,1.0f); // ""
   pyramidGeometry->setTexCoordArray(0,texcoords);

   return pyramidGeode;
}

Loading a Texture, Creating a State Set, assigning it to a Node

The method for rendering primitives is controlled using StateSets. This section of code demonstrates how to load a texture from file, create a StateSet in which this texture is enabled, and assign this StateSet to a node in the scene. The first section starts out the same as previous tutorials. Initialize a viewer and build a scene with a single pyramid.

int main()
{
   osgViewer::Viewer viewer;

   // Declare a group to act as root node of a scene:
   osg::Group* root = new osg::Group();
   osg::Geode* pyramidGeode = createPyramid();
   root->addChild(pyramidGeode);

Now for adding a texture. Here we'll declare a texture instance and set its data variance as 'DYNAMIC'. (If we don't declare the texture as dynamic, some of the osg's optimization routines could remove it.) The texture class encapsulates OpenGL texture modes (wrap, filiter, etc.) as well as an osg::Image. The code below shows how to read an osg::Image instance from a file and associate this image with a texture.

   osg::Texture2D* KLN89FaceTexture = new osg::Texture2D;

   // protect from being optimized away as static state:
   KLN89FaceTexture->setDataVariance(osg::Object::DYNAMIC); 

   // load an image by reading a file: 
   osg::Image* klnFace = osgDB::readImageFile("KLN89FaceB.tga");
   if (!klnFace)
   {
      std::cout << " couldn't find texture, quiting." << std::endl;
      return -1;
   }

   // Assign the texture to the image we read from file: 
   KLN89FaceTexture->setImage(klnFace);

Textures can be associated with rendering StateSets. The next step is to create a StateSet, associate and enable our texture with this state set and assign the StateSet to our geometry.

   // Create a new StateSet with default settings: 
   osg::StateSet* stateOne = new osg::StateSet();

   // Assign texture unit 0 of our new StateSet to the texture 
   // we just created and enable the texture.
   stateOne->setTextureAttributeAndModes
      (0,KLN89FaceTexture,osg::StateAttribute::ON);
   // Associate this state set with the Geode that contains
   // the pyramid: 
   pyramidGeode->setStateSet(stateOne);

The last step is the simulation loop:

   //The final step is to set up and enter a simulation loop.
   viewer.setSceneData( root );

   return viewer.run();
}

Continue with tutorial Transforms and States?

Attachments