Chapter 11: Higher-Order Surfaces in DirectX

text only
 
progress indicator progress indicator progress indicator progress indicator

Drawing a Bezier Patch with DirectX

The code for this example can be found on the CD in the \Code\ Chapter11 - DX8 Bezier Patches directory. As in previous chapters, I'm introducing the new concepts with Bezier patches simply because they are the least complex. I will walk you through a very simple application that will use DirectX to reproduce the sample you saw in Chapter 7, "Basic Surface Concepts and Bezier Surfaces."

If you look at the code, the first thing you'll notice is that there is much less of it than in previous chapters. This is because DirectX does the math for you. You no longer need to keep track of basis functions and so on. In fact, the class definition is much simpler, as shown next . The code is from BezierPatchApplication.h.

 class CBezierPatchApplication : public CPatchApplication { public:        CBezierPatchApplication();        virtual ~CBezierPatchApplication(); 

I have kept the function names the same for consistency, but FillPatchBuffer does much less work now. The only thing you need to do is set the control vertex positions .

 BOOL FillPatchBuffer();        virtual BOOL PostInitialize();        virtual BOOL PreTerminate();        virtual void Render(); 

There are also far fewer member variables . The index buffer is gone because DirectX handles that. The arrays of basis values are gone as well. You are left with a vertex buffer that holds the control point locations, a structure that defines the patch, and a shader handle.

 LPDIRECT3DVERTEXBUFFER8 m_pPatchVertices;        D3DRECTPATCH_INFO       m_PatchInfo;        DWORD                   m_ShaderHandle; }; 

The implementation is also much simpler, as you will see when you look at BezierPatchApplication.cpp. The first thing you'll notice is that I have defined the vertex buffer size so that it only holds the control points. In this case, I am creating a 4x4 Bezier patch.

 #define NUM_U_POINTS 4 #define NUM_V_POINTS 4 #define NUM_TOTAL_VERTICES (NUM_U_POINTS * NUM_V_POINTS) 

The PostInitialize function does less, but it does some new things. In addition to setting up the vertex buffer, I have also enabled real DirectX lighting because you don't have direct access to the vertex color . You could still program your own lighting in a vertex shader, but that is not covered here.

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

The call to CreateVertexBuffer has several subtle changes. First, the number of vertices is much smaller (16 in this case). Also, the vertices are set up to be patch vertices in addition to being dynamic. Finally, the vertex format is now limited to the position, as shown in the previous section.

 if (FAILED(m_pD3DDevice->CreateVertexBuffer(                              NUM_TOTAL_VERTICES * sizeof(PATCH_VERTEX),                              D3DUSAGE_RTPATCHES  D3DUSAGE_DYNAMIC,                              D3DFVF_PATCHVERTEX, D3DPOOL_DEFAULT,                              &m_pPatchVertices)))               return FALSE; 

Next, I set up the patch info structure. I'm doing it once in PostInitialize because it never changes. If you decide to change it dynamically, you'll have to move this code to the Render function. This structure specifies a 4x4 cubic Bezier patch. I don't have to worry about the offset values because the vertex buffer only contains vertices for this one patch.

 m_PatchInfo.StartVertexOffsetWidth  = 0;        m_PatchInfo.StartVertexOffsetHeight = 0;        m_PatchInfo.Width  = NUM_U_POINTS;        m_PatchInfo.Height = NUM_V_POINTS;        m_PatchInfo.Stride = NUM_U_POINTS;        m_PatchInfo.Basis = D3DBASIS_BEZIER;        m_PatchInfo.Order = D3DORDER_CUBIC; 

The following code is the most significant departure from the earlier samples. In the earlier samples, I wanted to limit the DirectX specificity as much as possible, so I avoided DirectX lights. That is unavoidable now that I don't have direct access to the vertex colors in the final patch. In light of that, the following code enables shading that will be calculated by DirectX. This requires a light and a material. To keep things simple, I create a white directional light and a white material. The net result will be exactly the same as the lighting calculations done in the previous chapter.

 D3DLIGHT8 Light;        D3DMATERIAL8 Material;        memset(&Light, 0, sizeof(D3DLIGHT8));        memset(&Material, 0, sizeof(D3DMATERIAL8));        Light.Type      = D3DLIGHT_DIRECTIONAL;        Light.Diffuse.r = Light.Diffuse.g = Light.Diffuse.b = 1.0f;        Light.Direction = D3DXVECTOR3(0.0f, -1.0f, 0.0f);        m_pD3DDevice->SetLight(0, &Light);        m_pD3DDevice->LightEnable(0, TRUE);        Material.Diffuse = Light.Diffuse;        m_pD3DDevice->SetMaterial(&Material); 

Next, I need to set up a fixed-function vertex shader with a custom declaration as discussed earlier. The following declaration tells the device to generate a patch based on the information found in stream 0. It also tells the device to tessellate the normals of the patch vertices based on the same control points. The actual computations will also be based on the parameters of the patch info structure.

 DWORD Declaration[] =        {               D3DVSD_STREAM(0),               D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3),               D3DVSD_STREAM_TESS(),               D3DVSD_TESSNORMAL(D3DVSDE_POSITION, D3DVSDE_NORMAL),               D3DVSD_END()        }; 

The following line creates a vertex shader that will take the place of the FVF shader used in the previous samples. The NULL parameter tells the device that it should still use the fixed-function pipeline, but the declaration tells it how to manipulate the incoming data.

 m_pD3DDevice->CreateVertexShader(Declaration, NULL,        &m_ShaderHandle, 0);        return TRUE; } 

Sadly, the magic is gone from FillPatchBuffer . The function no longer needs to compute patch values. Instead, it simply locks the vertex buffer, sets the control point positions, and unlocks the buffer. It's now so simple that I have chosen not to include the code in the text.

The real work is done by the device in response to a call to DrawRectPatch . This happens in the Render function.

 void CBezierPatchApplication::Render() {        CPatchApplication::Render(); 

Before you render, you need to enable lighting. The light itself was set up in PostInitialize , but you still need to enable the render state. The reason this is done every frame is that the underlying framework does not use lighting when rendering the background grid. The second call to SetRenderState ensures that the normal vectors are all normalized before they are fed into the lighting calculations. If they are not normalized, the lighting calculations will not yield the correct results.

 m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE);        m_pD3DDevice->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE); 

A call to FillPatchBuffer updates the control point positions. Remember, you would not need to do this every frame if the mesh was static.

 FillPatchBuffer(); 

The following lines do the setup for the patch itself. In this case, I have set the number of segments for each side to the same value of 8.0. I would encourage you to experiment with different values and different combinations to see how these values affect the final result. Also, the actual source code has a wireframe mode that you can easily enable. You will be able to see the effects more clearly if you switch to a wireframe fill mode.

 float NumSegments[4];       NumSegments[0] = NumSegments[1] = NumSegments[2] =       NumSegments[3] = 8.0f; 

In addition to setting the stream source, I also need to set the shader handle. In the previous samples, I didn't need to do this because the framework vertices and the patch vertices shared the same FVF. This is no longer the case. Also, remember that the vertex buffer only contains control point positions.

 m_pD3DDevice->SetVertexShader(m_ShaderHandle);       m_pD3DDevice->SetStreamSource(0, m_pPatchVertices,      sizeof(PATCH_VERTEX)); 

Now it's time to draw the actual patch. I use handle 0 because FillPatchBuffer changes the vertices every frame. There is no advantage to caching anything. The patch itself is defined by the patch info structure and the vertices in stream 0.

 m_pD3DDevice->DrawRectPatch(0, NumSegments, &m_PatchInfo); 

Finally, I disable lighting. This is done because the underlying framework does not use lighting. In other situations, you probably don't need to toggle this state in every frame.

 m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); } 

Figure 11.2 shows a screenshot from this application. As you can see, the results are very similar to Figure 7.11, but with far less code. If your hardware accelerates patches, the application probably also runs faster.


Figure 11.2: DirectX Bezier Patch.

Figure 11.3 shows a wireframe view. In this case, I have set the sides to have 1, 2, 3, and 4 segments. The control points are the same.


Figure 11.3: Wireframe with different segment counts per side.
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