Krohm said:
First of all, I wouldn't call it Model. I would also consider decoupling the shaders from their data (but that is to be evaluated in a bigger context).
Ok so context I'm working in is this: I have a separate Shaders namespace in which I create all my shaders and their respective input layouts. Like this:
namespace Shaders
{
//Shader collection
extern ID3D11VertexShader* WorldTransformVertexShader;
extern ID3D11VertexShader* LineVertexShader;
extern ID3D11PixelShader* LinePixelShader;
extern ID3D11PixelShader* TexturePixelShader;
extern ID3D11GeometryShader* LineGeometryShader;
//Common input layout for majority of shaders and a seperate one for line renderer
extern ID3D11InputLayout* CommonInputLayout;
extern ID3D11InputLayout* LineInputLayout;
//Vertex descriptions
extern D3D11_INPUT_ELEMENT_DESC* VertexLayoutDescription;
extern D3D11_INPUT_ELEMENT_DESC* LineVertexLayoutDescription;
//These compile shaders and output their blobs if needed,
//so far only using vertex, pixel and geometry shaders
void CompileAndCreateShader(ID3D11Device* device, std::wstring filename, std::string entrypoint, ID3D11VertexShader** outShader, ID3DBlob** outBlob = NULL);
void CompileAndCreateShader(ID3D11Device* device, std::wstring filename, std::string entrypoint, ID3D11PixelShader** outShader, ID3DBlob** outBlob = NULL);
void CompileAndCreateShader(ID3D11Device* device, std::wstring filename, std::string entrypoint, ID3D11GeometryShader** outShader, ID3DBlob** outBlob = NULL);
//General orchestrator functions that initializes all shaders the game is using
void CreateShadersAndInputLayouts(ID3D11Device* device);
};
So all the shaders which are used are pretty much in a global namespace Shaders::. This namespace is the owner of the pointers to also answer your last question.
I then pass the shaders and other resources to the Model's constructor which looks like this:
//Just pass in any shaders that the mesh is expected to use to render itself
//GenericObject is just a structure that holds the actual vertex and index arrays and texture filenames
Model(ID3D11Device* device, GenericObject LoadedObject, ID3D11InputLayout* input_layout, ID3D11VertexShader* vertex_shader, ID3D11PixelShader* pixel_shader, ID3D11GeometryShader* geometry_shader = NULL, ID3D11HullShader* hull_shader = NULL, ID3D11DomainShader* domain_shader = NULL);
The constructor only requires a vertex and pixel shaders, all other shaders are optional.
So that's just a bigger picture. And just a few questions about your comment:
When you say decouple shaders from their data, is what I'm doing above sufficient? The shaders themselves are in a separate namespace and just the data (textures, samplers) are associated with the Model, nothing else. Or is there an even cleaner way to decouple them?
By the way my constant buffers are also in a namespace of their own similar to that of shaders
namespace ConstantBuffers
{
__declspec(align(16)) struct ProjectionVariables
{
DirectX::XMMATRIX View;
DirectX::XMMATRIX Projection;
};
__declspec(align(16)) struct OrthographicProjectionVariables
{
DirectX::XMMATRIX ProjectionOrthographic;
};
__declspec(align(16)) struct WorldMatrices
{
DirectX::XMMATRIX World;
};
__declspec(align(16)) struct LineRendererVariables
{
DirectX::XMFLOAT3 CameraPosition;
float LineThickness;
};
void CreateConstantBuffers(ID3D11Device* device);
extern ID3D11Buffer* ViewProjBuffer;
extern ID3D11Buffer* WorldMatrixBuffer;
extern ID3D11Buffer* LineRendererBuffer;
extern ID3D11Buffer* OrthographicProjectionBuffer;
};
I then have methods in the Model class that set the respective shader's constant buffer list
class Model
{
public:
void SetVSConstantBuffers(ID3D11Buffer** ConstantBuffers, UINT numBuffers);
void SetPSConstantBuffers(ID3D11Buffer** ConstantBuffers, UINT numBuffers);
void SetGSConstantBuffers(...);
//and so on for all the shaders
So the shaders and constant buffers are kind of separate from the Model, which only is really responsibe for loading and creating the textures that the shaders will be using.
Also you say that “At the very least your pipeline could be a proper structure. In general less identifiers around = better.”. Can you an example of how that could be done? By reducing identifiers you mean that I should strive to completely remove references to DirectX API within the game code and use some abstractions to hide the implementations? So I should have like an effect system or something to that extent? Thanks for the feedback, by the way.
@ddlox
ddlox said:
for example, let's say u want render a rainy scene where 10 models are running, if u followed what @krohm said, u would do this:
- pipeline: set rain_vs, gs, ps once
- rasterizer: set zbuffer once
- shader: set shader resources i.e. 1 texture array containing 10 textures, Vbuffers, Cbuffer…etc
- 1 draw indexed call for 10 models
- shader: unset resources
- rasterizer: unset
- pipeline: unset
So I should focus more on scene centered approach? Something akin to what the old Effects framework in DirectX10 used to do? The reason I went with per object approach is because it's so intuitive for me. I define individually what each object will look like by assigning a shader (see my answer above about how I handle shader creation) that will handle that particular look. The way I was thinking about it was like this: a shader is anologous to a material an object is made of and how that material behaves. So it was natural for me to kind of couple that with the actual object. It's a bit more unnatural for me to think of a material as like an effect that I apply first and then pass the objects, and then unapply the effect and render the next batch of objects. I was actually thinking of fixing the redundant states by some simple solution like grouping models by the shaders they use and keeping track if a shader was set already and just not setting it was set previously.