52880.fb2
In this tutorial we will look at how to create a textured sphere, cylinder and cone. We'll set-up the normals so that the objects are shaded correctly. Also, we'll derive all of our classes from one base class called CBase, which has methods for HTML logging and Normal calculations. Our example application for this tutorial will consist of two cylinders, two cones and a simple model of the Moon rotating around Earth, which is rotating around the Sun. You can download the full source code by clicking the "Download Source" link above.
Our cylinder will be made up from two triangle fans, one for the top and one for the bottom. The sides of the cylinder will be made up from a triangle strip. Fig 9.1 below shows a diagram of the vertices and polygons for a cylinder. It also includes the normals for the four corners of one segment. To create our cylinder we have not used an index buffer, only a vertex buffer. This is because; although we do have shared vertices (between the sides and the top/bottom faces) we want different normals so the edges around the top and bottom appear sharp.
Fig 9.1
To create the cylinder we need to specify the number of segments. There are 8 segments in the diagram above. The more segments there are, the smoother and rounder the cylinder will appear. We also need to know the height and radius of the cylinder. Once we know the height, radius and number of segments, we can defined the position of our vertices together with their Normal value and texture coordinates. The following code snippet shows how this is done.
bool CCylinder::UpdateVertices() {
CYLINDER_CUSTOMVERTEX* pVertex;
WORD wVertexIndex = 0;
int nCurrentSegment;
//Lock the vertex buffer
if (FAILED(m_pVertexBuffer->Lock(0, 0, (BYTE**)&pVertex, 0))) {
LogError("<li>CCylinder: Unable to lock vertex buffer.");
return false;
}
float rDeltaSegAngle = (2.0f * D3DX_PI / m_nSegments);
float rSegmentLength = 1.0f / (float)m_nSegments;
//Create the sides triangle strip
for (nCurrentSegment = 0; nCurrentSegment <= m_nSegments; nCurrentSegment++) {
float x0 = m_rRadius * sinf(nCurrentSegment * rDeltaSegAngle);
float z0 = m_rRadius * cosf(nCurrentSegment * rDeltaSegAngle);
pVertex->x = x0;
pVertex->y = 0.0f + (m_rHeight / 2.0f);
pVertex->z = z0;
pVertex->nx = x0;
pVertex->ny = 0.0f;
pVertex->nz = z0;
pVertex->tu = 1.0f – (rSegmentLength * (float)nCurrentSegment);
pVertex->tv = 0.0f;
pVertex++;
pVertex->x = x0;
pVertex->y = 0.0f – (m_rHeight / 2.0f);
pVertex->z = z0;
pVertex->nx = x0;
pVertex->ny = 0.0f;
pVertex->nz = z0;
pVertex->tu = 1.0f – (rSegmentLength * (float)nCurrentSegment);
pVertex->tv = 1.0f;
pVertex++;
}
//Create the top triangle fan: Center
pVertex->x = 0.0f;
pVertex->y = 0.0f + (m_rHeight / 2.0f);
pVertex->z = 0.0f;
pVertex->nx = 0.0f;
pVertex->ny = 1.0f;
pVertex->nz = 0.0f;
pVertex->tu = 0.5f;
pVertex->tv = 0.5f;
pVertex++;
//Create the top triangle fan: Edges
for (nCurrentSegment = 0; nCurrentSegment <= m_nSegments; nCurrentSegment++) {
float x0 = m_rRadius * sinf(nCurrentSegment * rDeltaSegAngle);
float z0 = m_rRadius * cosf(nCurrentSegment * rDeltaSegAngle);
pVertex->x = x0;
pVertex->y = 0.0f + (m_rHeight / 2.0f);
pVertex->z = z0;
pVertex->nx = 0.0f;
pVertex->ny = 1.0f;
pVertex->nz = 0.0f;
float tu0 = (0.5f * sinf(nCurrentSegment * rDeltaSegAngle)) + 0.5f;
float tv0 = (0.5f * cosf(nCurrentSegment * rDeltaSegAngle)) + 0.5f;
pVertex->tu = tu0;
pVertex->tv = tv0;
pVertex++;
}
//Create the bottom triangle fan: Center
pVertex->x = 0.0f;
pVertex->y = 0.0f – (m_rHeight / 2.0f);
pVertex->z = 0.0f;
pVertex->nx = 0.0f;
pVertex->ny = –1.0f;
pVertex->nz = 0.0f;
pVertex->tu = 0.5f;
pVertex->tv = 0.5f;
pVertex++;
//Create the bottom triangle fan: Edges
for (nCurrentSegment = m_nSegments; nCurrentSegment >= 0; nCurrentSegment--) {
float x0 = m_rRadius * sinf(nCurrentSegment * rDeltaSegAngle);
float z0 = m_rRadius * cosf(nCurrentSegment * rDeltaSegAngle);
pVertex->x = x0;
pVertex->y = 0.0f – (m_rHeight / 2.0f);
pVertex->z = z0;
pVertex->nx = 0.0f;
pVertex->ny = –1.0f;
pVertex->nz = 0.0f;
float tu0 = (0.5f * sinf(nCurrentSegment * rDeltaSegAngle)) + 0.5f;
float tv0 = (0.5f * cosf(nCurrentSegment * rDeltaSegAngle)) + 0.5f;
pVertex->tu = tu0;
pVertex->tv = tv0;
pVertex++;
}
if (FAILED(m_pVertexBuffer->Unlock())) {
LogError("<li>CCylinder: Unable to unlock vertex buffer.");
return false;
}
return true;
}
So, what's going on here? Well, after locking our vertex buffer (so we can write to it) we calculate the angle for each segment. This is done by dividing the number of radians in a circle (2*PI) by the number of segments, this angle is stored in rDeltaSegAngle for use later when defining the vertices position. We then calculate the length of each segment for use when defining the texture coordinates for the triangle strip (sides). This is done by dividing the length of our texture (1.0) by the number of segments.
Once this is done, we can define the sides of our cylinder. We loop around, once for each segment + 1. In this loop we calculate the x and z position for the current segment and write these values for the top and bottom vertex for that segment. The y value is simply + or – half the height. We use the segment length to calculate the texture coordinates for the current vertex.
Once that is done, we can define the two triangle fans, one for the top and one for the bottom. First we define the centre point for the fan and then we use the same calculations to define the edge vertices of the fan. Once this is complete for the top and bottom fans, the vertex buffer is full and then unlocked ready for rendering.
Our cone will be made up from one triangle fan for the bottom and a triangle list for the sides. As for the cylinder above, we need to specify the number of segments, the height and the radius for our cone. Fig 9.2 below shows how the cone will be made up; there are 8 segments in this cone. The red arrows show the normals for one segment of this cone. To create the cone, we will use an index buffer and a vertex buffer. We will store the triangles for the sides in the index buffer and use the vertex buffer to store the vertices for the side triangles and the base triangle fan. This is so that we get the correct shading around the cone and a sharp edge between the sides and base.
Fig 9.2
To create the cone we need to specify the number of segments. There are 8 segments in the diagram above. The more segments there are, the smoother and rounder the cone will appear. We also need to know the height and radius of the cone. Once we know the height, radius and number of segments, we can defined the position of our vertices together with their Normal value and texture coordinates. The following code snippet shows how this is done.
bool CCone::UpdateVertices() {
CONE_CUSTOMVERTEX* pVertex;
WORD* pIndices;
WORD wVertexIndex = 0;
int nCurrentSegment;
//Lock the vertex buffer
if (FAILED(m_pVertexBuffer->Lock(0, 0, (BYTE**)&pVertex, 0))) {
LogError("<li>CCone: Unable to lock vertex buffer.");
return false;
}
//Lock the index buffer
if (FAILED(m_pIndexBuffer->Lock(0, m_dwNumOfIndices, (BYTE**)&pIndices, 0))) {
LogError("<li>CCone: Unable to lock index buffer.");
return false;
}
float rDeltaSegAngle = (2.0f * D3DX_PI / m_nSegments);
float rSegmentLength = 1.0f / (float)m_nSegments;
float ny0 = (90.0f – (float)D3DXToDegree(atan(m_rHeight / m_rRadius))) / 90.0f;
//For each segment, add a triangle to the sides triangle list
for (nCurrentSegment = 0; nCurrentSegment < m_nSegments; nCurrentSegment++) {
float x0 = m_rRadius * sinf(nCurrentSegment * rDeltaSegAngle);
float z0 = m_rRadius * cosf(nCurrentSegment * rDeltaSegAngle);
pVertex->x = 0.0f;
pVertex->y = 0.0f + (m_rHeight / 2.0f);
pVertex->z = 0.0f;
pVertex->nx = x0;
pVertex->ny = ny0;
pVertex->nz = z0;
pVertex->tu = 1.0f – (rSegmentLength * (float)nCurrentSegment);
pVertex->tv = 0.0f;
pVertex++;
pVertex->x = x0;
pVertex->y = 0.0f – (m_rHeight / 2.0f);
pVertex->z = z0;
pVertex->nx = x0;
pVertex->ny = ny0;
pVertex->nz = z0;
pVertex->tu = 1.0f – (rSegmentLength * (float)nCurrentSegment);
pVertex->tv = 1.0f;
pVertex++;
//Set three indices (1 triangle) per segment
*pIndices = wVertexIndex;
pIndices++;
wVertexIndex++;
*pIndices = wVertexIndex;
pIndices++;
wVertexIndex += 2;
if (nCurrentSegment == m_nSegments – 1) {
*pIndices = 1;
pIndices++;
wVertexIndex--;
} else {
*pIndices = wVertexIndex;
pIndices++;
wVertexIndex--;
}
}
//Create the bottom triangle fan: Center vertex
pVertex->x = 0.0f;
pVertex->y = 0.0f – (m_rHeight / 2.0f);
pVertex->z = 0.0f;
pVertex->nx = 0.0f;
pVertex->ny = –1.0f;
pVertex->nz = 0.0f;
pVertex->tu = 0.5f;
pVertex->tv = 0.5f;
pVertex++;
//Create the bottom triangle fan: Edge vertices
for (nCurrentSegment = m_nSegments; nCurrentSegment >= 0; nCurrentSegment--) {
float x0 = m_rRadius * sinf(nCurrentSegment * rDeltaSegAngle);
float z0 = m_rRadius * cosf(nCurrentSegment * rDeltaSegAngle);
pVertex->x = x0;
pVertex->y = 0.0f – (m_rHeight / 2.0f);
pVertex->z = z0;
pVertex->nx = 0.0f;
pVertex->ny = –1.0f;
pVertex->nz = 0.0f;
float tu0 = (0.5f * sinf(nCurrentSegment * rDeltaSegAngle)) + 0.5f;
float tv0 = (0.5f * cosf(nCurrentSegment * rDeltaSegAngle)) + 0.5f;
pVertex->tu = tu0;
pVertex->tv = tv0;
pVertex++;
}
if (FAILED(m_pVertexBuffer->Unlock())) {
LogError("<li>CCone: Unable to unlock vertex buffer.");
return false;
}
if (FAILED(m_pIndexBuffer->Unlock())) {
LogError("<li>CCone: Unable to unlock index buffer.");
return false;
}
return true;
}
So what is going on here? Well, as with the cylinder, we lock the vertex and index buffers ready for writing. Then we use the same calculations as before to calculate the segment angle and segment length. We also calculate the y part of the normals for the side's vertices. This is done by using simple trigonometry with the height and radius to form a right-angled triangle, so that we can find the correct Normal angle. Then, as with the cylinder, we loop around once for each segment adding a new triangle to the index buffer and vertices to the vertex buffer as required.
Once we've done that, we use the same method as the cylinder to define the triangle fan for the base of the cone. We then unlock the index and vertex buffers, and are ready for rendering.
Our sphere will be made up from a simple triangle list. We will need to specify the number of rings and segments for the sphere. The more rings and segments there are, the smoother and rounder the sphere will appear. The sphere will be made up using an index and vertex buffer. Fig 9.3 below shows a wireframe screenshot of a sphere. It shows how the sphere is constructed and divided into rings and segments.
Fig 9.3
To create the sphere we can use the following code snippet. The code below has been adapted from a sample by "Laurent" posted on the GameDev.net DirectX forum. To view the post in full go to http://www.gamedev.net/community/forums/topic.asp?topic_id=85779. I would like to thank Laurent for giving me permission to use the code in this tutorial.
bool CSphere::UpdateVertices() {
//Code adapted from a sample by "Laurent" posted on the GameDev.net DirectX forum
//http://www.gamedev.net/community/forums/topic.asp?topic_id=85779
WORD* pIndices;
SPHERE_CUSTOMVERTEX* pVertex;
WORD wVertexIndex = 0;
int nCurrentRing;
int nCurrentSegment;
D3DXVECTOR3 vNormal;
//Lock the vertex buffer
if (FAILED(m_pVertexBuffer->Lock(0, 0, (BYTE**)&pVertex, 0))) {
LogError("<li>CSphere: Unable to lock vertex buffer.");
return false;
}
//Lock the index buffer
if (FAILED(m_pIndexBuffer->Lock(0, m_dwNumOfIndices, (BYTE**)&pIndices, 0))) {
LogError("<li>CSphere: Unable to lock index buffer.");
return false;
}
//Establish constants used in sphere generation
FLOAT rDeltaRingAngle = (D3DX_PI / m_nRings);
FLOAT rDeltaSegAngle = (2.0f * D3DX_PI / m_nSegments);
//Generate the group of rings for the sphere
for (nCurrentRing = 0; nCurrentRing < m_nRings + 1; nCurrentRing++) {
FLOAT r0 = sinf(nCurrentRing * rDeltaRingAngle);
FLOAT y0 = cosf(nCurrentRing * rDeltaRingAngle);
//Generate the group of segments for the current ring
for (nCurrentSegment = 0; nCurrentSegment < m_nSegments + 1; nCurrentSegment++) {
FLOAT x0 = r0 * sinf(nCurrentSegment * rDeltaSegAngle);
FLOAT z0 = r0 * cosf(nCurrentSegment * rDeltaSegAngle);
vNormal.x = x0;
vNormal.y = y0;
vNormal.z = z0;
D3DXVec3Normalize(&vNormal, &vNormal);
//Add one vertex to the strip which makes up the sphere
pVertex->x = x0;
pVertex->y = y0;
pVertex->z = z0;
pVertex->nx = vNormal.x;
pVertex->ny = vNormal.y;
pVertex->nz = vNormal.z;
pVertex->tu = 1.0f – ((FLOAT)nCurrentSegment / (FLOAT)m_nSegments);
pVertex->tv = (FLOAT)nCurrentRing / (FLOAT)m_nRings;
pVertex++;
//Add two indices except for the last ring
if (nCurrentRing != m_nRings) {
*pIndices = wVertexIndex;
pIndices++;
*pIndices = wVertexIndex + (WORD)(m_nSegments + 1);
pIndices++;
wVertexIndex++;
}
}
}
if (FAILED(m_pIndexBuffer->Unlock())) {
LogError("<li>CSphere: Unable to unlock index buffer.");
return false;
}
if (FAILED(m_pVertexBuffer->Unlock())) {
LogError("<li>CSphere: Unable to unlock vertex buffer.");
return false;
}
return true;
}
So what is going on here? Once we have locked the index and vertex buffers we are ready to write to them. We use the same calculation as before to obtain the segment angle and a slight variation to obtain the ring angle. For the ring angle we only need to divide half a circle (PI radians) by the number of rings.
Once we have the ring and segment angles, we simply loop around once for each ring creating the vertices for each segment within that ring. We add an entry into the index buffer each time to create our triangle list. We set the normal values for each vertex to be the normalised position values. The radius of the sphere is always 1. You can use a scaling transformation matrix to increase or decrease the size of the sphere. Once complete, we unlock the vertex and index buffers ready for rendering.
The final scene when rendered will look something like the screenshot below:
You may have notices in the code above, some new logging functions called LogInfo and LogError. Well, by using these new logging functions we can create a more readable log file. Errors will be highlighted in red and normal information will be in black. To see the log file, take a look in the project folder at this file called Log.htm once you have run the sample code. There is also a LogWarning function, which you can use to log warnings that will appear in orange. There is also a LogMemoryUsage() function which will log the amount of memory your application is currently using. Do not use this function inside your game loop at it is far to slow.
Now we can create quite a few shapes: a cube, a sphere, a cone, a cylinder and even a simple terrain. These shapes are pretty basic, but can be very useful when you start creating your own scenes. In the next tutorial, we'll look at creating more complex objects like aliens, guns and vehicles.