Hi!
So I been struggling with evil evil DirectX 9 code, HLSL and texture atlas. While I still haven't completely figured the texture atlas to work 100% right, I've got everything else:

Now, considering there was a request before, I am going to share my code here, please note, some of it can still be optimized, I just finished.
Let us start with the way I construct my vertex buffers:
1. Before I begin, a class or two which may need some explanation, take a look at these two lines:
Code:
m_VertexBuffer.Create(sizeof(PolyVox::PositionMaterialNormal) * (mesh.getNoOfVertices()));
m_IndexBuffer.Create((mesh.getNoOfIndices() - problemVertices.size()) * sizeof(DWORD), 8UL, D3DFMT_INDEX32);
All you need to know is what they do in DirectX 9, here is a simplified version deleting most of the internal parts:
Code:
bool VertexBuffer::Create(UINT size, DWORD usage, DWORD FVF, D3DPOOL pool)
{
if(FAILED(gEngine->GraphicsDevice()->GetDeviceCOM()->CreateVertexBuffer( size,
usage, FVF,
pool, &m_VertexBuffer, NULL )))
{
Logger << "VertexBuffer: Failed creating vertex buffer!\n";
return false;
}
}
Now for the index buffer:
Code:
bool IndexBuffer::Create(UINT size, DWORD usage, D3DFORMAT format, D3DPOOL pool)
{
HRESULT hr;
if(FAILED(hr = gEngine->GraphicsDevice()->GetDeviceCOM()->CreateIndexBuffer(size, usage, format, pool, &m_IndexBuffer, NULL)))
{
Logger << "IndexBuffer: Failed creating index buffer!\n";
return false;
}
}
Ok, so now we know what those buffers do and how they really create the DX9 buffers, very simple so far.
Now for the next part, our mesh creation, I will first explain the mesh creation and the problems involved with it due to using triplanar texturing and texture atlas so keep this in mind, then I will go on to showing the shaders such as the atlas shader and triplanar texturing shader:
Code:
bool VoxelMesh::Construct(PolyVox::Volume<PolyVox::MaterialDensityPair44>& volume, PolyVox::Region region)
Here is what this is, my VoxelMesh represent part of a volume, not the whole volume (although they can, just pass them a full region), this function creates it.
Ok, now let's see how it works.
Code:
PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> mesh;
PolyVox::SurfaceExtractor<PolyVox::MaterialDensityPair44> surfaceExtractor(&volume, region, &mesh);
surfaceExtractor.execute();
const vector<uint32_t>& vecIndicest = mesh.getIndices();
const vector<PolyVox::PositionMaterialNormal>& vecVerticest = mesh.getVertices();
So far all should be familiar, we tell polyvox to create us a mesh.
Now comes the tricky part, with triplanar texturing AND texture atlas combined in DirectX 9 there will be some major artifacts if just rendered as-is, such as:

This is cause because of the following - imagine you have a triangle with one vertex having material 0 which represents grass and two other vertices having material 3, representing dirt.
Now in a 4x4 texture atlas that means that the first vertex would have texture coordinate between 0.0 to 0.25, the second and third vertices would have coordinates between 0.75 to 1.0, since everything that is between the differing materials' vertices gets interpolated this means that color values which are in between the two textures slots would also get it and the values would range anywhere from 0 to 1 interpolating 4 textures into one triangle. This whole a lot of data is also what causes it to appear stretched.
So this kind of a triangle we will now call a problematic triangle and its vertices would be "problemVertices".
Now after we had PolyVox create our mesh and we retrieved this data, we need to deal with our "problemVertices".
Code:
vector<uint32_t> problemVertices;
float mat1;
float mat2;
float mat3;
for (vector<uint32_t>::const_iterator iter = vecIndicest.begin(); iter != vecIndicest.end(); iter++)
{
mat1 = vecVerticest[*iter].getMaterial();
mat2 = vecVerticest[*(iter+1)].getMaterial();
mat3 = vecVerticest[*(iter+2)].getMaterial();
if (floatComparsion(mat1, mat2) == false || floatComparsion(mat1, mat3) == false)
{
problemVertices.push_back(*iter);
problemVertices.push_back(*(iter+1));
problemVertices.push_back(*(iter+2));
}
iter++;
iter++;
}
Here I initialize a vector to store the indices of our problematicVertices, or triangles actually. floatComparsion is just comparing floats with a slight epsilon to see if they are close enough to count as the same due to floating point precisions. I actually had incorrect identification of "problematicVertices" due to this imprecision, so here is the function:
Code:
inline bool floatComparsion(float one, float two)
{
if ( fabs(one-two) < 0.0001f )
return true;
return false;
}
Now that we have identified our offenders, it is time to have a fix. And how do we fix them? Well, there trick is to first render the offender triangles black, we do this by adding the triangle to the original mesh with a black material, in my case I set the material to be 300 and in the shader I check if a material is over 200 and return a black color if it is (feel free to play with those numbers as suits your use of PolyVox and the number of materials you might have).
Then we render the offender triangles up to 3 more times, each time with one of the different materials (textures) a vertex of the triangle has, with the other two vertices also set to have the same material but an alpha of 1, then the alpha is interpolated over from the vertex shader to the pixel shader and a smooth transition happens when using additive blend.
Here is how I set up our new meshes (please ignore the un-used count value):
Code:
if (!problemVertices.empty())
{
vector<uint32_t> vecIndices;
vecIndices.reserve(problemVertices.size() * 3);
vector<PolyVox::PositionMaterialNormal> vecVertices;
vecVertices.reserve(problemVertices.size() * 3);
PolyVox::PositionMaterialNormal fixVert1;
PolyVox::PositionMaterialNormal fixVert2;
PolyVox::PositionMaterialNormal fixVert3;
for (vector<uint32_t>::iterator iter = problemVertices.begin(); iter != problemVertices.end();)
{
float mat1;
float mat2;
float mat3;
fixVert1 = vecVerticest[*iter];
iter++;
fixVert2 = vecVerticest[*iter];
iter++;
fixVert3 = vecVerticest[*iter];
iter++;
mat1 = fixVert1.getMaterial();
mat2 = fixVert2.getMaterial();
mat3 = fixVert3.getMaterial();
fixVert1.setMaterial(300);
fixVert2.setMaterial(300);
fixVert3.setMaterial(300);
mesh.addTriangle(mesh.addVertex(fixVert1), mesh.addVertex(fixVert2), mesh.addVertex(fixVert3));
fixVert1.setMaterial(mat3+16);
fixVert2.setMaterial(mat3+16);
fixVert3.setMaterial(mat3);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert1);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert2);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert3);
fixVert1.setMaterial(mat2+16);
fixVert2.setMaterial(mat2);
fixVert3.setMaterial(mat2+16);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert1);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert2);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert3);
fixVert1.setMaterial(mat1);
fixVert2.setMaterial(mat1+16);
fixVert3.setMaterial(mat1+16);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert1);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert2);
vecIndices.push_back(vecVertices.size());
vecVertices.push_back(fixVert3);
}
m_NumVerticesFix = vecVertices.size();
m_NumFacesFix = vecIndices.size() / 3 ;
m_VertexBufferFix.Create(sizeof(PolyVox::PositionMaterialNormal) * (vecVertices.size()));
m_IndexBufferFix.Create(vecIndices.size() * sizeof(DWORD), 8UL, D3DFMT_INDEX32);
void* pVoid;
m_IndexBufferFix.Lock(&pVoid);
const void* pIndices = static_cast<const void*>(&(vecIndices[0]));
uint32_t count = 0;
memcpy((pVoid), pIndices, sizeof(uint32_t) * vecIndices.size());
m_IndexBufferFix.Unlock();
const void* pVertices = static_cast<const void*>(&(vecVertices[0]));
m_VertexBufferFix.Lock(&pVoid);
count = 0;
memcpy((pVoid), pVertices, sizeof(PolyVox::PositionMaterialNormal) * vecVertices.size());
m_VertexBufferFix.Unlock();
}
Ok, so here what we do is first create arrays and reserve the predicted amount of values to be inserted into them.
Next we go over our problematic vertices, save their materials, set the materials to 300 and add them to the original mesh. That will take care of them being rendered as black later on.
Next what we do is set the three corners as I explained earlier, we add the triangle three times, each time with a different material when the corner who originally had that material has it again in its material's triangle (it will have the other materials in the other two triangles [+16]) and the other two vertices have the same material but with 16 added. Why 16 is added? Because I didn't feel like creating and switching over additional FVFs, as well as creating a new structure to add the alpha value in it. This means that in the shader, if a material is greater or equal 16 then it will set the alpha to be 0 in the vertex shader so it is sent to be interpolated in the pixel shader (it will also reduce the material's value by 16 so it can actually be used).
Do note that you should change that number (16) if your application is going to use more than 16 materials/textures.
Next all we do is create the vertex buffer and index buffer for the "fixed" triangles and save the number of faces and number of vertices.
Now two tasks are left for us:
1. Create the original mesh's buffers.
2. Remove the offender triangles' indices from the mesh so they won't be rendered. Remember we added additional black triangles to replace them anyway (the black is so the fixed triangles will render with additive blending over it instead of add with the environment).
Hurray, let's continue with our code then:
Code:
const vector<uint32_t>& vecIndices = mesh.getIndices();
const vector<PolyVox::PositionMaterialNormal>& vecVertices = mesh.getVertices();
m_VertexBuffer.Create(sizeof(PolyVox::PositionMaterialNormal) * (mesh.getNoOfVertices()));
m_IndexBuffer.Create((mesh.getNoOfIndices() - problemVertices.size()) * sizeof(DWORD), 8UL, D3DFMT_INDEX32);
if (vecIndices.empty())
return false;
void* pVoid;
m_IndexBuffer.Lock(&pVoid);
const void* pIndices = static_cast<const void*>(&(vecIndices[0]));
uint32_t count = 0;
uint32_t* newIndices = (uint32_t*)pVoid;
for (vector<uint32_t>::const_iterator iter = vecIndicest.begin(); iter != vecIndicest.end(); iter++)
{
mat1 = vecVerticest[*iter].getMaterial();
mat2 = vecVerticest[*(iter+1)].getMaterial();
mat3 = vecVerticest[*(iter+2)].getMaterial();
if (floatComparsion(mat1, mat2) == false || floatComparsion(mat1, mat3) == false)
{
iter++;
iter++;
}
else
{
newIndices[count] = *iter;
iter++;
count++;
newIndices[count] = *iter;
iter++;
count++;
newIndices[count] = *iter;
count++;
}
}
m_IndexBuffer.Unlock();
Simple here, retrieve our mesh indices and mesh vertices (we changed it a bit, but probably no need to do this again as we did get a reference to it last time, not a copy, optimization note).
Create vertex buffer and index buffer big enough, when creating the index buffer remember we intend to remove some indices (shown in the code)
Now copy the indices to the locked buffer (to the GPU), skip the offenders (room for optimization in this loop).
For the next bit of code all we are left with is copying our vertices to the buffer and obviously saving the number of faces and number of vertices we have:
Code:
const void* pVertices = static_cast<const void*>(&(vecVertices[0]));
m_VertexBuffer.Lock(&pVoid);
count = 0;
memcpy((pVoid), pVertices, sizeof(PolyVox::PositionMaterialNormal) * vecVertices.size());
PolyVox::PositionMaterialNormal* vert = (PolyVox::PositionMaterialNormal*)pVoid;
m_VertexBuffer.Unlock();
m_NumVertices = vecVertices.size();
m_NumFaces = (vecIndices.size() - problemVertices.size()) / 3 ;
}
Done! (additional bit I did was also create a translation matrix to move the mesh from local space to region.getLowerCorner()'s position)
Now we are left with the rendering code (and the shaders):
Code:
void VoxelMesh::RenderImmediate()
Here we assign our world's position, assign our texture atlas to the appropriate texture slot, set our material (the color, not the PolyVox related) and then just set our index buffer, vertex buffer and deal with the offender triangles. Here is how we do that.
First we render the original mesh, no alpha blending, no alpha testing, nothing.
Code:
if (!m_NumFaces)
return;
m_WorldPosition.Assign();
if (m_pTexture)
gEngine->SetTexture(0, m_pTexture);
Material mat;
mat.Apply();
gEngine->GraphicsDevice()->GetDeviceCOM()->SetFVF(D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetStreamSource(0, *m_VertexBuffer.GetVertexBuffer(), 0, sizeof(PolyVox::PositionMaterialNormal));
gEngine->GraphicsDevice()->GetDeviceCOM()->SetIndices(*m_IndexBuffer.GetIndexBuffer());
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO );
gEngine->GraphicsDevice()->GetDeviceCOM()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0, m_NumVertices, 0,m_NumFaces);
Now we need to render our offenders, this we do with alpha blending mode set to additive, source blend would be source alpha and destination blend value would be dest alpha.
Code:
if (m_NumFacesFix != 0)
{
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_DESTALPHA);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetIndices(*m_IndexBufferFix.GetIndexBuffer());
gEngine->GraphicsDevice()->GetDeviceCOM()->SetStreamSource(0, *m_VertexBufferFix.GetVertexBuffer(), 0, sizeof(PolyVox::PositionMaterialNormal));
gEngine->GraphicsDevice()->GetDeviceCOM()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0, m_NumVerticesFix, 0,m_NumFacesFix);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO );
}
gEngine->GraphicsDevice()->GetDeviceCOM()->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
That's it for the DirectX part of the program! Rest is HLSL and I will continue this in the next post.