This tutorial is part of a Collection: 02. DirectX 10 - Braynzar Soft Tutorials
1783
views
05. Begin Drawing
We finally get to the fun part of drawing geometry! I know it doesn't look like much, but this is a big step and very important to the future of our 3d games. Almost EVERYTHING you will draw on the screen consists of triangles, lines, points, and squares. In this lesson i will cover the pipeline stages as well, some only breifly as we will cover them in more detail later on. Also, we have an addition of a new file called an effect file. We need this file to draw anything on the screen. I will breifly go over it in this lessons, but later we can talk more about it.
Here we will learn to draw a green triangle! Before we actually get started, we need to cover how direct3d 10 actually works, as it's a bit different than direct3d 9 as you may have noticed. First of all, a big notice is they have taken out the fixed function pipeline, leaving programmable pipeline the only choice! Although this makes setting up direct3d more difficult for beginners to learn by increasing complexity and code, it gives you FULL control over direct3d, and it really is the best way for beginners to learn how direct3d actually works. I will do my BEST to explain everything in detail so beginners will not get lost. The rendering pipeline is the set of steps direct3d uses to create a 2d image based on what the virtual camera sees. It consists of 7 Stages, which are as follows: **Input Assembler (IA) Stage Vertex Shader (VS) Stage Geometry Shader (GS) Stage Stream Output (SO) Stage Rasterizer (RS) Stage Pixel Shader (PS) Stage Output Merger (OM) Stage** +[http://www.braynzarsoft.net/image/100105][direct3d pipeline] ##Input Assembler (IA) Stage## The first stage you can see is the Input Assembler (IA). The IA reads geometric data, vertices and Indices. Then it uses the data to create geometric primitives like traingles, squares, lines, and points which will be fed into and used by the other stages. Indices define how the primitives should be put together by the vertices. We will discuss indices in a later lesson. To initialize the Input Assembler Stage, we first need to create a buffer. The two buffers used by the IA are the vertex and index buffers. In this lesson we will not worry about the index buffer yet. To create the buffer, we will fill out a D3D10_BUFFER_DESC structure. After creating the buffer or buffers, we need to create the input-layout object. What this does is tell direct3d what our vertext structure consists of, and what to do with each component in our vertex structure. We provide the information to direct3d with an array of D3D10_INPUT_ELEMENT_DESC elements. Each element in the D3D10_INPUT_ELEMENT_DESC array describes one element in the vertex structure. If your Vertex structure has a position element, and a color element, then your D3D10_INPUT_ELEMENT_DESC array will have one element for the position and one for the color. Here is an example: //The vertex Structure struct Vertex { D3DXVECTOR3 pos; D3DXCOLOR color; }; //The input-layout description D3D10_INPUT_ELEMENT_DESC layout[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0}, {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0} }; In this lesson, our vertex structure looks like this: struct Vertex { D3DXVECTOR3 pos; }; So our input layout description looks like this: D3D10_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, }; After describing the input layout with the D3D10_INPUT_ELEMENT_DESC structure, we need to create it with the ID3D10Device::CreateInputLayout function. The next step is binding our layout description to the IA. We can bind the our layout to the IA by calling the functions ID3D10Device::IASetVertexBuffers and ID3D10Device::IASetInputLayout. In this lessons, our code to set the input layout will look like this: UINT stride = sizeof( Vertex ); UINT offset = 0; d3dDevice->IASetVertexBuffers( 0, 1, &VertexBuffer, &stride, &offset ); d3dDevice->IASetInputLayout( VertexLayout ); Now we need to set the primitive type so that Direct3d will know how to use the vertices and make primitives such as triangles or lines or something. We call the function ID3D10Device::IASetPrimitiveTopology. I will cover the different types later in this lesson. After all that, we call a draw method to render the primitives. The method we call in this lessons is ID3D10Device::Draw() ##Vertex Shader (VS) Stage## The Vertex Shader Stage is what ALL the vertices go through after the primitives have been assembled in the AI. Every vertex drawn will be put through the VS. We have to create the VS, but the GPU executes it, so it's very quick. With the VS, you are able to do things like transformation, scaling, lighting, displacement mapping for textures and stuff like that. The Vertex Shader must always be implemented for the pipeline to work, even if the vertices in the program do not need to be modified. The shaders in the pipeline are written in the language HLSL. The language is similar to C++ syntax so it's not hard to learn. I will explain the effects file in each lesson we change it, and later we will have a lesson dedicated to HLSL. For this lesson, Our Vertex shader does nothing, so we just return the position of each vertex without modifying it. It looks like this: float4 VS( float4 Pos : POSITION ) : SV_POSITION { return Pos; } ##Geometry Shader (GS) Stage## This Shader stage is optional. it accepts primitives as imput, such as 3 vertices for triangles, 2 for lines, and one for a point. It also can take data from edge-adjacent primitives as input, like an additional 2 vertices for a line, or an additional 3 for a triangle. An advantage to the GS is that it can create or destroy primitives, where the VS cannot (it takes in one vertex, and outputs one). We could turn on point into a quad or a triangle with this stage. We are able to pass data from the GS to the rasterizer stage, and/or though the Stream Output to a vertex buffer in memory. We'll learn more about this shader stage in a later lesson. ##Stream Outpud (SO) Stage## This Stage is used to obtain Vertex data from the pipeline, specifically the Geometry Shader Stage or the Vertex Shader Stage if there is no GS. Vertex data sent to memory from the SO is put into one or more vertex buffers. Vertex data output from the SO are always sent out as lists, such as line lists or triangle lists. Incomplete primitives are NEVER send out, they are just silently discareded like in the vertex and geometry. Incomplete primitives are primitives such as triangles with only 2 vertices or a line with only one vertex. ##Rasterization Stage (RS) Stage## The RS stage takes the vector information (shapes and primitives) sent to it and turns them into pixels by interpolating per-vertex values across each primitive. It also handles the clipping, which is basically cutting primitives that are outside the view of the screen. ##Pixel Shader (PS) Stage## This stage does calculations and modifies each pixel that will be seen on the screen. It is another optional stage. The RS invokes the pixel shader once for each pixel in a primitive. Like we said before, the values and attributes of each vertex in a primitive are interpolated accross the entire primitive. Basically it's like the vertex shader, where in the vertex shader, each vertex is run through the shader, in the pixel shader, each pixel is run through the shader. The job of the pixel shader is to calculate the final color of each pixel fragment. A pixel fragment is each potential pixel that will be drawn to the screen. For an example, there is a solid square behind a solid circle. The pixels in the square are pixel fragments and the pixels in the circle are pixel fragments. Each has a chance to be written to the screen, but once it gets to the output merger stage, which decides the final pixel to be drawn to the screen, only the pixels from the circle will be drawn. The PS outputs a 4D color value. In this lesson, we use the pixel shader to make the triangle green by returning a 4D float value equal to the color green. There is no calculations done, just simply returning the color green, so every pixel that is run through the shader will be green. float4 PS( float4 Pos : SV_POSITION ) : SV_Target { return float4( 0.0f, 1.0f, 0.0f, 1.0f ); } ##Output Merger (OM) Stage## The final Stage in the Pipeline is the Output Merger Stage. Basically this stage takes the pixel fragments and depth/stencil buffers and determines which pixels are actually written to the screen. We can talk more about this later. ##Effect Files (HLSL)## We write our shader code in a language called HLSL, which is written in a an effect file (.fx). For this lesson, we will only be using the VS and PS, but later we will discuss more we can do with the HLSL language. After we write our shader code, we have another "function" which is called a Technique. Each effect file has one or more techniques, which contain one or more passes. Technique10 - A technique has one or more passes which create specific rendering techniques. We can combine more than one pass in each technique to create a special result. We will talk more about this later too. Pass - A pass has a vertex shader, and maybe a geometry shader, pixel shader, and/or render states. These states tell direct3d how to render the geometry for this state. In this lesson, our technique looks like this: technique10 Tech { pass P0 { SetVertexShader( CompileShader( vs_4_0, VS() ) ); SetPixelShader( CompileShader( ps_4_0, PS() ) ); } } Another thing to mention is effect files are written in plane text files, meaning the file will not be compiles, so we are able to change it without recompiling all our code. When we get to the code that creates the effect, we will talk a little more about it. The first 4 new lines we have are declaring the interfaces we need to use. We will talk about each one as we fill them out. ID3D10Effect* FX; ID3D10InputLayout* VertexLayout; ID3D10Buffer* VertexBuffer; ID3D10EffectTechnique* Technique; Here we are initializing the new function which will be used to release objects we create to avoid a memory leak. bool ReleaseObjects(); Here we have our vertext Structure. Direct3D gives us the flexibility to create our own vertex structure. In this lesson, our vertex structure will only consist of a 3D vectore describing it's position. We do not need to add the color to the vertex structure in this lesson, as we will be using the Pixel Shader to decide the color. struct Vertex { D3DXVECTOR3 pos; }; This is the array of vertices we will use to create our triangle. Simple enough i suppose. Vertex vertices[] = { D3DXVECTOR3( 0.0f, 0.5f, 0.5f ), D3DXVECTOR3( 0.5f, -0.5f, 0.5f ), D3DXVECTOR3( -0.5f, -0.5f, 0.5f ), }; Here we create the vertex buffer by filling out a D3D10_BUFFER_DESC structure. We need to create a resource called a buffer in order for the GPU to use the array of vertices we defined. A buffer that stores vertices is called a vertex buffer. First we fill the buffer description structure, D3D10_BUFFER_DESC, which describes the buffer we are going to create. Then we need to fill out a D3D10_SUBRESOURCE_DATA structure which specifies the data we want to initialize our buffer contents with. Finally, we need to create the buffer by calling the method ID3D10Device::CreateBuffer. This is what the D3D10_BUFFER_DESC structure looks like: typedef struct D3D10_BUFFER_DESC { UINT ByteWidth; D3D10_USAGE Usage; UINT BindFlags; UINT CPUAccessFlags; UINT MiscFlags; } D3D10_BUFFER_DESC; ByteWidth - This is the size in bytes of the vertex buffer we are going to create. Our buffer will hold 3 vertices to create our triangle, so we put sizeof( Vertex ) * 3 Usage - This will be a member of the D3D10_USAGE type specifying how our buffer will be used. Here are the four stages: **D3D10_USAGE_DEFAULT **- The GPU can read and write to the resource, the CPU cannot do either. **D3D10_USAGE_IMMUTABLE **- The GPU cannot write to the resource, only read. This offers some potential optimizations, since the resource will never change. The CPU can only write to the resource at creation time, not after that, and it cannot read from the resource. **D3D10_USAGE_DYNAMIC **- You can use this if the application (CPU) needs to write to the resource often, such as on a per frame basis. The CPU can only write to the resource, and the GPU can only read. **D3D10_USAGE_STAGING **- Use this if the application (CPU) needs to copy the resource from video memory to system memory. Means the CPU can read from the resource. BindFlags - We are creating a vertex buffer, so we specify D3D10_BIND_VERTEX_BUFFER here. CPUAccessFlags - We don't need our application to read or write to the buffer, so we just specify 0 here. We can specify D3D10_CPU_ACCESS_WRITE if we need to write to the buffer (we need to have D3D10_USAGE_DYNAMIC or D3D10_USAGE_STAGING specified in the field above), or D3D10_CPU_ACCESS_READ if we need to read from the buffer (we would need to have D3D10_USAGE_STAGING specified in the field above), but reading and writing are slow, so try not to do it if you can. MiscFlags - For a vertex buffer, we do not need any misc flags, so specify 0. D3D10_BUFFER_DESC bd; bd.Usage = D3D10_USAGE_DEFAULT; bd.ByteWidth = sizeof( Vertex ) * 3; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; Here we initialize the subresource data structure that will be used to fill the vertex buffers contents. We fill this structure with our vertex data. The D3D10_SUBRESOURCE_DATA structure looks like this: typedef struct D3D10_SUBRESOURCE_DATA { const void *pSysMem; UINT SysMemPitch; UINT SysMemSlicePitch; } D3D10_SUBRESOURCE_DATA; pSysMem - This is a pointer to the vertex data we will initialize our vertex buffer with. SysMemPitch - We don't need to worry about this for our vertex buffer. SysMemSlicePitch - We also don't need this for our vertex buffer. D3D10_SUBRESOURCE_DATA InitData; InitData.pSysMem = vertices; This is where we finally create our buffer by calling the ID3D10Device::CreateBuffer method. The first parameter is a pointer to our buffer description. The second pointer is a pointer to the data we want to fill the buffer with. And the third parameter is the returned buffer. After creating the vertex buffer, we need to bind it to an input slot in the device so we can use it to feed the vertices as input into the pipeline. we do this wit the method ID3D10Device::IASetVertexBuffers(). d3dDevice->CreateBuffer( &bd, &InitData, &VertexBuffer ); After creating the vertex buffer, we need to bind it to an input slot in the device so we can use it to feed the vertices as input into the pipeline. we do this wit the method ID3D10Device::IASetVertexBuffers(). Here is what the parameters of the method look like: void IASetVertexBuffers( UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets ); StartSlot - The input slot we will start binding vertex buffers to. Remember there are 16 of them, 0-15. NumBuffers - The number of vertex buffers we will be binding to input slots, starting at StartSlot. ppVertexBuffers - This is a pointer to the first element of an array of vertex buffers. pStrides - This is a pointer to an array of strides. Each element in the strides array are the size in bytes of the corresponding vertex buffer. the nth stride in an array of strides is the size in bytes of the nth buffer in an array of vertex buffers. pOffsets - Another pointer to an array. This array is an array of offsets. The offset tells the input assembler (IA) where to start reading vertex data in a buffer. The nth offset in an array of offsets is the starting position where the IA will start reading vertex data in the nth vertex buffer in an array of vertex buffers. That might sound confusing, but if you think about it for a minute its not too bad. UINT stride = sizeof( Vertex ); UINT offset = 0; d3dDevice->IASetVertexBuffers( 0, 1, &VertexBuffer, &stride, &offset ); Here we are describing our vertex structure. Since Direct3D lets us create our own vertex Structure, we need to tell direct3d how to read it and what to do with each component by describing it's format. We can tell direct3d with an input layout, which is specified by an array of D3D10_INPUT_ELEMENT_DESC elements. Each element in the array describes one component to the vertex structure. If a vertex structure has two components, pos and color, then the corresponding D3D10_INPUT_ELEMENT_DESC will have two elements. Our vertex structure only has one component, position, so we only need to have one element in the array. The D3D10_INPUT_ELEMENT_DESC looks like this: typedef struct D3D10_INPUT_ELEMENT_DESC { LPCSTR SemanticName; UINT SemanticIndex; DXGI_FORMAT Format; UINT InputSlot; UINT AlignedByteOffset; D3D10_INPUT_CLASSIFICATION InputSlotClass; UINT InstanceDataStepRate; } D3D10_INPUT_ELEMENT_DESC; SemanticName - This is just a string to associate with the element. This string will be used to map the elements in the vertex structure to the elements in the vertex shader. SemanticIndex - This is basically just a number after the semantic name to use as an index. For example, if we have 2 texture elements in the vertex structure, instead of creating 2 different texture semantic names, we can just use 2 different index's. If a semantic name in the vertex shader code has no index after it, it defaults to index 0. For example in our shader code, our semantic name is "POSITION", which is actually the same as "POSITION0". Format - This is just the format of our component in our vertex structure. It needs to be a member of the DXGI_FORMAT enumerated type. In this lesson, we have a 3d vector describing the position, so we can use the DXGI_FORMAT: DXGI_FORMAT_R32G32B32_FLOAT. If you need other formats, you can find them on msdn. Later we will be using other ones. InputSlot - Direct3D allows us to use 16 different element slots (0-15) which you can put vertex data through. If we have our vertex structure has a position and color, we could put both the elements through the same input slot, or we can put the position data through the first slot, and the color data through the second slot. We only need to use one, but you can experiment if you would like. AlignedByteOffset - This is the byte offset of the element you are describing. In a single input slot, if we have position and color, position could be 0 since it starts at the beginning of the vertex structer, and color would need to be the size of our vertex position, which is 12 bytes (remember our format for our vertex position is DXGI_FORMAT_R32G32B32_FLOAT, which is 96 bits, 32 for each component in the position. there are 8 bits in one byte, so 96/8 == 12). InputSlotClass - Right now we can just use D3D10_INPUT_PER_VERTEX_DATA. The other options are used for instancing, which is an advanced technique we will get to later. InstanceDataStepRate - This is also used only for instancing, so we will specify 0 for now Right now we only have position in our vertex structure, buf if we had a color component too, our input layout may look like this: D3D10_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 } }; D3D10_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, }; In order to create our layout, we need to create an effect, which essentially tells direct3d how to render our scene. We first create an effect from our effect file (vertex.fx in this lesson), which is represented by the ID3D10Effect interface. We create an effect with the function D3DX10CreateEffectFromFile(), which parameters are as follows: HRESULT D3DX10CreateEffectFromFile( __in LPCTSTR pFileName, __in const D3D10_SHADER_MACRO *pDefines, __in ID3D10Include *pInclude, __in LPCSTR pProfile, __in UINT HLSLFlags, __in UINT FXFlags, __in ID3D10Device *pDevice, __in ID3D10EffectPool *pEffectPool, __in ID3DX10ThreadPump *pPump, __out ID3D10Effect **ppEffect, __out ID3D10Blob **ppErrors, __out HRESULT *pHResult ); pFileName - Just a string for the name of the .fx file we want to use. pDefines - We don't need this right now. pInclude - We also don't need to use this one. pProfile - This is a string specifying the shader version we want to use. for direct3d 10 we use version 4.0. HLSLFlags - We will just use D3D10_SHADER_ENABLE_STRICTNESS, because by default the HLSL compiler lets in old syntax, we don't have any, and we don't want any, so we enable strictness which won't allow "legacy" syntax. For a complete list, go here FXFlags - We don't need to use this one either. pDevice - A pointer to our d3d device. pEffectPool - Don't worry about this one too. pPump - Don't worry about this one. ppEffect - A pointer to the created effect. ppErrors - A pointer to a string containing the compilation errors if there were any. pHResult - We don't need to worry about this, its used with the pPump. D3DX10CreateEffectFromFile( L"vertex.fx", NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, d3dDevice, NULL, NULL, &FX, NULL, NULL ); Technique = FX->GetTechniqueByName( "Tech" ); D3D10_PASS_DESC PassDesc; Technique->GetPassByIndex( 0 )->GetDesc( &PassDesc ); Now we obtain a pointer to the technique object in our effect file. We do this with the ID3D10_Effect::GetTechniqueByName() method. The only parameter this one takes is the name of our technique in our effect file. Then we store this technique into an ID3D10EffectTechnique pointer. Technique = FX->GetTechniqueByName( "Tech" ); D3D10_PASS_DESC PassDesc; Technique->GetPassByIndex( 0 )->GetDesc( &PassDesc ); Here we create a Pass Description so we can create our input layout for our IA. We store the pass description we will use in our layout with the following code. D3D10_PASS_DESC PassDesc; Technique->GetPassByIndex( 0 )->GetDesc( &PassDesc ); After describing our input layout, and our pass description, we need to create an input layout, which is stored in a pointer to the ID3D10InputLayout interface. We create the input layout using the ID3D10Device::CreateInputLayout method. Here is it's parameters: HRESULT ID3D10Device::CreateInputLayout( const D3D10_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D10InputLayout **ppInputLayout); pInputElementDescs - The first parameter is the array of D3D10_INPUT_ELEMENT_DESC elements describing the vertex structure we created. NumElements - This is just the number of elements in our vertex structure, which should be the exact same as the number of elements in our D3D10_INPUT_ELEMENT_DESC array. In this lesson, we just have the position, so we put 1 here. pShaderBytecodeWithInputSignature - A pointer to the shader byte-code of the input signature of the vertex shader. In Direc3D, the vertex shader needs to know what the elements of the vertex structure are, so they can be mapped to the corresponding inputs in the vertex shader. In direct3d 10, we pass the vertex shader signature when the input layout is created, which creates the mapping from the vertex structure to the vertex shader inputs at creation time. In Direct3D 9, we did this at draw time which is less efficient and slower. BytecodeLength - The length of the vertex shader input signature data passed into the pShaderBytecodeWithInputSignature. ppInputLayout - This is where the input layout will be stored d3dDevice->CreateInputLayout( layout, 1, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &VertexLayout ); Now we need to bind our input layout to the pipeline with the ID3D10Device::IASetInputLayout method. The only parameter for this is the returned pointer from above, the created vertex layout. d3dDevice->IASetInputLayout( VertexLayout ); Now we set the primitive topology with the ID3D10Device method. What this does is tell direct3d how to put the vertices we defined together. Here are our options: Point List - We can use D3D10_PRIMITIVE_TOPOLOGY_POINTLIST. By using this topology, every vertex will be drawn as an individual point. Line Strip - We can use D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP. This is basically like "connect the dots". All vertice's will be part of a line. Line List - We can use D3D10_PRIMITIVE_TOPOLOGY_LINELIST. Every two vertices will create a line. The difference between this and a line strip is that in a line strip, all vertices will be connected to create lines, a continuous line. Triangle Strip - We can use D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP. Here we create triangle. each triangle shares its vertices with the adjacent triangles. All triangles will be connected. Triangle List - We can use D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST. This says that every 3 vertices make a triangle, so that not all triangles have to be connected. It is slower than a triangle strip because more vertices must be used, unlike a triangle strip, where you can have 2 triangles made with 4 vertices. In a triangle list, you need to have 6 vertices to create 2 triangles. Primitives with Adjacency - An example is D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ. These are only used for the geometry shader. We will not really worry about them for now. d3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); Now we get to the ReleaseObjects() function. The first line just clears our device, and sets everything to its default or null. The next line is releasing COM objects we created. bool ReleaseObjects() { if( d3dDevice ) d3dDevice->ClearState(); if( VertexBuffer ) VertexBuffer->Release(); if( VertexLayout ) VertexLayout->Release(); if( FX ) FX->Release(); if( RenderTargetView ) RenderTargetView->Release(); if( SwapChain ) SwapChain->Release(); if( d3dDevice ) d3dDevice->Release(); return true; } Now we're finally in the draw scene function where we will actually draw our primitives. The first two lines in this function we already covered in the initializing direct3d lesson. we create a color then clear the back buffer to that color. void DrawScene() { D3DXCOLOR bgColor( 0.0f, 0.0f, 0.0f, 1.0f); d3dDevice->ClearRenderTargetView( RenderTargetView, bgColor ); First we create a technique description to store our technique from our effect file into. Then the next thing we do is get our techniques description from when we initialized our scene, and store it into the newly created technique description so we can get the number of passes. D3D10_TECHNIQUE_DESC techDesc; Technique->GetDesc( &techDesc ); Now we create a for loop. we initialize a new UINT to 0, and while p is smaller than the number of passes in our technique description, we go through the loop and add 1 to p. So in our loop, what the first line is doing, is getting the pass from our technique (in our example we just have one pass of index 0, so we are getting pass0, or pass(p)). After we get the pass we apply the pass with the method "apply". the single parameter in apply is unused in direct3d 10. The next line in our loop is drawing our vertices from our index buffer (the one we binded to the pipeline). the first paremeter in this draw method is the number of vertices we are going to draw, the second parameter is the index, or offset in our vertex buffer we will start drawing vertices. There are other draw methods we will use later, this is the most simple one, so we will use it for now. for( UINT p = 0; p < techDesc.Passes; ++p ) { Technique->GetPassByIndex( p )->Apply( 0 ); d3dDevice->Draw( 3, 0 ); } We talked about this before, what this line does is swap the back buffer with the front buffer, or basically "presents" the back buffer to the screen. SwapChain->Present( 0, 0 ); } Finally we call the DrawScene() function from our message loop. else{ // run game code DrawScene(); } We are done with this lesson. The final outcome should be a green square on a black background. I tried to just cover things very simply so you don't get lost, but direct3d is a little complex, so if i need to make this lesson more simple or add more, PLEASE let me know! At least please let me know what you think of this lesson, I am truly gratefull for any feedback! Here's the final code: main.cpp #include <Windows.h> #include <d3d10.h> #include <d3dx10.h> #include <string> #pragma comment(lib, "D3D10.lib") #pragma comment(lib, "D3Dx10d.lib") LPCTSTR WndClassName = L"firstwindow"; HWND hwnd = NULL; const int Width = 800; const int Height = 600; bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed); HRESULT hr; ID3D10Device* d3dDevice; IDXGISwapChain* SwapChain; ID3D10RenderTargetView* RenderTargetView; ID3D10DepthStencilView* DepthStencilView; ///////////////new////////////////////////////////////////////////////////////////// ID3D10Effect* FX; ID3D10InputLayout* VertexLayout; ID3D10Buffer* VertexBuffer; ID3D10EffectTechnique* Technique; ///////////////new////////////////////////////////////////////////////////////////// bool InitializeDirect3dApp(HINSTANCE hInstance); bool InitScene(); void DrawScene(); ///////////////new////////////////////////////////////////////////////////////////// bool ReleaseObjects(); ///////////////new////////////////////////////////////////////////////////////////// int messageloop(); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); ///////////////new////////////////////////////////////////////////////////////////// struct Vertex { D3DXVECTOR3 pos; }; ///////////////new////////////////////////////////////////////////////////////////// int WINAPI WinMain(HINSTANCE hInstance, //Main windows function HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { if(!InitializeWindow(hInstance, nShowCmd, Width, Height, true)) { MessageBox(0, L"Window Initialization - Failed", L"Error", MB_OK); return 0; } if(!InitializeDirect3dApp(hInstance)) { MessageBox(0, L"Direct3D Initialization - Failed", L"Error", MB_OK); return 0; } if(!InitScene()) { MessageBox(0, L"Scene Initialization - Failed", L"Error", MB_OK); return 0; } messageloop(); if(!ReleaseObjects()) { MessageBox(0, L"Object Releasing - Failed", L"Error", MB_OK); return 0; } return 0; } bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed) { typedef struct _WNDCLASS { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS; WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = NULL; wc.cbWndExtra = NULL; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2); wc.lpszMenuName = NULL; wc.lpszClassName = WndClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if (!RegisterClassEx(&wc)) { MessageBox(NULL, L"Error registering class", L"Error", MB_OK | MB_ICONERROR); return 1; } hwnd = CreateWindowEx( NULL, WndClassName, L"Window Title", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, NULL ); if (!hwnd) { MessageBox(NULL, L"Error creating window", L"Error", MB_OK | MB_ICONERROR); return 1; } ShowWindow(hwnd, ShowWnd); UpdateWindow(hwnd); return true; } bool InitializeDirect3dApp(HINSTANCE hInstance) { UINT createDeviceFlags = 0; D3D10_DRIVER_TYPE driverTypes[] = { D3D10_DRIVER_TYPE_HARDWARE, D3D10_DRIVER_TYPE_REFERENCE, }; UINT numDriverTypes = sizeof( driverTypes ) / sizeof( driverTypes[0] ); DXGI_SWAP_CHAIN_DESC scd; scd.BufferDesc.Width = Width; scd.BufferDesc.Height = Height; scd.BufferDesc.RefreshRate.Numerator = 60; scd.BufferDesc.RefreshRate.Denominator = 1; scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; scd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; scd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; //no multisampling scd.SampleDesc.Count = 1; scd.SampleDesc.Quality = 0; scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scd.BufferCount = 1; scd.OutputWindow = hwnd; scd.Windowed = true; scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; scd.Flags = 0; D3D10CreateDeviceAndSwapChain(0, D3D10_DRIVER_TYPE_HARDWARE, 0, 0, D3D10_SDK_VERSION, &scd, &SwapChain, &d3dDevice); ID3D10Texture2D* backBuffer; SwapChain->GetBuffer(0, _uuidof(ID3D10Texture2D), reinterpret_cast<void**>(&backBuffer)); d3dDevice->CreateRenderTargetView(backBuffer, 0, &RenderTargetView); backBuffer->Release(); d3dDevice->OMSetRenderTargets(1, &RenderTargetView, NULL); // Setup the viewport D3D10_VIEWPORT vp; vp.Width = Width; vp.Height = Height; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; d3dDevice->RSSetViewports( 1, &vp ); return true; } bool InitScene() { ///////////////new////////////////////////////////////////////////////////////////// Vertex vertices[] = { D3DXVECTOR3( 0.0f, 0.5f, 0.5f ), D3DXVECTOR3( 0.5f, -0.5f, 0.5f ), D3DXVECTOR3( -0.5f, -0.5f, 0.5f ), }; D3D10_BUFFER_DESC bd; bd.Usage = D3D10_USAGE_DEFAULT; bd.ByteWidth = sizeof( Vertex ) * 3; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; D3D10_SUBRESOURCE_DATA InitData; InitData.pSysMem = vertices; d3dDevice->CreateBuffer( &bd, &InitData, &VertexBuffer ); UINT stride = sizeof( Vertex ); UINT offset = 0; d3dDevice->IASetVertexBuffers( 0, 1, &VertexBuffer, &stride, &offset ); D3D10_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, }; D3DX10CreateEffectFromFile( L"vertex.fx", NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, d3dDevice, NULL, NULL, &FX, NULL, NULL ); Technique = FX->GetTechniqueByName( "Tech" ); D3D10_PASS_DESC PassDesc; Technique->GetPassByIndex( 0 )->GetDesc( &PassDesc ); d3dDevice->CreateInputLayout( layout, 1, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &VertexLayout ); d3dDevice->IASetInputLayout( VertexLayout ); d3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); ///////////////new////////////////////////////////////////////////////////////////// return true; } bool ReleaseObjects() { if( d3dDevice ) d3dDevice->ClearState(); if( VertexBuffer ) VertexBuffer->Release(); if( VertexLayout ) VertexLayout->Release(); if( FX ) FX->Release(); if( RenderTargetView ) RenderTargetView->Release(); if( SwapChain ) SwapChain->Release(); if( d3dDevice ) d3dDevice->Release(); return true; } void DrawScene() { //Draw Scene Here ///////////////new////////////////////////////////////////////////////////////////// D3DXCOLOR bgColor( 0.0f, 0.0f, 0.0f, 1.0f); d3dDevice->ClearRenderTargetView( RenderTargetView, bgColor ); D3D10_TECHNIQUE_DESC techDesc; Technique->GetDesc( &techDesc ); for( UINT p = 0; p < techDesc.Passes; ++p ) { Technique->GetPassByIndex( p )->Apply( 0 ); d3dDevice->Draw( 3, 0 ); } SwapChain->Present( 0, 0 ); ///////////////new////////////////////////////////////////////////////////////////// } int messageloop(){ MSG msg; ZeroMemory(&msg, sizeof(MSG)); while(true) { BOOL PeekMessageL( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg ); if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else{ // run game code DrawScene(); } } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch( msg ) { case WM_KEYDOWN: if( wParam == VK_ESCAPE ){ if(MessageBox(0, L"Are you sure you want to exit?", L"Really?", MB_YESNO | MB_ICONQUESTION) == IDYES) DestroyWindow(hwnd); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wParam, lParam); } vertex.fx // Vertex Shader float4 VS( float4 Pos : POSITION ) : SV_POSITION { return Pos; //No calculations done here, just returns the position of the vertices } // Pixel Shader float4 PS( float4 Pos : SV_POSITION ) : SV_Target { return float4( 0.0f, 1.0f, 0.0f, 1.0f ); // All pixels put through the pixel shader here will be green. } technique10 Tech { pass P0 { SetVertexShader( CompileShader( vs_4_0, VS() ) ); SetPixelShader( CompileShader( ps_4_0, PS() ) ); } }
Sign in to comment