ManualSphereMeshes         Creating a sphere mesh manually with nice texture coordinates and normals

You could always generate a {LEX()}sphere{LEX} in your favorite modeller, but given the nature of some of the Ogre exporters, it's not always so easy.

The following code snippet will make a sphere, making full use of hardware {LEX()}vertex{LEX} and {LEX()}index buffer{LEX}s for performance.

Original credit for this code goes to mcspy0312 from a post of his on the forums, but it looks like he got it from elsewhere too. His initial code was for a "sky sphere," with the faces pointing inwards. I modified it so that the faces had an outwards orientation and fixed a bug in the texture coordinate code.

C# version is also available in MOGRE ManualSphereMeshes.

If you have a screenshot of a sphere, please upload and add it.

Snippet

This code creates a sphere mesh with the given name:

void createSphere(const std::string& strName, const float r, const int nRings = 16, const int nSegments = 16)
 {
     MeshPtr pSphere = MeshManager::getSingleton().createManual(strName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
     SubMesh *pSphereVertex = pSphere->createSubMesh();
 
     pSphere->sharedVertexData = new VertexData();
     VertexData* vertexData = pSphere->sharedVertexData;
 
     // define the vertex format
     VertexDeclaration* vertexDecl = vertexData->vertexDeclaration;
     size_t currOffset = 0;
     // positions
     vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
     currOffset += VertexElement::getTypeSize(VET_FLOAT3);
     // normals
     vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
     currOffset += VertexElement::getTypeSize(VET_FLOAT3);
     // two dimensional texture coordinates
     vertexDecl->addElement(0, currOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
     currOffset += VertexElement::getTypeSize(VET_FLOAT2);
 
     // allocate the vertex buffer
     vertexData->vertexCount = (nRings + 1) * (nSegments+1);
     HardwareVertexBufferSharedPtr vBuf = HardwareBufferManager::getSingleton().createVertexBuffer(vertexDecl->getVertexSize(0), vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
     VertexBufferBinding* binding = vertexData->vertexBufferBinding;
     binding->setBinding(0, vBuf);
     float* pVertex = static_cast<float*>(vBuf->lock(HardwareBuffer::HBL_DISCARD));
 
     // allocate index buffer
     pSphereVertex->indexData->indexCount = 6 * nRings * (nSegments + 1);
     pSphereVertex->indexData->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(HardwareIndexBuffer::IT_16BIT, pSphereVertex->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
     HardwareIndexBufferSharedPtr iBuf = pSphereVertex->indexData->indexBuffer;
     unsigned short* pIndices = static_cast<unsigned short*>(iBuf->lock(HardwareBuffer::HBL_DISCARD));
 
     float fDeltaRingAngle = (Math::PI / nRings);
     float fDeltaSegAngle = (2 * Math::PI / nSegments);
     unsigned short wVerticeIndex = 0 ;
 
     // Generate the group of rings for the sphere
     for( int ring = 0; ring <= nRings; ring++ ) {
         float r0 = r * sinf (ring * fDeltaRingAngle);
         float y0 = r * cosf (ring * fDeltaRingAngle);
 
         // Generate the group of segments for the current ring
         for(int seg = 0; seg <= nSegments; seg++) {
             float x0 = r0 * sinf(seg * fDeltaSegAngle);
             float z0 = r0 * cosf(seg * fDeltaSegAngle);
 
             // Add one vertex to the strip which makes up the sphere
             *pVertex++ = x0;
             *pVertex++ = y0;
             *pVertex++ = z0;
 
             Vector3 vNormal = Vector3(x0, y0, z0).normalisedCopy();
             *pVertex++ = vNormal.x;
             *pVertex++ = vNormal.y;
             *pVertex++ = vNormal.z;
 
             *pVertex++ = (float) seg / (float) nSegments;
             *pVertex++ = (float) ring / (float) nRings;
 
             if (ring != nRings) {
                                // each vertex (except the last) has six indices pointing to it
                 *pIndices++ = wVerticeIndex + nSegments + 1;
                 *pIndices++ = wVerticeIndex;               
                 *pIndices++ = wVerticeIndex + nSegments;
                 *pIndices++ = wVerticeIndex + nSegments + 1;
                 *pIndices++ = wVerticeIndex + 1;
                 *pIndices++ = wVerticeIndex;
                 wVerticeIndex ++;
             }
         }; // end for seg
     } // end for ring
 
     // Unlock
     vBuf->unlock();
     iBuf->unlock();
     // Generate face list
     pSphereVertex->useSharedVertices = true;
 
     // the original code was missing this line:
     pSphere->_setBounds( AxisAlignedBox( Vector3(-r, -r, -r), Vector3(r, r, r) ), false );
     pSphere->_setBoundingSphereRadius(r);
         // this line makes clear the mesh is loaded (avoids memory leaks)
         pSphere->load();
  }

The parameters are

  • strName - The Mesh's name. You'll use this to access it.
  • r - The sphere's radius.
  • nRings - Number of lines of {LEX()}longitude{LEX} in the sphere. Defaults to 16.
  • nSegments - Number of lines of {LEX()}latitude{LEX} in the sphere. Also defaults to 16.

Example

Here's an example of how you would use this function and attach the new mesh to a SceneNode:

createSphere("mySphereMesh", 10, 64, 64);
 Entity* sphereEntity = sceneMgr->createEntity("mySphereEntity", "mySphereMesh");
 SceneNode* sphereNode = sceneMgr->getRootSceneNode()->createChildSceneNode();
 sphereEntity->setMaterialName("material_name_goes_here");
 sphereNode->attachObject(sphereEntity);

Don't forget to replace "material_name_goes_here" with the name of your {LEX()}material{LEX}.

Creating a sphere with ManualObject

You can also create the {LEX()}mesh{LEX} with the ManualObject class:

void createSphere(const std::string& strName, const float r, const int nRings = 16, const int nSegments = 16)
 {
     ManualObject * manual = sceneMgr->createManualObject(strName);
     manual->begin("BaseWhiteNoLighting", RenderOperation::OT_TRIANGLE_LIST);
         
     float fDeltaRingAngle = (Math::PI / nRings);
     float fDeltaSegAngle = (2 * Math::PI / nSegments);
     unsigned short wVerticeIndex = 0 ;
 
     // Generate the group of rings for the sphere
     for( int ring = 0; ring <= nRings; ring++ ) {
         float r0 = r * sinf (ring * fDeltaRingAngle);
         float y0 = r * cosf (ring * fDeltaRingAngle);
 
         // Generate the group of segments for the current ring
         for(int seg = 0; seg <= nSegments; seg++) {
             float x0 = r0 * sinf(seg * fDeltaSegAngle);
             float z0 = r0 * cosf(seg * fDeltaSegAngle);
 
             // Add one vertex to the strip which makes up the sphere
             manual->position( x0, y0, z0);
             manual->normal(Vector3(x0, y0, z0).normalisedCopy());
             manual->textureCoord((float) seg / (float) nSegments, (float) ring / (float) nRings);
 
             if (ring != nRings) {
                 // each vertex (except the last) has six indicies pointing to it
                 manual->index(wVerticeIndex + nSegments + 1);
                 manual->index(wVerticeIndex);               
                 manual->index(wVerticeIndex + nSegments);
                 manual->index(wVerticeIndex + nSegments + 1);
                 manual->index(wVerticeIndex + 1);
                 manual->index(wVerticeIndex);
                 wVerticeIndex ++;
                 }
         }; // end for seg
     } // end for ring
     manual->end();
     MeshPtr mesh = manual->convertToMesh(name);
     mesh->_setBounds( AxisAlignedBox( Vector3(-r, -r, -r), Vector3(r, r, r) ), false );
 
     mesh->_setBoundingSphereRadius(r);
        unsigned short src, dest;
        if (!mesh->suggestTangentVectorBuildParams(VES_TANGENT, src, dest))
        {
                  mesh->buildTangentVectors(VES_TANGENT, src, dest);
        }
 }

You can use the above example to include it in your code.

ManualObjectSphere
ManualObjectSphere

Screen capture shown above is from ManualObject method lit with point light of ColourValue( 1.0f, 0.80f, 0.0f ), textured with MRAMOR6X6.jpg