Jump to content
  • Advertisement
Sign in to follow this  
Medo Mex

Rendering multiple billboard with one draw call

This topic is 1888 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have created billboard class but it's drawing one single billboard per draw call, now I want to it draw ALL the billboards with one draw call.

 

I'm creating the vertex buffer as the following:

// Class Constructor
float scale = 20.0f;

device->CreateVertexBuffer(4 * sizeof(BILLBOARD_VERTEX),
                           D3DUSAGE_WRITEONLY,
                           CustomBillboardFVF,
                           D3DPOOL_DEFAULT,
                           &vbuffer,
                           NULL);

vbuffer->Lock(0, 0, (void**)&vertices, 0);
pVertices[0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*scale, 1.0f*scale, 0.0f), 0.0f, 0.0f);
pVertices[1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*scale, 1.0f*scale, 0.0f), 1.0f, 0.0f);
pVertices[2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*scale, -1.0f*scale, 0.0f), 0.0f, 1.0f);
pVertices[3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*scale, -1.0f*scale, 0.0f), 1.0f, 1.0f);
vbuffer->Unlock();

 

For rendering:

void render()
{
       device->SetFVF(CustomBillboardFVF);
       device->SetTexture(0, billboardTexture);
       device->SetStreamSource(0, vbuffer, 0, sizeof(BILLBOARD_VERTEX));
       device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
}

 

I'm storing the billboards in vector<billboard>.

 

How do I change my code to render all the billboards with one single draw call?

Share this post


Link to post
Share on other sites
Advertisement

This is from my "old" code base that i've used couple of years ago (when i was stupid, note that i am not much smarter nowadays biggrin.png ) , based on Irrlicht engine particle system, note that (although this works) some things should be done better, and i am to lazy to clean up my code but you should get some ideas how to use it:

 

bool ParticleSceneNode::Init(.......)
{
    D3D9Device = d3d9device;

    VDecl = new VertexDeclaration;
    if(!VDecl->Init(D3D9Device, EVT_3D_PNCT1))
    {
        return false;
    }
 
    // create vertex buffer arbitrarily large to hold some particle quads
    VBuffer = new VertexBuffer;
    if(!VBuffer->Init(D3D9Device, 512, sizeof(Vertex3D_PNCT1), true))
    {
        return false;
    }

    UINT numIndices = (VBuffer->GetNumVertices() * 2) - (VBuffer->GetNumVertices() / 2);
    IBuffer = new IndexBuffer;
    if(!IBuffer->Init(D3D9Device, numIndices, sizeof(USHORT)))
    {
        return false;
    }

    USHORT* pIndices = 0;
    IBuffer->Lock((LPVOID*)&pIndices);
    USHORT j = 0;
    for(USHORT i = 0; i < numIndices; i += 6)
    {
        pIndices[0+i] = 0+j;
        pIndices[1+i] = 2+j;
        pIndices[2+i] = 1+j;
        pIndices[3+i] = 0+j;
        pIndices[4+i] = 3+j;
        pIndices[5+i] = 2+j;
        j += 4;
    }
    IBuffer->Unlock();
 
    ...
    // HERE ADD SOME PARTICLE EMMITERS & AFFECTORS...
   ...

    ParticleSize  = 8;
    LastEmittTime = 0;

    return true;
}

void ParticleSceneNode::OnRender()
{
    DWORD time = ::GetTickCount();
    DoParticleSystem(time);

    const Matrix4& viewMat = Smgr->GetActiveCamera()->GetView();
    float f = 0.5f * (float)ParticleSize;
    Vector3D horizontal(viewMat.M00 * f, viewMat.M10 * f, viewMat.M20 * f);

    f = -0.5f * (float)ParticleSize;
    Vector3D vertical(viewMat.M01 * f, viewMat.M11 * f, viewMat.M21 * f);

    Vector3D view(-viewMat.M02, -viewMat.M12 , -viewMat.M22);

    D3D9Device->SetVertexDeclaration(VDecl->GetDeclaration());
    D3D9Device->SetIndices(IBuffer->GetBuffer());
    D3D9Device->SetStreamSource(0, VBuffer->GetBuffer(), 0, sizeof(Vertex3D_PNCT1));

    UINT numVert    = 0;
    UINT numTri     = 0;
    Vertex3D_PNCT1* pVertices = 0;
    VBuffer->Lock((LPVOID*)&pVertices, D3DLOCK_DISCARD);
    for(DWORD i = 0; i < (DWORD)AParticles.Size(); i++)
    {
        const Particle& particle                   = AParticles;

        Vector3D shorizontal = horizontal * particle.Scale;
        Vector3D svertical   = vertical * particle.Scale;
        
        pVertices[0 + numVert].Position         = particle.Position + shorizontal + svertical;
        pVertices[0 + numVert].Normal           = view;
        pVertices[0 + numVert].TexCoord0        = Vector2D(0.0f, 0.0f);
        pVertices[0 + numVert].Color            = particle.Color;

        pVertices[1 + numVert].Position         = particle.Position + shorizontal - svertical;
        pVertices[1 + numVert].Normal           = view;
        pVertices[1 + numVert].TexCoord0        = Vector2D(0.0f, 1.0f);
        pVertices[1 + numVert].Color            = particle.Color;

        pVertices[2 + numVert].Position         = particle.Position - shorizontal - svertical;
        pVertices[2 + numVert].Normal           = view;
        pVertices[2 + numVert].TexCoord0        = Vector2D(1.0f, 1.0f);
        pVertices[2 + numVert].Color            = particle.Color;

        pVertices[3 + numVert].Position         = particle.Position - shorizontal + svertical;
        pVertices[3 + numVert].Normal           = view;
        pVertices[3 + numVert].TexCoord0        = Vector2D(1.0f, 0.0f);
        pVertices[3 + numVert].Color            = particle.Color;

        if(particle.Rotation)
        {
            Vector2D centerCoord(0.5f, 0.5f);
            pVertices[0 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
            pVertices[1 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
            pVertices[2 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
            pVertices[3 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
        }

        numVert    += 4;
        numTri     += 2;
 
 
        // if exceded buffer size unlock -> draw -> lock
        if(numVert > (VBuffer->GetNumVertices() - 4))
        {
            VBuffer->Unlock();
            D3D9Device->DrawIndexedPrimitive(
                D3DPT_TRIANGLELIST, 0, 0, numVert, 0, numTri);
            pVertices = 0;
            numVert   = 0;
            numTri    = 0;
            VBuffer->Lock((LPVOID*)&pVertices, D3DLOCK_DISCARD);
        }
    }
    VBuffer->Unlock();
 
    // if more quads left draw it
    if(numVert > 0)
    {
        D3D9Device->DrawIndexedPrimitive(
                D3DPT_TRIANGLELIST, 0, 0, numVert, 0, numTri);
    }
}
Edited by belfegor

Share this post


Link to post
Share on other sites

Why not just make your vertex buffer bigger and have a different position value for each billboard - there is a limit to the size of the vertex buffer, but I do a brute force approach and pack a big vertex buffer full of all the billboards I'm going to need, and then draw them all in one call.

 

You can find the logic and code samples for rotating the billboard in the vertex shader here.

http://www.dhpoware.com/demos/xnaBillboards3.html and here https://developer.nvidia.com/content/gpu-gems-2-chapter-1-toward-photorealism-virtual-botany-1

Share this post


Link to post
Share on other sites

I changed my code, now I can't see the billboards anymore:

 

#define BillboardFVF (D3DFVF_XYZ | D3DFVF_TEX1)

 

Billboard class constructor:

// Class Constructor
BILLBOARD_VERTEX *vertices = NULL;

device->CreateVertexBuffer(10000 * sizeof(BILLBOARD_VERTEX),
  D3DUSAGE_WRITEONLY,
  CustomBillboardFVF,
  D3DPOOL_DEFAULT,
  &vbuffer,
  NULL);

device->CreateIndexBuffer(10000 * sizeof(BILLBOARD_VERTEX),
  D3DUSAGE_WRITEONLY, D3DFMT_INDEX32, 
  D3DPOOL_MANAGED, 
  &indexBuffer,
  NULL);

UINT numIndices = (10000 * 2) - (10000 / 2);
USHORT* pIndices = 0;
indexBuffer->Lock(0, 0, (LPVOID*)&pIndices, 0);
USHORT j = 0;
for(USHORT i = 0; i < numIndices; i += 6)
{
pIndices[0+i] = 0+j;
pIndices[1+i] = 2+j;
pIndices[2+i] = 1+j;
pIndices[3+i] = 0+j;
pIndices[4+i] = 3+j;
pIndices[5+i] = 2+j;
j += 4;
}
indexBuffer->Unlock();

Rendering code:

 

void render()
{
// Set FVF
d3ddev->SetFVF(CustomBillboardFVF);

for(DWORD i = 0; i < billboard.size(); i++)
{
     vbuffer->Lock(0, 0, (void**)&pVertices, 0);
     vertices[i*4+0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
     vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
     vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
     vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 1.0f, 1.0f);
     vbuffer->Unlock();
}

D3DXMATRIX matWorld;
D3DXMatrixIdentity(&matWorld);
device->SetTransform(D3DTS_WORLD, &matWorld);

// 2-Sided
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

// Texture
device->SetTexture(0, texture);

device->SetStreamSource(0, vbuffer, 0, sizeof(CUSTOM_VERTEX));
device->SetIndices(indexBuffer);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 10000, 0, 5000);
}

What's wrong?

Share this post


Link to post
Share on other sites

There are many problems in your code.

 

1. Check all functions that returns HRESULT for failure.

 

2.

 

device->CreateIndexBuffer(10000 * sizeof(BILLBOARD_VERTEX), ....

 

should be:

 

 

// if D3DFMT_INDEX32
device->CreateIndexBuffer(numIndices * sizeof(DWORD), ...
 
//if D3DFMT_INDEX16
device->CreateIndexBuffer(numIndices * sizeof(WORD), ...

 

There are 4 vertices for each particle/billboard. There are 2 triangles in each of those and there is a 3 indices per triangle:

 

maxNumParticlesInBuffer = 100;
numVertices                     = maxNumParticlesInBuffer * 4;
numTriangles                   = (maxNumParticlesInBuffer * 2);
numIndices                       = numTriangles * 3;

 

3.

This:

 

for(DWORD i = 0; i < billboard.size(); i++)
{
     vbuffer->Lock(0, 0, (void**)&pVertices, 0);
     vertices[i*4+0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
     vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
     vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
     vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 1.0f, 1.0f);
     vbuffer->Unlock();
}

You are placing each particle on the same spot, and locking/unlocking buffer for each one.

 

You should create something like this:

 

struct Particle
{
   Vec3 pos;
   Color col;
   ...// add more components that you need
};
...
std::vector<Particle> vParticles;
...// fill it with emitter and move, change color (affect) with affectors.

 

and then use this to update your buffer vertices.

 

4.

This says CUSTOM_VERTEX

 

device->SetStreamSource(0, vbuffer, 0, sizeof(CUSTOM_VERTEX));

but above for vertices you used BILLBOARD_VERTEX.

Edited by belfegor

Share this post


Link to post
Share on other sites

@belfegor: I notice that you make one draw call per billboard in the code you posted, while I want to draw all the billboards with one single draw call.

Share this post


Link to post
Share on other sites

I notice that you make one draw call per billboard in the code you posted,

That is not true!

 

 

 

// if exceded buffer size unlock -> draw -> lock
        if(numVert > (VBuffer->GetNumVertices() - 4))
        {
            VBuffer->Unlock();
            D3D9Device->DrawIndexedPrimitive(
                D3DPT_TRIANGLELIST, 0, 0, numVert, 0, numTri);
            pVertices = 0;
            numVert   = 0;
            numTri    = 0;
            VBuffer->Lock((LPVOID*)&pVertices, D3DLOCK_DISCARD);
        }

 

i only call draw if current number of particles exceed buffer size! If you make your buffer large enough this should not happen and you get only one draw call instead possible 2 - 3 witch isn't bad at all in that case either, and i can bet this is the common way it is done by other people.

Can you estimate how many billboards you will have? If so, you can set buffer at exact size so you will 100% get only one draw call.

Edited by belfegor

Share this post


Link to post
Share on other sites

@belfegor: How do I change the following code to set the billboard position (with respect to size)?

pVertices[i*4+0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 1.0f, 1.0f);

Share this post


Link to post
Share on other sites

Try to think about it.

 

You got your billboard struct:

struct Billboard
{
    D3DXVECTOR3 pos; // position
    float size; // billboard size
    float lifeTime; // time alive
};
 
vector< Billboard > billboard;

 

Over time you fill this vector with new billboards, remove old ones when their time expire and update its position/color the way you like.

Then you use that position to update its four vertices:

 

pVertices[i*4+0] = BILLBOARD_VERTEX(billboard.pos + D3DXVECTOR3(-1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(billboard.pos + D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(billboard.pos + D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(billboard.pos + D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 1.0f, 1.0f);

 

But this code doesn't take orientation into account. I gave you good sample in second post of this thread.

Share this post


Link to post
Share on other sites

I don't know how many billboard I will be having.

 

Should I create use device->CreateVertexBuffer() while rendering so I can make the buffer size as needed?

 

Will using device->CreateVertexBuffer() while rendering affect the performance?

 

My code will be like:

void render()
{
device->CreateVertexBuffer(billboards.size() * 4 * sizeof(BILLBOARD_VERTEX),
                           D3DUSAGE_WRITEONLY,
                           CustomBillboardFVF,
                           D3DPOOL_DEFAULT,
                           &vbuffer,
                           NULL);

device->CreateIndexBuffer(...); 

// Others codes for rendering here...
}

I'm thinking the above code could affect the performance since I should not be creating Vertex and Index buffer while rendering.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!