Dynamic Buffers

  CU_DynamicBuffers.zip (28.1 KiB, 4,043 hits)

In the Vertex and Index Buffers tutorial, we worked with static buffers. We didn’t alter the vertex data once we put it in the buffer. Dynamic vertex buffers allow us to alter data in a vertex buffer, which lets objects deform and change shape. When we use dynamic buffers, we usually change the contents of the buffer each frame. Some uses for dynamic buffers include particle engines and skinned meshes.

Static buffers are filled up once and then sent off to be rendered. Dynamic buffers, on the other hand, are filled up, rendered, filled up again with updated geometry, rendered, and so on. This tutorial shows you how to use a dynamic vertex buffer to create a basic particle effect. Each frame we will fill up the buffer with particles that are moved slightly from their previous positions.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Initialize application-specific resources and states here.
Returns: TRUE on success, FALSE on failure
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CGameApp::Initialize()
{

g_positions = new D3DXVECTOR3[1000];
g_speeds = new D3DXVECTOR3[1000];

// Seed random number generator
srand( (UINT)time(NULL) );
for ( int i = 0; i < 1000; i++ )
{

g_speeds[i].x = ((float)rand() / RAND_MAX * 1000.0f – 500.0f) / 50.0f;
g_speeds[i].y = ((float)rand() / RAND_MAX * 1000.0f – 500.0f) / 50.0f;
g_speeds[i].z = 0.0f;

g_positions[i].x = g_positions[i].y = g_positions[i].z = 0.0f;

}
return TRUE;

}

First, we’ll create two D3DXVECTOR3 arrays to store each particles’ position and velocity vector. The speeds are all randomly generated. We need to keep track of each particles’ position and speed in order to update the particles every frame.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been created. This is the best location to create D3DPOOL_DEFAULT resources. Resources created here should be released in the OnLostDevice callback.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnResetDevice( LPDIRECT3DDEVICE9 pDevice )
{

// Create the buffer
m_VB.CreateBuffer( pDevice, 1000, D3DFVF_XYZ | D3DFVF_DIFFUSE, sizeof( CUSTOMVERTEX ), TRUE );

// Clip…

float pointSize = 3.0f;
pDevice->SetRenderState( D3DRS_POINTSIZE, *((DWORD*)&pointSize) );

}

Second, we create the vertex buffer. Dynamic vertex buffers cannot be made in the D3DPOOL_MANAGED memory pool, therefore, we’ll create in in the D3DPOOL_DEFAULT memory pool. Since it is in the default memory pool, we create it in the OnResetDevice method. The CVertexBuffer class that we created in the vertex and index buffers tutorial supports the creation of dynamic buffers; we just set the last parameter to TRUE, which if not specified, is FALSE by default.

We can control the size of each particle by setting the D3DRS_POINTSIZE render state.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Creates the vertex buffer.
Parameters:
[in] pDevice – Pointer to IDirect3DDevice9
[in] numVertices – Max number of vertices allowed in the buffer
[in] FVF – Flexible Vertex Format
[in] vertexSize – Size of the vertex structure
[in] dynamic – TRUE for dynamic buffer, FALSE for static buffer
Returns: TRUE on success, FALSE on failure
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CVertexBuffer::CreateBuffer( LPDIRECT3DDEVICE9 pDevice, UINT numVertices, DWORD FVF, UINT vertexSize, BOOL dynamic )
{

Release();
m_numVertices = numVertices;
m_FVF = FVF;
m_vertexSize = vertexSize;

// Dynamic buffers can’t be in D3DPOOL_MANAGED
D3DPOOL pool = dynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED;
DWORD usage = dynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : D3DUSAGE_WRITEONLY;
if ( FAILED( pDevice->CreateVertexBuffer( m_numVertices * m_vertexSize, usage, m_FVF, pool, &m_pVB, NULL ) ) )
{

SHOWERROR( “CreateVertexBuffer failed.”, __FILE__, __LINE__ );
return FALSE;

}

return TRUE;

}

This is the buffer creation method in CVertexBuffer. When we specify a dynamic buffer, it will place the buffer in the D3DPOOL_DEFAULT memory pool and use D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC as its usage flag.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Updates the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
[in] elapsedTime – Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnUpdateFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime )
{

// Create the new vertices
CUSTOMVERTEX verts[1000];
for ( int i = 0; i < 1000; i++ )
{

g_positions[i] += g_speeds[i] * elapsedTime;
// Constrain points
if ( g_positions[i].x < –7.0f || g_positions[i].x > 7.0f ||
g_positions[i].y < –5.0f || g_positions[i].y > 5.0f )
{

g_positions[i].x = 0.0f;
g_positions[i].y = 0.0f;

}
// Form colors based on distance from origin
// Lots of parabola shift and squeeze math going on.
float distanceFromOrigin = D3DXVec3Length( &g_positions[i] );
int red = -(int)(distanceFromOrigin * distanceFromOrigin * 15.0f) + 255;
red = (red < 0) ? 0 : red;
red = (red > 255) ? 255 : red;
int green = -(int)((distanceFromOrigin – 5) * (distanceFromOrigin – 5) * 15) + 255;
green = (green < 0) ? 0 : green;
green = (green > 255) ? 255 : green;
int blue = -(int)((distanceFromOrigin – 8) * (distanceFromOrigin – 8) * 15) + 255;
blue = (blue < 0) ? 0 : blue;
blue = (blue > 255) ? 255 : blue;
D3DCOLOR color = D3DCOLOR_XRGB( red, green, blue );
ZeroMemory( &verts[i], sizeof( CUSTOMVERTEX ) );
verts[i].x = g_positions[i].x;
verts[i].y = g_positions[i].y;
verts[i].z = g_positions[i].z;
verts[i].color = color;

}
// Fill up the buffer with the new vertices
m_VB.SetData( 1000, verts );

}

This is the heart of dynamic buffers: updating it’s contents. Each frame, we update the particles’ position with the corresponding velocity vector. To create the illusion of particles being emitted, I reset the position of a particle to the origin whenever the particle goes outside of the bounding rectangle. The color is calculated based on the distance of the particle from the origin. The exact numbers took some tweeking until I got the effect that I wanted. Basically, I used the parabola equation y = x^2 for each color component. Using our clever math skills, we know we can shift a parabola with y = (x-shift)^2, and we can squeeze a parabola with y = (squeeze)x^2. Color components must have a value between 0 and 255 so I constrain the values to this range. Once all the positions and color values are set, I can insert_ the vertex data into the vertex buffer the SetData method that we implemented.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
[in] elapsedTime – Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnRenderFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime )
{

pDevice->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0 ), 1.0f, 0 );
pDevice->BeginScene();

m_VB.Render( pDevice, 1000, D3DPT_POINTLIST );

pDevice->EndScene();
pDevice->Present( 0, 0, 0, 0 );

}

Since we want to render a series of points, we use the D3DPT_POINTLIST primitive type. As you can see, dynamic buffers can be used to create many interesting effects.