Jump to content
  • Advertisement
Sign in to follow this  
Jacob Mnasin

Help Understanding Instancing

This topic is 2076 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

Hello all, Im trying to make a strategy game and I'm having a hard time wrapping my head around instancing objects. I was able to draw my terrain by instancing them, but how would I go about rendering the other models with various meshes and materials.

 

I have a Model class which stores a mesh and a material. The material class contains an array of textures and the pixel and vertex shader buffers. Some meshes are reused but use different Materials (spheres are used for planets and skydomes for example). The way I have it right now is that every time an object is created, the mesh initializes a new vertex and index buffer and it takes a second to compute even on fast computer, which is why I am looking into instancing.

 

The other thing I want to know is what would be the best way to keep track of the instanced data, since I assume a simple vector list will no longer work in this case. For example: I have a Unit and a Building class. The unit class is a class from which I will create multiple types of units, each with their own unique Model (mesh and matrial), and the same for the building class. I dont want to hard code a counter each type of unit since their data will be loaded from an XML file.

 

Any suggestions would be appreciated! Thank you!

Share this post


Link to post
Share on other sites
Advertisement

Never mind I figured out a better solution to my problem. Decided to go against instancing since all the extra trouble of sorting the items seems like it would be counter productive. Instead, I check if a mesh already exists, and if it does, I just copy that meshes buffers and material over.  I guess instancing is a sort of thing better left for particles and sprites and things of that sort. But that solved my problem of not having to initialize the buffers...feel kinda silly i didnt think of this before.

Share this post


Link to post
Share on other sites

You should not draw the same mesh with more then one draw call. Use instancing for every mesh you plan on drawing more then once. If you use the same mesh with different textures you can build a texture atlas or use more samplers.

Edited by Tispe

Share this post


Link to post
Share on other sites

Never mind I figured out a better solution to my problem. Decided to go against instancing since all the extra trouble of sorting the items seems like it would be counter productive. Instead, I check if a mesh already exists, and if it does, I just copy that meshes buffers and material over.  I guess instancing is a sort of thing better left for particles and sprites and things of that sort. But that solved my problem of not having to initialize the buffers...feel kinda silly i didnt think of this before.

You found a solution which works and which is quite easy for the start. But probably not optimal for performance ;) Especially if you draw a lot of copies of the same mesh. If you copy the buffers, they are completely unique for the device and you waste your memory (multiple copies of the same stuff). Instancing would be better. But that's just an idea how to improve it later, where to go. It's important to get it working somehow first - and that's what you already have.

Share this post


Link to post
Share on other sites

There is no reason not to use instancing. The difficulty is at the point where you support instancing and support drawing non-instanced objects. The difficulty is that you'll need separate shaders and code paths for the instancing. However, instancing supports drawing only one mesh, so it should be considered as a more general solution than just drawing a singular object. When I realized this, I dropped all support for drawing singular objects since that way I could simplify my program.

 

The basic solution is to gather objects with exactly the same attributes (shaders, materials, different transforms + some other per instance data) and draw them with one draw call. 

 

I assume that before drawing you are already collecting all visible objects so after what rests is just sorting them and finding the objects with same attributes.

 

Especially on PC the draw calls are expensive so instancing is almost obligatory to unleash all the power of the GPUs.

 

Cheers!

Share this post


Link to post
Share on other sites

If you don't want to go all the way and get into gpu instancing, you can get fairly good results by splitting out the varying properties of the model (world transform, material, bone transforms, etc) into another class (say, ModelInstance), and including a reference to the mesh as part of that class.  Then you load the mesh and create the buffers once, and for every object that uses the mesh, create a ModelInstance object to contain the object-specific data.

 

You definitely do not want to have multiple copies of the vertex and index buffers in memory.

Share this post


Link to post
Share on other sites

Thanks for the reply guys!

 

So if I understand correctly, I should create a MeshInstance struct that will hold the position, rotation, scale and  an array of textures. Instead of my GameObject class having a MeshClass, they will now have a MeshInstance. To sort the meshes I could maybe do this:

std::map<MeshName, std::vector<MeshInstance>> InstanceMap;

and loop through all the loaded meshes and return a list of instances per mesh?

 

ok sounds simple enough so far....but how would I go about passing this information to the shader? Is it better to store the position, rotation and scale separate or should I just pass in the objects worldMatrix. And how would I go about passing the textures? Is it possible to create a constant buffer with an ID3D11ShaderResourceView array??

Edited by JacobM

Share this post


Link to post
Share on other sites

Ok I almost got it all figured out...Now I just want to know, how would I modify the values in the instance buffer? (for example updating positions or adding more instances)?

Share this post


Link to post
Share on other sites

Yeah, that is about the way that I do it.  I find it easier to just pass the combined world matrix into the shader; depending on how you are transforming your objects, or if you need to get at any of the components in your shader, you could pass in the components instead.

 

So far as I know, textures and texture arrays cannot be part of constant buffers.  You have to define them at what I guess you would call "global" scope; outside any of your cbuffers or shader functions.  I use the Effects framework, rather than straight shaders, so I don't know the particulars of setting textures for the different pipeline stages.

Share this post


Link to post
Share on other sites

Thanks for all the help guys, thought id share my findings with everyone! Happy Trails!

 

Instancing Materials:

Note: This tutorial is meant for intermediate users and will assume you are already using instancing when rendering your meshes.

 

Sometimes, you may want to draw many instances of a model using different materials. In the following tutorial I will show you how you can achieve this. First of all, we will need to set up our mesh class in a way that it will hold all the information for all of the possible instances it contains. Each game object will have a pointer to that mesh Instance from which it can manipulate the data.

 

We begin by declaring a few variables in our Mesh class:

std::vector<MeshInstance*> meshInstanceList;

std::vector<MaterialInfo*> materialList;

int instanceCount;

ID3D11Buffer *matBuff;

 

Create strctures to be passed into our buffer. The Material Instanced structure will hold the information for a single material consisting of 3 textures: Diffuse, normal and specular. You can easily add more if you need to.

struct MaterialInfo

{

       XMFLOAT4 Ambient;

       XMFLOAT4 Diffuse;

       XMFLOAT4 Specular; // w = SpecPower

       XMFLOAT4 Reflect;

       ID3D11ShaderResourceView* texture[3];

};

 

struct MaterialInstanced

{

       MaterialInfo* matInfo[10];

};

 

 

The Mesh Instance will hold information about each instance. In this case, their position and material ID.

struct MeshInstance

{

       UINT MaterialID;

       XMFLOAT4x4 world;   

};

 

 

Modify the input layout:

 

D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[] =

{

{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},

{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},

{"NORMAL",    0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0},

{"TANGENT",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0},

 

{"MATERIALID",0, DXGI_FORMAT_R32_FLOAT,1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1},

{"WORLD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 4, D3D11_INPUT_PER_INSTANCE_DATA, 1},      

};      

 

We will then need to create a buffer which will hold the information of all the different material types that are assigned to the instances of that model. The Material buffer should be called once for every type of Mesh. Note that the Bind flags parameter is set to D3D11_BIND_SHADER_RESOURCE:

bool Mesh::CreateMaterialBuffer(ID3D11Device* device)

{

       bool result;

       D3D11_BUFFER_DESC materialBufferDesc;

       materialBufferDesc.Usage = D3D11_USAGE_DYNAMIC;

       materialBufferDesc.ByteWidth = sizeof(MaterialInstanced);

       materialBufferDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;

       materialBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

       materialBufferDesc.MiscFlags = 0;

       materialBufferDesc.StructureByteStride = 0;

 

       result = device->CreateBuffer(&materialBufferDesc, NULL, &matBuff);

       if(FAILED(result))

       {

              return false;

       }

}

 

In our draw call before we map the instance buffer, we must map the material array to our Material Buffer.

MaterialInstanced* materialData;

deviceContext->Map(matBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);

materialData = (MaterialInstanced*)resource.pData;

 

for(int x = 0; x< materialList.size(); x++)

{

     materialData->matInfo[x] = materialList[x];

}

 

deviceContext->Unmap(matBuff, 0);

deviceContext->PSSetConstantBuffers(0, 1, &matBuff);

 

 

In our instance buffer we will need to assign a material ID for each instance:

deviceContext->Map(instBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);

instanceData = reinterpret_cast<MeshInstance*>(mappedResource.pData);

for (int x = 0; x< instanceCount; ++x)

{     

instanceData[x].MaterialID = meshInstanceList[x]->MaterialID;

       instanceData[x].world = meshInstanceList[x]->world;

}     

deviceContext->Unmap(instBuff, 0);

 

HLSL:

In your effect file, you will need to create matching structure for our MaterialInstance class:

 

struct Material

{

       XMFLOAT4 Ambient;

       XMFLOAT4 Diffuse;

      //Diffuse = 0;

       //Normal = 1;

       //Specular= 2;

       ID3D11ShaderResourceView* texture[3];

};

 

Now that Material structure is created, we will create a texture buffer(tbuffer) that will hold an array of materials.

tbuffer MaterialBuffer

{

       Material material[10];    

}

 

Note that we did not create a structure for our mesh instance because our mesh will be sent through the vertex buffer along with an id that will correspond to our Material array. The instance data should look something like the following:

struct VertexInputType

{

       float4 position : POSITION;  

       float2 tex : TEXCOORD0;

       float3 normal : NORMAL;

       float3 tangent: TANGENT;      

       //Instanced Data:

    uint materialID: MATERIALID;

       float4x4 world : WORLD;   

};

struct PixelInputType

{

       float4 position : SV_POSITION;   

       float2 tex : TEXCOORD0;

       float3 normal : NORMAL;

       float3 tangent: TANGENT;

       int materialID: MATERIALID0;

};

 

In the vertex shader we will pass the materialID into the pixel shader:  

output.materialID = asint(input.materialID);

In the pixel shader, we will loop through materials and get our textures:

float4 diffuseTexture;

 

for(int x = 0; x < 10; x++)

{

       if(x == input.materialID)

       {

              diffuseTexture = material[x].Texture[0].Sample(SampleType, input.tex);

              break;

       }

}

Edited by JacobM

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.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!