Defining Sine, Cosine, and Tangent

text only
 
progress indicator progress indicator progress indicator progress indicator

Setting Up Buffers for a Generic Surface

There are a number of things that need to be set up before you can actually render a surface. The following points apply to all surfaces, so one could argue that they could have been pushed back into the basic framework, but I have chosen to explicitly expose them because they are important and not entirely trivial. The following code can be found on the CD in the directory \Code\Chapter07 - Bezier Patches\BezierPatchApplication.cpp, but it also appears unchanged in the source code for the later chapters as well.

Each surface in the examples is rendered as a set of lines showing the control grid and a set of triangles that form the actual shaded surface. With curves, I was able to get by with a simple line strip, but 3D rendering requires a bit more setup. First of all, I have created a set of values that define how many control points will be used and how many vertices should be created. These values will be used to determine the size of the vertex buffer.

These values determine the number of control points along the u and v directions. In the specific context of Bezier surfaces, they also determine the order of the surface. This is not necessarily the case with Bspline surfaces and NURBS surfaces.

 #define NUM_U_POINTS 4 #define NUM_V_POINTS 4 

These values determine how many vertices will be created in each direction. To simplify matters, the number of vertices is the same in both directions, but there is no constraint that forces you to do this. The number of triangles is based on the number of vertices. Two triangles are drawn between each set of four vertices, as shown in Figure 7.9.


Figure 7.9: Vertices and the resulting triangles.
 #define NUM_PATCH_VERTICES 20 #define NUM_PATCH_TRIANGLES (2 * (NUM_PATCH_VERTICES - 1)*                             (NUM_PATCH_VERTICES - 1)) 

The vertex buffer will hold the control points and the vertices needed for the surface, so the total size of the buffer is the sum of the total number of control points and vertices.

 #define NUM_TOTAL_VERTICES (NUM_U_POINTS * NUM_V_POINTS +                             NUM_PATCH_VERTICES * NUM_PATCH_VERTICES) 

For 3D rendering, indexed triangle lists and indexed line lists provide the best tradeoff between efficiency and simplicity. I use indexed line lists to render the control grid and indexed triangle lists to render the surface. To do that, I need to create an index buffer. The size of the buffer is determined by the following values.

 #define NUM_GRID_INDICES ((2 * NUM_U_POINTS * (NUM_V_POINTS - 1)) +                           (2 * NUM_V_POINTS * (NUM_U_POINTS - 1))) #define NUM_PATCH_INDICES (6 * NUM_PATCH_VERTICES * NUM_PATCH_VERTICES) #define NUM_TOTAL_INDICES (NUM_GRID_INDICES + NUM_PATCH_INDICES) 

The preceding macros should remain untouched for all of the surface chapters. However, you can change the number of control points by changing NUM_U_POINTS and NUM_V_POINTS . You can also increase or decrease the resolution of the surface by changing the value of NUM_PATCH_VERTICES . However, remember that the total number of vertices is the square of the value you set. Large values will result in slower rendering times.

Once the values are set, the vertex and index buffers are set up as shown next . Again, I am showing the code for CBezierPatchApplication, but the same code applies to all the surfaces.

 BOOL CBezierPatchApplication::PostInitialize() {           CPatchApplication::PostInitialize(); 

First, I create a vertex buffer and index buffer for the total number of vertices and indices. The vertex buffer will not be filled until later, but the index buffer will be filled right away and will remain unchanged for the duration of the application.

 if (FAILED(m_pD3DDevice->CreateVertexBuffer(NUM_TOTAL_VERTICES *                                                     sizeof(GENERAL_VERTEX),                                                       D3DUSAGE_WRITEONLY                                                        D3DUSAGE_DYNAMIC,                                                       D3DFVF_GENERALVERTEX,                                                       D3DPOOL_DEFAULT,                                                       &m_pPatchVertices)))                  return FALSE;           if (FAILED(m_pD3DDevice->CreateIndexBuffer(NUM_TOTAL_INDICES *                                                      sizeof(short),                                                      D3DUSAGE_WRITEONLY,                                                      D3DFMT_INDEX16,                                                      D3DPOOL_DEFAULT,                                                      &m_pPatchIndex)))                  return FALSE; 

Once the index buffer is created, lock the index buffer so that it can be filled.

 short *pIndices;           m_pPatchIndex->Lock(0, NUM_TOTAL_INDICES * sizeof(short),                               (BYTE**)&pIndices, 0); 

This first loop creates the indices for the lines of the control grid in the v direction.

 for (long UIndex = 0; UIndex < NUM_U_POINTS; UIndex++)           {                  for (long VIndex = 0; VIndex < NUM_V_POINTS - 1; VIndex++)                  {                         *pIndices = VIndex + (UIndex * NUM_V_POINTS);                         *(pIndices + 1) = VIndex + (UIndex * NUM_V_POINTS) + 1;                         pIndices = pIndices + 2;                  }           } 

The second loop creates the indices for the lines of the control grid in the u direction. When this loop is complete, the first part of the index buffer will contain the values needed to draw the control grid with an indexed line list. A line list allows you to draw all of the lines with a single call to DrawIndexedPrimitive .

 for (long VIndex = 0; VIndex < NUM_V_POINTS; VIndex++)           {                  for (long UIndex = 0; UIndex < NUM_U_POINTS - 1; UIndex++)                  {                         *pIndices = VIndex + (UIndex * NUM_V_POINTS);                         *(pIndices + 1) = VIndex + ((UIndex + 1) * NUM_V_POINTS);                         pIndices = pIndices + 2;                  }           } 

This final block of code sets up the indices for the indexed triangle list. Notice that the index buffer contains indices for two types of primitives. This is okay as long as you use the right indices at the right time. Figure 7.10 shows how the index values are determined. For the sake of simplicity, the preceding control point vertices are ignored.


Figure 7.10: Making sense of the index buffer.

These indices will apply equally well to any patch surface as long as you are consistent with how you define the actual vertices. Remember, the index buffer is set up assuming that the vertex buffer is arranged in a certain order. You will see how the vertex buffer is filled later in the chapter.

 for (UIndex = 0; UIndex < NUM_PATCH_VERTICES - 1; UIndex++)           {                  for (long VIndex = 0; VIndex < NUM_PATCH_VERTICES - 1; VIndex++)                  {                         *pIndices = (NUM_U_POINTS * NUM_V_POINTS) +                                     VIndex + (UIndex * NUM_PATCH_VERTICES);                         *(pIndices + 1) = (NUM_U_POINTS * NUM_V_POINTS) +                                           VIndex +                                           (UIndex * NUM_PATCH_VERTICES) + 1;                         *(pIndices + 2) = (NUM_U_POINTS * NUM_V_POINTS) +                                           VIndex +                                           ((UIndex + 1) * NUM_PATCH_VERTICES);                         *(pIndices + 3) = (NUM_U_POINTS * NUM_V_POINTS) +                                           VIndex +                                           (UIndex * NUM_PATCH_VERTICES) + 1;                         *(pIndices + 4) = (NUM_U_POINTS * NUM_V_POINTS) +                                           VIndex +                                           ((UIndex + 1) * NUM_PATCH_VERTICES);                         *(pIndices + 5) = (NUM_U_POINTS * NUM_V_POINTS) +                                           VIndex +                                           ((UIndex + 1) * NUM_PATCH_VERTICES) + 1;                         pIndices = pIndices + 6;                  }           }           m_pPatchIndex->Unlock();           return TRUE; } 

There is one last piece of code that applies to every surface example. The Render function performs the actual drawing. For the most part, it is the same for every application.

 void CBezierPatchApplication::Render() { 

Make sure you call the base class Render function if you want to render the grid lines. Also, the base class handles setting the FVF and other basic setup, so you probably don't want to remove this line without adding or rearranging the setup code.

 CPatchApplication::Render(); 

FillPatchBuffer computes the actual mesh. Each application implements FillPatchBuffer in its own way. The code for Bezier patches is described next.

 FillPatchBuffer(); 

Set the vertex and index buffers. Both buffers are used for the control grid and surface.

 m_pD3DDevice->SetStreamSource(0, m_pPatchVertices,                                      sizeof(GENERAL_VERTEX));        m_pD3DDevice->SetIndices(m_pPatchIndex, 0); 

I have added a line that you can enable or disable if you want to draw the surface in wireframe mode. After that, you are ready to draw the surface. First, a call to DrawIndexedPrimitive renders the surface as an indexed triangle list. After that, the control grid is rendered as an indexed line list.

 //m_pD3DDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);        m_pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,                                           NUM_TOTAL_VERTICES,                                           NUM_GRID_INDICES,                                           NUM_PATCH_TRIANGLES);        m_pD3DDevice->DrawIndexedPrimitive(D3DPT_LINELIST, 0,                                           (NUM_U_POINTS * NUM_V_POINTS),                                           0, (NUM_GRID_INDICES / 2)); } 

This completes the general code. As you can see, there is nothing included in the code that is specific to Bezier surfaces. The bulk of computation for each surface occurs in FillPatchBuffer .

The FillPatchBuffer function is where all of the magic happens; although, in the case of Bezier patches, the magic is fairly simple. This is really the first bit of source code that is not general to all the surface chapters. This implementation of FillPatchBuffer builds a Bezier surface based on a 4x4 control grid.

The function requires a set of basis functions and their derivatives. These are defined as macros at the top of BezierPatchApplication.cpp. Notice that I don't explicitly define separate basis functions for u and v because they are identical in this case. Therefore, I chose to keep t as the parameter for the macros to accentuate the fact that the basis functions are the same as seen in Chapter 3, "Parametric Equations and Bezier Curves."

 #define B0(t) ((1.0f - t) * (1.0f - t) * (1.0f - t)) #define B1(t) (3.0f * t * (1.0f - t) * (1.0f - t)) #define B2(t) (3.0f * t * t * (1.0f - t)) #define B3(t) (t * t * t) #define dB0(t) ((6.0f * t) - (3.0f * t * t) - 3.0f) #define dB1(t) (3.0f - (12.0f * t) + (9.0f * t * t)) #define dB2(t) ((6.0f * t) - (9.0f * t * t)) #define dB3(t) (3.0f * t * t) 

The macros are used to compute the vertex coordinates in the patch buffer.

 BOOL CBezierPatchApplication::FillPatchBuffer() { 

The first several lines lock the vertex buffer as you've seen in previous chapters.

 if (!m_pPatchVertices)               return FALSE;        GENERAL_VERTEX *pVertices;        if (FAILED(m_pPatchVertices->Lock(0, NUM_TOTAL_VERTICES *                                          sizeof(GENERAL_VERTEX),                                          (BYTE **)&pVertices,                                          D3DLOCK_DISCARD)))               return FALSE; 

I start by positioning the control points in a uniform 4x4 grid at a height of 100 units. Remember, these are the control points-not the vertices that comprise the surface. You don't have to start out with a uniform grid, but it makes things slightly clearer.

 for (long U = 0; U < NUM_U_POINTS; U++)        {               for (long V = 0; V < NUM_V_POINTS; V++)               {                      pVertices[U * NUM_V_POINTS + V].x = U * (500.0f /                                                    (float)(NUM_U_POINTS - 1));                      pVertices[U * NUM_V_POINTS + V].y = 100.0f;                      pVertices[U * NUM_V_POINTS + V].z = V * (500.0f /                                                    (float)(NUM_V_POINTS - 1));                      pVertices[U * NUM_V_POINTS + V].color = 0xFF000000;               }        } 

I have also chosen to animate the heights of a few of the control points. As you will see next, this code is tailored for a 4x4 control grid, so it is fairly safe to use hardcoded indices. When you move on to the next chapter, it might not be safe to make such assumptions because you will be able to set arbitrary control grids.

 pVertices[0].y = 100.0f + ANIMATE(100.0f);        pVertices[1].y = 100.0f + ANIMATE(100.0f);        pVertices[2].y = 100.0f + ANIMATE(100.0f);        pVertices[3].y = 100.0f + ANIMATE(100.0f);        pVertices[15].y = 200.0f + ANIMATE(200.0f);        pVertices[12].y = 200.0f + ANIMATE(200.0f); 

You are not limited to changing the height of each control point. The following code also changes the x coordinate, which will produce a wave effect. In this case, I take the safer approach by finding the middle control point as a function of the total number of control points.

 long MidPoint = NUM_V_POINTS * NUM_U_POINTS / 2 + NUM_V_POINTS / 2;        pVertices[MidPoint].y = 500.0f;        pVertices[MidPoint].x = pVertices[MidPoint].x + ANIMATE(800.0f);        pVertices[MidPoint - 1].y = 500.0f;        pVertices[MidPoint - 1].x = pVertices[MidPoint - 1].x +        ANIMATE(800.0f); 

Now I begin computing the vertices for the surface. Earlier, I said that the basis functions are the same for both u and v. This is true, but the result of those functions is different for different values of u and v. Therefore, I have created two arrays to hold the evaluated basis function values. Two more arrays hold the evaluated derivatives needed for the slope values.

 float BU[4];        float BV[4];        float DU[4];        float DV[4]; 

These four variables will be used to compute the lighting intensity for each vertex. The lighting code will be shown later, but notice the direction of the light. In this case, I wanted the light to be shining straight down. The intensity is based on the dot product of the normal and the opposite of the light direction, so I set the vector to point straight up.

 D3DXVECTOR3 dPdU;        D3DXVECTOR3 dPdV;        D3DXVECTOR3 Normal;        D3DXVECTOR3 LightDirection(0.0f, 1.0f, 0.0f); 

Now I'm ready to compute the vertex positions . The following nested loop computes the values for each point on the surface. Remember that the index buffer ordering is related to the order you compute things here. If you change the order in which the vertices are computed, the resulting indexed surface might not be correct. Feel free to change both, but remember that you cannot change one without considering the effects on the other.

 for (U = 0; U < NUM_PATCH_VERTICES; U++)        {               for (long V = 0; V < NUM_PATCH_VERTICES; V++)               { 

The u and v values are uniformly distributed between 0 and 1 over the surface. Before any computation, you first need to know what the u and v values are.

 float UVal = (float)U / (float)(NUM_PATCH_VERTICES - 1);                     float VVal = (float)V / (float)(NUM_PATCH_VERTICES - 1); 

The following code finds the values of all the basis functions at the u and v "positions". It also finds the values needed to compute the normal vector.

 BU[0] = B0(UVal); BU[1] = B1(UVal);                     BU[2] = B2(UVal); BU[3] = B3(UVal);                     BV[0] = B0(VVal); BV[1] = B1(VVal);                     BV[2] = B2(VVal); BV[3] = B3(VVal);                     DU[0] = dB0(UVal); DU[1] = dB1(UVal);                     DU[2] = dB2(UVal); DU[3] = dB3(UVal);                     DV[0] = dB0(VVal); DV[1] = dB1(VVal);                     DV[2] = dB2(VVal); DV[3] = dB3(VVal); 

This code processes everything in two dimensions, but the vertex buffer is a one-dimensional array of vertices. This variable maps the 2D parametric value to its proper index in the array.

 long Current = (NUM_U_POINTS * NUM_V_POINTS) +                                    (U * NUM_PATCH_VERTICES) + V; 

First, clear all the values to 0. This will make it slightly easier to compute the position as the sum of the influence of each control point.

 pVertices[Current].x = 0.0f;                     pVertices[Current].y = 0.0f;                     pVertices[Current].z = 0.0f;                     memset(&dPdU, 0, sizeof(D3DXVECTOR3));                     memset(&dPdV, 0, sizeof(D3DXVECTOR3)); 

This nested loop sums the influences of each of the control points based on the values of the basis functions that were computed earlier.

 for (long UStep = 0; UStep < 4; UStep++)                        {                               for (long VStep = 0; VStep < 4; VStep++)                               { 

First, compute the position of the vertex as the weighted sum of the influences of each control point. The control points are stored as 16 vertices at the beginning of the vertex buffer. This is essentially the code for Equation 7.1.

 pVertices[Current].x += BU[UStep] * BV[VStep]                                              * pVertices[UStep * 4 + VStep].x;                                  pVertices[Current].y += BU[UStep] * BV[VStep]                                              * pVertices[UStep * 4 + VStep].y;                                  pVertices[Current].z += BU[UStep] * BV[VStep]                                              * pVertices[UStep * 4 + VStep].z; 

Likewise, the following lines are essentially the code for Equation 7.2. These will serve as the foundation for computing the normal vector.

 dPdU.x += DU[UStep] * BV[VStep] *                                              pVertices[UStep * 4 + VStep].x;                                    dPdU.y += DU[UStep] * BV[VStep] *                                              pVertices[UStep * 4 + VStep].y;                                    dPdU.z += DU[UStep] * BV[VStep] *                                              pVertices[UStep * 4 + VStep].z;                                    dPdV.x += BU[UStep] * DV[VStep] *                                              pVertices[UStep * 4 + VStep].x;                                    dPdV.y += BU[UStep] * DV[VStep] *                                              pVertices[UStep * 4 + VStep].y;                                    dPdV.z += BU[UStep] * DV[VStep] *                                              pVertices[UStep * 4 + VStep].z;                             }                   } 

This is another bit of code that isn't entirely necessary. Here, I correct the vertex position down a little bit so that you can clearly see the control grid above the surface. If you don't care about seeing the grid, you can remove this line.

 pVertices[Current].y -= 10.0f; 

I begin computing the actual normal vector by normalizing the individual tangent vectors. This can be worthwhile because there are cases (such as bumpmapping) where you'd want the normalized tangent vectors in addition to the normal vector.

 D3DXVec3Normalize(&dPdU, &dPdU);                       D3DXVec3Normalize(&dPdV, &dPdV); 

The cross product gives you the actual normal vector.

 D3DXVec3Cross(&Normal, &dPdV, &dPdU); 

Compute the lighting intensity as the dot product of the normal and the light direction. Keep in mind that the light direction vector was set as the opposite of the actual light direction.

 float Intensity = D3DXVec3Dot(&Normal,                       &LightDirection); 

Clamp the intensity to 0. If the normal is facing away from the light, the intensity is 0. Once the value has been clamped, I set the vertex color to equal the intensity of the light. This assumes the light color is white. This is the last step for a given vertex.

 if (Intensity < 0.0f) Intensity = 0.0f;                       pVertices[Current].color =                       D3DCOLOR_COLORVALUE(Intensity, Intensity, Inten                       sity, 1.0f);               }         } 

All vertices have been set. Unlock the buffer and return so the Render function can draw the surface.

 m_pPatchVertices->Unlock();        return TRUE; } 

These pieces fit together to provide a general application framework plus the specific code needed to draw a Bezier surface. Figure 7.11 shows a screenshot from the application. Notice how the changes to both the x and y values create warping in two directions. The mesh is also correctly shaded as if a light was shining straight down.


Figure 7.11: A screenshot of the Bezier surface.

Figure 7.12 shows the same mesh as a wireframe. Wireframes can be helpful in understanding exactly how the vertices move.


Figure 7.12: A screenshot of the Bezier surface in wireframe.
progress indicator progress indicator progress indicator progress indicator


Focus on Curves and Surfaces
Focus On Curves and Surfaces (Focus on Game Development)
ISBN: 159200007X
EAN: 2147483647
Year: 2003
Pages: 104
Authors: Kelly Dempski

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net