Hello all,
Im trying to implement a particle system from Frank Lunas Intro to DX11 book. I'm trying to do so without using the effect framework and have been successful for many of the topics, but I am completely stumped on this one. Mostly because my understanding of the geometry shader is lacking. I've read through the guide on Microsoft's site but it is hard to follow and had some incorrect code.
particle declaration:
struct Particle
{
XMFLOAT3 InitialPos;
XMFLOAT3 InitialVel;
XMFLOAT2 Size;
float Age;
unsigned int Type;
};
struct ParticleBuffer
{
XMFLOAT3 gEyePosW;
// for when the emit position/direction is varying
XMFLOAT3 gEmitPosW;
XMFLOAT3 gEmitDirW;
float gGameTime;
float gTimeStep;
XMFLOAT4X4 gViewProj;
float pad;
};
vertex declaration:
D3D11_INPUT_ELEMENT_DESC particleLayout[5] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 2, DXGI_FORMAT_R32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 3, DXGI_FORMAT_R32_FLOAT, 0, 36, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
ParticleSystem.cpp
#include "ParticleSystem.h"
#include "Vertex.h"
#include "BaseCamera.h"
ParticleSystem::ParticleSystem()
: mInitVB(0), mDrawVB(0), mStreamOutVB(0), mTexArraySRV(0), mRandomTexSRV(0)
{
mFirstRun = true;
mGameTime = 0.0f;
mTimeStep = 0.0f;
mAge = 0.0f;
mEyePosW = XMFLOAT3(0.0f, 0.0f, 0.0f);
mEmitPosW = XMFLOAT3(0.0f, 0.0f, 0.0f);
mEmitDirW = XMFLOAT3(0.0f, 1.0f, 0.0f);
}
ParticleSystem::~ParticleSystem()
{
ReleaseCOM(mInitVB);
ReleaseCOM(mDrawVB);
ReleaseCOM(mStreamOutVB);
}
float ParticleSystem::GetAge()const
{
return mAge;
}
void ParticleSystem::SetEyePos(const XMFLOAT3& eyePosW)
{
mEyePosW = eyePosW;
}
void ParticleSystem::SetEmitPos(const XMFLOAT3& emitPosW)
{
mEmitPosW = emitPosW;
}
void ParticleSystem::SetEmitDir(const XMFLOAT3& emitDirW)
{
mEmitDirW = emitDirW;
}
void ParticleSystem::Init(ID3D11Device* device, ID3D11ShaderResourceView* texArraySRV,
ID3D11ShaderResourceView* randomTexSRV, UINT maxParticles)
{
mMaxParticles = maxParticles;
mTexArraySRV = texArraySRV;
mRandomTexSRV = randomTexSRV;
BuildVB(device);
BuildMB(device);
CreateShader(device);
}
void ParticleSystem::Reset()
{
mFirstRun = true;
mAge = 0.0f;
}
void ParticleSystem::Update(float dt, float gameTime)
{
mGameTime = gameTime;
mTimeStep = dt;
mAge += dt;
}
void ParticleSystem::Draw(ID3D11DeviceContext* dc, BaseCamera* cam, XMFLOAT4X4 projectionMatrix)
{
XMMATRIX VP = (XMMatrixMultiply(cam->GetViewMatrix(), XMLoadFloat4x4(&projectionMatrix)));
ParticleBuffer* dataPtr;
D3D11_MAPPED_SUBRESOURCE mappedResource;
//
// Set constants.
//
dc->Map(mBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
dataPtr = (ParticleBuffer*)mappedResource.pData;
XMStoreFloat4x4(&dataPtr->viewProj, VP);
dataPtr->mGameTime = mGameTime;
dataPtr->mTimeStep = mTimeStep;
dataPtr->mEyePosW = mEyePosW;
dataPtr->mEmitPosW = mEmitPosW;
dataPtr->mEmitDirW = mEmitDirW;
dataPtr->gEyePosW = XMFLOAT4(cam->Transform->GetPosition().x, cam->Transform->GetPosition().y, cam->Transform->GetPosition().z, 1);
dc->Unmap(mBuff, 0);
dc->PSSetConstantBuffers(0, 1, &mBuff);
dc->PSSetShaderResources(0, 1, &mTexArraySRV);
dc->PSSetShaderResources(1, 1, &mRandomTexSRV);
//
// Set IA stage.
//
dc->IASetInputLayout(pVertexLayout);
dc->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
UINT stride = sizeof(Particle);
UINT offset = 0;
// On the first pass, use the initialization VB. Otherwise, use
// the VB that contains the current particle list.
if( mFirstRun )
dc->IASetVertexBuffers(0, 1, &mInitVB, &stride, &offset);
else
dc->IASetVertexBuffers(0, 1, &mDrawVB, &stride, &offset);
//
// Draw the current particle list using stream-out only to update them.
// The updated vertices are streamed-out to the target VB.
//
dc->SOSetTargets(1, &mStreamOutVB, &offset);
if( mFirstRun )
{
dc->Draw(1, 0);
mFirstRun = false;
}
else
{
dc->DrawAuto();
}
// done streaming-out--unbind the vertex buffer
ID3D11Buffer* bufferArray[1] = {0};
dc->SOSetTargets(1, bufferArray, &offset);
// ping-pong the vertex buffers
std::swap(mDrawVB, mStreamOutVB);
//
// Draw the updated particle system we just streamed-out.
//
dc->IASetVertexBuffers(0, 1, &mDrawVB, &stride, &offset);
dc->VSSetShader(vertexShader, NULL, 0);
//dc->GSSetShader(geoShader, NULL, 0);
dc->PSSetShader(pixelShader, NULL, 0);
// Set the sampler state in the pixel shader.
dc->PSSetSamplers(0, 1, &sampleState);
dc->DrawAuto();
}
void ParticleSystem::BuildVB(ID3D11Device* device)
{
//
// Create the buffer to kick-off the particle system.
//
D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_DEFAULT;
vbd.ByteWidth = sizeof(Particle) * 1;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
// The initial particle emitter has type 0 and age 0. The rest
// of the particle attributes do not apply to an emitter.
Particle p;
ZeroMemory(&p, sizeof(Particle));
p.Age = 0.0f;
p.Type = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = &p
device->CreateBuffer(&vbd, &vinitData, &mInitVB);
//
// Create the ping-pong buffers for stream-out and drawing.
//
vbd.ByteWidth = sizeof(Particle) * mMaxParticles;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;
device->CreateBuffer(&vbd, 0, &mDrawVB);
device->CreateBuffer(&vbd, 0, &mStreamOutVB);
}
void ParticleSystem::BuildMB(ID3D11Device* device)
{
D3D11_BUFFER_DESC matrixBufferDesc;
// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
matrixBufferDesc.ByteWidth = sizeof(ParticleBuffer);
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
matrixBufferDesc.MiscFlags = 0;
matrixBufferDesc.StructureByteStride = 0;
// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
device->CreateBuffer(&matrixBufferDesc, NULL, &mBuff);
}
bool ParticleSystem::CreateShader(ID3D11Device* device)
{
ID3DBlob* pixelShaderBuffer;
ID3DBlob* geoShaderBuffer;
ID3DBlob* vertexShaderBuffer;
D3D11_SAMPLER_DESC samplerDesc;
bool result;
LPCSTR materialShaderName = "Assets/EngineParticle.fx";
string shaderVersion = "4_0";
string vs = "vs_" + shaderVersion;
result = D3DX11CompileFromFileA(materialShaderName, NULL, NULL, "VS", vs.c_str(), D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&vertexShaderBuffer, NULL, NULL);
if(FAILED(result))
{
return false;
}
vs = "gs_4_0";
result = D3DX11CompileFromFileA(materialShaderName, NULL, NULL, "GS", vs.c_str(), D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&geoShaderBuffer, NULL, NULL);
if(FAILED(result))
{
return false;
}
vs = "ps_" + shaderVersion;
result = D3DX11CompileFromFileA(materialShaderName, NULL, NULL, "PS", vs.c_str(), D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&pixelShaderBuffer, NULL, NULL);
if(FAILED(result))
{
return false;
}
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &vertexShader);
if(FAILED(result))
{
return false;
}
D3D11_SO_DECLARATION_ENTRY pDecl[3] =
{
// semantic name, semantic index, start component, component count, output slot
{ 0, "SV_POSITION", 0, 0, 4, 0 }, // output all components of position
{ 0, "COLOR", 0, 0, 4, 0 }, // output the first 3 of the normal
{ 0, "TEXCOORD0", 0, 0, 2, 0 }, // output the first 2 texture coordinates
};
result = device->CreateGeometryShaderWithStreamOutput(geoShaderBuffer->GetBufferPointer(), geoShaderBuffer->GetBufferSize(), pDecl, sizeof(pDecl), NULL, 0, 0, NULL, &geoShader);
//result = device->CreateGeometryShader(geoShaderBuffer->GetBufferPointer(), geoShaderBuffer->GetBufferSize(), NULL, &geoShader);
if(FAILED(result))
{
return false;
}
result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &pixelShader);
if(FAILED(result))
{
return false;
}
// Create a texture sampler state description.
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
samplerDesc.BorderColor[0] = 0;
samplerDesc.BorderColor[1] = 0;
samplerDesc.BorderColor[2] = 0;
samplerDesc.BorderColor[3] = 0;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
// Create the texture sampler state.
result = device->CreateSamplerState(&samplerDesc, &sampleState);
if(FAILED(result))
{
return false;
}
pixelShaderBuffer->Release();
InitializeLayout(device, vertexShaderBuffer);
vertexShaderBuffer->Release();
}
void ParticleSystem::InitializeLayout( ID3D11Device* device, ID3DBlob* vertexShaderBuffer)
{
HRESULT hr;
if(!vertexShaderBuffer)
{
return;
}
// Get a count of the elements in the layout.
int numElements = sizeof(particleLayout) / sizeof(particleLayout[0]);
hr = device->CreateInputLayout(particleLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &pVertexLayout);
}
Game.cpp
// Initialize
mFlareTexSRV = content->LoadTexture(L"assets/flare0.dds");
particle.Init(device,mFlareTexSRV, mFlareTexSRV, 500);
particle.SetEmitPos(XMFLOAT3(0.0f, 1.0f, 0.0f));
//Update
particle.Update(gameTime, gameTimeElapsed);
//Render
float blendFactor[] = {0.0f, 0.0f, 0.0f, 0.0f};
particle.SetEyePos(m_Camera->Transform->GetPosition());
particle.Draw(context, m_Camera, projectionMatrix);
context->OMSetBlendState(0, blendFactor, 0xffffffff);
Particle.Fx
//***********************************************
// GLOBALS *
//***********************************************
cbuffer MatrixBuffer: register(b0)
{
float3 gEyePosW;
// for when the emit position/direction is varying
float3 gEmitPosW;
float3 gEmitDirW;
float gGameTime;
float gTimeStep;
float4x4 gViewProj;
};
cbuffer cbFixed
{
// Net constant acceleration used to accerlate the particles.
float3 gAccelW = {0.0f, 7.8f, 0.0f};
// Texture coordinates used to stretch texture over quad
// when we expand point particle into a quad.
float2 gQuadTexC[4] =
{
float2(0.0f, 1.0f),
float2(1.0f, 1.0f),
float2(0.0f, 0.0f),
float2(1.0f, 0.0f)
};
};
// Array of textures for texturing the particles.
Texture2DArray gTexArray;
// Random texture used to generate random numbers in shaders.
Texture1D gRandomTex;
SamplerState samLinear
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
DepthStencilState DisableDepth
{
DepthEnable = FALSE;
DepthWriteMask = ZERO;
};
DepthStencilState NoDepthWrites
{
DepthEnable = TRUE;
DepthWriteMask = ZERO;
};
BlendState AdditiveBlending
{
AlphaToCoverageEnable = FALSE;
BlendEnable[0] = TRUE;
SrcBlend = SRC_ALPHA;
DestBlend = ONE;
BlendOp = ADD;
SrcBlendAlpha = ZERO;
DestBlendAlpha = ZERO;
BlendOpAlpha = ADD;
RenderTargetWriteMask[0] = 0x0F;
};
//***********************************************
// HELPER FUNCTIONS *
//***********************************************
float3 RandUnitVec3(float offset)
{
// Use game time plus offset to sample random texture.
float u = (gGameTime + offset);
// coordinates in [-1,1]
float3 v = gRandomTex.SampleLevel(samLinear, u, 0).xyz;
// project onto unit sphere
return normalize(v);
}
//***********************************************
// STREAM-OUT TECH *
//***********************************************
#define PT_EMITTER 0
#define PT_FLARE 1
struct Particle
{
float3 InitialPosW : POSITION;
float3 InitialVelW : TEXCOORD0;
float2 SizeW : TEXCOORD1;
float Age : TEXCOORD2;
uint Type : TEXCOORD3;
};
Particle StreamOutVS(Particle vin)
{
return vin;
}
// The stream-out GS is just responsible for emitting
// new particles and destroying old particles. The logic
// programed here will generally vary from particle system
// to particle system, as the destroy/spawn rules will be
// different.
[maxvertexcount(2)]
void StreamOutGS(point Particle gin[1],
inout PointStream<Particle> ptStream)
{
gin[0].Age += gTimeStep;
if( gin[0].Type == PT_EMITTER )
{
// time to emit a new particle?
if( gin[0].Age > 0.005f )
{
float3 vRandom = RandUnitVec3(0.0f);
vRandom.x *= 0.5f;
vRandom.z *= 0.5f;
Particle p;
p.InitialPosW = gEmitPosW.xyz;
p.InitialVelW = 4.0f*vRandom;
p.SizeW = float2(3.0f, 3.0f);
p.Age = 0.0f;
p.Type = PT_FLARE;
ptStream.Append(p);
// reset the time to emit
gin[0].Age = 0.0f;
}
// always keep emitters
ptStream.Append(gin[0]);
}
else
{
// Specify conditions to keep particle; this may vary from system to system.
if( gin[0].Age <= 1.0f )
ptStream.Append(gin[0]);
}
}
GeometryShader gsStreamOut = ConstructGSWithSO(
CompileShader( gs_4_0, StreamOutGS() ),
"POSITION.xyz; VELOCITY.xyz; SIZE.xy; AGE.x; TYPE.x" );
//***********************************************
// DRAW TECH *
//***********************************************
struct VertexOut
{
float3 PosW : POSITION;
float2 SizeW : TEXCOORD1;
float4 Color : COLOR;
uint Type : TEXCOORD3;
};
VertexOut VS(Particle vin)
{
VertexOut vout;
float t = vin.Age;
// constant acceleration equation
vout.PosW = 0.5f*t*t*gAccelW + t*vin.InitialVelW + vin.InitialPosW;
// fade color with time
float opacity = 1.0f - smoothstep(0.0f, 1.0f, t/1.0f);
vout.Color = float4(1.0f, 1.0f, 1.0f, opacity);
vout.SizeW = vin.SizeW;
vout.Type = vin.Type;
return vout;
}
struct GeoOut
{
float4 PosH : SV_Position;
float4 Color : COLOR;
float2 Tex : TEXCOORD;
};
// The draw GS just expands points into camera facing quads.
[maxvertexcount(4)]
void GS(point VertexOut gin[1],
inout TriangleStream<GeoOut> triStream)
{
// do not draw emitter particles.
if( gin[0].Type != PT_EMITTER )
{
//
// Compute world matrix so that billboard faces the camera.
//
float3 look = normalize(gEyePosW.xyz - gin[0].PosW);
float3 right = normalize(cross(float3(0,1,0), look));
float3 up = cross(look, right);
//
// Compute triangle strip vertices (quad) in world space.
//
float halfWidth = 0.5f*gin[0].SizeW.x;
float halfHeight = 0.5f*gin[0].SizeW.y;
float4 v[4];
v[0] = float4(gin[0].PosW + halfWidth*right - halfHeight*up, 1.0f);
v[1] = float4(gin[0].PosW + halfWidth*right + halfHeight*up, 1.0f);
v[2] = float4(gin[0].PosW - halfWidth*right - halfHeight*up, 1.0f);
v[3] = float4(gin[0].PosW - halfWidth*right + halfHeight*up, 1.0f);
//
// Transform quad vertices to world space and output
// them as a triangle strip.
//
GeoOut gout;
[unroll]
for(int i = 0; i < 4; ++i)
{
gout.PosH = mul(v[i], gViewProj);
gout.Tex = gQuadTexC[i];
gout.Color = gin[0].Color;
triStream.Append(gout);
}
}
}
float4 PS(GeoOut pin) : SV_TARGET
{
return gTexArray.Sample(samLinear, float3(pin.Tex, 0))*pin.Color;
}
I do not get any kind of errors and the code seems to compile just fine. However, the particles do not render.
Sorry for the large amount of code...I didnt even know where to look to solve this problem.