Sign in to follow this  
Medo Mex

Rendering multiple billboard with one draw call

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

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[i];

        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[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
     vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
     vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
     vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].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[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
     vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
     vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
     vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].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

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[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].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[i].pos + D3DXVECTOR3(-1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(billboard[i].pos + D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(billboard[i].pos + D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(billboard[i].pos + D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].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

No. Create VB only at init time. Number of vertices depends on particular effect you want to achieve.

For example, for smoke steam going out of pipe i used around 60x4 (240 vertices), which for me looks satisfactory.

 

Your container of particles should change dynamically over time so you cant use it to determine
VB size, unless it is constant and you never remove or add new ones. You must use some kind of emitter, and then update your particle/billboards vector:

 

void onUpdate( float deltaTime )
{
    emitter->emitt(deltaTime, vParticles);
    

    // affectors affect particle
    // adds to their direction/position/scale/rotation whatever
    for(std::size_t i = 0; i < vAffectors.size(); ++i)
    {
        if(!vAffectors[i]->isEnabled())
            continue;
        vAffectors[i]->affect(deltaTime, vParticles);
    }

    for(std::size_t i = 0; i < vParticles.size(); ++i)
    {
        vParticles[i].pos      += vParticles[i].dir * deltaTime; // particle.dir is their emitted direction
        vParticles[i].currTime += deltaTime; // increment their life time

        if(vParticles[i].currTime > vParticles[i].endTime) // if time to die, remove particle
        {
            std::swap(vParticles[i--], vParticles.back());
            vParticles.pop_back();
        }
    }
}
Edited by belfegor

Share this post


Link to post
Share on other sites

I will be having a single class that will handle all the billboards, so I don't know how many billboards I will be using, I could use 100 or I could use 5000 or maybe even 20000 billboard.

 

So it depends on the game, the billboard class will be related to the engine, so the game developer will not be editing the class.

 

The class should be able to handle unlimited number of billboards.

Share this post


Link to post
Share on other sites

What about you get this working first (at least) and then think about "advanced class that will manage all billboards with unlimited count"?

My example above can already handle "unlimited number of billboard" but the draw calls depends on initial size of VB. You will always be faced with some "limitations", which isn't that bad at all.

Share this post


Link to post
Share on other sites

Okay, since I don't know how many billboard I will be drawing in the class constructor, is it a good idea to set the initial size of the VB to a large number? for example: 40000*sizeof(DWORD) or 80000*sizeof(DWORD).

Share this post


Link to post
Share on other sites

This looks like a great time to just thrash whatever you have done with billboards and start from scratch - otherwise you will just kill inordinate amounts of time trying to fix something completely broken.

 

Seriously, just delete the code and start again using the knowledge about billboards. If you really understand how a billboard is constructed, you should be up&running within 2 hrs anyway.

 

Start small - one quad, then 2 quads, then 200 quads and save the general case for the last...

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this