RasterTek: Direct3D Introduction
RasterTek: Direct3D Introduction
http://www.rastertek.com/dx11tut02.html
The Framework
The frame work will begin with four items. It will have a WinMain function to handle the entry point of the application. It will also have a system class that encapsulates the entire application that will be called from within the WinMain function. Inside the system class we will have a input class for handling user input and a graphics class for handling the DirectX graphics code. Here is a diagram of the framework setup:
Now that we see how the framework will be setup lets start by looking at the WinMain function inside the main.cpp file.
WinMain
//////////////////////////////////////////////////////////////////////////////// // Filename: main.cpp //////////////////////////////////////////////////////////////////////////////// #include "systemclass.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow) { SystemClass* System; bool result;
// Create the system object. System = new SystemClass; if(!System) { return 0; } // Initialize and run the system object. result = System->Initialize(); if(result) { System->Run(); } // Shutdown and release the system object. System->Shutdown(); delete System; System = 0; return 0; }
1 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
As you can see we kept the WinMain function fairly simple. We create the system class and then initialize it. If it initializes with no problems then we call the system class Run function. The Run function will run its own loop and do all the application code until it completes. After the Run function finishes we then shut down the system object and do the clean up of the system object. So we have kept it very simple and encapsulated the entire application inside the system class. Now lets take a look at the system class header file.
Systemclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: systemclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _SYSTEMCLASS_H_ #define _SYSTEMCLASS_H_ Here we define WIN32_LEAN_AND_MEAN. We do this to speed up the build process, it reduces the size of the Win32 header files by excluding some of the less used APIs. /////////////////////////////// // PRE-PROCESSING DIRECTIVES // /////////////////////////////// #define WIN32_LEAN_AND_MEAN Windows.h is included so that we can call the functions to create/destroy windows and be able to use the other useful win32 functions. ////////////// // INCLUDES // ////////////// #include <windows.h> We have included the headers to the other two classes in the frame work at this point so we can use them in the system class. /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "inputclass.h" #include "graphicsclass.h" The definition of the class is fairly simple. We see the Initialize, Shutdown, and Run function that was called in WinMain defined here. There are also some private functions that will be called inside those functions. We have also put a MessageHandler function in the class to handle the windows system messages that will get sent to the application while it is running. And finally we have some private variables m_Input and m_Graphics which will be pointers to the two objects that will handle graphics and input. //////////////////////////////////////////////////////////////////////////////// // Class name: SystemClass //////////////////////////////////////////////////////////////////////////////// class SystemClass { public: SystemClass(); SystemClass(const SystemClass&); ~SystemClass(); bool Initialize(); void Shutdown(); void Run(); LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM); private: bool Frame(); void InitializeWindows(int&, int&); void ShutdownWindows(); private: LPCWSTR m_applicationName;
2 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
///////////////////////// // FUNCTION PROTOTYPES // ///////////////////////// static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
///////////// // GLOBALS // ///////////// static SystemClass* ApplicationHandle = 0; #endif The WndProc function and ApplicationHandle pointer are also included in this class file so we can re-direct the windows system messaging into our MessageHandler function inside the system class. Now lets take a look at the system class source file:
Systemclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: systemclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "systemclass.h" In the class constructor I initialize the object pointers to null. This is important because if the initialization of these objects fail then the Shutdown function further on will attempt to clean up those objects. If the objects are not null then it assumes they were valid created objects and that they need to be cleaned up. It is also good practice to initialize all pointers and variables to null in your applications. Some release builds will fail if you do not do so. SystemClass::SystemClass() { m_Input = 0; m_Graphics = 0; } Here I create an empty copy constructor and empty class destructor. In this class I don't have need of them but if not defined some compilers will generate them for you, and in which case I'd rather they be empty. You will also notice I don't do any object clean up in the class destructor. I instead do all my object clean up in the Shutdown function you will see further down. The reason being is that I don't trust it to be called. Certain windows functions like ExitThread() are known for not calling your class destructors resulting in memory leaks. You can of course call safer versions of these functions now but I'm just being careful when programming on windows. SystemClass::SystemClass(const SystemClass& other) { }
SystemClass::~SystemClass() { } The following Initialize function does all the setup for the application. It first calls InitializeWindows which will create the window for our application to use. It also creates and initializes both the input and graphics objects that the application will use for handling user input and rendering graphics to the screen.
3 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
// Initialize the width and height of the screen to zero before sending the variables into the function. screenWidth = 0; screenHeight = 0; // Initialize the windows api. InitializeWindows(screenWidth, screenHeight); // Create the input object. This object will be used to handle reading the keyboard input from the user. m_Input = new InputClass; if(!m_Input) { return false; } // Initialize the input object. m_Input->Initialize(); // Create the graphics object. This object will handle rendering all the graphics for this application. m_Graphics = new GraphicsClass; if(!m_Graphics) { return false; } // Initialize the graphics object. result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd); if(!result) { return false; } return true; } The Shutdown function does the clean up. It shuts down and releases everything associated with the graphics and input object. As well it also shuts down the window and cleans up the handles associated with it. void SystemClass::Shutdown() { // Release the graphics object. if(m_Graphics) { m_Graphics->Shutdown(); delete m_Graphics; m_Graphics = 0; } // Release the input object. if(m_Input) { delete m_Input; m_Input = 0; } // Shutdown the window. ShutdownWindows(); return; }
4 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
The Run function is where our application will loop and do all the application processing until we decide to quit. The application processing is done in the Frame function which is called each loop. This is an important concept to understand as now the rest of our application must be written with this in mind. The pseudo code looks like the following: while not done check for windows system messages process system messages process application loop check if user wanted to quit during the frame processing void SystemClass::Run() { MSG msg; bool done, result;
// Initialize the message structure. ZeroMemory(&msg, sizeof(MSG)); // Loop until there is a quit message from the window or the user. done = false; while(!done) { // Handle the windows messages. if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } // If windows signals to end the application then exit out. if(msg.message == WM_QUIT) { done = true; } else { // Otherwise do the frame processing. result = Frame(); if(!result) { done = true; } } } return; } The following Frame function is where all the processing for our application is done. So far it is fairly simple, we check the input object to see if the user has pressed escape and wants to quit. If they don't want to quit then we call the graphics object to do its frame processing which will render the graphics for that frame. As the application grows we'll place more code inside here. bool SystemClass::Frame() { bool result;
// Check if the user pressed escape and wants to exit the application. if(m_Input->IsKeyDown(VK_ESCAPE)) { return false; }
5 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
// Do the frame processing for the graphics object. result = m_Graphics->Frame(); if(!result) { return false; } return true; } The MessageHandler function is where we direct the windows system messages into. This way we can listen for certain information that we are interested in. Currently we will just read if a key is pressed or if a key is released and pass that information on to the input object. All other information we will pass back to the windows default message handler. LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) { switch(umsg) { // Check if a key has been pressed on the keyboard. case WM_KEYDOWN: { // If a key is pressed send it to the input object so it can record that state. m_Input->KeyDown((unsigned int)wparam); return 0; } // Check if a key has been released on the keyboard. case WM_KEYUP: { // If a key is released then send it to the input object so it can unset the state for that key. m_Input->KeyUp((unsigned int)wparam); return 0; } // Any other messages send to the default message handler as our application won't make use of them. default: { return DefWindowProc(hwnd, umsg, wparam, lparam); } } } The InitializeWindows function is where we put the code to build the window we will use to render to. It returns screenWidth and screenHeight back to the calling function so we can make use of them throughout the application. We create the window using some default settings to initialize a plain black window with no borders. The function will make either a small window or make a full screen window depending on a global variable called FULL_SCREEN. If this is set to true then we make the screen cover the entire users desktop window. If it is set to false we just make a 800x600 window in the middle of the screen. I placed the FULL_SCREEN global variable at the top of the graphicsclass.h file in case you want to modify it. It will make sense later why I placed the global in that file instead of the header for this file. void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight) { WNDCLASSEX wc; DEVMODE dmScreenSettings; int posX, posY;
// Get an external pointer to this object. ApplicationHandle = this; // Get the instance of this application. m_hinstance = GetModuleHandle(NULL); // Give the application a name. m_applicationName = L"Engine";
6 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
// Setup the windows class with default settings. wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = m_hinstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); wc.hIconSm = wc.hIcon; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = m_applicationName; wc.cbSize = sizeof(WNDCLASSEX); // Register the window class. RegisterClassEx(&wc); // Determine the resolution of the clients desktop screen. screenWidth = GetSystemMetrics(SM_CXSCREEN); screenHeight = GetSystemMetrics(SM_CYSCREEN); // Setup the screen settings depending on whether it is running in full screen or in windowed mode. if(FULL_SCREEN) { // If full screen set the screen to maximum size of the users desktop and 32bit. memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); dmScreenSettings.dmSize = sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth; dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight; dmScreenSettings.dmBitsPerPel = 32; dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; // Change the display settings to full screen. ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN); // Set the position of the window to the top left corner. posX = posY = 0; } else { // If windowed then set it to 800x600 resolution. screenWidth = 800; screenHeight = 600; // Place the window in the middle of the screen. posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2; posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2; } // Create the window with the screen settings and get the handle to it. m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP, posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL); // Bring the window up on the screen and set it as main focus. ShowWindow(m_hwnd, SW_SHOW); SetForegroundWindow(m_hwnd); SetFocus(m_hwnd); // Hide the mouse cursor. ShowCursor(false); return; } ShutdownWindows does just that. It returns the screen settings back to normal and releases the window and the handles associated
7 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
with it. void SystemClass::ShutdownWindows() { // Show the mouse cursor. ShowCursor(true); // Fix the display settings if leaving full screen mode. if(FULL_SCREEN) { ChangeDisplaySettings(NULL, 0); } // Remove the window. DestroyWindow(m_hwnd); m_hwnd = NULL; // Remove the application instance. UnregisterClass(m_applicationName, m_hinstance); m_hinstance = NULL; // Release the pointer to this class. ApplicationHandle = NULL; return; } The WndProc function is where windows sends its messages to. You'll notice we tell windows the name of it when we initialize the window class with wc.lpfnWndProc = WndProc in the InitializeWindows function above. I included it in this class file since we tie it directly into the system class by having it send all the messages to the MessageHandler function defined inside SystemClass. This allows us to hook the messaging functionality straight into our class and keep the code clean. LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam) { switch(umessage) { // Check if the window is being destroyed. case WM_DESTROY: { PostQuitMessage(0); return 0; } // Check if the window is being closed. case WM_CLOSE: { PostQuitMessage(0); return 0; } // All other messages pass to the message handler in the system class. default: { return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam); } } }
Inputclass.h
To keep the tutorials simple I used the windows input for the time being until I do a tutorial on DirectInput (which is far superior). The input class handles the user input from the keyboard. This class is given input from the SystemClass::MessageHandler function. The input object will store the state of each key in a keyboard array. When queried it will tell the calling functions if a certain key is pressed. Here is the header:
8 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
//////////////////////////////////////////////////////////////////////////////// // Class name: InputClass //////////////////////////////////////////////////////////////////////////////// class InputClass { public: InputClass(); InputClass(const InputClass&); ~InputClass(); void Initialize(); void KeyDown(unsigned int); void KeyUp(unsigned int); bool IsKeyDown(unsigned int); private: bool m_keys[256]; }; #endif
Inputclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: inputclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "inputclass.h"
InputClass::InputClass() { }
InputClass::~InputClass() { }
// Initialize all the keys to being released and not pressed. for(i=0; i<256; i++) { m_keys[i] = false; }
9 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
return; }
void InputClass::KeyDown(unsigned int input) { // If a key is pressed then save that state in the key array. m_keys[input] = true; return; }
void InputClass::KeyUp(unsigned int input) { // If a key is released then clear that state in the key array. m_keys[input] = false; return; }
bool InputClass::IsKeyDown(unsigned int key) { // Return what state the key is in (pressed/not pressed). return m_keys[key]; }
Graphicsclass.h
The graphics class is the other object that is created by the system class. All the graphics functionality in this application will be encapsulated in this class. I will also use the header in this file for all the graphics related global settings that we may want to change such as full screen or windowed mode. Currently this class will be empty but in future tutorials will contain all the graphics objects.
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = false; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; We'll need these four globals to start with. //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass();
10 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: }; #endif
Graphicsclass.cpp
I have kept this class entirely empty for now as we are just building the framework for this tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { }
GraphicsClass::~GraphicsClass() { }
bool GraphicsClass::Render() {
11 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut02.html
return true; }
Summary
So now we have a framework and a window that will pop up on the screen. This frame work will now be the base for all future tutorials so understanding this frame work is fairly important. Please try the To Do exercise to make sure the code compiles and is working for you before moving on to the next tutorial. If you don't understand this frame work you should still be fine to move onto the other tutorials and they may make more sense to you later once the frame work is filled out more.
To Do Exercises
1. Change the FULL_SCREEN parameter to true in the graphicsclass.h header then recompile and run the program. Press the escape key to quit after the window displays.
Source Code
Visual Studio 2010 Project: dx11tut02.zip Source Only: dx11src02.zip Executable Only: dx11exe02.zip
12 of 12
3/8/2013 12:14 PM
http://www.rastertek.com/dx11tut03.html
Updated Framework
We are going to add another class to the framework which will handle all the Direct3D system functions. We will call the class D3DClass. I have updated the framework diagram below:
As you can see the D3DClass will be located inside the GraphicsClass. The previous tutorial mentioned that all new graphics related classes will be encapsulated in the GraphicsClass and that is why it is the best location for the new D3DClass. Now lets take a look at the changes made to the GraphicsClass:
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ Here is the first change. We have taken out the include for windows.h and instead included the new d3dclass.h. /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = false; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass();
1 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: And the second change is the new private pointer to the D3DClass which we have called m_D3D. In case you were wondering I use the prefix m_ on all class variables. That way when I'm coding I can remember quickly what variables are members of the class and which are not. D3DClass* m_D3D; }; #endif
Graphicsclass.cpp
If you'll remember from the previous tutorial this class was entirely empty with no code in it at all. Now that we have a D3DClass member we will start to fill out some code inside the GraphicsClass to initialize and shutdown the D3DClass object. We will also add calls to BeginScene and EndScene in the Render function so that we are now drawing to the window using Direct3D. So the very first change is in the class constructor. Here we initialize the pointer to null for safety reasons as we do with all class pointers. GraphicsClass::GraphicsClass() { m_D3D = 0; } The second change is in the Initialize function inside the GraphicsClass. Here we create the D3DClass object and then call the D3DClass Initialize function. We send this function the screen width, screen height, handle to the window, and the four global variables from the Graphicsclass.h file. The D3DClass will use all these variables to setup the Direct3D system. We'll go into more detail about that once we look at the d3dclass.cpp file. bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result;
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NE if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK); return false; } return true; } The next change is in the Shutdown function in the GraphicsClass. Shut down of all graphics objects occur here so we have placed the D3DClass shutdown in this function. Note that I check to see if the pointer was initialized or not. If it wasn't we can assume it was never set up and not try to shut it down. That is why it is important to set all the pointers to null in the class constructor. If it does find the pointer has been initialized then it will attempt to shut down the D3DClass and then clean up the pointer afterwards.
2 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
void GraphicsClass::Shutdown() { if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } The Frame function has been updated so that it now calls the Render function each frame. bool GraphicsClass::Frame() { bool result;
// Render the graphics scene. result = Render(); if(!result) { return false; } return true; } The final change to this class is in the Render function. We call the D3D object to clear the screen to a grey color. After that we call EndScene so that the grey color is presented to the window. bool GraphicsClass::Render() { // Clear the buffers to begin the scene. m_D3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);
// Present the rendered scene to the screen. m_D3D->EndScene(); return true; } Now lets take a look at the new D3DClass header file:
D3dclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: d3dclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _D3DCLASS_H_ #define _D3DCLASS_H_ First thing in the header is to specify the libraries to link when using this object module. These libraries contain all the Direct3D functionality for setting up and drawing 3D graphics in DirectX as well as tools to interface with the hardware on the computer to obtain information about the refresh rate of the monitor, the video card being used, and so forth. You will notice that some DirectX 10 libraries are still used, this is because those libraries were never upgraded for DirectX 11 as their functionality did not need to change. ///////////// // LINKING // ///////////// #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib")
3 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
#pragma comment(lib, "d3dx11.lib") #pragma comment(lib, "d3dx10.lib") The next thing we do is include the headers for those libraries that we are linking to this object module as well as headers for DirectX type definitions and such. ////////////// // INCLUDES // ////////////// #include <dxgi.h> #include <d3dcommon.h> #include <d3d11.h> #include <d3dx10math.h> The class definition for the D3DClass is kept as simple as possible here. It has the regular constructor, copy constructor, and destructor. Then more importantly it has the Initialize and Shutdown function. This will be what we are mainly focused on in this tutorial. Other than that I have a couple helper functions which aren't important to this tutorial and a number of private member variables that will be looked at when we examine the d3dclass.cpp file. For now just realize the Initialize and Shutdown functions are what concerns us. //////////////////////////////////////////////////////////////////////////////// // Class name: D3DClass //////////////////////////////////////////////////////////////////////////////// class D3DClass { public: D3DClass(); D3DClass(const D3DClass&); ~D3DClass(); bool Initialize(int, int, bool, HWND, bool, float, float); void Shutdown(); void BeginScene(float, float, float, float); void EndScene(); ID3D11Device* GetDevice(); ID3D11DeviceContext* GetDeviceContext(); void GetProjectionMatrix(D3DXMATRIX&); void GetWorldMatrix(D3DXMATRIX&); void GetOrthoMatrix(D3DXMATRIX&); void GetVideoCardInfo(char*, int&); private: bool m_vsync_enabled; int m_videoCardMemory; char m_videoCardDescription[128]; IDXGISwapChain* m_swapChain; ID3D11Device* m_device; ID3D11DeviceContext* m_deviceContext; ID3D11RenderTargetView* m_renderTargetView; ID3D11Texture2D* m_depthStencilBuffer; ID3D11DepthStencilState* m_depthStencilState; ID3D11DepthStencilView* m_depthStencilView; ID3D11RasterizerState* m_rasterState; D3DXMATRIX m_projectionMatrix; D3DXMATRIX m_worldMatrix; D3DXMATRIX m_orthoMatrix; }; #endif For those familiar with Direct3D already you may notice I don't have a view matrix variable in this class. The reason being is that I will be putting it in a camera class that we will be looking at in future tutorials.
4 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
D3dclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: d3dclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "d3dclass.h" So like most classes we begin with initializing all the member pointers to null in the class constructor. All pointers from the header file have all been accounted for here. D3DClass::D3DClass() { m_swapChain = 0; m_device = 0; m_deviceContext = 0; m_renderTargetView = 0; m_depthStencilBuffer = 0; m_depthStencilState = 0; m_depthStencilView = 0; m_rasterState = 0; }
D3DClass::~D3DClass() { } The Initialize function is what does the entire setup of Direct3D for DirectX 11. I have placed all the code necessary in here as well as some extra stuff that will facilitate future tutorials. I could have simplified it and taken out some items but it is probably better to get all of this covered in a single tutorial dedicated to it. The screenWidth and screenHeight variables that are given to this function are the width and height of the window we created in the SystemClass. Direct3D will use these to initialize and use the same window dimensions. The hwnd variable is a handle to the window. Direct3D will need this handle to access the window previously created. The fullscreen variable is whether we are running in windowed mode or fullscreen. Direct3D needs this as well for creating the window with the correct settings. The screenDepth and screenNear variables are the depth settings for our 3D environment that will be rendered in the window. The vsync variable indicates if we want Direct3D to render according to the users monitor refresh rate or to just go as fast as possible. bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen, float screenDepth, float screenNear) { HRESULT result; IDXGIFactory* factory; IDXGIAdapter* adapter; IDXGIOutput* adapterOutput; unsigned int numModes, i, numerator, denominator, stringLength; DXGI_MODE_DESC* displayModeList; DXGI_ADAPTER_DESC adapterDesc; int error; DXGI_SWAP_CHAIN_DESC swapChainDesc; D3D_FEATURE_LEVEL featureLevel; ID3D11Texture2D* backBufferPtr; D3D11_TEXTURE2D_DESC depthBufferDesc; D3D11_DEPTH_STENCIL_DESC depthStencilDesc; D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc; D3D11_RASTERIZER_DESC rasterDesc; D3D11_VIEWPORT viewport; float fieldOfView, screenAspect;
5 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
// Store the vsync setting. m_vsync_enabled = vsync; Before we can initialize Direct3D we have to get the refresh rate from the video card/monitor. Each computer may be slightly different so we will need to query for that information. We query for the numerator and denominator values and then pass them to DirectX during the setup and it will calculate the proper refresh rate. If we don't do this and just set the refresh rate to a default value which may not exist on all computers then DirectX will respond by performing a blit instead of a buffer flip which will degrade performance and give us annoying errors in the debug output. // Create a DirectX graphics interface factory. result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory); if(FAILED(result)) { return false; } // Use the factory to create an adapter for the primary graphics interface (video card). result = factory->EnumAdapters(0, &adapter); if(FAILED(result)) { return false; } // Enumerate the primary adapter output (monitor). result = adapter->EnumOutputs(0, &adapterOutput); if(FAILED(result)) { return false; } // Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor). result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numM if(FAILED(result)) { return false; } // Create a list to hold all the possible display modes for this monitor/video card combination. displayModeList = new DXGI_MODE_DESC[numModes]; if(!displayModeList) { return false; } // Now fill the display mode list structures. result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numM if(FAILED(result)) { return false; } // Now go through all the display modes and find the one that matches the screen width and height. // When a match is found store the numerator and denominator of the refresh rate for that monitor. for(i=0; i<numModes; i++) { if(displayModeList[i].Width == (unsigned int)screenWidth) { if(displayModeList[i].Height == (unsigned int)screenHeight) { numerator = displayModeList[i].RefreshRate.Numerator; denominator = displayModeList[i].RefreshRate.Denominator; } } }
6 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
We now have the numerator and denominator for the refresh rate. The last thing we will retrieve using the adapter is the name of the video card and the amount of memory on the video card. // Get the adapter (video card) description. result = adapter->GetDesc(&adapterDesc); if(FAILED(result)) { return false; } // Store the dedicated video card memory in megabytes. m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024); // Convert the name of the video card to a character array and store it. error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128); if(error != 0) { return false; } Now that we have stored the numerator and denominator for the refresh rate and the video card information we can release the structures and interfaces used to get that information. // Release the display mode list. delete [] displayModeList; displayModeList = 0; // Release the adapter output. adapterOutput->Release(); adapterOutput = 0; // Release the adapter. adapter->Release(); adapter = 0; // Release the factory. factory->Release(); factory = 0; Now that we have the refresh rate from the system we can start the DirectX initialization. The first thing we'll do is fill out the description of the swap chain. The swap chain is the front and back buffer to which the graphics will be drawn. Generally you use a single back buffer, do all your drawing to it, and then swap it to the front buffer which then displays on the user's screen. That is why it is called a swap chain. // Initialize the swap chain description. ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); // Set to a single back buffer. swapChainDesc.BufferCount = 1; // Set the width and height of the back buffer. swapChainDesc.BufferDesc.Width = screenWidth; swapChainDesc.BufferDesc.Height = screenHeight; // Set regular 32-bit surface for the back buffer. swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; The next part of the description of the swap chain is the refresh rate. The refresh rate is how many times a second it draws the back buffer to the front buffer. If vsync is set to true in our graphicsclass.h header then this will lock the refresh rate to the system settings (for example 60hz). That means it will only draw the screen 60 times a second (or higher if the system refresh rate is more than 60). However if we set vsync to false then it will draw the screen as many times a second as it can, however this can cause some visual artifacts. // Set the refresh rate of the back buffer. if(m_vsync_enabled) {
7 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator; swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator; } else { swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; swapChainDesc.BufferDesc.RefreshRate.Denominator = 1; } // Set the usage of the back buffer. swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // Set the handle for the window to render to. swapChainDesc.OutputWindow = hwnd; // Turn multisampling off. swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; // Set to full screen or windowed mode. if(fullscreen) { swapChainDesc.Windowed = false; } else { swapChainDesc.Windowed = true; } // Set the scan line ordering and scaling to unspecified. swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // Discard the back buffer contents after presenting. swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // Don't set the advanced flags. swapChainDesc.Flags = 0; After setting up the swap chain description we also need to setup one more variable called the feature level. This variable tells DirectX what version we plan to use. Here we set the feature level to 11.0 which is DirectX 11. You can set this to 10 or 9 to use a lower level version of DirectX if you plan on supporting multiple versions or running on lower end hardware. // Set the feature level to DirectX 11. featureLevel = D3D_FEATURE_LEVEL_11_0; Now that the swap chain description and feature level have been filled out we can create the swap chain, the Direct3D device, and the Direct3D device context. The Direct3D device and Direct3D device context are very important, they are the interface to all of the Direct3D functions. We will use the device and device context for almost everything from this point forward. Those of you reading this who are familiar with the previous versions of DirectX will recognize the Direct3D device but will be unfamiliar with the new Direct3D device context. Basically they took the functionality of the Direct3D device and split it up into two different devices so you need to use both now. Note that if the user does not have a DirectX 11 video card this function call will fail to create the device and device context. Also if you are testing DirectX 11 functionality yourself and don't have a DirectX 11 video card then you can replace D3D_DRIVER_TYPE_HARDWARE with D3D_DRIVER_TYPE_REFERENCE and DirectX will use your CPU to draw instead of the video card hardware. Note that this runs 1/1000 the speed but it is good for people who don't have DirectX 11 video cards yet on all their machines. // Create the swap chain, Direct3D device, and Direct3D device context. result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext); if(FAILED(result)) { return false;
8 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
} Sometimes this call to create the device will fail if the primary video card is not compatible with DirectX 11. Some machines may have the primary card as a DirectX 10 video card and the secondary card as a DirectX 11 video card. Also some hybrid graphics cards work that way with the primary being the low power Intel card and the secondary being the high power Nvidia card. To get around this you will need to not use the default device and instead enumerate all the video cards in the machine and have the user choose which one to use and then specify that card when creating the device. Now that we have a swap chain we need to get a pointer to the back buffer and then attach it to the swap chain. We'll use the CreateRenderTargetView function to attach the back buffer to our swap chain. // Get the pointer to the back buffer. result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr); if(FAILED(result)) { return false; } // Create the render target view with the back buffer pointer. result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView); if(FAILED(result)) { return false; } // Release pointer to the back buffer as we no longer need it. backBufferPtr->Release(); backBufferPtr = 0; We will also need to set up a depth buffer description. We'll use this to create a depth buffer so that our polygons can be rendered properly in 3D space. At the same time we will attach a stencil buffer to our depth buffer. The stencil buffer can be used to achieve effects such as motion blur, volumetric shadows, and other things. // Initialize the description of the depth buffer. ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc)); // Set up the description of the depth buffer. depthBufferDesc.Width = screenWidth; depthBufferDesc.Height = screenHeight; depthBufferDesc.MipLevels = 1; depthBufferDesc.ArraySize = 1; depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthBufferDesc.SampleDesc.Count = 1; depthBufferDesc.SampleDesc.Quality = 0; depthBufferDesc.Usage = D3D11_USAGE_DEFAULT; depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; depthBufferDesc.CPUAccessFlags = 0; depthBufferDesc.MiscFlags = 0; Now we create the depth/stencil buffer using that description. You will notice we use the CreateTexture2D function to make the buffers, hence the buffer is just a 2D texture. The reason for this is that once your polygons are sorted and then rasterized they just end up being colored pixels in this 2D buffer. Then this 2D buffer is drawn to the screen. // Create the texture for the depth buffer using the filled out description. result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer); if(FAILED(result)) { return false; } Now we need to setup the depth stencil description. This allows us to control what type of depth test Direct3D will do for each pixel. // Initialize the description of the stencil state. ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc)); // Set up the description of the stencil state.
9 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
depthStencilDesc.DepthEnable = true; depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS; depthStencilDesc.StencilEnable = true; depthStencilDesc.StencilReadMask = 0xFF; depthStencilDesc.StencilWriteMask = 0xFF; // Stencil operations if pixel is front-facing. depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // Stencil operations if pixel is back-facing. depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; With the description filled out we can now create a depth stencil state. // Create the depth stencil state. result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState); if(FAILED(result)) { return false; } With the created depth stencil state we can now set it so that it takes effect. Notice we use the device context to set it. // Set the depth stencil state. m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1); The next thing we need to create is the description of the view of the depth stencil buffer. We do this so that Direct3D knows to use the depth buffer as a depth stencil texture. After filling out the description we then call the function CreateDepthStencilView to create it. // Initailze the depth stencil view. ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc)); // Set up the depth stencil view description. depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; depthStencilViewDesc.Texture2D.MipSlice = 0; // Create the depth stencil view. result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView); if(FAILED(result)) { return false; } With that created we can now call OMSetRenderTargets. This will bind the render target view and the depth stencil buffer to the output render pipeline. This way the graphics that the pipeline renders will get drawn to our back buffer that we previously created. With the graphics written to the back buffer we can then swap it to the front and display our graphics on the user's screen. // Bind the render target view and depth stencil buffer to the output render pipeline. m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView); Now that the render targets are setup we can continue on to some extra functions that will give us more control over our scenes for future tutorials. First thing is we'll create is a rasterizer state. This will give us control over how polygons are rendered. We can do things like make our scenes render in wireframe mode or have DirectX draw both the front and back faces of polygons. By default DirectX already has a rasterizer state set up and working the exact same as the one below but you have no control to change it unless you set up one yourself. // Setup the raster description which will determine how and what polygons will be drawn.
10 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
rasterDesc.AntialiasedLineEnable = false; rasterDesc.CullMode = D3D11_CULL_BACK; rasterDesc.DepthBias = 0; rasterDesc.DepthBiasClamp = 0.0f; rasterDesc.DepthClipEnable = true; rasterDesc.FillMode = D3D11_FILL_SOLID; rasterDesc.FrontCounterClockwise = false; rasterDesc.MultisampleEnable = false; rasterDesc.ScissorEnable = false; rasterDesc.SlopeScaledDepthBias = 0.0f; // Create the rasterizer state from the description we just filled out. result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState); if(FAILED(result)) { return false; } // Now set the rasterizer state. m_deviceContext->RSSetState(m_rasterState); The viewport also needs to be setup so that Direct3D can map clip space coordinates to the render target space. Set this to be the entire size of the window. // Setup the viewport for rendering. viewport.Width = (float)screenWidth; viewport.Height = (float)screenHeight; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; // Create the viewport. m_deviceContext->RSSetViewports(1, &viewport); Now we will create the projection matrix. The projection matrix is used to translate the 3D scene into the 2D viewport space that we previously created. We will need to keep a copy of this matrix so that we can pass it to our shaders that will be used to render our scenes. // Setup the projection matrix. fieldOfView = (float)D3DX_PI / 4.0f; screenAspect = (float)screenWidth / (float)screenHeight; // Create the projection matrix for 3D rendering. D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth); We will also create another matrix called the world matrix. This matrix is used to convert the vertices of our objects into vertices in the 3D scene. This matrix will also be used to rotate, translate, and scale our objects in 3D space. From the start we will just initialize the matrix to the identity matrix and keep a copy of it in this object. The copy will be needed to be passed to the shaders for rendering also. // Initialize the world matrix to the identity matrix. D3DXMatrixIdentity(&m_worldMatrix); This is where you would generally create a view matrix. The view matrix is used to calculate the position of where we are looking at the scene from. You can think of it as a camera and you only view the scene through this camera. Because of its purpose I am going to create it in a camera class in later tutorials since logically it fits better there and just skip it for now. And the final thing we will setup in the Initialize function is an orthographic projection matrix. This matrix is used for rendering 2D elements like user interfaces on the screen allowing us to skip the 3D rendering. You will see this used in later tutorials when we look at rendering 2D graphics and fonts to the screen. // Create an orthographic projection matrix for 2D rendering. D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth); return true; }
11 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
The Shutdown function will release and clean up all the pointers used in the Initialize function, its pretty straight forward. However before doing that I put in a call to force the swap chain to go into windowed mode first before releasing any pointers. If this is not done and you try to release the swap chain in full screen mode it will throw some exceptions. So to avoid that happening we just always force windowed mode before shutting down Direct3D. void D3DClass::Shutdown() { // Before shutting down set to windowed mode or when you release the swap chain it will throw an exception. if(m_swapChain) { m_swapChain->SetFullscreenState(false, NULL); } if(m_rasterState) { m_rasterState->Release(); m_rasterState = 0; } if(m_depthStencilView) { m_depthStencilView->Release(); m_depthStencilView = 0; } if(m_depthStencilState) { m_depthStencilState->Release(); m_depthStencilState = 0; } if(m_depthStencilBuffer) { m_depthStencilBuffer->Release(); m_depthStencilBuffer = 0; } if(m_renderTargetView) { m_renderTargetView->Release(); m_renderTargetView = 0; } if(m_deviceContext) { m_deviceContext->Release(); m_deviceContext = 0; } if(m_device) { m_device->Release(); m_device = 0; } if(m_swapChain) { m_swapChain->Release(); m_swapChain = 0; } return; } In the D3DClass I have a couple helper functions. The first two are BeginScene and EndScene. BeginScene will be called whenever we are going to draw a new 3D scene at the beginning of each frame. All it does is initializes the buffers so they are blank and ready to be
12 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
drawn to. The other function is Endscene, it tells the swap chain to display our 3D scene once all the drawing has completed at the end of each frame. void D3DClass::BeginScene(float red, float green, float blue, float alpha) { float color[4];
// Setup the color to clear the buffer to. color[0] = red; color[1] = green; color[2] = blue; color[3] = alpha; // Clear the back buffer. m_deviceContext->ClearRenderTargetView(m_renderTargetView, color); // Clear the depth buffer. m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0); return; }
void D3DClass::EndScene() { // Present the back buffer to the screen since rendering is complete. if(m_vsync_enabled) { // Lock to screen refresh rate. m_swapChain->Present(1, 0); } else { // Present as fast as possible. m_swapChain->Present(0, 0); } return; } These next functions simply get pointers to the Direct3D device and the Direct3D device context. These helper functions will be called by the framework often. ID3D11Device* D3DClass::GetDevice() { return m_device; }
ID3D11DeviceContext* D3DClass::GetDeviceContext() { return m_deviceContext; } The next three helper functions give copies of the projection, world, and orthographic matrices to calling functions. Most shaders will need these matrices for rendering so there needed to be an easy way for outside objects to get a copy of them. We won't call these functions in this tutorial but I'm just explaining why they are in the code. void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix) { projectionMatrix = m_projectionMatrix; return; }
13 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut03.html
void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix) { orthoMatrix = m_orthoMatrix; return; } The last helper function returns by reference the name of the video card and the amount of dedicated memory on the video card. Knowing the video card name and amount of video memory can help in debugging on different configurations. void D3DClass::GetVideoCardInfo(char* cardName, int& memory) { strcpy_s(cardName, 128, m_videoCardDescription); memory = m_videoCardMemory; return; }
Summary
So now we are finally able to initialize and shut down Direct3D as well as render a color to the window. Compiling and running the code will produce the same window as the last tutorial but Direct3D is initialized now and the window is cleared to grey. Compiling and running the code will also show if your compiler is set up properly and if it can see the headers and libraries files from the DirectX SDK.
To Do Exercises
1. Re-compile the code and run the program to ensure DirectX works, if not look at the steps from the first tutorial. Press the escape key to quit after the window displays. 2. Change the global in graphicsclass.h to full screen and re-compile/run. 3. Change the clear color in GraphicsClass::Render to yellow. 4. Write the video card name and memory out to a text file.
Source Code
Visual Studio 2010 Project: dx11tut03.zip Source Only: dx11src03.zip Executable Only: dx11exe03.zip
14 of 14
3/8/2013 12:13 PM
http://www.rastertek.com/dx11tut04.html
Vertex Buffers
The first concept to understand is vertex buffers. To illustrate this concept let us take the example of a 3D model of a sphere:
Each of the triangles in the sphere model has three points to it, we call each point a vertex. So for us to render the sphere model we need to put all the vertices that form the sphere into a special data array that we call a vertex buffer. Once all the points of the sphere model are in the vertex buffer we can then send the vertex buffer to the GPU so that it can render the model.
Index Buffers
Index buffers are related to vertex buffers. Their purpose is to record the location of each vertex that is in the vertex buffer. The GPU then uses the index buffer to quickly find specific vertices in the vertex buffer. The concept of an index buffer is similar to the concept using an index in a book, it helps find the topic you are looking for at a much higher speed. The DirectX SDK documentation says that using index buffers can also increase the possibility of caching the vertex data in faster locations in video memory. So it is highly advised to use these for performance reasons as well.
1 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
Vertex Shaders
Vertex shaders are small programs that are written mainly for transforming the vertices from the vertex buffer into 3D space. There are other calculations that can be done such as calculating normals for each vertex. The vertex shader program will be called by the GPU for each vertex it needs to process. For example a 5,000 polygon model will run your vertex shader program 15,000 times each frame just to draw that single model. So if you lock your graphics program to 60 fps it will call your vertex shader 900,000 times a second to draw just 5,000 triangles. As you can tell writing efficient vertex shaders is important.
Pixel Shaders
Pixel shaders are small programs that are written for doing the coloring of the polygons that we draw. They are run by the GPU for every visible pixel that will be drawn to the screen. Coloring, texturing, lighting, and most other effects you plan to do to your polygon faces are handled by the pixel shader program. Pixel shaders must be efficiently written due to the number of times they will be called by the GPU.
HLSL
HLSL is the language we use in DirectX 11 to code these small vertex and pixel shader programs. The syntax is pretty much identical to the C language with some pre-defined types. HLSL program files are composed of global variables, type defines, vertex shaders, pixel shaders, and geometry shaders. As this is the first HLSL tutorial we will do a very simple HLSL program using DirectX 11 to get started.
Updated Framework
The framework has been updated for this tutorial. Under GraphicsClass we have added three new classes called CameraClass, ModelClass, and ColorShaderClass. CameraClass will take care of our view matrix we talked about previously. It will handle the location of the camera in the world and pass it to shaders when they need to draw and figure out where we are looking at the scene from. The ModelClass will handle the geometry of our 3D models, in this tutorial the 3D model will just be a single triangle for simplicity reasons. And finally ColorShaderClass will be responsible for rendering the model to the screen invoking our HLSL shader. We will begin the tutorial code by looking at the HLSL shader programs first.
Color.vs
These will be our first shader programs. Shaders are small programs that do the actual rendering of models. These shaders are written in HLSL and stored in source files called color.vs and color.ps. I placed the files with the .cpp and .h files in the engine for now. The purpose of this shader is just to draw colored triangles as I am keeping things simple as possible in this first HLSL tutorial. Here is the code for the vertex shader first: //////////////////////////////////////////////////////////////////////////////// // Filename: color.vs //////////////////////////////////////////////////////////////////////////////// In shader programs you begin with the global variables. These globals can be modified externally from your C++ code. You can use many types of variables such as int or float and then set them externally for the shader program to use. Generally you will put most
2 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
globals in buffer object types called "cbuffer" even if it is just a single global variable. Logically organizing these buffers is important for efficient execution of shaders as well as how the graphics card will store the buffers. In this example I've put three matrices in the same buffer since I will update them each frame at the same time. ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; Similar to C we can create our own type definitions. We will use different types such as float4 that are available to HLSL which make programming shaders easier and readable. In this example we are creating types that have x, y, z, w position vectors and red, green, blue, alpha colors. The POSITION, COLOR, and SV_POSITION are semantics that convey to the GPU the use of the variable. I have to create two different structures here since the semantics are different for vertex and pixel shaders even though the structures are the same otherwise. POSITION works for vertex shaders and SV_POSITION works for pixel shaders while COLOR works for both. If you want more than one of the same type then you have to add a number to the end such as COLOR0, COLOR1, and so forth. ////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float4 color : COLOR; }; struct PixelInputType { float4 position : SV_POSITION; float4 color : COLOR; }; The vertex shader is called by the GPU when it is processing data from the vertex buffers that have been sent to it. This vertex shader which I named ColorVertexShader will be called for every single vertex in the vertex buffer. The input to the vertex shader must match the data format in the vertex buffer as well as the type definition in the shader source file which in this case is VertexInputType. The output of the vertex shader will be sent to the pixel shader. In this case the output type is called PixelInputType which is defined above as well. With that in mind you see that the vertex shader creates an output variable that is of the PixelInputType type. It then takes the position of the input vertex and multiplies it by the world, view, and then projection matrices. This will place the vertex in the correct location for rendering in 3D space according to our view and then onto the 2D screen. After that the output variable takes a copy of the input color and then returns the output which will be used as input to the pixel shader. Also note that I do set the W value of the input position to 1.0 otherwise it is undefined since we only read in a XYZ vector for position. //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType ColorVertexShader(VertexInputType input) { PixelInputType output;
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the input color for the pixel shader to use. output.color = input.color;
3 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
return output; }
Color.ps
The pixel shader draws each pixel on the polygons that will be rendered to the screen. In this pixel shader it uses PixelInputType as input and returns a float4 as output which represents the final pixel color. This pixel shader program is very simple as we just tell it to color the pixel the same as the input value of the color. Note that the pixel shader gets its input from the vertex shader output. //////////////////////////////////////////////////////////////////////////////// // Filename: color.ps ////////////////////////////////////////////////////////////////////////////////
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float4 color : COLOR; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 ColorPixelShader(PixelInputType input) : SV_TARGET { return input.color; }
Modelclass.h
As stated previously the ModelClass is responsible for encapsulating the geometry for 3D models. In this tutorial we will manually setup the data for a single green triangle. We will also create a vertex and index buffer for the triangle so that it can be rendered. //////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private: Here is the definition of our vertex type that will be used with the vertex buffer in this ModelClass. Also take note that this typedef must match the layout in the ColorShaderClass that will be looked at later in the tutorial.
4 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR4 color; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); The functions here handle initializing and shutdown of the model's vertex and index buffers. The Render function puts the model geometry on the video card to prepare it for drawing by the color shader. bool Initialize(ID3D11Device*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); The private variables in the ModelClass are the vertex and index buffer as well as two integers to keep track of the size of each buffer. Note that all DirectX 11 buffers generally use the generic ID3D11Buffer type and are more clearly identified by a buffer description when they are first created. private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; }; #endif
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h" The class constructor initializes the vertex and index buffer pointers to null. ModelClass::ModelClass() { m_vertexBuffer = 0; m_indexBuffer = 0; }
ModelClass::~ModelClass() { } The Initialize function will call the initialization functions for the vertex and index buffers. bool ModelClass::Initialize(ID3D11Device* device)
5 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
{ bool result;
// Initialize the vertex and index buffer that hold the geometry for the triangle. result = InitializeBuffers(device); if(!result) { return false; } return true; } The Shutdown function will call the shutdown functions for the vertex and index buffers. void ModelClass::Shutdown() { // Release the vertex and index buffers. ShutdownBuffers(); return; } Render is called from the GraphicsClass::Render function. This function calls RenderBuffers to put the vertex and index buffers on the graphics pipeline so the color shader will be able to render them. void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return; } GetIndexCount returns the number of indexes in the model. The color shader will need this information to draw this model. int ModelClass::GetIndexCount() { return m_indexCount; } The InitializeBuffers function is where we handle creating the vertex and index buffers. Usually you would read in a model and create the buffers from that data file. For this tutorial we will just set the points in the vertex and index buffer manually since it is only a single triangle. bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; First create two temporary arrays to hold the vertex and index data that we will use later to populate the final buffers with. // Set the number of vertices in the vertex array. m_vertexCount = 3; // Set the number of indices in the index array. m_indexCount = 3; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) {
6 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } Now fill both the vertex and index array with the three points of the triangle as well as the index to each of the points. Please note that I create the points in the clockwise order of drawing them. If you do this counter clockwise it will think the triangle is facing the opposite direction and not draw it due to back face culling. Always remember that the order in which you send your vertices to the GPU is very important. The color is set here as well since it is part of the vertex description. I set the color to green. // Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left. vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle. vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); // Load the index array with data. indices[0] = 0; // Bottom left. indices[1] = 1; // Top middle. indices[2] = 2; // Bottom right. With the vertex array and index array filled out we can now use those to create the vertex buffer and index buffer. Creating both buffers is done in the same fashion. First fill out a description of the buffer. In the description the ByteWidth (size of the buffer) and the BindFlags (type of buffer) are what you need to ensure are filled out correctly. After the description is filled out you need to also fill out a subresource pointer which will point to either your vertex or index array you previously created. With the description and subresource pointer you can call CreateBuffer using the D3D device and it will return a pointer to your new buffer. // Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices;
7 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } After the vertex buffer and index buffer have been created you can delete the vertex and index arrays as they are no longer needed since the data was copied into the buffers. // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; } The ShutdownBuffers function just releases the vertex buffer and index buffer that were created in the InitializeBuffers function. void ModelClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; } RenderBuffers is called from the Render function. The purpose of this function is to set the vertex buffer and index buffer as active on the input assembler in the GPU. Once the GPU has an active vertex buffer it can then use the shader to render that buffer. This function also defines how those buffers should be drawn such as triangles, lines, fans, and so forth. In this tutorial we set the vertex buffer and index buffer as active on the input assembler and tell the GPU that the buffers should be drawn as triangles using the IASetPrimitiveTopology DirectX function. void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { unsigned int stride; unsigned int offset;
// Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
8 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
Colorshaderclass.h
The ColorShaderClass is what we will use to invoke our HLSL shaders for drawing the 3D models that are on the GPU. //////////////////////////////////////////////////////////////////////////////// // Filename: colorshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _COLORSHADERCLASS_H_ #define _COLORSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: ColorShaderClass //////////////////////////////////////////////////////////////////////////////// class ColorShaderClass { private: Here is the definition of the cBuffer type that will be used with the vertex shader. This typedef must be exactly the same as the one in the vertex shader as the model data needs to match the typedefs in the shader for proper rendering. struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: ColorShaderClass(); ColorShaderClass(const ColorShaderClass&); ~ColorShaderClass(); The functions here handle initializing and shutdown of the shader. The render function sets the shader parameters and then draws the prepared model vertices using the shader. bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); void RenderShader(ID3D11DeviceContext*, int);
9 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; }; #endif
Colorshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: colorshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "colorshaderclass.h" As usual the class constructor initializes all the private pointers in the class to null. ColorShaderClass::ColorShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; }
ColorShaderClass::~ColorShaderClass() { } The Initialize function will call the initialization function for the shaders. We pass in the name of the HLSL shader files, in this tutorial they are named color.vs and color.ps. bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result;
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps"); if(!result) { return false; } return true; } The Shutdown function will call the shutdown of the shader. void ColorShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
10 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
Render will first set the parameters inside the shader using the SetShaderParameters function. Once the parameters are set it then calls RenderShader to draw the green triangle using the HLSL shader. bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; } Now we will start with one of the more important functions to this tutorial which is called InitializeShader. This function is what actually loads the shader files and makes it usable to DirectX and the GPU. You will also see the setup of the layout and how the vertex buffer data is going to look on the graphics pipeline in the GPU. The layout will need the match the VertexType in the modelclass.h file as well as the one defined in the color.vs file. bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Here is where we compile the shader programs into buffers. We give it the name of the shader file, the name of the shader, the shader version (5.0 in DirectX 11), and the buffer to compile the shader into. If it fails compiling the shader it will put an error message inside the errorMessage string which we send to another function to write out the error. If it still fails and there is no errorMessage string then it means it could not find the shader file in which case we pop up a dialog box saying so. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNE &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; }
11 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
// Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNE &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } Once the vertex shader and pixel shader code has successfully compiled into buffers we then use those buffers to create the shader objects themselves. We will use these pointers to interface with the vertex and pixel shader from this point forward. // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vert if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelSha if(FAILED(result)) { return false; } The next step is to create the layout of the vertex data that will be processed by the shader. As this shader uses a position and color vector we need to create both in the layout specifying the size of both. The semantic name is the first thing to fill out in the layout, this allows the shader to determine the usage of this element of the layout. As we have two different elements we use POSITION for the first one and COLOR for the second. The next important part of the layout is the Format. For the position vector we use DXGI_FORMAT_R32G32B32_FLOAT and for the color we use DXGI_FORMAT_R32G32B32A32_FLOAT. The final thing you need to pay attention to is the AlignedByteOffset which indicates how the data is spaced in the buffer. For this layout we are telling it the first 12 bytes are position and the next 16 bytes will be color, AlignedByteOffset shows where each element begins. You can use D3D11_APPEND_ALIGNED_ELEMENT instead of placing your own values in AlignedByteOffset and it will figure out the spacing for you. The other settings I've made default for now as they are not needed in this tutorial. // Now setup the layout of the data that goes into the shader. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "COLOR"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; Once the layout description has been setup we can get the size of it and then create the input layout using the D3D device. Also release
12 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
the vertex and pixel shader buffers since they are no longer needed once the layout has been created. // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; The final thing that needs to be setup to utilize the shader is the constant buffer. As you saw in the vertex shader we currently have just one constant buffer so we only need to setup one here so we can interface with the shader. The buffer usage needs to be set to dynamic since we will be updating it each frame. The bind flags indicate that this buffer will be a constant buffer. The cpu access flags need to match up with the usage so it is set to D3D11_CPU_ACCESS_WRITE. Once we fill out the description we can then create the constant buffer interface and then use that to access the internal variables in the shader using the function SetShaderParameters. // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } return true; } ShutdownShader releases the four interfaces that were setup in the InitializeShader function. void ColorShaderClass::ShutdownShader() { // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release();
13 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; } The OutputShaderErrorMessage writes out error messages that are generating when compiling either vertex shaders or pixel shaders. void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } The SetShaderVariables function exists to make setting the global variables in the shader easier. The matrices used in this function are created inside the GraphicsClass, after which this function is called to send them from there into the vertex shader during the Render function call. bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; Make sure to transpose matrices before sending them into the shader, this is a requirement for DirectX 11. // Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
14 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); Lock the m_matrixBuffer, set the new matrices inside it, and then unlock it. // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); Now set the updated matrix buffer in the HLSL vertex shader. // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Finanly set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); return true; } RenderShader is the second function called in the Render function. SetShaderParameters is called before this to ensure the shader parameters are setup correctly. The first step in this function is to set our input layout to active in the input assembler. This lets the GPU know the format of the data in the vertex buffer. The second step is to set the vertex shader and pixel shader we will be using to render this vertex buffer. Once the shaders are set we render the triangle by calling the DrawIndexed DirectX 11 function using the D3D device context. Once this function is called it will render the green triangle. void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Cameraclass.h
We have examined how to code HLSL shaders, how to setup vertex and index buffers, and how to invoke the HLSL shaders to draw those buffers using the ColorShaderClass. The one thing we are missing however is the view point to draw them from. For this we will require a camera class to let DirectX 11 know from where and also how we are viewing the scene. The camera class will keep track of where the camera is and its current rotation. It will use the position and rotation information to generate a view matrix which will be passed into the HLSL shader for rendering.
15 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
//////////////////////////////////////////////////////////////////////////////// // Class name: CameraClass //////////////////////////////////////////////////////////////////////////////// class CameraClass { public: CameraClass(); CameraClass(const CameraClass&); ~CameraClass(); void SetPosition(float, float, float); void SetRotation(float, float, float); D3DXVECTOR3 GetPosition(); D3DXVECTOR3 GetRotation(); void Render(); void GetViewMatrix(D3DXMATRIX&); private: float m_positionX, m_positionY, m_positionZ; float m_rotationX, m_rotationY, m_rotationZ; D3DXMATRIX m_viewMatrix; }; #endif The CameraClass header is quite simple with just four functions that will be used. The SetPosition and SetRotation functions will be used to set the position and rotation of the camera object. Render will be used to create the view matrix based on the position and rotation of the camera. And finally GetViewMatrix will be used to retrieve the view matrix from the camera object so that the shaders can use it for rendering.
Cameraclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: cameraclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "cameraclass.h" The class constructor will initialize the position and rotation of the camera to be at the origin of the scene. CameraClass::CameraClass() { m_positionX = 0.0f; m_positionY = 0.0f; m_positionZ = 0.0f; m_rotationX = 0.0f; m_rotationY = 0.0f; m_rotationZ = 0.0f; }
16 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
CameraClass::~CameraClass() { } The SetPosition and SetRotation functions are used for setting up the position and rotation of the camera. void CameraClass::SetPosition(float x, float y, float z) { m_positionX = x; m_positionY = y; m_positionZ = z; return; }
void CameraClass::SetRotation(float x, float y, float z) { m_rotationX = x; m_rotationY = y; m_rotationZ = z; return; } The GetPosition and GetRotation functions return the location and rotation of the camera to calling functions. D3DXVECTOR3 CameraClass::GetPosition() { return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ); }
D3DXVECTOR3 CameraClass::GetRotation() { return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ); } The Render function uses the position and rotation of the camera to build and update the view matrix. We first setup our variables for up, position, rotation, and so forth. Then at the origin of the scene we first rotate the camera based on the x, y, and z rotation of the camera. Once it is properly rotated when then translate the camera to the position in 3D space. With the correct values in the position, lookAt, and up we can then use the D3DXMatrixLookAtLH function to create the view matrix to represent the current camera rotation and translation. void CameraClass::Render() { D3DXVECTOR3 up, position, lookAt; float yaw, pitch, roll; D3DXMATRIX rotationMatrix;
// Setup the vector that points upwards. up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; // Setup the position of the camera in the world. position.x = m_positionX; position.y = m_positionY; position.z = m_positionZ;
17 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
// Setup where the camera is looking by default. lookAt.x = 0.0f; lookAt.y = 0.0f; lookAt.z = 1.0f; // Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians. pitch = m_rotationX * 0.0174532925f; yaw = m_rotationY * 0.0174532925f; roll = m_rotationZ * 0.0174532925f; // Create the rotation matrix from the yaw, pitch, and roll values. D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll); // Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin. D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix); D3DXVec3TransformCoord(&up, &up, &rotationMatrix); // Translate the rotated camera position to the location of the viewer. lookAt = position + lookAt; // Finally create the view matrix from the three updated vectors. D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up); return; } After the Render function has been called to create the view matrix we can provide the update view matrix to calling functions using this GetViewMatrix function. The view matrix will be one of the three main matrices used in the HLSL vertex shader. void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix) { viewMatrix = m_viewMatrix; return; }
Graphicsclass.h
GraphicsClass now has the three new classes added to it. CameraClass, ModelClass, and ColorShaderClass have headers added here as well as private member variables. Remember that GraphicsClass is the main class that is used to render the scene by invoking all the needed class objects for the project. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "colorshaderclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
18 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; ColorShaderClass* m_ColorShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" The first change to GraphicsClass is initializing the camera, model, and color shader objects in the class constructor to null. GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_ColorShader = 0; } The Initialize function has also been updated to create and initialize the three new objects. bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result;
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NE if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
19 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice()); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the color shader object. m_ColorShader = new ColorShaderClass; if(!m_ColorShader) { return false; } // Initialize the color shader object. result = m_ColorShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK); return false; } return true; } Shutdown is also updated to shutdown and release the three new objects. void GraphicsClass::Shutdown() { // Release the color shader object. if(m_ColorShader) { m_ColorShader->Shutdown(); delete m_ColorShader; m_ColorShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; }
20 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
// Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the Direct3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } The Frame function has remained the same as the previous tutorial. bool GraphicsClass::Frame() { bool result;
// Render the graphics scene. result = Render(); if(!result) { return false; } return true; } As you would expect the Render function had the most changes to it. It still begins with clearing the scene except that it is cleared to black. After that it calls the Render function for the camera object to create a view matrix based on the camera's location that was set in the Initialize function. Once the view matrix is created we get a copy of it from the camera class. We also get copies of the world and projection matrix from the D3DClass object. We then call the ModelClass::Render function to put the green triangle model geometry on the graphics pipeline. With the vertices now prepared we call the color shader to draw the vertices using the model information and the three matrices for positioning each vertex. The green triangle is now drawn to the back buffer. With that the scene is complete and we call EndScene to display it to the screen. bool GraphicsClass::Render() { D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix; bool result;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the model using the color shader. result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMat if(!result)
21 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut04.html
{ return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
So in summary you should have learned the basics about how vertex and index buffers work. You should have also learned the basics of vertex and pixel shaders and how to write them using HLSL. And finally you should understand how we've incorporated these new concepts into our frame work to produce a green triangle that renders to the screen. I also want to mention that I realize the code is fairly long just to draw a single triangle and it could have all been stuck inside a single main() function. However I did it this way with a proper frame work so that the coming tutorials require very few changes in the code to do far more complex graphics.
To Do Exercises
1. Compile and run the tutorial. Ensure it draws a green triangle to the screen. Press escape to quit once it does. 2. Change the color of the triangle to red. 3. Change the triangle to a square. 4. Move the camera back 10 more units. 5. Change the pixel shader to ouput the color half as bright. (huge hint: multiply something in ColorPixelShader by 0.5f)
Source Code
Visual Studio 2010 Project: dx11tut04.zip Source Only: dx11src04.zip Executable Only: dx11exe04.zip
22 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
Tutorial 5: Texturing
This tutorial will explain how to use texturing in DirectX 11. Texturing allows us to add photorealism to our scenes by applying photographs and other images onto polygon faces. For example in this tutorial we will take the following image:
And then apply it to the polygon from the previous tutorial to produce the following:
The format of the textures we will be using are .dds files. This is the Direct Draw Surface format that DirectX uses. The tool used to produce .dds files comes with the DirectX SDK. It is under DirectX Utilities and is called DirectX Texture Tool. You can create a new texture of any size and format and then cut and paste your image or other format texture onto it and save it as a .dds file. It is very simple to use. And before we get into the code we should discuss how texture mapping works. To map pixels from the .dds image onto the polygon we use what is called the Texel Coordinate System. This system converts the integer value of the pixel into a floating point value between 0.0f and 1.0f. For example if a texture width is 256 pixels wide then the first pixel will map to 0.0f, the 256th pixel will map to 1.0f, and a middle pixel of 128 would map to 0.5f. In the texel coordinate system the width value is named "U" and the height value is named "V". The width goes from 0.0 on the left to 1.0 on the right. The height goes from 0.0 on the top to 1.0 on the bottom. For example top left would be denoted as U 0.0, V 0.0 and bottom right would be denoted as U 1.0, V 1.0. I have made a diagram below to illustrate this system:
Now that we have a basic understanding of how to map textures onto polygons we can look at the updated frame work for this tutorial:
1 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
The changes to the frame work since the previous tutorial is the new TextureClass which is inside ModelClass and the new TextureShaderClass which replaces the ColorShaderClass. We'll start the code section by looking at the new HLSL texture shaders first.
Texture.vs
The texture vertex shader is similar to the previous color shader except that there have been some changes to accommodate texturing. //////////////////////////////////////////////////////////////////////////////// // Filename: texture.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; We are no longer using color in our vertex type and have instead moved to using texture coordinates. Since texture coordinates take a U and V float coordinate we use float2 as its type. The semantic for texture coordinates is TEXCOORD0 for vertex shaders and pixel shaders. You can change the zero to any number to indicate which set of coordinates you are working with as multiple texture coordinates are allowed. ////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
2 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); The only difference in the texture vertex shader in comparison to the color vertex shader from the previous tutorial is that instead of taking a copy of the color from the input vertex we take a copy of the texture coordinates and pass them to the pixel shader. // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Texture.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: texture.ps //////////////////////////////////////////////////////////////////////////////// The texture pixel shader has two global variables. The first is Texture2D shaderTexture which is the texture resource. This will be our texture resource that will be used for rendering the texture on the model. The second new variable is the SamplerState SampleType. The sampler state allows us to modify how the pixels are written to the polygon face when shaded. For example if the polygon is really far away and only makes up 8 pixels on the screen then we use the sample state to figure out which pixels or what combination of pixels will actually be drawn from the original texture. The original texture may be 256 pixels by 256 pixels so deciding which pixels get drawn is really important to ensure that the texture still looks decent on the really small polygon face. We will setup the sampler state in the TextureShaderClass also and then attach it to the resource pointer so this pixel shader can use it to determine which sample of pixels to draw. ///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType; The PixelInputType for the texture pixel shader is also modified using texture coordinates instead of the color values. ////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; The pixel shader has been modified so that it now uses the HLSL sample function. The sample function uses the sampler state we defined above and the texture coordinates for this pixel. It uses these two variables to determine and return the pixel value for this UV location on the polygon face. //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 TexturePixelShader(PixelInputType input) : SV_TARGET { float4 textureColor;
// Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex);
3 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
return textureColor; }
Textureclass.h
The TextureClass encapsulates the loading, unloading, and accessing of a single texture resource. For each texture needed an object of this class must be instantiated. //////////////////////////////////////////////////////////////////////////////// // Filename: textureclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTURECLASS_H_ #define _TEXTURECLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: TextureClass //////////////////////////////////////////////////////////////////////////////// class TextureClass { public: TextureClass(); TextureClass(const TextureClass&); ~TextureClass(); The first two functions will load a texture from a given file name and unload that texture when it is no longer needed. bool Initialize(ID3D11Device*, WCHAR*); void Shutdown(); The GetTexture function returns a pointer to the texture resource so that it can be used for rendering by shaders. ID3D11ShaderResourceView* GetTexture(); private: This is the private texture resource. ID3D11ShaderResourceView* m_texture; }; #endif
Textureclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: textureclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "textureclass.h" The class constructor will initialize the texture shader resource pointer to null. TextureClass::TextureClass() { m_texture = 0;
4 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
TextureClass::~TextureClass() { } Initialize takes in the Direct3D device and file name of the texture and then loads the texture file into the shader resource variable called m_texture. The texture can now be used to render with. bool TextureClass::Initialize(ID3D11Device* device, WCHAR* filename) { HRESULT result;
// Load the texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename, NULL, NULL, &m_texture, NULL); if(FAILED(result)) { return false; } return true; } The Shutdown function releases the texture resource if it has been loaded and then sets the pointer to null. void TextureClass::Shutdown() { // Release the texture resource. if(m_texture) { m_texture->Release(); m_texture = 0; } return; } GetTexture is the function that is called by other objects that need access to the texture shader resource so that they can use the texture for rendering. ID3D11ShaderResourceView* TextureClass::GetTexture() { return m_texture; }
Modelclass.h
The ModelClass has had changes since the previous tutorial so that it can now accommodate texturing. //////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_
//////////////
5 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
// INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> The TextureClass header is now included in the ModelClass header. /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "textureclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private: The VertexType has replaced the color component with a texture coordinate component. The texture coordinate is now replacing the green color that was used in the previous tutorial. struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D11Device*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); The ModelClass also has a GetTexture function so it can pass its own texture resource to shaders that will draw this model. ID3D11ShaderResourceView* GetTexture(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); ModelClass has both a private LoadTexture and ReleaseTexture for loading and releasing the texture that will be used to render this model. bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; The m_Texture variable is used for loading, releasing, and accessing the texture resource for this model. TextureClass* m_Texture; }; #endif
6 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
ModelClass::ModelClass() { m_vertexBuffer = 0; m_indexBuffer = 0; The class constructor now initializes the new texture object to null. m_Texture = 0; }
ModelClass::~ModelClass() { } Initialize now takes as input the file name of the .dds texture that the model will be using. bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename) { bool result;
// Initialize the vertex and index buffer that hold the geometry for the triangle. result = InitializeBuffers(device); if(!result) { return false; } The Initialize function now calls a new private function that will load the texture. // Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; }
void ModelClass::Shutdown() { The Shutdown function now calls a new private function to release the texture object that was loaded during initialization. // Release the model texture. ReleaseTexture(); // Release the vertex and index buffers.
7 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
ShutdownBuffers(); return; }
void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return; }
int ModelClass::GetIndexCount() { return m_indexCount; } GetTexture returns the model texture resource. The texture shader will need access to this texture to render the model. ID3D11ShaderResourceView* ModelClass::GetTexture() { return m_Texture->GetTexture(); }
bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result;
// Set the number of vertices in the vertex array. m_vertexCount = 3; // Set the number of indices in the index array. m_indexCount = 3; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } The vertex array now has a texture component instead of a color component. The texture vector is always U first and V second. For example the first texture coordinate is bottom left of the triangle which corresponds to U 0.0, V 1.0. Use the diagram at the top of this page to figure out what your coordinates need to be. Note that you can change the coordinates to map any part of the texture to any part of the polygon face. In this tutorial I'm just doing a direct mapping for simplicity reasons. // Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left. vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);
8 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle. vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f); // Load the index array with data. indices[0] = 0; // Bottom left. indices[1] = 1; // Top middle. indices[2] = 2; // Bottom right. // Set up the description of the vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
9 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
// Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; } LoadTexture is a new private function that will create the texture object and then initialize it with the input file name provided. This function is called during initialization. bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename) { bool result;
// Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result) { return false; } return true; } The ReleaseTexture function will release the texture object that was created and loaded during the LoadTexture function. void ModelClass::ReleaseTexture() { // Release the texture object.
10 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
Textureshaderclass.h
The TextureShaderClass is just an updated version of the ColorShaderClass from the previous tutorial. This class will be used to draw the 3D models using vertex and pixel shaders. //////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTURESHADERCLASS_H_ #define _TEXTURESHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: TextureShaderClass //////////////////////////////////////////////////////////////////////////////// class TextureShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: TextureShaderClass(); TextureShaderClass(const TextureShaderClass&); ~TextureShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader;
11 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; There is a new private variable for the sampler state pointer. This pointer will be used to interface with the texture shader. ID3D11SamplerState* m_sampleState; }; #endif
Textureshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "textureshaderclass.h"
TextureShaderClass::TextureShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; The new sampler variable is set to null in the class constructor. m_sampleState = 0; }
TextureShaderClass::~TextureShaderClass() { }
bool TextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; The new texture.vs and texture.ps HLSL files are loaded for this shader. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/texture.vs", L"../Engine/texture.ps"); if(!result) { return false; }
return true; } The Shutdown function calls the release of the shader variables. void TextureShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects.
12 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
ShutdownShader(); return; } The Render function now takes a new parameter called texture which is the pointer to the texture resource. This is then sent into the SetShaderParameters function so that the texture can be set in the shader and then used for rendering. bool TextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewM D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; } InitializeShader sets up the texture shader. bool TextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; We have a new variable to hold the description of the texture sampler that will be setup in this function. D3D11_SAMPLER_DESC samplerDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load in the new texture vertex and pixel shaders. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICT &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); }
13 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
return false; } // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTN &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vert if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelSha if(FAILED(result)) { return false; } The input layout has changed as we now have a texture element instead of color. The first position element stays unchanged but the SemanticName and Format of the second element have been changed to TEXCOORD and DXGI_FORMAT_R32G32_FLOAT. These two changes will now align this layout with our new VertexType in both the ModelClass definition and the typedefs in the shader files. // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->Ge &m_layout); if(FAILED(result))
14 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
{ return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } The sampler state description is setup here and then can be passed to the pixel shader after. The most important element of the texture sampler description is Filter. Filter will determine how it decides which pixels will be used or combined to create the final look of the texture on the polygon face. In the example here I use D3D11_FILTER_MIN_MAG_MIP_LINEAR which is more expensive in terms of processing but gives the best visual result. It tells the sampler to use linear interpolation for minification, magnification, and mip-level sampling. AddressU and AddressV are set to Wrap which ensures that the coordinates stay between 0.0f and 1.0f. Anything outside of that wraps around and is placed between 0.0f and 1.0f. All other settings for the sampler state description are defaults. // 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, &m_sampleState); if(FAILED(result)) { return false; } return true; } The ShutdownShader function releases all the variables used in the TextureShaderClass. void TextureShaderClass::ShutdownShader() { The ShutdownShader function now releases the new sampler state that was created during initialization.
15 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
// Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; } OutputShaderErrorMessage writes out errors to a text file if the HLSL shader could not be loaded. void TextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message.
16 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } SetShaderParameters function now takes in a pointer to a texture resource and then assigns it to the shader using the new texture resource pointer. Note that the texture has to be set before rendering of the buffer occurs. bool TextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewM D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); The SetShaderParameters function has been modified from the previous tutorial to include setting the texture in the pixel shader now. // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); return true; } RenderShader calls the shader technique to render the polygons. void TextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle.
17 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); The RenderShader function has been changed to include setting the sample state in the pixel shader before rendering. // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" The GraphicsClass now includes the new TextureShaderClass header and the ColorShaderClass header has been removed. #include "textureshaderclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera;
18 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
ModelClass* m_Model; A new TextureShaderClass private object has been added. TextureShaderClass* m_TextureShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" The m_TextureShader variable is set to null in the constructor. GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_TextureShader = 0; }
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NE if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; }
19 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
// Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } The ModelClass::Initialize function now takes in the name of the texture that will be used for rendering the model. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } The new TextureShaderClass object is created and initialized. // Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { The TextureShaderClass object is also released in the Shutdown function. // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; }
20 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
// Release the Direct3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
// Render the graphics scene. result = Render(); if(!result) { return false; } return true; }
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the view, projection, and world matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetWorldMatrix(worldMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDevice()); The texture shader is called now instead of the color shader to render the model. Notice it also takes the texture resource pointer from the model so the texture shader has access to the texture from the model object. // Render the model using the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionM m_Model->GetTexture()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
21 of 22
3/8/2013 12:15 PM
Tutorial 5: Texturing
http://www.rastertek.com/dx11tut05.html
Summary
You should now understand the basics of loading a texture, mapping it to a polygon face, and then rendering it with a shader.
To Do Exercises
1. Re-compile the code and ensure that a texture mapped triangle does appear on your screen. Press escape to quit once done. 2. Create your own dds texture and place it in the same directory with seafloor.dds. Inside the GraphicsClass::Initialize function change the model initialization to have your texture name and then re-compile and run the program. 3. Change the code to create two triangles that form a square. Map the entire texture to this square so that the entire texture shows up correctly on the screen. 4. Move the camera to different distances to see the effect of the MIN_MAG_MIP_LINEAR filter. 5. Try some of the other filters and move the camera to different distances to see the different results.
Source Code
Visual Studio 2010 Project: dx11tut05.zip Source Only: dx11src05.zip Executable Only: dx11exe05.zip
22 of 22
3/8/2013 12:15 PM
http://www.rastertek.com/dx11tut06.html
Framework
For this tutorial we will create a new class called LightClass which will represent the light sources in the scenes. LightClass won't actually do anything other than hold the direction and color of the light. We will also remove the TextureShaderClass and replace it with LightShaderClass which handles the light shading on the model. With the addition of the new classes the frame work now looks like the following:
We will start the code section by looking at the HLSL light shader. You will notice that the light shader is just an updated version of the texture shader from the previous tutorial.
1 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
Light.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: light.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; Both structures now have a 3 float normal vector. The normal vector is used for calculating the amount of light by using the angle between the direction of the normal and the direction of the light. ////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; };
2 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; The normal vector for this vertex is calculated in world space and then normalized before being sent as input into the pixel shader. // Calculate the normal vector against the world matrix only. output.normal = mul(input.normal, (float3x3)worldMatrix); // Normalize the normal vector. output.normal = normalize(output.normal); return output; }
Light.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: light.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType; We have two new global variables inside the LightBuffer that hold the diffuse color and the direction of the light. These two variables will be set from values in the new LightClass object. cbuffer LightBuffer {
3 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 LightPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float3 lightDir; float lightIntensity; float4 color;
// Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); This is where the lighting equation that was discussed earlier is now implemented. The light intensity value is calculated as the dot product between the normal vector of triangle and the light direction vector. // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel. lightIntensity = saturate(dot(input.normal, lightDir)); And finally the diffuse value of the light is combined with the texture pixel value to produce the color result. // Determine the final amount of diffuse color based on the diffuse color combined with the light intensity. color = saturate(diffuseColor * lightIntensity);
4 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// Multiply the texture pixel and the final diffuse color to get the final pixel color result. color = color * textureColor; return color; }
Lightshaderclass.h
The new LightShaderClass is just the TextureShaderClass from the previous tutorials re-written slightly to incorporate lighting. //////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTSHADERCLASS_H_ #define _LIGHTSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: LightShaderClass //////////////////////////////////////////////////////////////////////////////// class LightShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; The new LightBufferType structure will be used for holding lighting information. This typedef is the same as the new typedef in the pixel shader. Do note that I add a extra float for size padding to ensure the structure is a multiple of 16. Since the structure without an extra float is only 28 bytes CreateBuffer would have failed if we
5 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
used a sizeof(LightBufferType) because it requires sizes that are a multiple of 16 to succeed. struct LightBufferType { D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float padding; // Added extra padding so structure is a multiple of 16 for CreateBuffer function requirements. }; public: LightShaderClass(); LightShaderClass(const LightShaderClass&); ~LightShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECT void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer; There is a new private constant buffer for the light information (color and direction). The light buffer will be used by this class to set the global light variables inside the HLSL pixel shader. ID3D11Buffer* m_lightBuffer; }; #endif
Lightshaderclass.cpp
6 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
LightShaderClass::LightShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_sampleState = 0; m_matrixBuffer = 0; Set the light constant buffer to null in the class constructor. m_lightBuffer = 0; }
LightShaderClass::~LightShaderClass() { }
bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; The new light.vs and light.ps HLSL shader files are used as input to initialize the light shader. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps"); if(!result) { return false; } return true; }
7 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
void LightShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes in the light direction and light diffuse color as inputs. These variables are then sent into the SetShaderParameters function and finally set inside the shader itself. bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, diffuseColor); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; The polygonLayout variable has been changed to have three elements instead of two. This is so that it can accommodate a normal vector in the layout. D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc;
8 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
D3D11_BUFFER_DESC matrixBufferDesc; We also add a new description variable for the light constant buffer. D3D11_BUFFER_DESC lightBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load in the new light vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Load in the new light pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); }
9 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; One of the major changes to the shader initialization is here in the polygonLayout. We add a third element for the normal vector that will be used for lighting. The semantic name is NORMAL and the format is the regular DXGI_FORMAT_R32G32B32_FLOAT which handles 3 floats for the x, y, and z of the normal vector. The layout will now match the expected input to the HLSL vertex shader.
10 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
polygonLayout[2].SemanticName = "NORMAL"; polygonLayout[2].SemanticIndex = 0; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 0; polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // 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, &m_sampleState); if(FAILED(result)) {
11 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
return false; } // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } Here we setup the light constant buffer description which will handle the diffuse light color and light direction. Pay attention to the size of the constant buffers, if they are not multiples of 16 you need to pad extra space on to the end of them or the CreateBuffer function will fail. In this case the constant buffer is 28 bytes with 4 bytes padding to make it 32. // Setup the description of the light dynamic constant buffer that is in the pixel shader. // Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; } return true; }
void LightShaderClass::ShutdownShader() {
12 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
The new light constant buffer is released in the ShutdownShader function. // Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return;
13 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } The SetShaderParameters function now takes in lightDirection and diffuseColor as inputs. bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { HRESULT result;
14 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); The light constant buffer is setup the same way as the matrix constant buffer. We first lock the buffer and get a pointer to it. After that we set the diffuse color and light direction using that pointer. Once the data is set we unlock the buffer and then set it in the pixel shader. Note that we use the PSSetConstantBuffers function instead of VSSetConstantBuffers since this is a pixel shader buffer we are setting. // Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result))
15 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
{ return false; } // Get a pointer to the data in the constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData; // Copy the lighting variables into the constant buffer. dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; dataPtr2->padding = 0.0f; // Unlock the constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer); return true; }
void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
16 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
Modelclass.h
The ModelClass has been slightly modified to handle lighting components. //////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private: The VertexType structure now has a normal vector to accommodate lighting. struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; D3DXVECTOR3 normal; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass();
17 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
bool Initialize(ID3D11Device*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView* GetTexture();
private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; TextureClass* m_Texture; }; #endif
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
18 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
{ }
ModelClass::~ModelClass() { }
// Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } // Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; }
void ModelClass::Shutdown() { // Release the model texture. ReleaseTexture(); // Shutdown the vertex and index buffers. ShutdownBuffers(); return; }
19 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
{ // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return; }
bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result;
// Set the number of vertices in the vertex array. m_vertexCount = 3; // Set the number of indices in the index array. m_indexCount = 3; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices)
20 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
{ return false; } The only change to the InitializeBuffers function is here in the vertex setup. Each vertex now has normals associated with it for lighting calculations. The normal is a line that is perpendicular to the face of the polygon so that the exact direction the face is pointing can be calculated. For simplicity purposes I set the normal for each vertex along the Z axis by setting each Z component to -1.0f which makes the normal point towards the viewer. // Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left. vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f); vertices[0].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle. vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f); vertices[1].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f); vertices[2].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); // Load the index array with data. indices[0] = 0; // Bottom left. indices[1] = 1; // Top middle. indices[2] = 2; // Bottom right. // Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; }
21 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
void ModelClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release();
22 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
m_vertexBuffer = 0; } return; }
// Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
// Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result)
23 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
void ModelClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
Lightclass.h
Now we will look at the new light class which is very simple. Its purpose is only to maintain the direction and color of lights. //////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTCLASS_H_ #define _LIGHTCLASS_H_
24 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
{ public: LightClass(); LightClass(const LightClass&); ~LightClass(); void SetDiffuseColor(float, float, float, float); void SetDirection(float, float, float); D3DXVECTOR4 GetDiffuseColor(); D3DXVECTOR3 GetDirection(); private: D3DXVECTOR4 m_diffuseColor; D3DXVECTOR3 m_direction; }; #endif
Lightclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightclass.h"
LightClass::LightClass() { }
LightClass::~LightClass() { }
25 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha) { m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha); return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" The GraphicsClass now has two new includes for the LightShaderClass and the LightClass.
26 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: Render now takes a float value as input. bool Render(float); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; There are two new private variables for the light shader and the light object. LightShaderClass* m_LightShader; LightClass* m_Light; }; #endif
27 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; The light shader and light object are set to null in the class constructor. m_LightShader = 0; m_Light = 0; }
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; }
28 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } The new light shader object is created and initialized here. // Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) { return false; } // Initialize the light shader object. result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
29 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
if(!result) { MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK); return false; } The new light object is created here. // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } The color of the light is set to purple and the light direction is set to point down the positive Z axis. // Initialize the light object. m_Light->SetDiffuseColor(1.0f, 0.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f); return true; }
void GraphicsClass::Shutdown() { The Shutdown function releases the new light and light shader objects. // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown(); delete m_LightShader; m_LightShader = 0; }
30 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
bool GraphicsClass::Frame() { bool result; We add a new static variable to hold an updated rotation value each frame that will be passed into the Render function. static float rotation = 0.0f;
// Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.01f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene. result = Render(rotation);
31 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); Here we rotate the world matrix by the rotation value so that when we render the triangle using this updated world matrix it will spin the triangle by the rotation amount. // Rotate the world matrix by the rotation value so that the triangle will spin. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); The light shader is called here to render the triangle. The new light object is used to send the diffuse light color and light direction into the Render function so that the shader has access to those values. // Render the model using the light shader. result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor()); if(!result) { return false; }
32 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
Summary
With a few changes to the code we were able to implement some basic directional lighting. Make sure you understand how normal vectors work and why they are important to calculating lighting on polygon faces. Note that the back of the spinning triangle will not light up since we have back face culling enabled in our D3DClass.
To Do Exercises
1. Recompile the project and ensure you get a spinning textured triangle that is being illuminated by a purple light. Press escape to quit. 2. Comment out "color = color * textureColor;" in the pixel shader so that the shaderTexture is no longer used and you should see the lighting effect without the texture. 3. Change the color of the light to green at the m_Light->SetDiffuseColor line of code in the GraphicsClass. 4. Change the direction of the light to go down the positive and the negative X axis. You might want to change the speed of the rotation also.
33 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut06.html
Source Code
Visual Studio 2010 Project: dx11tut06.zip Source Only: dx11src06.zip Executable Only: dx11exe06.zip
34 of 34
3/8/2013 12:27 PM
http://www.rastertek.com/dx11tut07.html
Cube.txt
Vertex Count: 36 Data: -1.0 1.0 -1.0 0.0 0.0 1.0 1.0 -1.0 1.0 0.0 -1.0 -1.0 -1.0 0.0 1.0 -1.0 -1.0 -1.0 0.0 1.0 1.0 1.0 -1.0 1.0 0.0 1.0 -1.0 -1.0 1.0 1.0 1.0 1.0 -1.0 0.0 0.0 1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 -1.0 0.0 -1.0 0.0 -1.0 0.0 -1.0 0.0 -1.0 0.0 -1.0 0.0 0.0 0.0 0.0
1 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
1.0 -1.0 -1.0 0.0 1.0 1.0 0.0 1.0 -1.0 -1.0 0.0 1.0 1.0 0.0 1.0 1.0 1.0 1.0 0.0 1.0 0.0 1.0 -1.0 1.0 1.0 1.0 1.0 0.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 -1.0 1.0 1.0 1.0 0.0 0.0 0.0 1.0 -1.0 1.0 0.0 1.0 0.0 0.0 1.0 -1.0 1.0 0.0 1.0 0.0 0.0 -1.0 1.0 1.0 1.0 0.0 0.0 0.0 -1.0 -1.0 1.0 1.0 1.0 0.0 0.0 -1.0 1.0 1.0 0.0 0.0 -1.0 0.0 -1.0 1.0 -1.0 1.0 0.0 -1.0 0.0 -1.0 -1.0 1.0 0.0 1.0 -1.0 0.0 -1.0 -1.0 1.0 0.0 1.0 -1.0 0.0 -1.0 1.0 -1.0 1.0 0.0 -1.0 0.0 -1.0 -1.0 -1.0 1.0 1.0 -1.0 0.0 -1.0 1.0 1.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0 1.0 0.0 0.0 1.0 -1.0 1.0 -1.0 0.0 1.0 0.0 1.0 -1.0 1.0 -1.0 0.0 1.0 0.0 1.0 1.0 1.0 1.0 1.0 0.0 0.0 1.0 1.0 1.0 -1.0 1.0 1.0 0.0 1.0 -1.0 -1.0 -1.0 0.0 0.0 0.0 -1.0 1.0 -1.0 -1.0 1.0 0.0 0.0 -1.0 -1.0 -1.0 1.0 0.0 1.0 0.0 -1.0 -1.0 -1.0 1.0 0.0 1.0 0.0 -1.0 1.0 -1.0 -1.0 1.0 0.0 0.0 -1.0 1.0 -1.0 1.0 1.0 1.0 0.0 -1.0
0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
So as you can see there are 36 lines of x, y, z, tu, tv, nx, ny, nz data. Every three lines composes its own triangle giving us 12 triangles that will form a cube. The format is very straight forward and can be read directly into our vertex buffers and rendered without any modifications. Now one thing to watch out for is that some 3D modeling programs export the data in different orders such as left hand or right hand coordinate systems. Remember that by default DirectX 11 is a left-handed coordinate system by default and so the model data needs to match that. Keep an eye out for those differences and ensure your parsing program can handle converting data into the correct format/order.
Modelclass.h
For this tutorial all we needed to do was make some minor changes to the ModelClass for it to render 3D models from our text model files. //////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h
2 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> The fstream library is now included to handle reading from the model text file. #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private: struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; D3DXVECTOR3 normal; }; The next change is the addition of a new structure to represent the model format. It is called ModelType. It contains position, texture, and normal vectors the same as our file format does. struct ModelType { float x, y, z; float tu, tv; float nx, ny, nz; };
3 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); The Initialize function will now take as input the character string file name of the model to be loaded. bool Initialize(ID3D11Device*, char*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView* GetTexture();
private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); We also have two new functions to handle loading and unloading the model data from the text file. bool LoadModel(char*); void ReleaseModel(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; TextureClass* m_Texture; The final change is a new private variable called m_model which is going to be an array of the new private structure ModelType. This variable will be used to read in and hold the model data before it is placed in the vertex buffer. ModelType* m_model; }; #endif
4 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
ModelClass::ModelClass() { m_vertexBuffer = 0; m_indexBuffer = 0; m_Texture = 0; The new model structure is set to null in the class constructor. m_model = 0; }
ModelClass::~ModelClass() { } The Initialize function now takes as input the file name of the model that should be loaded. bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename) { bool result; In the Initialize function we now call the new LoadModel function first. It will load the model data from the file name we provide into the new m_model array. Once this model array is filled we can then build the vertex and index buffers from it. Since InitializeBuffers now depends on this model data you have to make sure to call the functions in the correct order. // Load in the model data, result = LoadModel(modelFilename); if(!result) { return false;
5 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
} // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } // Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; }
void ModelClass::Shutdown() { // Release the model texture. ReleaseTexture(); // Shutdown the vertex and index buffers. ShutdownBuffers(); In the Shutdown function we add a call to the ReleaseModel function to delete the m_model array data once we are done. // Release the model data. ReleaseModel(); return; }
void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return; }
6 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i; Take note that we will no longer manually set the vertex and index count here. Once we get to the ModelClass::LoadModel function you will see that we read the vertex and index counts in at that point instead. // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } Loading the vertex and index arrays has changed a bit. Instead of setting the values manually we loop through all the elements in the new m_model array and copy that data from there into the vertex array. The index array is easy to build as each vertex we load has the same index number as the position in the array it was loaded into. // Load the vertex array and index array with data. for(i=0; i<m_vertexCount; i++)
7 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
{ vertices[i].position = D3DXVECTOR3(m_model[i].x, m_model[i].y, m_model[i].z); vertices[i].texture = D3DXVECTOR2(m_model[i].tu, m_model[i].tv); vertices[i].normal = D3DXVECTOR3(m_model[i].nx, m_model[i].ny, m_model[i].nz); indices[i] = i; } // Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) {
8 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
void ModelClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; }
// Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered.
9 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
// Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result) { return false; } return true; }
void ModelClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; }
10 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
return; } This is the new LoadModel function which handles loading the model data from the text file into the m_model array variable. It opens the text file and reads in the vertex count first. After reading the vertex count it creates the ModelType array and then reads each line into the array. Both the vertex count and index count are now set in this function. bool ModelClass::LoadModel(char* filename) { ifstream fin; char input; int i;
// Open the model file. fin.open(filename); // If it could not open the file then exit. if(fin.fail()) { return false; } // Read up to the value of vertex count. fin.get(input); while(input != ':') { fin.get(input); } // Read in the vertex count. fin >> m_vertexCount; // Set the number of indices to be the same as the vertex count. m_indexCount = m_vertexCount; // Create the model using the vertex count that was read in. m_model = new ModelType[m_vertexCount]; if(!m_model) { return false; } // Read up to the beginning of the data. fin.get(input);
11 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
while(input != ':') { fin.get(input); } fin.get(input); fin.get(input); // Read in the vertex data. for(i=0; i<m_vertexCount; i++) { fin >> m_model[i].x >> m_model[i].y >> m_model[i].z; fin >> m_model[i].tu >> m_model[i].tv; fin >> m_model[i].nx >> m_model[i].ny >> m_model[i].nz; } // Close the model file. fin.close(); return true; } The ReleaseModel function handles deleting the model data array. void ModelClass::ReleaseModel() { if(m_model) { delete [] m_model; m_model = 0; } return; }
Graphicsclass.h
The header for the GraphicsClass has not changed since the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_
12 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
#define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "lightshaderclass.h" #include "lightclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(float); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; LightShaderClass* m_LightShader; LightClass* m_Light;
13 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
}; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false;
14 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
} // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } The model initialization now takes in the filename of the model file it is loading. In this tutorial we will use the cube.txt file so this model loads in a 3D cube object for rendering. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) { return false; }
15 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
// Initialize the light shader object. result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } I have changed the diffuse light color to white for this tutorial. // Initialize the light object. m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f); return true; }
void GraphicsClass::Shutdown() { // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown(); delete m_LightShader; m_LightShader = 0; } // Release the model object. if(m_Model) {
16 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
// Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.01f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene. result = Render(rotation); if(!result) { return false; } return true;
17 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Rotate the world matrix by the rotation value so that the triangle will spin. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the model using the light shader. result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
18 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut07.html
With the changes to the ModelClass we can now load in 3D models and render them. The format used here is just for basic static objects with lighting, however it is a good start to understanding how model formats work.
To Do Exercises
1. Recompile the code and run the program. You should get a rotating cube with the same seafloor.dds texture on it. Press escape to quit once done. 2. Find a decent 3D modeling package (hopefully something free) and create your own simple models and export them. Start looking at the format. 3. Write a simple parser program that takes the model exports and converts it to the format used here. Replace cube.txt with your model and run the program.
Source Code
Visual Studio 2010 Project: dx11tut07.zip Source Only: dx11src07.zip Executable Only: dx11exe07.zip
19 of 19
3/8/2013 12:28 PM
http://www.rastertek.com/dx11tut08.html
Cube.obj
# This file uses centimeters as units for non-parametric coordinates. mtllib cube.mtl g default v -0.500000 -0.500000 0.500000 v 0.500000 -0.500000 0.500000 v -0.500000 0.500000 0.500000 v 0.500000 0.500000 0.500000 v -0.500000 0.500000 -0.500000 v 0.500000 0.500000 -0.500000 v -0.500000 -0.500000 -0.500000 v 0.500000 -0.500000 -0.500000 vt 0.001992 0.001992 vt 0.998008 0.001992 vt 0.001992 0.998008 vt 0.998008 0.998008 vt 0.001992 0.001992 vt 0.998008 0.001992 vt 0.001992 0.998008 vt 0.998008 0.998008 vt 0.001992 0.001992 vt 0.998008 0.001992
1 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
vt 0.001992 0.998008 vt 0.998008 0.998008 vt 0.001992 0.001992 vt 0.998008 0.001992 vt 0.001992 0.998008 vt 0.998008 0.998008 vt 0.001992 0.001992 vt 0.998008 0.001992 vt 0.001992 0.998008 vt 0.998008 0.998008 vt 0.998008 0.998008 vt 0.001992 0.998008 vt 0.998008 0.001992 vt 0.001992 0.001992 vn 0.000000 0.000000 1.000000 vn 0.000000 0.000000 1.000000 vn 0.000000 0.000000 1.000000 vn 0.000000 0.000000 1.000000 vn 0.000000 1.000000 0.000000 vn 0.000000 1.000000 0.000000 vn 0.000000 1.000000 0.000000 vn 0.000000 1.000000 0.000000 vn 0.000000 0.000000 -1.000000 vn 0.000000 0.000000 -1.000000 vn 0.000000 0.000000 -1.000000 vn 0.000000 0.000000 -1.000000 vn 0.000000 -1.000000 0.000000 vn 0.000000 -1.000000 0.000000 vn 0.000000 -1.000000 0.000000 vn 0.000000 -1.000000 0.000000 vn 1.000000 0.000000 0.000000 vn 1.000000 0.000000 0.000000 vn 1.000000 0.000000 0.000000 vn 1.000000 0.000000 0.000000 vn -1.000000 0.000000 0.000000 vn -1.000000 0.000000 0.000000 vn -1.000000 0.000000 0.000000 vn -1.000000 0.000000 0.000000 s1 g pCube1 usemtl file1SG f 1/1/1 2/2/2 3/3/3 f 3/3/3 2/2/2 4/4/4 s2 f 3/13/5 4/14/6 5/15/7
2 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
f 5/15/7 4/14/6 6/16/8 s3 f 5/21/9 6/22/10 7/23/11 f 7/23/11 6/22/10 8/24/12 s4 f 7/17/13 8/18/14 1/19/15 f 1/19/15 8/18/14 2/20/16 s5 f 2/5/17 8/6/18 4/7/19 f 4/7/19 8/6/18 6/8/20 s6 f 7/9/21 1/10/22 5/11/23 f 5/11/23 1/10/22 3/12/24 This particular .OBJ model file represents a 3D cube. It has 8 vertices, 24 texture coordinates and normal vectors, and 6 sides made up of 12 faces in total. When examining the file you can ignore every line unless it starts with a "V", "VT", "VN", or "F". The extra information in the file will not be needed for converting .obj to our file format. Lets look at what each of the important lines means: 1. The "V" lines are for the vertices. The cube is made up of 8 vertices for the eight corners of the cube. Each is listed in X, Y, Z float format. 2. The "VT" lines are for the texture coordinates. The cube is has 24 texture coordinates and most of them are duplicated since it records them for every vertex in every triangle in the cube model. They are listed in TU, TV float format. 3. The "VN" lines are for the normal vectors. The cube is has 24 normal vectors and most of them are duplicated again since it records them for every vertex in every triangle in the cube model. They are listed in NX, NY, NZ float format. 4. The "F" lines are for each triangle (face) in the cube model. The values listed are indexes into the vertices, texture coordinates, and normal vectors. The format of each face is: f Vertex1/Texture1/Normal1 Vertex2/Texture2/Normal2 Vertex3/Texture3/Normal3 So a line that says "f 3/13/5 4/14/6 5/15/7" then translates to "Vertex3/Texture13/Normal5 Vertex4/Texture14/Normal6 Vertex5/Texture15/Normal7". The order the data is listed in the .obj file is very important. For example the first vertex in the file corresponds to Vertex1 in the face list. This is the same for texture coordinates and normals as well. Looking at the face lines in the .obj file notice that the three index groups per line make an individual triangle. And in the case of this cube model the 12 total faces make up the 6 sides of the cube that has 2 triangles per side.
3 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
which DirectX 11 is by default you have to do the following: 1. Invert the Z coordinate vertices. In the code you will see it do this: vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f; 2. Invert the TV texture coordinate. In the code you will see it do this: texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y; 3. Invert the NZ normal vertex. In the code you will see it do this: normals[normalIndex].z = normals[normalIndex].z * -1.0f; 4. Convert the drawing order from counter clockwise to clockwise. In the code I simply read in the indexes in reverse order instead of re-organizing it after the fact: fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3; fin >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2; fin >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1; With those four steps complete the model data will be ready for DirectX 11 to render correctly.
Main.cpp
The program to convert the Maya 2011 .obj files into our DirectX 11 format is fairly simple and is a single program file called main.cpp. It opens a command prompt and asks for the name of the .obj file to convert. Once the user types in the name it will attempt to open the file and read in the data counts and build the structures required to read the data into. After that it reads the data into those structures and converts it to a left hand system. Once that is done it then writes the data out to a model.txt file. That file can then be renamed and used for rendering in DirectX 11 using the 3D model render project from the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: main.cpp ////////////////////////////////////////////////////////////////////////////////
////////////// // INCLUDES // ////////////// #include <iostream> #include <fstream> using namespace std;
4 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
}VertexType; typedef struct { int vIndex1, vIndex2, vIndex3; int tIndex1, tIndex2, tIndex3; int nIndex1, nIndex2, nIndex3; }FaceType;
///////////////////////// // FUNCTION PROTOTYPES // ///////////////////////// void GetModelFilename(char*); bool ReadFileCounts(char*, int&, int&, int&, int&); bool LoadDataStructures(char*, int, int, int, int);
////////////////// // MAIN PROGRAM // ////////////////// int main() { bool result; char filename[256]; int vertexCount, textureCount, normalCount, faceCount; char garbage;
// Read in the name of the model file. GetModelFilename(filename); // Read in the number of vertices, tex coords, normals, and faces so that the data structures can be initialized with the exact sizes needed. result = ReadFileCounts(filename, vertexCount, textureCount, normalCount, faceCount); if(!result) { return -1; } // Display the counts to the screen for information purposes. cout << endl; cout << "Vertices: " << vertexCount << endl; cout << "UVs: " << textureCount << endl; cout << "Normals: " << normalCount << endl; cout << "Faces: " << faceCount << endl;
5 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
// Now read the data from the file into the data structures and then output it in our model format. result = LoadDataStructures(filename, vertexCount, textureCount, normalCount, faceCount); if(!result) { return -1; } // Notify the user the model has been converted. cout << "\nFile has been converted." << endl; cout << "\nDo you wish to exit (y/n)? "; cin >> garbage; return 0; }
// Loop until we have a file name. done = false; while(!done) { // Ask the user for the filename. cout << "Enter model filename: "; // Read in the filename. cin >> filename; // Attempt to open the file. fin.open(filename); if(fin.good()) { // If the file exists and there are no problems then exit since we have the file name. done = true; } else { // If the file does not exist or there was an issue opening it then notify the user and repeat the process. fin.clear();
6 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
cout << endl; cout << "File " << filename << " could not be opened." << endl << endl; } } return; }
bool ReadFileCounts(char* filename, int& vertexCount, int& textureCount, int& normalCount, int& faceCount) { ifstream fin; char input;
// Initialize the counts. vertexCount = 0; textureCount = 0; normalCount = 0; faceCount = 0; // Open the file. fin.open(filename); // Check if it was successful in opening the file. if(fin.fail() == true) { return false; } // Read from the file and continue to read until the end of the file is reached. fin.get(input); while(!fin.eof()) { // If the line starts with 'v' then count either the vertex, the texture coordinates, or the normal vector. if(input == 'v') { fin.get(input); if(input == ' ') { vertexCount++; } if(input == 't') { textureCount++; } if(input == 'n') { normalCount++; } } // If the line starts with 'f' then increment the face count. if(input == 'f')
7 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
{ fin.get(input); if(input == ' ') { faceCount++; } } // Otherwise read in the remainder of the line. while(input != '\n') { fin.get(input); } // Start reading the beginning of the next line. fin.get(input); } // Close the file. fin.close(); return true; }
bool LoadDataStructures(char* filename, int vertexCount, int textureCount, int normalCount, int faceCount) { VertexType *vertices, *texcoords, *normals; FaceType *faces; ifstream fin; int vertexIndex, texcoordIndex, normalIndex, faceIndex, vIndex, tIndex, nIndex; char input, input2; ofstream fout;
// Initialize the four data structures. vertices = new VertexType[vertexCount]; if(!vertices) { return false; } texcoords = new VertexType[textureCount]; if(!texcoords) { return false; }
8 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
normals = new VertexType[normalCount]; if(!normals) { return false; } faces = new FaceType[faceCount]; if(!faces) { return false; } // Initialize the indexes. vertexIndex = 0; texcoordIndex = 0; normalIndex = 0; faceIndex = 0; // Open the file. fin.open(filename); // Check if it was successful in opening the file. if(fin.fail() == true) { return false; } // Read in the vertices, texture coordinates, and normals into the data structures. // Important: Also convert to left hand coordinate system since Maya uses right hand coordinate system. fin.get(input); while(!fin.eof()) { if(input == 'v') { fin.get(input); // Read in the vertices. if(input == ' ') { fin >> vertices[vertexIndex].x >> vertices[vertexIndex].y >> vertices[vertexIndex].z; // Invert the Z vertex to change to left hand system. vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f; vertexIndex++; }
9 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
// Read in the texture uv coordinates. if(input == 't') { fin >> texcoords[texcoordIndex].x >> texcoords[texcoordIndex].y; // Invert the V texture coordinates to left hand system. texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y; texcoordIndex++; } // Read in the normals. if(input == 'n') { fin >> normals[normalIndex].x >> normals[normalIndex].y >> normals[normalIndex].z; // Invert the Z normal to change to left hand system. normals[normalIndex].z = normals[normalIndex].z * -1.0f; normalIndex++; } } // Read in the faces. if(input == 'f') { fin.get(input); if(input == ' ') { // Read the face data in backwards to convert it to a left hand system from right hand system. fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3 >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2 >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1; faceIndex++; } } // Read in the remainder of the line. while(input != '\n') { fin.get(input); } // Start reading the beginning of the next line. fin.get(input); }
10 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
// Close the file. fin.close(); // Open the output file. fout.open("model.txt"); // Write out the file header that our model format uses. fout << "Vertex Count: " << (faceCount * 3) << endl; fout << endl; fout << "Data:" << endl; fout << endl; // Now loop through all the faces and output the three vertices for each face. for(int i=0; i<faceIndex; i++) { vIndex = faces[i].vIndex1 - 1; tIndex = faces[i].tIndex1 - 1; nIndex = faces[i].nIndex1 - 1; fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' ' << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' ' << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl; vIndex = faces[i].vIndex2 - 1; tIndex = faces[i].tIndex2 - 1; nIndex = faces[i].nIndex2 - 1; fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' ' << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' ' << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl; vIndex = faces[i].vIndex3 - 1; tIndex = faces[i].tIndex3 - 1; nIndex = faces[i].nIndex3 - 1; fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' ' << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' ' << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl; } // Close the output file. fout.close(); // Release the four data structures.
11 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
if(vertices) { delete [] vertices; vertices = 0; } if(texcoords) { delete [] texcoords; texcoords = 0; } if(normals) { delete [] normals; normals = 0; } if(faces) { delete [] faces; faces = 0; } return true; }
Summary
We can now convert Maya 2011 .obj files into our simple model format.
12 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
To Do Exercises
1. Recompile the program and run it with the supplied .obj model file. 2. Create (or have someone else make for you) a Maya 2011 model and export it in .obj format and run this program to convert it. 3. Convert this code to read in and export a different model format that you might prefer.
Source Code
Visual Studio 2010 Project: dx11tut08.zip Source Only: dx11src08.zip Executable Only: dx11exe08.zip
13 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut08.html
14 of 14
3/8/2013 12:29 PM
http://www.rastertek.com/dx11tut09.html
The image produced doesn't look realistic because ambient light is almost always everywhere giving everything their proper shape even if it is only slightly illuminated. Now if we just add 15% ambient white light to the same scene we get the following image instead:
1 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
This now gives us a more realistic lighting effect that we as humans are used to. We will now look at the changes to the code to implement ambient lighting. This tutorial is built on the previous tutorials that used diffuse lighting. We will now add the ambient component with just a few changes.
Light.vs
The light shader is just the diffuse light shader updated from the previous tutorials. The vertex shader has no code changes to it, only the pixel shader was modified. //////////////////////////////////////////////////////////////////////////////// // Filename: light.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
//////////////
2 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
// TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; // Calculate the normal vector against the world matrix only. output.normal = mul(input.normal, (float3x3)worldMatrix); // Normalize the normal vector. output.normal = normalize(output.normal); return output; }
3 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
Light.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: light.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType; The light constant buffer is updated with a new 4 float ambient color value. This will allow the ambient color to be set in this shader by outside classes. cbuffer LightBuffer { float4 ambientColor; float4 diffuseColor; float3 lightDirection; float padding; };
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 LightPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float3 lightDir;
4 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
// Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); We set the output color value to the base ambient color. All pixels will now be illuminated by a minimum of the ambient color value. // Set the default output color to the ambient light value for all pixels. color = ambientColor; // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel. lightIntensity = saturate(dot(input.normal, lightDir)); Check if the N dot L is greater than zero. If it is then add the diffuse color to the ambient color. If not then you need to be careful to not add the diffuse color. The reason being is that the diffuse color could be negative and it will subtract away some of the ambient color in the addition which is not correct. if(lightIntensity > 0.0f) { // Determine the final diffuse color based on the diffuse color and the amount of light intensity. color += (diffuseColor * lightIntensity); } Make sure to saturate the final output light color since the combination of ambient and diffuse could have been greater than 1. // Saturate the final light color. color = saturate(color); // Multiply the texture pixel and the final diffuse color to get the final pixel color result. color = color * textureColor; return color; }
Lightshaderclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.h
5 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: LightShaderClass //////////////////////////////////////////////////////////////////////////////// class LightShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; The LightBufferType has been updated to have a ambient color component. struct LightBufferType { D3DXVECTOR4 ambientColor; D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float padding; }; public: LightShaderClass(); LightShaderClass(const LightShaderClass&); ~LightShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown();
6 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DX private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECT void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer; ID3D11Buffer* m_lightBuffer; }; #endif
Lightshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightshaderclass.h"
7 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
LightShaderClass::~LightShaderClass() { }
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps"); if(!result) { return false; } return true; }
void LightShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes in an ambient color value which is then sets in the shader before rendering. bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambientColor, diffuseColor); if(!result) {
8 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_BUFFER_DESC lightBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false;
9 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
} // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0;
10 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; polygonLayout[2].SemanticName = "NORMAL"; polygonLayout[2].SemanticIndex = 0; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 0; polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // 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;
11 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
samplerDesc.BorderColor[3] = 0; samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; // Create the texture sampler state. result = device->CreateSamplerState(&samplerDesc, &m_sampleState); if(FAILED(result)) { return false; } // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } // Setup the description of the light dynamic constant buffer that is in the pixel shader. // Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; } return true; }
12 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
void LightShaderClass::ShutdownShader() { // Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; }
13 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
return; }
void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } The SetShaderParameters function now takes in a ambient light color value. bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor) {
14 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; unsigned int bufferNumber; MatrixBufferType* dataPtr; LightBufferType* dataPtr2;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); // Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; }
15 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
// Get a pointer to the data in the constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData; The ambient light color is mapped into the light buffer and then set as a constant in the pixel shader before rendering. // Copy the lighting variables into the constant buffer. dataPtr2->ambientColor = ambientColor; dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; dataPtr2->padding = 0.0f; // Unlock the constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer); return true; }
void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
16 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
Lightclass.h
The LightClass was updated for this tutorial to have an ambient component and related helper functions. //////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTCLASS_H_ #define _LIGHTCLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: LightClass //////////////////////////////////////////////////////////////////////////////// class LightClass { public: LightClass(); LightClass(const LightClass&); ~LightClass(); void SetAmbientColor(float, float, float, float); void SetDiffuseColor(float, float, float, float); void SetDirection(float, float, float); D3DXVECTOR4 GetAmbientColor(); D3DXVECTOR4 GetDiffuseColor(); D3DXVECTOR3 GetDirection(); private: D3DXVECTOR4 m_ambientColor; D3DXVECTOR4 m_diffuseColor; D3DXVECTOR3 m_direction; }; #endif
17 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
Lightclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightclass.h"
LightClass::LightClass() { }
LightClass::~LightClass() { }
void LightClass::SetAmbientColor(float red, float green, float blue, float alpha) { m_ambientColor = D3DXVECTOR4(red, green, blue, alpha); return; }
void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha) { m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha); return; }
18 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
Graphicsclass.h
The header for the GraphicsClass hasn't changed for this tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "lightshaderclass.h" #include "lightclass.h"
19 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(float); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; LightShaderClass* m_LightShader; LightClass* m_Light; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() {
20 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; }
21 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
// Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) { return false; } // Initialize the light shader object. result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } Set the intensity of the ambient light to 15% white color. Also set the direction of the light to point down the positive X axis so we can directly see the effect of ambient lighting on the cube. // Initialize the light object. m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
22 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
void GraphicsClass::Shutdown() { // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown(); delete m_LightShader; m_LightShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0;
23 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
} return; }
bool GraphicsClass::Frame() { bool result; static float rotation = 0.0f; I have slowed down the rotation by half so the effect is easier to see. // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene. result = Render(rotation); if(!result) { return false; } return true; }
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix);
24 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Rotate the world matrix by the rotation value so that the triangle will spin. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); The light shader now takes in as input the ambient color of the light. // Render the model using the light shader. result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
With the addition of ambient lighting all surfaces now illuminate to a minimum degree to produce a more realistic lighting effect.
25 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut09.html
To Do Exercises
1. Recompile the code and ensure you get a spinning cube that is illuminated on the dark side now. 2. Change the ambient light value to (0.0f, 0.0f, 0.0f, 1.0f) to see just the diffuse component again. 3. Comment out the color = color * textureColor; line in the pixel shader to see just the lighting effect.
Source Code
Visual Studio 2010 Project: dx11tut09.zip Source Only: dx11src09.zip Executable Only: dx11exe09.zip
26 of 26
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
Specular lighting is most commonly used to give light reflection off of metallic surfaces such as mirrors and highly polished/reflective metal surfaces. It is also used on other materials such as reflecting sunlight off of water. Used well it can add a degree of photo realism to most 3D scenes. The equation for specular lighting is the following: SpecularLighting = SpecularColor * (SpecularColorOfLight * ((NormalVector dot HalfWayVector) power SpecularReflectionPower) * Attentuation * Spotlight)
1 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
We will modify the equation to produce just the basic specular lighting effect as follows: SpecularLighting = SpecularLightColor * (ViewingDirection dot ReflectionVector) power SpecularReflectionPower The reflection vector in this equation has to be produced by multiplying double the light intensity by the vertex normal. The direction of the light is subtracted which then gives the reflection vector between the light source and the viewing angle: ReflectionVector = 2 * LightIntensity * VertexNormal - LightDirection The viewing direction in the equation is produced by subtracting the location of the camera by the position of the vertex: ViewingDirection = CameraPosition - VertexPosition Lets take a look at the modified light shader to see how this is implemented:
Light.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: light.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; We add a new constant buffer to hold camera information. In this shader we require the position of the camera to determine where this vertex is being viewed from for specular calculations. cbuffer CameraBuffer { float3 cameraPosition; float padding; };
2 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; The PixelInputType structure is modified as the viewing direction needs to be calculated in the vertex shader and then sent into the pixel shader for specular lighting calculations. struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 viewDirection : TEXCOORD1; };
//////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType LightVertexShader(VertexInputType input) { PixelInputType output; float4 worldPosition;
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; // Calculate the normal vector against the world matrix only. output.normal = mul(input.normal, (float3x3)worldMatrix);
3 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
// Normalize the normal vector. output.normal = normalize(output.normal); The viewing direction is calculated here in the vertex shader. We calculate the world position of the vertex and subtract that from the camera position to determine where we are viewing the scene from. The final value is normalized and sent into the pixel shader. // Calculate the position of the vertex in the world. worldPosition = mul(input.position, worldMatrix); // Determine the viewing direction based on the position of the camera and the position of the vertex in the world. output.viewDirection = cameraPosition.xyz - worldPosition.xyz; // Normalize the viewing direction vector. output.viewDirection = normalize(output.viewDirection); return output; }
Light.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: light.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType; The light buffer has been updated to hold specularColor and specularPower values for specular lighting calculations. cbuffer LightBuffer { float4 ambientColor; float4 diffuseColor; float3 lightDirection; float specularPower; float4 specularColor; };
4 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
////////////// // TYPEDEFS // ////////////// The PixelInputType structure is modified here as well to reflect the changes to it in the vertex shader. struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 viewDirection : TEXCOORD1; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 LightPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float3 lightDir; float lightIntensity; float4 color; float3 reflection; float4 specular;
// Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); // Set the default output color to the ambient light value for all pixels. color = ambientColor; // Initialize the specular color. specular = float4(0.0f, 0.0f, 0.0f, 0.0f); // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel. lightIntensity = saturate(dot(input.normal, lightDir)); if(lightIntensity > 0.0f)
5 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
{ // Determine the final diffuse color based on the diffuse color and the amount of light intensity. color += (diffuseColor * lightIntensity); // Saturate the ambient and diffuse color. color = saturate(color); The reflection vector for specular lighting is calculated here in the pixel shader provided the light intensity is greater than zero. This is the same equation as listed at the beginning of the tutorial. // Calculate the reflection vector based on the light intensity, normal vector, and light direction. reflection = normalize(2 * lightIntensity * input.normal - lightDir); The amount of specular light is then calculated using the reflection vector and the viewing direction. The smaller the angle between the viewer and the light source the greater the specular light reflection will be. The result is taken to the power of the specularPower value. The lower the specularPower value the greater the final effect is. // Determine the amount of specular light based on the reflection vector, viewing direction, and specular power. specular = pow(saturate(dot(reflection, input.viewDirection)), specularPower); } // Multiply the texture pixel and the input color to get the textured result. color = color * textureColor; We don't add the specular effect until the end. It is a highlight and needs to be added to the final value or it will not show up properly. // Add the specular component last to the output color. color = saturate(color + specular); return color; }
Lightshaderclass.h
The LightShaderClass has been modified from the previous tutorial to handle specular lighting now. //////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTSHADERCLASS_H_ #define _LIGHTSHADERCLASS_H_
6 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: LightShaderClass //////////////////////////////////////////////////////////////////////////////// class LightShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; We add a new camera buffer structure to match the new camera constant buffer in the vertex shader. Note we add a padding to make the structure size a multiple of 16 to prevent CreateBuffer failing when using sizeof with this structure. struct CameraBufferType { D3DXVECTOR3 cameraPosition; float padding; }; The LightBufferType has been modified to hold a specular color and specular power to match the light constant buffer in the pixel shader. Pay attention to the fact that I placed the specular power by the light direction to form a 4 float slot instead of using padding so that the structure could be kept in multiples of 16 bytes. Also had specular power been placed last in the structure and no padding used beneath light direction then the shader would not have functioned correctly. This is because even though the structure was a multiple of 16 the individual slots themselves were not aligned logically to 16 bytes each. struct LightBufferType { D3DXVECTOR4 ambientColor; D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float specularPower; D3DXVECTOR4 specularColor;
7 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
}; public: LightShaderClass(); LightShaderClass(const LightShaderClass&); ~LightShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DX D3DXVECTOR3, D3DXVECTOR4, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECT D3DXVECTOR3, D3DXVECTOR4, float); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer; We add a new camera constant buffer here which will be used for setting the camera position in the vertex shader. ID3D11Buffer* m_cameraBuffer; ID3D11Buffer* m_lightBuffer; }; #endif
Lightshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightshaderclass.h"
8 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
LightShaderClass::LightShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_sampleState = 0; m_matrixBuffer = 0; Initialize the new camera constant buffer to null in the class constructor. m_cameraBuffer = 0; m_lightBuffer = 0; }
LightShaderClass::~LightShaderClass() { }
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps"); if(!result) { return false; } return true; }
void LightShaderClass::Shutdown() {
9 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
// Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes in cameraPosition, specularColor, and specularPower values and sends them into the SetShaderParameters function to make them active in the light shader before rendering occurs. bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 cameraPosition, D3DXVECTOR4 specularColor, float specularPower) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambientColor, diffuseColor, cameraPosition, specularColor, specularPower); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_BUFFER_DESC cameraBufferDesc; D3D11_BUFFER_DESC lightBufferDesc;
10 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
11 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; polygonLayout[2].SemanticName = "NORMAL"; polygonLayout[2].SemanticIndex = 0; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 0; polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result))
12 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
{ return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // 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, &m_sampleState); if(FAILED(result)) { return false; } // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) {
13 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
return false; } We setup the description of the new camera buffer and then create a buffer using that description. This will allow us to interface with and set the camera position in the vertex shader. // Setup the description of the camera dynamic constant buffer that is in the vertex shader. cameraBufferDesc.Usage = D3D11_USAGE_DYNAMIC; cameraBufferDesc.ByteWidth = sizeof(CameraBufferType); cameraBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cameraBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; cameraBufferDesc.MiscFlags = 0; cameraBufferDesc.StructureByteStride = 0; // Create the camera constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&cameraBufferDesc, NULL, &m_cameraBuffer); if(FAILED(result)) { return false; } // Setup the description of the light dynamic constant buffer that is in the pixel shader. // Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; } return true; }
14 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
{ m_lightBuffer->Release(); m_lightBuffer = 0; } Release the new camera constant buffer in the ShutdownShader function. // Release the camera constant buffer. if(m_cameraBuffer) { m_cameraBuffer->Release(); m_cameraBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) {
15 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } The SetShaderParameters function has been modified to take as input cameraPosition, specularColor, and specularPower.
16 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 cameraPosition, D3DXVECTOR4 specularColor, float specularPower) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; unsigned int bufferNumber; MatrixBufferType* dataPtr; LightBufferType* dataPtr2; CameraBufferType* dataPtr3;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); Here we lock the camera buffer and set the camera position value in it. // Lock the camera constant buffer so it can be written to.
17 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
result = deviceContext->Map(m_cameraBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr3 = (CameraBufferType*)mappedResource.pData; // Copy the camera position into the constant buffer. dataPtr3->cameraPosition = cameraPosition; dataPtr3->padding = 0.0f; // Unlock the camera constant buffer. deviceContext->Unmap(m_cameraBuffer, 0); Note that we set the bufferNumber to 1 instead of 0 before setting the constant buffer. This is because it is the second buffer in the vertex shader (the first being the matrix buffer). // Set the position of the camera constant buffer in the vertex shader. bufferNumber = 1; // Now set the camera constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_cameraBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); // Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the light constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData; The light constant buffer now sets the specular color and specular power so that the pixel shader can perform specular lighting calculations. // Copy the lighting variables into the light constant buffer. dataPtr2->ambientColor = ambientColor; dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; dataPtr2->specularColor = specularColor;
18 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
dataPtr2->specularPower = specularPower; // Unlock the light constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer); return true; }
void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Lightclass.h
The LightClass has been modified for this tutorial to include specular components and specular related helper functions. //////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTCLASS_H_ #define _LIGHTCLASS_H_
19 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
//////////////////////////////////////////////////////////////////////////////// // Class name: LightClass //////////////////////////////////////////////////////////////////////////////// class LightClass { public: LightClass(); LightClass(const LightClass&); ~LightClass(); void SetAmbientColor(float, float, float, float); void SetDiffuseColor(float, float, float, float); void SetDirection(float, float, float); void SetSpecularColor(float, float, float, float); void SetSpecularPower(float); D3DXVECTOR4 GetAmbientColor(); D3DXVECTOR4 GetDiffuseColor(); D3DXVECTOR3 GetDirection(); D3DXVECTOR4 GetSpecularColor(); float GetSpecularPower(); private: D3DXVECTOR4 m_ambientColor; D3DXVECTOR4 m_diffuseColor; D3DXVECTOR3 m_direction; D3DXVECTOR4 m_specularColor; float m_specularPower; }; #endif
Lightclass.cpp
20 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
LightClass::LightClass() { }
LightClass::~LightClass() { }
void LightClass::SetAmbientColor(float red, float green, float blue, float alpha) { m_ambientColor = D3DXVECTOR4(red, green, blue, alpha); return; }
void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha) { m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha); return; }
void LightClass::SetSpecularColor(float red, float green, float blue, float alpha) { m_specularColor = D3DXVECTOR4(red, green, blue, alpha);
21 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
return; }
Graphicsclass.h
22 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
The header file for the GraphicsClass has not changed for this tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "lightshaderclass.h" #include "lightclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(float);
23 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; LightShaderClass* m_LightShader; LightClass* m_Light; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::~GraphicsClass() { }
24 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) {
25 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
return false; } // Initialize the light shader object. result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } In the light class object we now set the specular color and the specular power. For this tutorial we set the specular color to white and set the specular power to 32. Remember that the lower the specular power value the greater the specular effect will be. // Initialize the light object. m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f); m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f); m_Light->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetSpecularPower(32.0f); return true; }
void GraphicsClass::Shutdown() { // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown();
26 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
delete m_LightShader; m_LightShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
// Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene.
27 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Rotate the world matrix by the rotation value so that the triangle will spin. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); The light shader render function now takes in the camera position, the light specular color, and the light specular power. // Render the model using the light shader. result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), m_Camera->GetPosition(), m_Light->GetSpecularColor(), m_Light->GetSpecularPower()); if(!result) { return false; } // Present the rendered scene to the screen.
28 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
Summary
With the addition of specular lighting we now get a bright white flash each time the cube surface evenly faces the camera viewing direction.
To Do Exercises
1. Recompile and run the project and ensure you get a spinning cube that flashes a bright specular highlight each time if faces the camera. 2. Change the direction of the light such as m_Light->SetDirection(1.0f, 0.0f, 1.0f) to see the effect if the light source is from a different direction. 3. Create a 5000+ poly sphere model with a red texture to recreate the sphere images at the top of the tutorial.
Source Code
Visual Studio 2010 Project: dx11tut10.zip Source Only: dx11src10.zip
29 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut10.html
30 of 30
3/8/2013 12:30 PM
http://www.rastertek.com/dx11tut11.html
2D Screen Coordinates
To render 2D images to the screen you will need to calculate the screen X and Y coordinates. For DirectX the middle of the screen is 0,0. From there the left side of the screen and the bottom side of the screen go in the negative direction. The right side of the screen and the top of the screen go in the positive direction. As an example take a screen that is 1024x768 resolution, the coordinates for the borders of the screen would be as follows:
So keep in mind that all your 2D rendering will need to work with these screen coordinate calculations and that you will also need the size of the user's window/screen for correct placement of 2D images.
1 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
To turn the Z buffer on and off you will need to create a second depth stencil state the same as your 3D one except with DepthEnable set to false. Then just use OMSetDepthStencilState to switch between the two states to turn the Z buffer on and off.
Framework
The code in this tutorial is based on the previous tutorials. The major difference in this tutorial is that ModelClass has been replaced with BitmapClass and that we are using the TextureShaderClass again instead of the LightShaderClass. The framework will look like the following:
2 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
Bitmapclass.h
BitmapClass will be used to represent an individual 2D image that needs to be rendered to the screen. For every 2D image you have you will need a new BitmapClass for each. Note that this class is just the ModelClass re-written to handle 2D images instead of 3D objects. //////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _BITMAPCLASS_H_ #define _BITMAPCLASS_H_
3 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
{ private: Each bitmap image is still a polygon object that gets rendered similar to 3D objects. For 2D images we just need a position vector and texture coordinates. struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; public: BitmapClass(); BitmapClass(const BitmapClass&); ~BitmapClass(); bool Initialize(ID3D11Device*, int, int, WCHAR*, int, int); void Shutdown(); bool Render(ID3D11DeviceContext*, int, int); int GetIndexCount(); ID3D11ShaderResourceView* GetTexture(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); bool UpdateBuffers(ID3D11DeviceContext*, int, int); void RenderBuffers(ID3D11DeviceContext*); bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; TextureClass* m_Texture; The BitmapClass will need to maintain some extra information that a 3D model wouldn't such as the screen size, the bitmap size, and the last place it was rendered. We have added extra private variables here to track that extra information. int m_screenWidth, m_screenHeight; int m_bitmapWidth, m_bitmapHeight; int m_previousPosX, m_previousPosY; };
4 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
#endif
Bitmapclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "bitmapclass.h" The class constructor initializes all the private pointers in the class. BitmapClass::BitmapClass() { m_vertexBuffer = 0; m_indexBuffer = 0; m_Texture = 0; }
BitmapClass::~BitmapClass() { }
bool BitmapClass::Initialize(ID3D11Device* device, int screenWidth, int screenHeight, WCHAR* textureFilename, int bitmapWidth, int bitmapHeight) { bool result; In the Initialize function both the screen size and image size are stored. These will be required for generating exact vertex locations during rendering. Note that the pixels of the image do not need to be exactly the same as the texture that is used, you can set this to any size and use any size texture you want also. // Store the screen size. m_screenWidth = screenWidth; m_screenHeight = screenHeight; // Store the size in pixels that this bitmap should be rendered at. m_bitmapWidth = bitmapWidth;
5 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
m_bitmapHeight = bitmapHeight; The previous rendering location is first initialized to negative one. This will be an important variable that will locate where it last drew this image. If the image location hasn't changed since last frame then it won't modify the dynamic vertex buffer which will save us some cycles. // Initialize the previous rendering position to negative one. m_previousPosX = -1; m_previousPosY = -1; The buffers are then created and the texture for this bitmap image is also loaded in. // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } // Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; } The Shutdown function will release the vertex and index buffers as well as the texture that was used for the bitmap image. void BitmapClass::Shutdown() { // Release the model texture. ReleaseTexture(); // Shutdown the vertex and index buffers. ShutdownBuffers(); return; } Render puts the buffers of the 2D image on the video card. As input it takes the position of where to render the image on the screen. The UpdateBuffers function is called with the position parameters. If the position has changed since the last frame it will then update the location of the vertices in the dynamic vertex buffer to the new location. If not it will skip the UpdateBuffers function. After that the RenderBuffers function will prepare the final vertices/indices for rendering.
6 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen. result = UpdateBuffers(deviceContext, positionX, positionY); if(!result) { return false; } // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return true; } GetIndexCount returns the number of indexes for the 2D image. This will pretty much always be six. int BitmapClass::GetIndexCount() { return m_indexCount; } The GetTexture function returns a pointer to the texture resource for this 2D image. The shader will call this function so it has access to the image when drawing the buffers. ID3D11ShaderResourceView* BitmapClass::GetTexture() { return m_Texture->GetTexture(); } InitializeBuffers is the function that is used to build the vertex and index buffer that will be used to draw the 2D image. bool BitmapClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i; We set the vertices to six since we are making a square out of two triangles, so six points are needed. The indices will be the same.
7 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Set the number of vertices in the vertex array. m_vertexCount = 6; // Set the number of indices in the index array. m_indexCount = m_vertexCount; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } // Initialize vertex array to zeros at first. memset(vertices, 0, (sizeof(VertexType) * m_vertexCount)); // Load the index array with data. for(i=0; i<m_indexCount; i++) { indices[i] = i; } Here is the big change in comparison to the ModelClass. We are now creating a dynamic vertex buffer so we can modify the data inside the vertex buffer each frame if we need to. To make it dynamic we set Usage to D3D11_USAGE_DYNAMIC and CPUAccessFlags to D3D11_CPU_ACCESS_WRITE in the description. // Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0;
8 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } We don't need to make the index buffer dynamic since the six indices will always point to the same six vertices even though the coordinates of the vertex may change. // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; } ShutdownBuffers releases the vertex and index buffers. void BitmapClass::ShutdownBuffers() { // Release the index buffer.
9 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; } The UpdateBuffers function is called each frame to update the contents of the dynamic vertex buffer to re-position the 2D bitmap image on the screen if need be. bool BitmapClass::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY) { float left, right, top, bottom; VertexType* vertices; D3D11_MAPPED_SUBRESOURCE mappedResource; VertexType* verticesPtr; HRESULT result; We check if the position to render this image has changed. If it hasn't changed then we just exit since the vertex buffer doesn't need any changes for this frame. This check can save us a lot of processing. // If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it // currently has the correct parameters. if((positionX == m_previousPosX) && (positionY == m_previousPosY)) { return true; } If the position to render this image has changed then we record the new location for the next time we come through this function. // If it has changed then update the position it is being rendered to. m_previousPosX = positionX; m_previousPosY = positionY; The four sides of the image need to be calculated. See the diagram at the top of the tutorial for a complete explaination. // Calculate the screen coordinates of the left side of the bitmap.
10 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
left = (float)((m_screenWidth / 2) * -1) + (float)positionX; // Calculate the screen coordinates of the right side of the bitmap. right = left + (float)m_bitmapWidth; // Calculate the screen coordinates of the top of the bitmap. top = (float)(m_screenHeight / 2) - (float)positionY; // Calculate the screen coordinates of the bottom of the bitmap. bottom = top - (float)m_bitmapHeight; Now that the coordinates are calculated create a temporary vertex array and fill it with the new six vertex points. // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Load the vertex array with data. // First triangle. vertices[0].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[1].texture = D3DXVECTOR2(1.0f, 1.0f); vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f); // Bottom left. vertices[2].texture = D3DXVECTOR2(0.0f, 1.0f); // Second triangle. vertices[3].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[4].position = D3DXVECTOR3(right, top, 0.0f); // Top right. vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f); vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[5].texture = D3DXVECTOR2(1.0f, 1.0f); Now copy the contents of the vertex array into the vertex buffer using the Map and memcpy functions. // Lock the vertex buffer so it can be written to. result = deviceContext->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
11 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
if(FAILED(result)) { return false; } // Get a pointer to the data in the vertex buffer. verticesPtr = (VertexType*)mappedResource.pData; // Copy the data into the vertex buffer. memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount)); // Unlock the vertex buffer. deviceContext->Unmap(m_vertexBuffer, 0); // Release the vertex array as it is no longer needed. delete [] vertices; vertices = 0; return true; } The RenderBuffers function sets up the vertex and index buffers on the gpu to be drawn by the shader. void BitmapClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { unsigned int stride; unsigned int offset;
// Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
12 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
The following function loads the texture that will be used for drawing the 2D image. bool BitmapClass::LoadTexture(ID3D11Device* device, WCHAR* filename) { bool result;
// Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result) { return false; } return true; } This ReleaseTexture function releases the texture that was loaded. void BitmapClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
D3dclass.h
The D3DClass has been modified to handle enabling and disabling the Z buffer.
13 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
///////////// // LINKING // ///////////// #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3dx11.lib") #pragma comment(lib, "d3dx10.lib")
////////////// // INCLUDES // ////////////// #include <dxgi.h> #include <d3dcommon.h> #include <d3d11.h> #include <d3dx10math.h>
//////////////////////////////////////////////////////////////////////////////// // Class name: D3DClass //////////////////////////////////////////////////////////////////////////////// class D3DClass { public: D3DClass(); D3DClass(const D3DClass&); ~D3DClass(); bool Initialize(int, int, bool, HWND, bool, float, float); void Shutdown(); void BeginScene(float, float, float, float); void EndScene(); ID3D11Device* GetDevice(); ID3D11DeviceContext* GetDeviceContext(); void GetProjectionMatrix(D3DXMATRIX&);
14 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
void GetWorldMatrix(D3DXMATRIX&); void GetOrthoMatrix(D3DXMATRIX&); void GetVideoCardInfo(char*, int&); We now have two new function in the D3DClass for turning the Z buffer on and off when rendering 2D images. void TurnZBufferOn(); void TurnZBufferOff(); private: bool m_vsync_enabled; int m_videoCardMemory; char m_videoCardDescription[128]; IDXGISwapChain* m_swapChain; ID3D11Device* m_device; ID3D11DeviceContext* m_deviceContext; ID3D11RenderTargetView* m_renderTargetView; ID3D11Texture2D* m_depthStencilBuffer; ID3D11DepthStencilState* m_depthStencilState; ID3D11DepthStencilView* m_depthStencilView; ID3D11RasterizerState* m_rasterState; D3DXMATRIX m_projectionMatrix; D3DXMATRIX m_worldMatrix; D3DXMATRIX m_orthoMatrix; There is also a new depth stencil state for 2D drawing. ID3D11DepthStencilState* m_depthDisabledStencilState; }; #endif
D3dclass.cpp
We will just cover the functions that have changed in this class since the texturing tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: d3dclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "d3dclass.h"
15 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
D3DClass::D3DClass() { m_swapChain = 0; m_device = 0; m_deviceContext = 0; m_renderTargetView = 0; m_depthStencilBuffer = 0; m_depthStencilState = 0; m_depthStencilView = 0; m_rasterState = 0; Initialize the new depth stencil state to null in the class constructor. m_depthDisabledStencilState = 0; }
D3DClass::~D3DClass() { }
bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen, float screenDepth, float screenNear) { HRESULT result; IDXGIFactory* factory; IDXGIAdapter* adapter; IDXGIOutput* adapterOutput; unsigned int numModes, i, numerator, denominator, stringLength; DXGI_MODE_DESC* displayModeList; DXGI_ADAPTER_DESC adapterDesc; int error; DXGI_SWAP_CHAIN_DESC swapChainDesc; D3D_FEATURE_LEVEL featureLevel; ID3D11Texture2D* backBufferPtr; D3D11_TEXTURE2D_DESC depthBufferDesc; D3D11_DEPTH_STENCIL_DESC depthStencilDesc; D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc; D3D11_RASTERIZER_DESC rasterDesc;
16 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
D3D11_VIEWPORT viewport; float fieldOfView, screenAspect; We have a new depth stencil description variable for setting up the new depth stencil. D3D11_DEPTH_STENCIL_DESC depthDisabledStencilDesc;
// Store the vsync setting. m_vsync_enabled = vsync; // Create a DirectX graphics interface factory. result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory); if(FAILED(result)) { return false; } // Use the factory to create an adapter for the primary graphics interface (video card). result = factory->EnumAdapters(0, &adapter); if(FAILED(result)) { return false; } // Enumerate the primary adapter output (monitor). result = adapter->EnumOutputs(0, &adapterOutput); if(FAILED(result)) { return false; } // Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor). result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL); if(FAILED(result)) { return false; } // Create a list to hold all the possible display modes for this monitor/video card combination. displayModeList = new DXGI_MODE_DESC[numModes]; if(!displayModeList) { return false; }
17 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Now fill the display mode list structures. result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList); if(FAILED(result)) { return false; } // Now go through all the display modes and find the one that matches the screen width and height. // When a match is found store the numerator and denominator of the refresh rate for that monitor. for(i=0; i<numModes; i++) { if(displayModeList[i].Width == (unsigned int)screenWidth) { if(displayModeList[i].Height == (unsigned int)screenHeight) { numerator = displayModeList[i].RefreshRate.Numerator; denominator = displayModeList[i].RefreshRate.Denominator; } } } // Get the adapter (video card) description. result = adapter->GetDesc(&adapterDesc); if(FAILED(result)) { return false; } // Store the dedicated video card memory in megabytes. m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024); // Convert the name of the video card to a character array and store it. error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128); if(error != 0) { return false; } // Release the display mode list. delete [] displayModeList; displayModeList = 0; // Release the adapter output. adapterOutput->Release();
18 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
adapterOutput = 0; // Release the adapter. adapter->Release(); adapter = 0; // Release the factory. factory->Release(); factory = 0; // Initialize the swap chain description. ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); // Set to a single back buffer. swapChainDesc.BufferCount = 1; // Set the width and height of the back buffer. swapChainDesc.BufferDesc.Width = screenWidth; swapChainDesc.BufferDesc.Height = screenHeight; // Set regular 32-bit surface for the back buffer. swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // Set the refresh rate of the back buffer. if(m_vsync_enabled) { swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator; swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator; } else { swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; swapChainDesc.BufferDesc.RefreshRate.Denominator = 1; } // Set the usage of the back buffer. swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // Set the handle for the window to render to. swapChainDesc.OutputWindow = hwnd; // Turn multisampling off. swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0;
19 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Set to full screen or windowed mode. if(fullscreen) { swapChainDesc.Windowed = false; } else { swapChainDesc.Windowed = true; } // Set the scan line ordering and scaling to unspecified. swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // Discard the back buffer contents after presenting. swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // Don't set the advanced flags. swapChainDesc.Flags = 0; // Set the feature level to DirectX 11. featureLevel = D3D_FEATURE_LEVEL_11_0; // Create the swap chain, Direct3D device, and Direct3D device context. result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, D3D11_SDK_VERSION, &swapChainDesc, &m_device, NULL, &m_deviceContext); if(FAILED(result)) { return false; } // Get the pointer to the back buffer. result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr); if(FAILED(result)) { return false; } // Create the render target view with the back buffer pointer. result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView); if(FAILED(result)) { return false; }
20 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Release pointer to the back buffer as we no longer need it. backBufferPtr->Release(); backBufferPtr = 0; // Initialize the description of the depth buffer. ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc)); // Set up the description of the depth buffer. depthBufferDesc.Width = screenWidth; depthBufferDesc.Height = screenHeight; depthBufferDesc.MipLevels = 1; depthBufferDesc.ArraySize = 1; depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthBufferDesc.SampleDesc.Count = 1; depthBufferDesc.SampleDesc.Quality = 0; depthBufferDesc.Usage = D3D11_USAGE_DEFAULT; depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; depthBufferDesc.CPUAccessFlags = 0; depthBufferDesc.MiscFlags = 0; // Create the texture for the depth buffer using the filled out description. result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer); if(FAILED(result)) { return false; } // Initialize the description of the stencil state. ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc)); // Set up the description of the stencil state. depthStencilDesc.DepthEnable = true; depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS; depthStencilDesc.StencilEnable = true; depthStencilDesc.StencilReadMask = 0xFF; depthStencilDesc.StencilWriteMask = 0xFF; // Stencil operations if pixel is front-facing. depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
21 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Stencil operations if pixel is back-facing. depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // Create the depth stencil state. result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState); if(FAILED(result)) { return false; } // Set the depth stencil state. m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1); // Initialize the depth stencil view. ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc)); // Set up the depth stencil view description. depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; depthStencilViewDesc.Texture2D.MipSlice = 0; // Create the depth stencil view. result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView); if(FAILED(result)) { return false; } // Bind the render target view and depth stencil buffer to the output render pipeline. m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView); // Setup the raster description which will determine how and what polygons will be drawn. rasterDesc.AntialiasedLineEnable = false; rasterDesc.CullMode = D3D11_CULL_BACK; rasterDesc.DepthBias = 0; rasterDesc.DepthBiasClamp = 0.0f; rasterDesc.DepthClipEnable = true; rasterDesc.FillMode = D3D11_FILL_SOLID; rasterDesc.FrontCounterClockwise = false; rasterDesc.MultisampleEnable = false; rasterDesc.ScissorEnable = false; rasterDesc.SlopeScaledDepthBias = 0.0f;
22 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
// Create the rasterizer state from the description we just filled out. result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState); if(FAILED(result)) { return false; } // Now set the rasterizer state. m_deviceContext->RSSetState(m_rasterState); // Setup the viewport for rendering. viewport.Width = (float)screenWidth; viewport.Height = (float)screenHeight; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; // Create the viewport. m_deviceContext->RSSetViewports(1, &viewport); // Setup the projection matrix. fieldOfView = (float)D3DX_PI / 4.0f; screenAspect = (float)screenWidth / (float)screenHeight; // Create the projection matrix for 3D rendering. D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth); // Initialize the world matrix to the identity matrix. D3DXMatrixIdentity(&m_worldMatrix); // Create an orthographic projection matrix for 2D rendering. D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth); Here we setup the description of the depth stencil. Notice the only difference between this new depth stencil and the old one is the DepthEnable is set to false here for 2D drawing. // Clear the second depth stencil state before setting the parameters. ZeroMemory(&depthDisabledStencilDesc, sizeof(depthDisabledStencilDesc)); // Now create a second depth stencil state which turns off the Z buffer for 2D rendering. The only difference is // that DepthEnable is set to false, all other parameters are the same as the other depth stencil state. depthDisabledStencilDesc.DepthEnable = false; depthDisabledStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
23 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
depthDisabledStencilDesc.DepthFunc = D3D11_COMPARISON_LESS; depthDisabledStencilDesc.StencilEnable = true; depthDisabledStencilDesc.StencilReadMask = 0xFF; depthDisabledStencilDesc.StencilWriteMask = 0xFF; depthDisabledStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; depthDisabledStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; depthDisabledStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; depthDisabledStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; Now create the new depth stencil. // Create the state using the device. result = m_device->CreateDepthStencilState(&depthDisabledStencilDesc, &m_depthDisabledStencilState); if(FAILED(result)) { return false; } return true; }
void D3DClass::Shutdown() { // Before shutting down set to windowed mode or when you release the swap chain it will throw an exception. if(m_swapChain) { m_swapChain->SetFullscreenState(false, NULL); } Here we release the new depth stencil during the Shutdown function. if(m_depthDisabledStencilState) { m_depthDisabledStencilState->Release(); m_depthDisabledStencilState = 0; } if(m_rasterState) { m_rasterState->Release();
24 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
m_rasterState = 0; } if(m_depthStencilView) { m_depthStencilView->Release(); m_depthStencilView = 0; } if(m_depthStencilState) { m_depthStencilState->Release(); m_depthStencilState = 0; } if(m_depthStencilBuffer) { m_depthStencilBuffer->Release(); m_depthStencilBuffer = 0; } if(m_renderTargetView) { m_renderTargetView->Release(); m_renderTargetView = 0; } if(m_deviceContext) { m_deviceContext->Release(); m_deviceContext = 0; } if(m_device) { m_device->Release(); m_device = 0; } if(m_swapChain) { m_swapChain->Release(); m_swapChain = 0; }
25 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
return; }
void D3DClass::BeginScene(float red, float green, float blue, float alpha) { float color[4];
// Setup the color to clear the buffer to. color[0] = red; color[1] = green; color[2] = blue; color[3] = alpha; // Clear the back buffer. m_deviceContext->ClearRenderTargetView(m_renderTargetView, color); // Clear the depth buffer. m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0); return; }
void D3DClass::EndScene() { // Present the back buffer to the screen since rendering is complete. if(m_vsync_enabled) { // Lock to screen refresh rate. m_swapChain->Present(1, 0); } else { // Present as fast as possible. m_swapChain->Present(0, 0); } return; }
ID3D11Device* D3DClass::GetDevice() {
26 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
return m_device; }
void D3DClass::GetVideoCardInfo(char* cardName, int& memory) { strcpy_s(cardName, 128, m_videoCardDescription); memory = m_videoCardMemory; return; } These are the new functions for enabling and disabling the Z buffer. To turn Z buffering on we set the original depth stencil. To turn Z buffering off we set the new depth stencil that has depthEnable set to false. Generally the best way to use these functions is first do all your 3D rendering, then turn the Z buffer off and do your 2D rendering, and then turn the Z buffer on again. void D3DClass::TurnZBufferOn() { m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);
27 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "textureshaderclass.h" Here we include the new BitmapClass header file. #include "bitmapclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
28 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
//////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(float); private: D3DClass* m_D3D; CameraClass* m_Camera; TextureShaderClass* m_TextureShader; We create a new private BitmapClass object here. BitmapClass* m_Bitmap; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_TextureShader = 0; We initialize the new bitmap object to null in the class constructor.
29 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
m_Bitmap = 0; }
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the texture shader object.
30 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } Here is where we create and initialize the new BitmapClass object. It uses the seafloor.dds as the texture and I set the size to 256x256. You can change this size to whatever you like as it does not need to reflect the exact size of the texture. // Create the bitmap object. m_Bitmap = new BitmapClass; if(!m_Bitmap) { return false; } // Initialize the bitmap object. result = m_Bitmap->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, L"../Engine/data/seafloor.dds", 256, 256); if(!result) { MessageBox(hwnd, L"Could not initialize the bitmap object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { The BitmapClass object is released in the Shutdown function. // Release the bitmap object. if(m_Bitmap) { m_Bitmap->Shutdown();
31 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
delete m_Bitmap; m_Bitmap = 0; } // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
// Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene.
32 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
bool GraphicsClass::Render(float rotation) { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix; bool result;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); We now also get the ortho matrix from the D3DClass for 2D rendering. We will pass this in instead of the projection matrix. m_D3D->GetOrthoMatrix(orthoMatrix); The Z buffer is turned off before we do any 2D rendering. // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); We then render the bitmap to the 100, 100 location on the screen. You can change this to wherever you want it rendered. // Put the bitmap vertex and index buffers on the graphics pipeline to prepare them for drawing. result = m_Bitmap->Render(m_D3D->GetDeviceContext(), 100, 100); if(!result) { return false; }
33 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
Once the vertex/index buffers are prepared we draw them using the texture shader. Notice we send in the orthoMatrix instead of the projectionMatrix for rendering 2D. Due note also that if your view matrix is changing you will need to create a default one for 2D rendering and use it instead of the regular view matrix. In this tutorial using the regular view matrix is fine as the camera in this tutorial is stationary. // Render the bitmap with the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Bitmap->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_Bitmap->GetTexture()); if(!result) { return false; } After all the 2D rendering is done we turn the Z buffer back on for the next round of 3D rendering. // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
With these new concepts we can now render 2D images onto the screen. This opens the door for rendering user interfaces and font systems.
34 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut11.html
To Do Exercises
1. Recompile the code and ensure you get a 2D image drawn to the 100, 100 location on your screen. 2. Change the location on the screen where the image is drawn to. 3. Change the size of the image in the m_Bitmap->Initialize function call in the GraphicsClass. 4. Change the texture that is used for the 2D image.
Source Code
Visual Studio 2010 Project: dx11tut11.zip Source Only: dx11src11.zip Executable Only: dx11exe11.zip
35 of 35
3/8/2013 12:31 PM
http://www.rastertek.com/dx11tut16.html
Framework
The frame work has mostly classes from several of the previous tutorials. We do have three new classes called FrustumClass, PositionClass, and ModelListClass. FrustumClass will encapsulate the frustum culling ability this tutorial is focused on. ModelListClass will contain a list of the position and color information of the 25 spheres that will be randomly generated each time we run the program. PositionClass will handle the viewing rotation of the camera based on if the user is pressing the left or right arrow key.
1 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
Frustumclass.h
The header file for the FrustumClass is fairly simple. The class doesn't require any initialization or shutdown. Each frame the ConstructFrustum function is called after the camera has first been rendered. The ConstructFrustum function uses the private m_planes to calculate and store the six planes of the view frustum based on the updated viewing location. From there we can call any of the four check functions to seen if either a point, cube, sphere, or rectangle are inside the viewing frustum or not. //////////////////////////////////////////////////////////////////////////////// // Filename: frustumclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _FRUSTUMCLASS_H_ #define _FRUSTUMCLASS_H_
2 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
//////////////////////////////////////////////////////////////////////////////// class FrustumClass { public: FrustumClass(); FrustumClass(const FrustumClass&); ~FrustumClass(); void ConstructFrustum(float, D3DXMATRIX, D3DXMATRIX); bool CheckPoint(float, float, float); bool CheckCube(float, float, float, float); bool CheckSphere(float, float, float, float); bool CheckRectangle(float, float, float, float, float, float); private: D3DXPLANE m_planes[6]; }; #endif
Frustumclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: frustumclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "frustumclass.h"
FrustumClass::FrustumClass() { }
FrustumClass::~FrustumClass() { }
3 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
ConstructFrustum is called every frame by the GraphicsClass. It passes in the the depth of the screen, the projection matrix, and the view matrix. We then use these input variables to calculate the matrix of the view frustum at that frame. With the new frustum matrix we then calculate the six planes that form the view frustum. void FrustumClass::ConstructFrustum(float screenDepth, D3DXMATRIX projectionMatrix, D3DXMATRIX viewMatrix) { float zMinimum, r; D3DXMATRIX matrix;
// Calculate the minimum Z distance in the frustum. zMinimum = -projectionMatrix._43 / projectionMatrix._33; r = screenDepth / (screenDepth - zMinimum); projectionMatrix._33 = r; projectionMatrix._43 = -r * zMinimum; // Create the frustum matrix from the view matrix and updated projection matrix. D3DXMatrixMultiply(&matrix, &viewMatrix, &projectionMatrix); // Calculate near plane of frustum. m_planes[0].a = matrix._14 + matrix._13; m_planes[0].b = matrix._24 + matrix._23; m_planes[0].c = matrix._34 + matrix._33; m_planes[0].d = matrix._44 + matrix._43; D3DXPlaneNormalize(&m_planes[0], &m_planes[0]); // Calculate far plane of frustum. m_planes[1].a = matrix._14 - matrix._13; m_planes[1].b = matrix._24 - matrix._23; m_planes[1].c = matrix._34 - matrix._33; m_planes[1].d = matrix._44 - matrix._43; D3DXPlaneNormalize(&m_planes[1], &m_planes[1]); // Calculate left plane of frustum. m_planes[2].a = matrix._14 + matrix._11; m_planes[2].b = matrix._24 + matrix._21; m_planes[2].c = matrix._34 + matrix._31; m_planes[2].d = matrix._44 + matrix._41; D3DXPlaneNormalize(&m_planes[2], &m_planes[2]); // Calculate right plane of frustum. m_planes[3].a = matrix._14 - matrix._11; m_planes[3].b = matrix._24 - matrix._21; m_planes[3].c = matrix._34 - matrix._31; m_planes[3].d = matrix._44 - matrix._41; D3DXPlaneNormalize(&m_planes[3], &m_planes[3]);
4 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
// Calculate top plane of frustum. m_planes[4].a = matrix._14 - matrix._12; m_planes[4].b = matrix._24 - matrix._22; m_planes[4].c = matrix._34 - matrix._32; m_planes[4].d = matrix._44 - matrix._42; D3DXPlaneNormalize(&m_planes[4], &m_planes[4]); // Calculate bottom plane of frustum. m_planes[5].a = matrix._14 + matrix._12; m_planes[5].b = matrix._24 + matrix._22; m_planes[5].c = matrix._34 + matrix._32; m_planes[5].d = matrix._44 + matrix._42; D3DXPlaneNormalize(&m_planes[5], &m_planes[5]); return; } CheckPoint checks if a single point is inside the viewing frustum. This is the most general of the four checking algorithms but can be very efficient if used correctly in the right situation over the other checking methods. It takes the point and checks to see if it is inside all six planes. If the point is inside all six then it returns true, otherwise it returns false if not. bool FrustumClass::CheckPoint(float x, float y, float z) { int i;
// Check if the point is inside all six planes of the view frustum. for(i=0; i<6; i++) { if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x, y, z)) < 0.0f) { return false; } } return true; } CheckCube checks if any of the eight corner points of the cube are inside the viewing frustum. It only requires as input the center point of the cube and the radius, it uses those to calculate the 8 corner points of the cube. It then checks if any one of the corner points are inside all 6 planes of the viewing frustum. If it does find a point inside all six planes of the viewing frustum it returns true, otherwise it returns false. bool FrustumClass::CheckCube(float xCenter, float yCenter, float zCenter, float radius) {
5 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
int i;
// Check if any one point of the cube is in the view frustum. for(i=0; i<6; i++) { if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter - radius), (zCenter - radius))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter - radius), (zCenter - radius))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter + radius), (zCenter - radius))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter + radius), (zCenter - radius))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter - radius), (zCenter + radius))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter - radius), (zCenter + radius))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter + radius), (zCenter + radius))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter + radius), (zCenter + radius))) >= 0.0f) { continue; }
6 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
return false; } return true; } CheckSphere checks if the radius of the sphere from the center point is inside all six planes of the viewing frustum. If it is outside any of them then the sphere cannot be seen and the function will return false. If it is inside all six the function returns true that the sphere can be seen. bool FrustumClass::CheckSphere(float xCenter, float yCenter, float zCenter, float radius) { int i;
// Check if the radius of the sphere is inside the view frustum. for(i=0; i<6; i++) { if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(xCenter, yCenter, zCenter)) < -radius) { return false; } } return true; } CheckRectangle works the same as CheckCube except that that it takes as input the x radius, y radius, and z radius of the rectangle instead of just a single radius of a cube. It can then calculate the 8 corner points of the rectangle and do the frustum checks similar to the CheckCube function. bool FrustumClass::CheckRectangle(float xCenter, float yCenter, float zCenter, float xSize, float ySize, float zSize) { int i;
// Check if any of the 6 planes of the rectangle are inside the view frustum. for(i=0; i<6; i++) { if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f) {
7 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f) { continue; } if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f) { continue; } return false; } return true; }
Modellistclass.h
ModelListClass is a new class for maintaining information about all the models in the scene. For this tutorial it only maintains the size and color of the sphere
8 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
models since we only have one model type. This class can be expanded to maintain all the different types of models in the scene and indexes to their ModelClass but I am keeping this tutorial simple for now. /////////////////////////////////////////////////////////////////////////////// // Filename: modellistclass.h /////////////////////////////////////////////////////////////////////////////// #ifndef _MODELLISTCLASS_H_ #define _MODELLISTCLASS_H_
/////////////////////////////////////////////////////////////////////////////// // Class name: ModelListClass /////////////////////////////////////////////////////////////////////////////// class ModelListClass { private: struct ModelInfoType { D3DXVECTOR4 color; float positionX, positionY, positionZ; }; public: ModelListClass(); ModelListClass(const ModelListClass&); ~ModelListClass(); bool Initialize(int); void Shutdown(); int GetModelCount(); void GetData(int, float&, float&, float&, D3DXVECTOR4&); private: int m_modelCount; ModelInfoType* m_ModelInfoList; };
9 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
#endif
Modellistclass.cpp
/////////////////////////////////////////////////////////////////////////////// // Filename: modellistclass.cpp /////////////////////////////////////////////////////////////////////////////// #include "modellistclass.h" The class constructor initializes the model information list to null. ModelListClass::ModelListClass() { m_ModelInfoList = 0; }
ModelListClass::~ModelListClass() { }
bool ModelListClass::Initialize(int numModels) { int i; float red, green, blue; First store the number of models that will be used and then create the list array of them using the ModelInfoType structure. // Store the number of models. m_modelCount = numModels; // Create a list array of the model information. m_ModelInfoList = new ModelInfoType[m_modelCount]; if(!m_ModelInfoList) {
10 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
return false; } Seed the random number generator with the current time and then randomly generate the position of color of the models and store them in the list array. // Seed the random generator with the current time. srand((unsigned int)time(NULL)); // Go through all the models and randomly generate the model color and position. for(i=0; i<m_modelCount; i++) { // Generate a random color for the model. red = (float)rand() / RAND_MAX; green = (float)rand() / RAND_MAX; blue = (float)rand() / RAND_MAX; m_ModelInfoList[i].color = D3DXVECTOR4(red, green, blue, 1.0f); // Generate a random position in front of the viewer for the mode. m_ModelInfoList[i].positionX = (((float)rand()-(float)rand())/RAND_MAX) * 10.0f; m_ModelInfoList[i].positionY = (((float)rand()-(float)rand())/RAND_MAX) * 10.0f; m_ModelInfoList[i].positionZ = ((((float)rand()-(float)rand())/RAND_MAX) * 10.0f) + 5.0f; } return true; } The Shutdown function releases the model information list array. void ModelListClass::Shutdown() { // Release the model information list. if(m_ModelInfoList) { delete [] m_ModelInfoList; m_ModelInfoList = 0; } return; } GetModelCount returns the number of models that this class maintains information about. int ModelListClass::GetModelCount() {
11 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
return m_modelCount; } The GetData function extracts the position and color of a sphere at the given input index location. void ModelListClass::GetData(int index, float& positionX, float& positionY, float& positionZ, D3DXVECTOR4& color) { positionX = m_ModelInfoList[index].positionX; positionY = m_ModelInfoList[index].positionY; positionZ = m_ModelInfoList[index].positionZ; color = m_ModelInfoList[index].color; return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; The GraphicsClass for this tutorial includes a number of class we have used in the previous tutorials. It also includes the frustumclass.h and modellistclass.h header which are new. /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "textclass.h"
12 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
#include "modelclass.h" #include "lightshaderclass.h" #include "lightclass.h" #include "modellistclass.h" #include "frustumclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(float); bool Render(); private: Two of the new private class objects are the m_Frustum and m_ModelList. D3DClass* m_D3D; CameraClass* m_Camera; TextClass* m_Text; ModelClass* m_Model; LightShaderClass* m_LightShader; LightClass* m_Light; ModelListClass* m_ModelList; FrustumClass* m_Frustum; }; #endif
Graphicsclass.cpp
I will just cover the functions that have changed since the previous tutorials.
13 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" The class constructor initializes the private member variables to null. GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Text = 0; m_Model = 0; m_LightShader = 0; m_Light = 0; m_ModelList = 0; m_Frustum = 0; }
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; D3DXMATRIX baseViewMatrix;
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) {
14 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
return false; } // Initialize a base view matrix with the camera for 2D user interface rendering. m_Camera->SetPosition(0.0f, 0.0f, -1.0f); m_Camera->Render(); m_Camera->GetViewMatrix(baseViewMatrix); // Create the text object. m_Text = new TextClass; if(!m_Text) { return false; } // Initialize the text object. result = m_Text->Initialize(m_D3D->GetDevice(), m_D3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix); if(!result) { MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK); return false; } // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } We load a sphere model instead of a cube model for this tutorial. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds", "../Engine/data/sphere.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) { return false;
15 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
} // Initialize the light shader object. result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } // Initialize the light object. m_Light->SetDirection(0.0f, 0.0f, 1.0f); Here we create the new ModelListClass object and have it create 25 randomly placed/colored sphere models. // Create the model list object. m_ModelList = new ModelListClass; if(!m_ModelList) { return false; } // Initialize the model list object. result = m_ModelList->Initialize(25); if(!result) { MessageBox(hwnd, L"Could not initialize the model list object.", L"Error", MB_OK); return false; } Here we create the new FrustumClass object. It doesn't need any initialization since that is done every frame using the ConstructFrustum function. // Create the frustum object. m_Frustum = new FrustumClass; if(!m_Frustum) { return false; }
16 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
return true; }
void GraphicsClass::Shutdown() { We release the new FrustumClass and ModelListClass objects here in the Shutdown function. // Release the frustum object. if(m_Frustum) { delete m_Frustum; m_Frustum = 0; } // Release the model list object. if(m_ModelList) { m_ModelList->Shutdown(); delete m_ModelList; m_ModelList = 0; } // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown(); delete m_LightShader; m_LightShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model;
17 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
m_Model = 0; } // Release the text object. if(m_Text) { m_Text->Shutdown(); delete m_Text; m_Text = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the Direct3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } The Frame function now takes in the rotation of the camera from the SystemClass that calls it. The position and rotation of the camera are then set so the view matrix can be properly updated in the Render function. bool GraphicsClass::Frame(float rotationY) { // Set the position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Set the rotation of the camera. m_Camera->SetRotation(0.0f, rotationY, 0.0f); return true; }
bool GraphicsClass::Render()
18 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
{ D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix; int modelCount, renderCount, index; float positionX, positionY, positionZ, radius; D3DXVECTOR4 color; bool renderModel, result;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); The major change to the Render function is that we now construct the viewing frustum each frame based on the updated viewing matrix. This construction has to occur each time the view matrix changes or the frustum culling checks we do will not be correct. // Construct the frustum. m_Frustum->ConstructFrustum(SCREEN_DEPTH, projectionMatrix, viewMatrix); // Get the number of models that will be rendered. modelCount = m_ModelList->GetModelCount(); // Initialize the count of models that have been rendered. renderCount = 0; Now loop through all the models in the ModelListClass object. // Go through all the models and render them only if they can be seen by the camera view. for(index=0; index<modelCount; index++) { // Get the position and color of the sphere model at this index. m_ModelList->GetData(index, positionX, positionY, positionZ, color); // Set the radius of the sphere to 1.0 since this is already known. radius = 1.0f; Here is where we use the new FrustumClass object. We check if the sphere is viewable in the viewing frustum. If it can be seen we render it, if it cannot be seen we skip it and check the next one. This is where we will gain all the speed by using frustum culling.
19 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
// Check if the sphere model is in the view frustum. renderModel = m_Frustum->CheckSphere(positionX, positionY, positionZ, radius); // If it can be seen then render it, if not skip this model and check the next sphere. if(renderModel) { // Move the model to the location it should be rendered at. D3DXMatrixTranslation(&worldMatrix, positionX, positionY, positionZ); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the model using the light shader. m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), color); // Reset to the original world matrix. m_D3D->GetWorldMatrix(worldMatrix); // Since this model was rendered then increase the count for this frame. renderCount++; } } We use the slightly modified TextClass to display how many spheres were actually rendered. We can also infer for this number that the spheres that were not rendered were instead culled using the new FrustumClass object. // Set the number of models that was actually rendered this frame. result = m_Text->SetRenderCount(renderCount, m_D3D->GetDeviceContext()); if(!result) { return false; } // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Turn on the alpha blending before rendering the text. m_D3D->TurnOnAlphaBlending(); // Render the text string of the render count. m_Text->Render(m_D3D->GetDeviceContext(), worldMatrix, orthoMatrix); if(!result) { return false;
20 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
} // Turn off alpha blending after rendering the text. m_D3D->TurnOffAlphaBlending(); // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Positionclass.h
To allow for camera movement by using the left and right arrow key in this tutorial we create a new class to calculate and maintain the position of the viewer. This class will only handle turning left and right for now but can be expanded to maintain all different movement changes. The movement also includes acceleration and deceleration to create a smooth camera effect. //////////////////////////////////////////////////////////////////////////////// // Filename: positionclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _POSITIONCLASS_H_ #define _POSITIONCLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: PositionClass //////////////////////////////////////////////////////////////////////////////// class PositionClass { public: PositionClass(); PositionClass(const PositionClass&); ~PositionClass();
21 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
void SetFrameTime(float); void GetRotation(float&); void TurnLeft(bool); void TurnRight(bool); private: float m_frameTime; float m_rotationY; float m_leftTurnSpeed, m_rightTurnSpeed; }; #endif
Positionclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: positionclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "positionclass.h" The class constructor initializes the private member variables to zero to start with. PositionClass::PositionClass() { m_frameTime = 0.0f; m_rotationY = 0.0f; m_leftTurnSpeed = 0.0f; m_rightTurnSpeed = 0.0f; }
PositionClass::~PositionClass() { }
22 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
The SetFrameTime function is used to set the frame speed in this class. PositionClass will use that frame time speed to calculate how fast the viewer should be moving and rotating. This function should always be called at the beginning of each frame before using this class to move the viewing position. void PositionClass::SetFrameTime(float time) { m_frameTime = time; return; } GetRotation returns the Y-axis rotation of the viewer. This is the only helper function we need for this tutorial but could be expanded to get more information about the location of the viewer. void PositionClass::GetRotation(float& y) { y = m_rotationY; return; } The movement functions both work the same. Both functions are called each frame. The keydown input variable to each function indicates if the user is pressing the left key or the right key. If they are pressing the key then each frame the speed will accelerate until it hits a maximum. This way the camera speeds up similar to the acceleration in a vehicle creating the effect of smooth movement and high responsiveness. Likewise if the user releases the key and the keydown variable is false it will then smoothly slow down each frame until the speed hits zero. The speed is calculated against the frame time to ensure the movement speed remains the same regardless of the frame rate. Each function then uses some basic math to calculate the new position of the camera. void PositionClass::TurnLeft(bool keydown) { // If the key is pressed increase the speed at which the camera turns left. If not slow down the turn speed. if(keydown) { m_leftTurnSpeed += m_frameTime * 0.01f; if(m_leftTurnSpeed > (m_frameTime * 0.15f)) { m_leftTurnSpeed = m_frameTime * 0.15f; } } else { m_leftTurnSpeed -= m_frameTime* 0.005f; if(m_leftTurnSpeed < 0.0f) { m_leftTurnSpeed = 0.0f; } }
23 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
// Update the rotation using the turning speed. m_rotationY -= m_leftTurnSpeed; if(m_rotationY < 0.0f) { m_rotationY += 360.0f; } return; }
void PositionClass::TurnRight(bool keydown) { // If the key is pressed increase the speed at which the camera turns right. If not slow down the turn speed. if(keydown) { m_rightTurnSpeed += m_frameTime * 0.01f; if(m_rightTurnSpeed > (m_frameTime * 0.15f)) { m_rightTurnSpeed = m_frameTime * 0.15f; } } else { m_rightTurnSpeed -= m_frameTime* 0.005f; if(m_rightTurnSpeed < 0.0f) { m_rightTurnSpeed = 0.0f; } } // Update the rotation using the turning speed. m_rotationY += m_rightTurnSpeed; if(m_rotationY > 360.0f) { m_rotationY -= 360.0f; } return; }
24 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
Systemclass.h
The SystemClass has been modified to use the new PostionClass. //////////////////////////////////////////////////////////////////////////////// // Filename: systemclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _SYSTEMCLASS_H_ #define _SYSTEMCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "inputclass.h" #include "graphicsclass.h" #include "timerclass.h" #include "positionclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: SystemClass //////////////////////////////////////////////////////////////////////////////// class SystemClass { public: SystemClass(); SystemClass(const SystemClass&); ~SystemClass();
25 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
bool Initialize(); void Shutdown(); void Run(); LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM); private: bool Frame(); void InitializeWindows(int&, int&); void ShutdownWindows(); private: LPCWSTR m_applicationName; HINSTANCE m_hinstance; HWND m_hwnd; InputClass* m_Input; GraphicsClass* m_Graphics; TimerClass* m_Timer; PositionClass* m_Position; };
///////////////////////// // FUNCTION PROTOTYPES // ///////////////////////// static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
#endif
Systemclass.cpp
I will cover just the functions that have changed in this class since the previous tutorials. ////////////////////////////////////////////////////////////////////////////////
26 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
SystemClass::SystemClass() { m_Input = 0; m_Graphics = 0; m_Timer = 0; The new PositionClass object is initialized to null in the class constructor. m_Position = 0; }
// Initialize the width and height of the screen to zero before sending the variables into the function. screenWidth = 0; screenHeight = 0; // Initialize the windows api. InitializeWindows(screenWidth, screenHeight); // Create the input object. This object will be used to handle reading the keyboard input from the user. m_Input = new InputClass; if(!m_Input) { return false; } // Initialize the input object. result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight); if(!result) { MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK); return false; }
27 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
// Create the graphics object. This object will handle rendering all the graphics for this application. m_Graphics = new GraphicsClass; if(!m_Graphics) { return false; } // Initialize the graphics object. result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd); if(!result) { return false; } // Create the timer object. m_Timer = new TimerClass; if(!m_Timer) { return false; } // Initialize the timer object. result = m_Timer->Initialize(); if(!result) { MessageBox(m_hwnd, L"Could not initialize the Timer object.", L"Error", MB_OK); return false; } Create the new PositionClass object here. It doesn't require any initialization. // Create the position object. m_Position = new PositionClass; if(!m_Position) { return false; } return true; }
void SystemClass::Shutdown() {
28 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
The PositionClass object is released here in the Shutdown function. // Release the position object. if(m_Position) { delete m_Position; m_Position = 0; } // Release the timer object. if(m_Timer) { delete m_Timer; m_Timer = 0; } // Release the graphics object. if(m_Graphics) { m_Graphics->Shutdown(); delete m_Graphics; m_Graphics = 0; } // Release the input object. if(m_Input) { m_Input->Shutdown(); delete m_Input; m_Input = 0; } // Shutdown the window. ShutdownWindows(); return; }
29 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
// Update the system stats. m_Timer->Frame(); // Do the input frame processing. result = m_Input->Frame(); if(!result) { return false; } During each frame the PositionClass object is update with the frame time. // Set the frame time for calculating the updated position. m_Position->SetFrameTime(m_Timer->GetTime()); After the frame time update the PositionClass movement functions can be updated with the current state of the keyboard. The movement functions will update the position of the camera to the new location for this frame. // Check if the left or right arrow key has been pressed, if so rotate the camera accordingly. keyDown = m_Input->IsLeftArrowPressed(); m_Position->TurnLeft(keyDown); keyDown = m_Input->IsRightArrowPressed(); m_Position->TurnRight(keyDown); The new rotation of the camera is retrieved and sent to the Graphics::Frame function to update the camera position. // Get the current view point rotation. m_Position->GetRotation(rotationY); // Do the frame processing for the graphics object. result = m_Graphics->Frame(rotationY); if(!result) { return false; } // Finally render the graphics to the screen. result = m_Graphics->Render(); if(!result) { return false; } return true;
30 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
Summary
Now you have seen how to cull objects. The only trick from here is determining whether a cube, rectangle, sphere, or clever use of a point is better for culling your different objects.
31 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut16.html
To Do Exercises
1. Recompile and run the program. Use the left and right arrow key to move the camera and update the render count in the upper left corner. 2. Load the cube model instead and change the cull check to CheckCube. 3. Create some different models and test which of the culling checks works best for them.
Source Code
Visual Studio 2008 Project: dx11tut16.zip Source Only: dx11src16.zip Executable Only: dx11exe16.zip
32 of 32
3/8/2013 12:34 PM
http://www.rastertek.com/dx11tut17.html
The second texture we will use to combine with the first one will be called the color texture. It looks like the following:
1 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
These two textures will be combined in the pixel shader on a pixel by pixel basis. The blending equation we will use will be the following: blendColor = basePixel * colorPixel * gammaCorrection; Using that equation and the two textures we will get the following result:
Now you may be wondering why I didn't just add together the average of the pixels such as the following: blendColor = (basePixel * 0.5) + (colorPixel * 0.5); The reason being is that the pixel color presented to us has been corrected to the gamma of the monitor. This makes the pixel values from 0.0 to 1.0 follow a
2 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
non-linear curve. Therefore we need gamma correction when working in the pixel shader to deal with non-linear color values. If we don't correct for gamma and just do the average addition function we get a washed out result such as this:
Also note that most devices have different gamma values and most require a look up table or a gamma slider so the user can choose the gamma settings for their device. In this example I just choose 2.0 as my gamma value to make the tutorial simple. We'll start the code section by first looking at the new multitexture shader which was originally based on the texture shader file with some slight changes.
Multitexture.vs
The only change to the vertex shader is the name. //////////////////////////////////////////////////////////////////////////////// // Filename: multitexture.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix;
3 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
};
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Multitexture.ps
4 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
///////////// // GLOBALS // ///////////// We have added a two element texture array resource here for the two different textures that will be blended together. Texture arrays are more efficient that using single texture resources in terms of performance on the graphics card. Switching textures was very costly in earlier versions of DirectX forcing most engines to be written around texture and material switches. Texture arrays help reduce that performance cost. Texture2D shaderTextures[2]; SamplerState SampleType;
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; The pixel shader is where all the work of this tutorial is done. We take a sample of the pixel from both textures at this current texture coordinate. After that we combine them using multiplication since they are non-linear due to gamma correction. We also multiply by a gamma value, we have used 2.0 in this example as it is close to most monitor's gamma value. Once we have the blended pixel we saturate it and then return it as our final result. Notice also the indexing method used to access the two textures in the texture array. //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 MultiTexturePixelShader(PixelInputType input) : SV_TARGET { float4 color1; float4 color2; float4 blendColor;
// Get the pixel color from the first texture. color1 = shaderTextures[0].Sample(SampleType, input.tex); // Get the pixel color from the second texture.
5 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
color2 = shaderTextures[1].Sample(SampleType, input.tex); // Blend the two pixels together and multiply by the gamma value. blendColor = color1 * color2 * 2.0; // Saturate the final color. blendColor = saturate(blendColor); return blendColor; }
Multitextureshaderclass.h
The multitexture shader code is based on the TextureShaderClass with some slight modifications. //////////////////////////////////////////////////////////////////////////////// // Filename: multitextureshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MULTITEXTURESHADERCLASS_H_ #define _MULTITEXTURESHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: MultiTextureShaderClass //////////////////////////////////////////////////////////////////////////////// class MultiTextureShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view;
6 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
D3DXMATRIX projection; }; public: MultiTextureShaderClass(); MultiTextureShaderClass(const MultiTextureShaderClass&); ~MultiTextureShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; }; #endif
Multitextureshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: multitextureshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "multitextureshaderclass.h"
7 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
MultiTextureShaderClass::~MultiTextureShaderClass() { }
bool MultiTextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; The multitexture HLSL shader files are loaded here in the Initialize function. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/multitexture.vs", L"../Engine/multitexture.ps"); if(!result) { return false; } return true; } Shutdown calls the ShutdownShader function to release the shader related interfaces. void MultiTextureShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes as input a pointer to the texture array. This will give the shader access to the two textures for blending operations. bool MultiTextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix,
8 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; } The InitializeShader function loads the vertex and pixel shader as well as setting up the layout, matrix buffer, and sample state. bool MultiTextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; The multitexture vertex shader is loaded here. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "MultiTextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message.
9 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } The multitexture pixel shader is loaded here. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "MultiTexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
10 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the matrix dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
11 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); 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, &m_sampleState); if(FAILED(result)) { return false; } return true; } ShutdownShader releases all the interfaces that were setup in the InitializeShader function. void MultiTextureShaderClass::ShutdownShader() { // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0;
12 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
} // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; } The OutputShaderErrorMessage function writes out an error to a file if there is an issue compiling the vertex or pixel shader HLSL files. void MultiTextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
13 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
// Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } SetShaderParameters sets the matrices and texture array in the shader before rendering. bool MultiTextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result))
14 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
{ return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); Here is where the texture array is set before rendering. The PSSetShaderResources function is used to set the texture array. The first parameter is where to start in the array. The second parameter is how many textures are in the array that is being passed in. And the third parameter is a pointer to the texture array. // Set shader texture array resource in the pixel shader. deviceContext->PSSetShaderResources(0, 2, textureArray); return true; } The RenderShader function sets the layout, shaders, and sampler. It then draws the model using the shader. void MultiTextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangles.
15 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
Texturearrayclass.h
The TextureArrayClass replaces the TextureClass that was used before. Instead of having just a single texture it can now have multiple textures and give calling objects access to those textures. For this tutorial it just handles two textures but it can easily be expanded. //////////////////////////////////////////////////////////////////////////////// // Filename: texturearrayclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTUREARRAYCLASS_H_ #define _TEXTUREARRAYCLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: TextureArrayClass //////////////////////////////////////////////////////////////////////////////// class TextureArrayClass { public: TextureArrayClass(); TextureArrayClass(const TextureArrayClass&); ~TextureArrayClass(); bool Initialize(ID3D11Device*, WCHAR*, WCHAR*); void Shutdown(); ID3D11ShaderResourceView** GetTextureArray(); private: This is the two element texture array private variable.
16 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
Texturearrayclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: texturearrayclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "texturearrayclass.h" The class constructor initializes the texture array elements to null. TextureArrayClass::TextureArrayClass() { m_textures[0] = 0; m_textures[1] = 0; }
TextureArrayClass::~TextureArrayClass() { } Initialize takes in the two texture file names and creates two texture resources in the texture array from those files. bool TextureArrayClass::Initialize(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2) { HRESULT result;
// Load the first texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename1, NULL, NULL, &m_textures[0], NULL); if(FAILED(result)) { return false;
17 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
} // Load the second texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename2, NULL, NULL, &m_textures[1], NULL); if(FAILED(result)) { return false; } return true; } The Shutdown function releases each element in the texture array. void TextureArrayClass::Shutdown() { // Release the texture resources. if(m_textures[0]) { m_textures[0]->Release(); m_textures[0] = 0; } if(m_textures[1]) { m_textures[1]->Release(); m_textures[1] = 0; } return; } GetTextureArray returns a pointer to the texture array so calling objects can have access to the textures in the texture array. ID3D11ShaderResourceView** TextureArrayClass::GetTextureArray() { return m_textures; }
Modelclass.h
ModelClass has been modified to now use texture arrays.
18 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <fstream> using namespace std; The TextureArrayClass header file is included instead of the previous TextureClass header file. /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "texturearrayclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private: struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; struct ModelType { float x, y, z; float tu, tv; float nx, ny, nz; }; public: ModelClass(); ModelClass(const ModelClass&);
19 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
~ModelClass(); bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView** GetTextureArray(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*); void ReleaseTextures(); bool LoadModel(char*); void ReleaseModel(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; ModelType* m_model; We now have a TextureArrayClass variable instead of a TextureClass variable. TextureArrayClass* m_TextureArray; }; #endif
Modelclass.cpp
I will just cover the functions that have changed since the previous tutorials. //////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
20 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
ModelClass::ModelClass() { m_vertexBuffer = 0; m_indexBuffer = 0; m_model = 0; We initialize the new TextureArray variable in the class constructor. m_TextureArray = 0; }
bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2) { bool result;
// Load in the model data, result = LoadModel(modelFilename); if(!result) { return false; } // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } We call the LoadTextures function which takes in multiple file names for textures that will be loaded into the texture array and rendered as a blended result on this model. // Load the textures for this model. result = LoadTextures(device, textureFilename1, textureFilename2); if(!result) { return false; } return true; }
21 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
void ModelClass::Shutdown() { ReleaseTextures is called to release the texture array in the Shutdown function. // Release the model textures. ReleaseTextures(); // Shutdown the vertex and index buffers. ShutdownBuffers(); // Release the model data. ReleaseModel(); return; } We have a new function called GetTextureArray which gives calling objects access to the texture array that is used for rendering this model. ID3D11ShaderResourceView** ModelClass::GetTextureArray() { return m_TextureArray->GetTextureArray(); } LoadTextures has been changed to create a TextureArrayClass object and then initialize it by loading in the two textures that are given as input to this function. bool ModelClass::LoadTextures(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2) { bool result;
// Create the texture array object. m_TextureArray = new TextureArrayClass; if(!m_TextureArray) { return false; } // Initialize the texture array object. result = m_TextureArray->Initialize(device, filename1, filename2); if(!result) { return false; }
22 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
return true; } The ReleaseTextures function releases the TextureArrayClass object. void ModelClass::ReleaseTextures() { // Release the texture array object. if(m_TextureArray) { m_TextureArray->Shutdown(); delete m_TextureArray; m_TextureArray = 0; } return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h"
23 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
The header for the MultiTextureShaderClass is now included in the GraphicsClass. #include "multitextureshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; Here we create the new MultiTextureShaderClass object. MultiTextureShaderClass* m_MultiTextureShader; }; #endif
Graphicsclass.cpp
We will cover just the functions that have changed since the previous tutorials. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
24 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; We initialize the new multitexture shader object to null in the class constructor. m_MultiTextureShader = 0; }
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; D3DXMATRIX baseViewMatrix;
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Initialize a base view matrix with the camera for 2D user interface rendering. m_Camera->SetPosition(0.0f, 0.0f, -1.0f); m_Camera->Render(); m_Camera->GetViewMatrix(baseViewMatrix);
25 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
// Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } The ModelClass object is now initialized differently. For this tutorial we load in the square.txt model as the effect we want to display works best on just a plain square. We also now load in two textures for the texture array instead of just a single texture like we did in previous tutorials. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/stone01.dds", L"../Engine/data/dirt01.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } Here we create and initialize the new multitexture shader object. // Create the multitexture shader object. m_MultiTextureShader = new MultiTextureShaderClass; if(!m_MultiTextureShader) { return false; } // Initialize the multitexture shader object. result = m_MultiTextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the multitexture shader object.", L"Error", MB_OK); return false; } return true; }
26 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
// Release the multitexture shader object. if(m_MultiTextureShader) { m_MultiTextureShader->Shutdown(); delete m_MultiTextureShader; m_MultiTextureShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
bool GraphicsClass::Frame() { We set the position of the camera a bit closer to see the blending effect more clearly. // Set the position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -5.0f); return true; }
27 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and D3D objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); We use the new multitexture shader to render the model. Notice that we send in the texture array from the ModelClass as input to the shader. // Render the model using the multitexture shader. m_MultiTextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTextureArray()); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
We now have a shader that will combine two textures evenly and apply gamma correction. We also now know how to use texture arrays for improved graphics performance.
28 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
To Do Exercises
1. Recompile the code and run the program to see the resulting image. Press escape to quit. 2. Replace the two textures with two new ones to see the results.
Source Code
Visual Studio 2008 Project: dx11tut17.zip Source Only: dx11src17.zip Executable Only: dx11exe17.zip
29 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut17.html
30 of 30
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
The second texture we need is the light map. Usually this is just a black and white texture with white representing the intensity of the light at each pixel. I created a spotlight style light map that we will use in this tutorial:
1 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
Once we have our color texture and our light map we can combine them in the pixel shader to produce the light mapped texture. The shader is very simple as we just multiply the two pixels together which will produce the following output:
Lightmap.vs
The light map vertex shader is the same as the multitexture vertex shader from the previous tutorial. The only thing that has changed is the name. ////////////////////////////////////////////////////////////////////////////////
2 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix);
3 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Lightmap.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: lightmap.ps ////////////////////////////////////////////////////////////////////////////////
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; The light map pixel shader is very simple. It multiplies the color texture pixel and the light map texture value to get the desired output. This is not much different from just a regular multitexture blend other than there is no need to correct for gamma. //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 LightMapPixelShader(PixelInputType input) : SV_TARGET { float4 color; float4 lightColor; float4 finalColor;
4 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
// Get the pixel color from the color texture. color = shaderTextures[0].Sample(SampleType, input.tex); // Get the pixel color from the light map. lightColor = shaderTextures[1].Sample(SampleType, input.tex); // Blend the two pixels together. finalColor = color * lightColor; return finalColor; }
Lightmapshaderclass.h
The LightMapShaderClass is just the MultiTextureShaderClass from the previous that has now been updated for light mapping. //////////////////////////////////////////////////////////////////////////////// // Filename: lightmapshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTMAPSHADERCLASS_H_ #define _LIGHTMAPSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
5 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: LightMapShaderClass(); LightMapShaderClass(const LightMapShaderClass&); ~LightMapShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; }; #endif
Lightmapshaderclass.cpp
I will just go over the changes since the previous tutorial. Other than the name of the functions there are only a couple changes. //////////////////////////////////////////////////////////////////////////////// // Filename: lightmapshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightmapshaderclass.h"
6 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
LightMapShaderClass::~LightMapShaderClass() { }
bool LightMapShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; We now load in the lightmap.vs and lightmap.ps HLSL shader files. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/lightmap.vs", L"../Engine/lightmap.ps"); if(!result) { return false; } return true; }
void LightMapShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return;
7 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
bool LightMapShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool LightMapShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; The light map vertex shader is loaded here. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightMapVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL);
8 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } The light map pixel shader is loaded here. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightMapPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; }
9 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
// Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the matrix dynamic constant buffer that is in the vertex shader.
10 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); 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, &m_sampleState); if(FAILED(result)) { return false; } return true; }
11 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void LightMapShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
12 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
// Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool LightMapShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result))
13 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
{ return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); The texture array is set the same as the previous tutorial. However instead of two color textures there is now just one color texture and the second texture in the array is a light map. // Set shader texture array resource in the pixel shader. deviceContext->PSSetShaderResources(0, 2, textureArray); return true; }
void LightMapShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangles. deviceContext->DrawIndexed(indexCount, 0, 0);
14 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" The header for the LightMapShaderClass is now included in the GraphicsClass header file. #include "lightmapshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&);
15 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; We have a new variable for the LightMapShaderClass object. LightMapShaderClass* m_LightMapShader; }; #endif
Graphicsclass.cpp
I will just cover the functions that have changed since the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; The new LightMapShaderClass object is initialized in the class constructor. m_LightMapShader = 0; }
16 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Initialize a base view matrix with the camera for 2D user interface rendering. m_Camera->SetPosition(0.0f, 0.0f, -1.0f); m_Camera->Render(); m_Camera->GetViewMatrix(baseViewMatrix); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } The ModelClass object takes as input the new light01.dds light map texture for the light map shading on this model. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/stone01.dds", L"../Engine/data/light01.dds"); if(!result)
17 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
{ MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } The new LightMapShaderClass object is created and initialized here. // Create the light map shader object. m_LightMapShader = new LightMapShaderClass; if(!m_LightMapShader) { return false; } // Initialize the light map shader object. result = m_LightMapShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light map shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { The LightMapShaderClass object is released here in the Shutdown function. // Release the light map shader object. if(m_LightMapShader) { m_LightMapShader->Shutdown(); delete m_LightMapShader; m_LightMapShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0;
18 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
} // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and D3D objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); The model is rendered here using the light map shader. // Render the model using the light map shader. m_LightMapShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
19 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
m_Model->GetTextureArray()); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
This tutorial isn't much different than the previous blending tutorial but produces a very useful effect that can be very efficient in terms of processing speed.
To Do Exercises
1. Recompile the code and ensure you get a light mapped texture on the screen.
20 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut18.html
2. Create some of your own light maps and try them out. 3. Multiply the final output pixel in the pixel shader by 2.0. Notice you can create stronger and softer lighting effects by doing this.
Source Code
Visual Studio 2008 Project: dx11tut18.zip Source Only: dx11src18.zip Executable Only: dx11exe18.zip
21 of 21
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
Each pixel is just a 0.0 to 1.0 float range indicating how to combine two textures. For example if the alpha value at a certain pixel is 0.3 you would take 30% of the base texture pixel value and combine it with 70% of the color texture pixel. In this tutorial we will combine the following two textures based on the alpha map:
1 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
In this tutorial I will be separating the alpha map onto its own individual texture. This gives us the ability to create several alpha maps and then combine the same two color and base textures in many different ways. The code in this tutorial is based on the previous tutorial.
Alphamap.vs
2 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
The alpha map vertex shader is just the light map shader renamed from the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: alphamap.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f;
3 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
// Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Alphamap.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: alphamap.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// The first change to the pixel shader is the addition of a third element in the texture array for holding the alpha map texture. Texture2D shaderTextures[3]; SamplerState SampleType;
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; In the alpha map pixel shader we first take a sample of the pixel from the two color textures and alpha texture. Then we multiply the alpha value by the base color to get the pixel value for the base texture. After that we multiply the inverse of the alpha (1.0 - alpha) by the second color texture to get the pixel value for the second texture. We then add the two pixel values together and saturate to produce the final blended pixel. ////////////////////////////////////////////////////////////////////////////////
4 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
// Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 AlphaMapPixelShader(PixelInputType input) : SV_TARGET { float4 color1; float4 color2; float4 alphaValue; float4 blendColor;
// Get the pixel color from the first texture. color1 = shaderTextures[0].Sample(SampleType, input.tex); // Get the pixel color from the second texture. color2 = shaderTextures[1].Sample(SampleType, input.tex); // Get the alpha value from the alpha map texture. alphaValue = shaderTextures[2].Sample(SampleType, input.tex); // Combine the two textures based on the alpha value. blendColor = (alphaValue * color1) + ((1.0 - alphaValue) * color2); // Saturate the final color value. blendColor = saturate(blendColor); return blendColor; }
Alphamapshaderclass.h
The AlphaMapShaderClass is the LightMapShaderClass slightly modified from the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: alphamapshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _ALPHAMAPSHADERCLASS_H_ #define _ALPHAMAPSHADERCLASS_H_
5 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
#include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: AlphaMapShaderClass //////////////////////////////////////////////////////////////////////////////// class AlphaMapShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: AlphaMapShaderClass(); AlphaMapShaderClass(const AlphaMapShaderClass&); ~AlphaMapShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; };
6 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
#endif
Alphamapshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: alphamapshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "alphamapshaderclass.h"
AlphaMapShaderClass::~AlphaMapShaderClass() { }
bool AlphaMapShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; The first change is that the alphamap.vs and alphamap.ps HLSL shader files are now loaded. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/alphamap.vs", L"../Engine/alphamap.ps"); if(!result) { return false; }
7 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
return true; }
void AlphaMapShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
bool AlphaMapShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool AlphaMapShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc;
8 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; The alpha map vertex shader is loaded here. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "AlphaMapVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } The alpha map pixel shader is loaded here. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "AlphaMapPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); }
9 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) {
10 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the matrix dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); 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, &m_sampleState); if(FAILED(result)) { return false;
11 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
} return true; }
void AlphaMapShaderClass::ShutdownShader() { // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
12 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
void AlphaMapShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool AlphaMapShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr;
13 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); The next major change is that we now set three textures in the shader instead of two like in the previous tutorials. // Set shader texture array resource in the pixel shader. deviceContext->PSSetShaderResources(0, 3, textureArray); return true; }
void AlphaMapShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout);
14 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
// Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangles. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Texturearrayclass.h
The TextureArrayClass has been changed to handle three textures instead of two. //////////////////////////////////////////////////////////////////////////////// // Filename: texturearrayclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTUREARRAYCLASS_H_ #define _TEXTUREARRAYCLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: TextureArrayClass //////////////////////////////////////////////////////////////////////////////// class TextureArrayClass { public: TextureArrayClass(); TextureArrayClass(const TextureArrayClass&); ~TextureArrayClass();
15 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
bool Initialize(ID3D11Device*, WCHAR*, WCHAR*, WCHAR*); void Shutdown(); ID3D11ShaderResourceView** GetTextureArray(); private: The number of elements in the texture array is changed to three. ID3D11ShaderResourceView* m_textures[3]; }; #endif
Texturearrayclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: texturearrayclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "texturearrayclass.h" The three textures are initialized to null in the class constructor. TextureArrayClass::TextureArrayClass() { m_textures[0] = 0; m_textures[1] = 0; m_textures[2] = 0; }
TextureArrayClass::~TextureArrayClass() { } The Initialize function now loads three textures into the texture array.
16 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
bool TextureArrayClass::Initialize(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2, WCHAR* filename3) { HRESULT result;
// Load the first texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename1, NULL, NULL, &m_textures[0], NULL); if(FAILED(result)) { return false; } // Load the second texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename2, NULL, NULL, &m_textures[1], NULL); if(FAILED(result)) { return false; } // Load the third texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename3, NULL, NULL, &m_textures[2], NULL); if(FAILED(result)) { return false; } return true; } Shutdown now releases three textures. void TextureArrayClass::Shutdown() { // Release the texture resources. if(m_textures[0]) { m_textures[0]->Release(); m_textures[0] = 0; } if(m_textures[1]) { m_textures[1]->Release(); m_textures[1] = 0; }
17 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
Modelclass.h
The ModelClass has been modified just slightly to handle three textures instead of two. //////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <fstream> using namespace std;
18 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private: struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; struct ModelType { float x, y, z; float tu, tv; float nx, ny, nz; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView** GetTextureArray(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*, WCHAR*); void ReleaseTextures(); bool LoadModel(char*); void ReleaseModel(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount;
19 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
Modelclass.cpp
I will only cover the functions that have changed since the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h" The Initialize function now takes as input three texture names. The first two are the color texture and the third is the alpha texture. bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2, WCHAR* textureFilename3) { bool result;
// Load in the model data, result = LoadModel(modelFilename); if(!result) { return false; } // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } LoadTextures now takes the three texture names as input. // Load the textures for this model. result = LoadTextures(device, textureFilename1, textureFilename2, textureFilename3); if(!result)
20 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
{ return false; } return true; } The LoadTextures function now takes the three texture file names as input and then creates and loads a texture array using the three texture files. Once again the first two textures are the color textures and the third is the alpha texture. bool ModelClass::LoadTextures(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2, WCHAR* filename3) { bool result;
// Create the texture array object. m_TextureArray = new TextureArrayClass; if(!m_TextureArray) { return false; } // Initialize the texture array object. result = m_TextureArray->Initialize(device, filename1, filename2, filename3); if(!result) { return false; } return true; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////
21 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
// GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" The new AlphaMapShaderClass header is now included in the GraphicsClass header file. #include "alphamapshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; We create the new AlphaMapShaderClass object here. AlphaMapShaderClass* m_AlphaMapShader; };
22 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
#endif
Graphicsclass.cpp
I will only cover the functions that have changed since the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; The new AlphaMapShaderClass object is initialized to null in the class constructor. m_AlphaMapShader = 0; }
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; D3DXMATRIX baseViewMatrix;
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
23 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Initialize a base view matrix with the camera for 2D user interface rendering. m_Camera->SetPosition(0.0f, 0.0f, -1.0f); m_Camera->Render(); m_Camera->GetViewMatrix(baseViewMatrix); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } The ModelClass object is initialized with three textures. The first two textures are the color textures. The third input texture is the alpha texture that will be used to blend the first two textures. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/stone01.dds", L"../Engine/data/dirt01.dds", L"../Engine/data/alpha01.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } The new AlphaMapShaderClass object is created and initialized here. // Create the alpha map shader object. m_AlphaMapShader = new AlphaMapShaderClass; if(!m_AlphaMapShader) { return false; } // Initialize the alpha map shader object. result = m_AlphaMapShader->Initialize(m_D3D->GetDevice(), hwnd);
24 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
if(!result) { MessageBox(hwnd, L"Could not initialize the alpha map shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { The new AlphaMapShaderClass object is released here in the Shutdown function. // Release the alpha map shader object. if(m_AlphaMapShader) { m_AlphaMapShader->Shutdown(); delete m_AlphaMapShader; m_AlphaMapShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; }
25 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
return; }
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and D3D objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); The new AlphaMapShaderClass object is used to render the model object using alpha blending. // Render the model using the alpha map shader. m_AlphaMapShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTextureArray()); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
Alpha mapping provides an easy way of controlling on a very fine level of how to combine textures. Many terrain based applications use this to provide smooth transitions between different textures over a very large landscape.
26 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
To Do Exercises
1. Recompile and run the program to see the alpha mapped texture combination. Press escape to quit. 2. Make some of your own alpha maps and use them to combine the two textures in different ways.
Source Code
Visual Studio 2008 Project: dx11tut19.zip Source Only: dx11src19.zip Executable Only: dx11exe19.zip
27 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut19.html
28 of 28
3/8/2013 12:37 PM
http://www.rastertek.com/dx11tut20.html
A normal map for the above texture would look like the following:
1 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
Using the normal map with the current light direction for each pixel would then produce the following bump mapped texture:
As you can see the effect is very realistic and the cost of producing it using bump mapping is far less expensive then rendering a high polygon surface to get the same result. To create a normal map you usually need someone to produce a 3D model of the surface and then use a tool to convert that 3D model into a normal map. There are also certain tools that will work with 2D textures to produce a somewhat decent normal map but it is obviously not as accurate as the 3D model version would be. The tools that create normal maps take the x, y, z coordinates and translate them to red, green, blue pixels with the intensity of each color indicating the angle of the normal they represent. The normal of our polygon surface is still calculated the same way as before. However the two other normals we need to calculate require the vertex and texture coordinates for that polygon surface. These two normals are called the tangent and binormal. The diagram below shows the direction of each
2 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
normal:
The normal is still pointing straight out towards the viewer. The tangent and binormal however run across the surface of the polygon with the tangent going along the x-axis and the binormal going along the y-axis. These two normals then directly translate to the tu and tv texture coordinates of the normal map with the texture U coordinate mapping to the tangent and the texture V coordinate mapping to the binormal. We will need to do some precalculation to determine the binormal and tangent vector using the normal and texture coordinates. Also note that you should never do this inside the shader as it is fairly expensive with all the floating point math involved, I instead use a function in my C++ code that you will see to do this during the model loading. Also if you are looking to use this effect on a large number of high polygon models it may be best to precalculate these different normals and store them in your model format. Once we have precalculated the tangent and binormal we can use this equation to determine the bump normal at any pixel using the normal map: bumpNormal = normal + bumpMap.x * tangent + bumpMap.y * binormal; Once we have the normal for that pixel we can then calculate against the light direction and multiply by the color value of the pixel from the color texture to get our final result.
Framework
The frame work for this tutorial looks like the following. The only new class is the BumpMapShaderClass.
3 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
We will start the tutorial by looking at the bump map HLSL shader code:
Bumpmap.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmap.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; Both the VertexInputType and PixelInputType now have a tangent and binormal vector for bump map calculations. ////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0;
4 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; // Calculate the normal vector against the world matrix only and then normalize the final value. output.normal = mul(input.normal, (float3x3)worldMatrix); output.normal = normalize(output.normal); Both the input tangent and binormal are calculated against the world matrix and then normalized the same as the input normal vector. // Calculate the tangent vector against the world matrix only and then normalize the final value. output.tangent = mul(input.tangent, (float3x3)worldMatrix); output.tangent = normalize(output.tangent); // Calculate the binormal vector against the world matrix only and then normalize the final value.
5 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
Bumpmap.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmap.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// The bump map shader requires two textures. The first texture in the array is the color texture. The second texture is the normal map. Texture2D shaderTextures[2]; SamplerState SampleType; Just like most light shaders the direction and color of the light is required for lighting calculations. cbuffer LightBuffer { float4 diffuseColor; float3 lightDirection; };
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; };
6 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
The pixel shader works as we described above with a couple additional lines of code. First we sample the pixel from the color texture and the normal map. We then multiply the normal map value by two and then subtract one to move it into the -1.0 to +1.0 float range. We have to do this because the sampled value that is presented to us in the 0.0 to +1.0 texture range which only covers half the range we need for bump map normal calculations. After that we then calculate the bump normal which uses the equation we described earlier. This bump normal is normalized and then used to determine the light intensity at this pixel by doing a dot product with the light direction. Once we have the light intensity at this pixel the bump mapping is now done. We use the light intensity with the light color and texture color to get the final pixel color. //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 BumpMapPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float4 bumpMap; float3 bumpNormal; float3 lightDir; float lightIntensity; float4 color;
// Sample the texture pixel at this location. textureColor = shaderTextures[0].Sample(SampleType, input.tex); // Sample the pixel in the bump map. bumpMap = shaderTextures[1].Sample(SampleType, input.tex); // Expand the range of the normal value from (0, +1) to (-1, +1). bumpMap = (bumpMap * 2.0f) - 1.0f; // Calculate the normal from the data in the bump map. bumpNormal = input.normal + bumpMap.x * input.tangent + bumpMap.y * input.binormal; // Normalize the resulting bump normal. bumpNormal = normalize(bumpNormal); // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel based on the bump map normal value. lightIntensity = saturate(dot(bumpNormal, lightDir)); // Determine the final diffuse color based on the diffuse color and the amount of light intensity. color = saturate(diffuseColor * lightIntensity); // Combine the final bump light color with the texture color.
7 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
Bumpmapshaderclass.h
The BumpMapShaderClass is just a modified version of the shader classes from the previous tutorials. //////////////////////////////////////////////////////////////////////////////// // Filename: bumpmapshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _BUMPMAPSHADERCLASS_H_ #define _BUMPMAPSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: BumpMapShaderClass //////////////////////////////////////////////////////////////////////////////// class BumpMapShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; struct LightBufferType { D3DXVECTOR4 diffuseColor;
8 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
D3DXVECTOR3 lightDirection; float padding; }; public: BumpMapShaderClass(); BumpMapShaderClass(const BumpMapShaderClass&); ~BumpMapShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**, D3DXVECTOR3, D3DXVECTOR4); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**, D3DXVECTOR3, D3DXVECTOR4); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; The bump map shader will require a constant buffer to interface with the light direction and light color. ID3D11Buffer* m_lightBuffer; }; #endif
Bumpmapshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmapshaderclass.cpp ////////////////////////////////////////////////////////////////////////////////
9 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
#include "bumpmapshaderclass.h" The class constructor initializes the pointers to null. BumpMapShaderClass::BumpMapShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; m_sampleState = 0; m_lightBuffer = 0; }
BumpMapShaderClass::~BumpMapShaderClass() { } The Initialize function will call the shader to load the bump map HLSL files. bool BumpMapShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result;
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/bumpmap.vs", L"../Engine/bumpmap.ps"); if(!result) { return false; } return true; } Shutdown releases the shader effect. void BumpMapShaderClass::Shutdown() {
10 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
// Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function sets the shader parameters first and then renders the model using the bump map shader. bool BumpMapShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray, lightDirection, diffuseColor); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; } InitializeShader sets up the bump map shader. bool BumpMapShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; The polygon layout is now set to five elements to accommodate the tangent and binormal. D3D11_INPUT_ELEMENT_DESC polygonLayout[5]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC lightBufferDesc;
11 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; The bump map vertex shader is loaded here. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "BumpMapVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } The bump map pixel shader is loaded here. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "BumpMapPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); }
12 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; polygonLayout[2].SemanticName = "NORMAL"; polygonLayout[2].SemanticIndex = 0; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 0; polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0;
13 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
The layout now includes a tangent and binormal element which are setup the same as the normal element with the exception of the semantic name. polygonLayout[3].SemanticName = "TANGENT"; polygonLayout[3].SemanticIndex = 0; polygonLayout[3].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[3].InputSlot = 0; polygonLayout[3].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[3].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[3].InstanceDataStepRate = 0; polygonLayout[4].SemanticName = "BINORMAL"; polygonLayout[4].SemanticIndex = 0; polygonLayout[4].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[4].InputSlot = 0; polygonLayout[4].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[4].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[4].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the matrix dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class.
14 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); 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, &m_sampleState); if(FAILED(result)) { return false; } The light constant buffer is setup here. // Setup the description of the light dynamic constant buffer that is in the pixel shader. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; }
15 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
return true; } The ShutdownShader function releases all the pointers that were setup in the InitializeShader function. void BumpMapShaderClass::ShutdownShader() { // Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) {
16 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
m_vertexShader->Release(); m_vertexShader = 0; } return; } OutputShaderErrorMessage writes out errors to a text file if the HLSL shader file won't compile properly. void BumpMapShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } The SetShaderParameters function sets the shader parameters before rendering occurs.
17 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
bool BumpMapShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; LightBufferType* dataPtr2;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); The texture array is set here, it contains two textures. The first texture is the color texture and the second texture is the normal map. // Set shader texture array resource in the pixel shader. deviceContext->PSSetShaderResources(0, 2, textureArray);
18 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
The light buffer in the pixel shader is then set with the diffuse light color and light direction. // Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData; // Copy the lighting variables into the constant buffer. dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; // Unlock the constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer); return true; } RenderShader draws the model using the bump map shader. void BumpMapShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangles. deviceContext->DrawIndexed(indexCount, 0, 0);
19 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
return; }
Modelclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private: The VertexType structure has been changed to now have a tangent and binormal vector. struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; D3DXVECTOR3 normal; D3DXVECTOR3 tangent;
20 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
D3DXVECTOR3 binormal; }; The ModelType structure has also been changed to have a tangent and binormal vector. struct ModelType { float x, y, z; float tu, tv; float nx, ny, nz; float tx, ty, tz; float bx, by, bz; }; The following two structures will be used for calculating the tangent and binormal. struct TempVertexType { float x, y, z; float tu, tv; float nx, ny, nz; }; struct VectorType { float x, y, z; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView** GetTextureArray(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*);
21 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*); void ReleaseTextures(); bool LoadModel(char*); void ReleaseModel(); We have three new functions for calculating the tangent and binormal vectors for the model. void CalculateModelVectors(); void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&); void CalculateNormal(VectorType, VectorType, VectorType&); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; ModelType* m_model; TextureArrayClass* m_TextureArray; }; #endif
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h" The Initialize function now takes in two texture filenames. The first texture filename is for the color texture. The second texture filename is for the normal map that will be used to create the bump effect. bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2) { bool result;
22 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
After the model data has been loaded we now call the new CalculateModelVectors function to calculate the tangent and binormal. It also recalculates the normal vector. // Calculate the normal, tangent, and binormal vectors for the model. CalculateModelVectors(); // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } The two textures for the model are loaded here. The first is the color texture and the second is the normal map. // Load the textures for this model. result = LoadTextures(device, textureFilename1, textureFilename2); if(!result) { return false; } return true; }
bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i;
// Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount];
23 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
if(!indices) { return false; } The InitializeBuffers function has changed at this point where the vertex array is loaded with data from the ModelType array. The ModelType array now has tangent and binormal values for the model so they need to be copied into the vertex array which will then be copied into the vertex buffer. // Load the vertex array and index array with data. for(i=0; i<m_vertexCount; i++) { vertices[i].position = D3DXVECTOR3(m_model[i].x, m_model[i].y, m_model[i].z); vertices[i].texture = D3DXVECTOR2(m_model[i].tu, m_model[i].tv); vertices[i].normal = D3DXVECTOR3(m_model[i].nx, m_model[i].ny, m_model[i].nz); vertices[i].tangent = D3DXVECTOR3(m_model[i].tx, m_model[i].ty, m_model[i].tz); vertices[i].binormal = D3DXVECTOR3(m_model[i].bx, m_model[i].by, m_model[i].bz); indices[i] = i; } // Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0;
24 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; } LoadTextures now creates a vertex array and then loads the color texture and normal map into the two element texture array. bool ModelClass::LoadTextures(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2) { bool result;
// Create the texture array object. m_TextureArray = new TextureArrayClass; if(!m_TextureArray) { return false; } // Initialize the texture array object. result = m_TextureArray->Initialize(device, filename1, filename2); if(!result) { return false; }
25 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
return true; } CalculateModelVectors generates the tangent and binormal for the model as well as a recalculated normal vector. To start it calculates how many faces (triangles) are in the model. Then for each of those triangles it gets the three vertices and uses that to calculate the tangent, binormal, and normal. After calculating those three normal vectors it then saves them back into the model structure. void ModelClass::CalculateModelVectors() { int faceCount, i, index; TempVertexType vertex1, vertex2, vertex3; VectorType tangent, binormal, normal;
// Calculate the number of faces in the model. faceCount = m_vertexCount / 3; // Initialize the index to the model data. index = 0; // Go through all the faces and calculate the the tangent, binormal, and normal vectors. for(i=0; i<faceCount; i++) { // Get the three vertices for this face from the model. vertex1.x = m_model[index].x; vertex1.y = m_model[index].y; vertex1.z = m_model[index].z; vertex1.tu = m_model[index].tu; vertex1.tv = m_model[index].tv; vertex1.nx = m_model[index].nx; vertex1.ny = m_model[index].ny; vertex1.nz = m_model[index].nz; index++; vertex2.x = m_model[index].x; vertex2.y = m_model[index].y; vertex2.z = m_model[index].z; vertex2.tu = m_model[index].tu; vertex2.tv = m_model[index].tv; vertex2.nx = m_model[index].nx; vertex2.ny = m_model[index].ny; vertex2.nz = m_model[index].nz; index++;
26 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
vertex3.x = m_model[index].x; vertex3.y = m_model[index].y; vertex3.z = m_model[index].z; vertex3.tu = m_model[index].tu; vertex3.tv = m_model[index].tv; vertex3.nx = m_model[index].nx; vertex3.ny = m_model[index].ny; vertex3.nz = m_model[index].nz; index++; // Calculate the tangent and binormal of that face. CalculateTangentBinormal(vertex1, vertex2, vertex3, tangent, binormal); // Calculate the new normal using the tangent and binormal. CalculateNormal(tangent, binormal, normal); // Store the normal, tangent, and binormal for this face back in the model structure. m_model[index-1].nx = normal.x; m_model[index-1].ny = normal.y; m_model[index-1].nz = normal.z; m_model[index-1].tx = tangent.x; m_model[index-1].ty = tangent.y; m_model[index-1].tz = tangent.z; m_model[index-1].bx = binormal.x; m_model[index-1].by = binormal.y; m_model[index-1].bz = binormal.z; m_model[index-2].nx = normal.x; m_model[index-2].ny = normal.y; m_model[index-2].nz = normal.z; m_model[index-2].tx = tangent.x; m_model[index-2].ty = tangent.y; m_model[index-2].tz = tangent.z; m_model[index-2].bx = binormal.x; m_model[index-2].by = binormal.y; m_model[index-2].bz = binormal.z; m_model[index-3].nx = normal.x; m_model[index-3].ny = normal.y; m_model[index-3].nz = normal.z; m_model[index-3].tx = tangent.x; m_model[index-3].ty = tangent.y; m_model[index-3].tz = tangent.z; m_model[index-3].bx = binormal.x; m_model[index-3].by = binormal.y;
27 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
m_model[index-3].bz = binormal.z; } return; } The CalculateTangentBinormal function takes in three vertices and then calculates and returns the tangent and binormal of those three vertices. void ModelClass::CalculateTangentBinormal(TempVertexType vertex1, TempVertexType vertex2, TempVertexType vertex3, VectorType& tangent, VectorType& binormal) { float vector1[3], vector2[3]; float tuVector[2], tvVector[2]; float den; float length;
// Calculate the two vectors for this face. vector1[0] = vertex2.x - vertex1.x; vector1[1] = vertex2.y - vertex1.y; vector1[2] = vertex2.z - vertex1.z; vector2[0] = vertex3.x - vertex1.x; vector2[1] = vertex3.y - vertex1.y; vector2[2] = vertex3.z - vertex1.z; // Calculate the tu and tv texture space vectors. tuVector[0] = vertex2.tu - vertex1.tu; tvVector[0] = vertex2.tv - vertex1.tv; tuVector[1] = vertex3.tu - vertex1.tu; tvVector[1] = vertex3.tv - vertex1.tv; // Calculate the denominator of the tangent/binormal equation. den = 1.0f / (tuVector[0] * tvVector[1] - tuVector[1] * tvVector[0]); // Calculate the cross products and multiply by the coefficient to get the tangent and binormal. tangent.x = (tvVector[1] * vector1[0] - tvVector[0] * vector2[0]) * den; tangent.y = (tvVector[1] * vector1[1] - tvVector[0] * vector2[1]) * den; tangent.z = (tvVector[1] * vector1[2] - tvVector[0] * vector2[2]) * den; binormal.x = (tuVector[0] * vector2[0] - tuVector[1] * vector1[0]) * den; binormal.y = (tuVector[0] * vector2[1] - tuVector[1] * vector1[1]) * den; binormal.z = (tuVector[0] * vector2[2] - tuVector[1] * vector1[2]) * den;
28 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
// Calculate the length of this normal. length = sqrt((tangent.x * tangent.x) + (tangent.y * tangent.y) + (tangent.z * tangent.z)); // Normalize the normal and then store it tangent.x = tangent.x / length; tangent.y = tangent.y / length; tangent.z = tangent.z / length; // Calculate the length of this normal. length = sqrt((binormal.x * binormal.x) + (binormal.y * binormal.y) + (binormal.z * binormal.z)); // Normalize the normal and then store it binormal.x = binormal.x / length; binormal.y = binormal.y / length; binormal.z = binormal.z / length; return; } The CalculateNormal function takes in the tangent and binormal and then does a cross product to give back the normal vector. void ModelClass::CalculateNormal(VectorType tangent, VectorType binormal, VectorType& normal) { float length;
// Calculate the cross product of the tangent and binormal which will give the normal vector. normal.x = (tangent.y * binormal.z) - (tangent.z * binormal.y); normal.y = (tangent.z * binormal.x) - (tangent.x * binormal.z); normal.z = (tangent.x * binormal.y) - (tangent.y * binormal.x); // Calculate the length of the normal. length = sqrt((normal.x * normal.x) + (normal.y * normal.y) + (normal.z * normal.z)); // Normalize the normal. normal.x = normal.x / length; normal.y = normal.y / length; normal.z = normal.z / length; return; }
29 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" The new BumpMapShaderClass header file is included here in the GraphicsClass header. #include "bumpmapshaderclass.h" #include "lightclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame();
30 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; The new BumpMapShaderClass object is created here. BumpMapShaderClass* m_BumpMapShader; LightClass* m_Light; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; We initialize the BumpMapShaderClass object to null in the class constructor. m_BumpMapShader = 0; m_Light = 0; }
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; D3DXMATRIX baseViewMatrix;
31 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Initialize a base view matrix with the camera for 2D user interface rendering. m_Camera->SetPosition(0.0f, 0.0f, -1.0f); m_Camera->Render(); m_Camera->GetViewMatrix(baseViewMatrix); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } The ModelClass object is initialized with the cube model, the stone01.dds color texture, and the bump01.dds normal map. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/stone01.dds", L"../Engine/data/bump01.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; }
32 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
Here we create and initialize the BumpMapShaderClass object. // Create the bump map shader object. m_BumpMapShader = new BumpMapShaderClass; if(!m_BumpMapShader) { return false; } // Initialize the bump map shader object. result = m_BumpMapShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the bump map shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } The light color is set to white and the light direction is set down the positive Z axis. // Initialize the light object. m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f); return true; }
void GraphicsClass::Shutdown() { // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } The new BumpMapShaderClass is released here in the Shutdown function.
33 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
// Release the bump map shader object. if(m_BumpMapShader) { m_BumpMapShader->Shutdown(); delete m_BumpMapShader; m_BumpMapShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
bool GraphicsClass::Render() { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix; static float rotation = 0.0f;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position.
34 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and D3D objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); Rotate the cube model each frame to show the effect. // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.0025f; if(rotation > 360.0f) { rotation -= 360.0f; } // Rotate the world matrix by the rotation value. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); Render the model using the bump map shader. // Render the model using the bump map shader. m_BumpMapShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTextureArray(), m_Light->GetDirection(), m_Light->GetDiffuseColor()); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
With the bump map shader you can create very detailed scenes that look 3D with just two 2D textures.
35 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
To Do Exercises
1. Recompile and run the program. You should see a bump mapped rotating cube. Press escape to quit. 2. Change the bump map effect from 2.5 to something smaller (like 1.0) and larger (like 5.0) in the shader to see the change in the bump depth. 3. Comment out the color = color * textureColor; line in the pixel shader to see just the bump lighting effect. 4. Move the camera and light position around to see the effect from different angles.
Source Code
Visual Studio 2008 Project: dx11tut20.zip
36 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut20.html
37 of 37
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
We then add 0.5 to the texture X coordinate in the pixel shader and it translates the texture coordinates over by half:
Framework
The frame work has been updated to include the new TranslateShaderClass.
1 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
Translate.vs
The vertex shader is the same as the texture vertex shader. //////////////////////////////////////////////////////////////////////////////// // Filename: translate.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; };
2 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Translate.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: translate.ps ////////////////////////////////////////////////////////////////////////////////
3 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
We add a new buffer with a float variable called textureTranslation. This is set in the graphicsclass.cpp during the Render call to update the position of the texture translation. The value set here will be between 0 and 1. cbuffer TranslationBuffer { float textureTranslation; };
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; The pixel shader is where we do the work of translating the texture coordinate by the textureTranslation amount. We take the input texture coordinate and add the translation amount for each pixel shader call. This will uniformly translate the texture across the polygon face. //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 TranslatePixelShader(PixelInputType input) : SV_TARGET { // Translate the position where we sample the pixel from. input.tex.x += textureTranslation; return shaderTexture.Sample(SampleType, input.tex); }
Translateshaderclass.h
The TranslateShaderClass is just the TextureShaderClass with extra functionality for texture translation. //////////////////////////////////////////////////////////////////////////////// // Filename: translateshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TRANSLATESHADERCLASS_H_ #define _TRANSLATESHADERCLASS_H_
4 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: TranslateShaderClass //////////////////////////////////////////////////////////////////////////////// class TranslateShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; This is the new structure type for the texture translation buffer. We had padding to ensure it is a multiple of 16. struct TranslateBufferType { float translation; D3DXVECTOR3 padding; }; public: TranslateShaderClass(); TranslateShaderClass(const TranslateShaderClass&); ~TranslateShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
5 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; We add a new buffer pointer for the texture translation buffer. ID3D11Buffer* m_translateBuffer; }; #endif
Translateshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: translateshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "translateshaderclass.h"
TranslateShaderClass::TranslateShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; m_sampleState = 0; Initialize the texture translation buffer to null in the class constructor. m_translateBuffer = 0; }
6 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
TranslateShaderClass::~TranslateShaderClass() { }
bool TranslateShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; Load the texture translate shader HLSL files. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/translate.vs", L"../Engine/translate.ps"); if(!result) { return false; } return true; }
void TranslateShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes as input the translation value to be used for translating the texture in the pixel shader. It then sends that value into the SetShaderParameters function so that it is set before rendering. bool TranslateShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float translation) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, translation);
7 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool TranslateShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC translateBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load the texture translate vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TranslateVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else {
8 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Load the texture translate pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TranslatePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader.
9 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result))
10 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
{ 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, &m_sampleState); if(FAILED(result)) { return false; } Create the translate buffer so we have an interface to setting the texture translation value in the pixel shader. // Setup the description of the texture translation dynamic constant buffer that is in the pixel shader. translateBufferDesc.Usage = D3D11_USAGE_DYNAMIC; translateBufferDesc.ByteWidth = sizeof(TranslateBufferType); translateBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; translateBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; translateBufferDesc.MiscFlags = 0; translateBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the pixel shader constant buffer from within this class. result = device->CreateBuffer(&translateBufferDesc, NULL, &m_translateBuffer); if(FAILED(result)) { return false; } return true; }
11 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
void TranslateShaderClass::ShutdownShader() { Release the new translate buffer in the ShutdownShader function. // Release the texture translation constant buffer. if(m_translateBuffer) { m_translateBuffer->Release(); m_translateBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) {
12 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
void TranslateShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
13 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float translation) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; TranslateBufferType* dataPtr2;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the matrix constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the matrix constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); Here is where we set the texture translation value in the pixel shader. We first lock the texture translation constant buffer and then copy the translation value into the buffer. After that we unlock the buffer and set the constant buffer in the pixel shader.
14 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
// Lock the texture translation constant buffer so it can be written to. result = deviceContext->Map(m_translateBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the texture translation constant buffer. dataPtr2 = (TranslateBufferType*)mappedResource.pData; // Copy the translation value into the texture translation constant buffer. dataPtr2->translation = translation; // Unlock the buffer. deviceContext->Unmap(m_translateBuffer, 0); // Set the position of the texture translation constant buffer in the pixel shader. bufferNumber = 0; // Now set the texture translation constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_translateBuffer); return true; }
void TranslateShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
15 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" Include the header file for the new TranslateShaderClass. #include "translateshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown();
16 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
bool Frame(); bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; Create a new object for the new TranslateShaderClass. TranslateShaderClass* m_TranslateShader; }; #endif
Graphicsclass.cpp
I will just cover the functions that have changed since the previous tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; Set the TranslateShaderClass object to null in the class constructor. m_TranslateShader = 0; }
17 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds", "../Engine/data/triangle.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } Create and initialize the TranslateShaderClass object. // Create the texture translation shader object. m_TranslateShader = new TranslateShaderClass; if(!m_TranslateShader) { return false; }
18 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
// Initialize the texture translation shader object. result = m_TranslateShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture translation shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { Release the new TranslateShaderClass object in the Shutdown function. // Release the texture translation shader object. if(m_TranslateShader) { m_TranslateShader->Shutdown(); delete m_TranslateShader; m_TranslateShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown();
19 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
bool GraphicsClass::Render() { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; bool result; static float textureTranslation = 0.0f; Here the translation value is incremented by a small amount each frame. If it goes over 1.0 then it is reset down to 0.0 again. // Increment the texture translation position. textureTranslation += 0.01f; if(textureTranslation > 1.0f) { textureTranslation -= 1.0f; } // Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); Now render the model with the TranslateShaderClass and send in the texture translation value. // Render the model with the texture translation shader. result = m_TranslateShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), textureTranslation); if(!result) { return false;
20 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
Summary
Using a very simple pixel shader we can achieve a very useful effect. This can be modified to affect how textures appear on surfaces and we can create a wide variety of effects by doing some simple math with the texture coordinates or by animating different textures over planes to simulation motion.
To Do Exercises
1. Recompile the code and ensure you get a texture translating across the polygon surface. 2. Change the pixel shader to affect the Y axis instead of the X axis. 3. Change the pixel shader to affect both the Y axis and then X axis by the same translation value.
Source Code
Visual Studio 2008 Project: dx11tut25.zip
21 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut25.html
22 of 22
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut26.html
We can make it half transparent and then render it over another texture to create the following transparency effect:
1 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
Transparency is implemented in DirectX 11 and HLSL by using alpha blending. The alpha component of any pixel is used to determine the transparency of that pixel. For example if a pixel alpha value is 0.5 then it would appear half transparent. Many textures have an alpha component that can be modified to make the texture transparent in some areas and opaque in others. However for alpha values to take effect you have to first turn on alpha blending in the shader and set the blending equation for how to blend pixels. In this tutorial we modify the blending state in the D3DClass such as follows: blendStateDescription.RenderTarget[0].BlendEnable = TRUE; blendStateDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; blendStateDescription.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; DestBlend is for the destination color. It is the pixel that already exists in this location. The blending function we are going to use for the destination is INV_SRC_ALPHA which is the inverse of the source alpha. This equates to one minus the source alpha value. For example if the source alpha is 0.3 then the dest alpha would be 0.7 so we will combine 70% of the destination pixel color in the final combine. SrcBlend is for the source pixel which will be the input texture color value in this tutorial. The function we are going to use is SRC_ALPHA which is the source color multiplied by its own alpha value. The source and destination value will be added together to produce the final output pixel.
2 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
Framework
The framework for this tutorial has a new class called TransparentShaderClass. This class is the same as the TextureShaderClass except that is has a alpha blending value.
D3dclass.cpp
As discussed we have just modified the blend state inside the D3DClass::Initialize function so that texture transparency works. This is the only change to that class. // Create an alpha enabled blend state description. blendStateDescription.RenderTarget[0].BlendEnable = TRUE; blendStateDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; blendStateDescription.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; blendStateDescription.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; blendStateDescription.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; blendStateDescription.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; blendStateDescription.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; blendStateDescription.RenderTarget[0].RenderTargetWriteMask = 0x0f;
Transparent.vs
The transparent texture vertex shader is the same as the normal texture vertex shader.
3 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix);
4 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Transparent.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: transparent.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType; We add a new constant buffer to hold the blendAmount variable. The blendAmount indicates was percentage to blend the texture. The acceptable value range is a float value from 0 to 1. In this tutorial it is set to 0.5 which means 50% transparency. cbuffer TransparentBuffer { float blendAmount; };
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
////////////////////////////////////////////////////////////////////////////////
5 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
// Sample the texture pixel at this location. color = shaderTexture.Sample(SampleType, input.tex); Here we use the blendAmount variable to set the transparency of the texture. We set the alpha value of the pixel to the blend amount and when rendered it will use the alpha value with the blend state we setup to create the transparency effect. // Set the alpha value of this pixel to the blending amount to create the alpha blending effect. color.a = blendAmount; return color; }
Transparentshaderclass.h
The TransparentShaderClass is the same as the TextureShaderClass with some extra changes to accommodate transparency. //////////////////////////////////////////////////////////////////////////////// // Filename: transparentshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TRANSPARENTSHADERCLASS_H_ #define _TRANSPARENTSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
6 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
//////////////////////////////////////////////////////////////////////////////// class TransparentShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; We have a new buffer type to match the constant buffer inside the pixel shader. We add 12 bytes of padding so the CreateBuffer function won't fail. struct TransparentBufferType { float blendAmount; D3DXVECTOR3 padding; }; public: TransparentShaderClass(); TransparentShaderClass(const TransparentShaderClass&); ~TransparentShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; We have a new buffer variable to provide an interface to the blendAmount value inside the transparent HLSL pixel shader.
7 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
Transparentshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: transparentshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "transparentshaderclass.h"
TransparentShaderClass::TransparentShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; m_sampleState = 0; Initialize the new transparent constant buffer to null inside the class constructor. m_transparentBuffer = 0; }
TransparentShaderClass::~TransparentShaderClass() { }
bool TransparentShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; Load the transparent HLSL shader files.
8 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/transparent.vs", L"../Engine/transparent.ps"); if(!result) { return false; } return true; }
void TransparentShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes as input a blend float value which will set the amount of transparency to use in the shader. bool TransparentShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float blend) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, blend); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool TransparentShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result;
9 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC transparentBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load the transparent vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TransparentVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Load the transparent pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TransparentPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage)
10 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
{ OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0;
11 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
// Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); 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;
12 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
samplerDesc.BorderColor[3] = 0; samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; // Create the texture sampler state. result = device->CreateSamplerState(&samplerDesc, &m_sampleState); if(FAILED(result)) { return false; } Setup the constant buffer interface to TransparentBuffer in the pixel shader which holds the blendAmount value. // Setup the description of the transparent dynamic constant buffer that is in the pixel shader. transparentBufferDesc.Usage = D3D11_USAGE_DYNAMIC; transparentBufferDesc.ByteWidth = sizeof(TransparentBufferType); transparentBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; transparentBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; transparentBufferDesc.MiscFlags = 0; transparentBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the pixel shader constant buffer from within this class. result = device->CreateBuffer(&transparentBufferDesc, NULL, &m_transparentBuffer); if(FAILED(result)) { return false; } return true; }
void TransparentShaderClass::ShutdownShader() { Release the new transparent buffer in the ShutdownShader function. // Release the transparent constant buffer. if(m_transparentBuffer) { m_transparentBuffer->Release(); m_transparentBuffer = 0; } // Release the sampler state.
13 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void TransparentShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
14 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool TransparentShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float blend) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; TransparentBufferType* dataPtr2;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to.
15 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the matrix constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the matrix constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); Here is where we set the blendAmount value inside the pixel shader before rendering. We lock the transparent constant buffer, copy the blend amount value into the buffer, and then unlock it again so the shader can access the new value. // Lock the transparent constant buffer so it can be written to. result = deviceContext->Map(m_transparentBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the transparent constant buffer. dataPtr2 = (TransparentBufferType*)mappedResource.pData; // Copy the blend amount value into the transparent constant buffer. dataPtr2->blendAmount = blend; // Unlock the buffer. deviceContext->Unmap(m_transparentBuffer, 0);
16 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
// Set the position of the transparent constant buffer in the pixel shader. bufferNumber = 0; // Now set the texture translation constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_transparentBuffer); return true; }
void TransparentShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true;
17 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "textureshaderclass.h" Include the header filer for the new TransparentShaderClass. #include "transparentshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; Create two models for this tutorial. ModelClass* m_Model1; ModelClass* m_Model2; TextureShaderClass* m_TextureShader; Create a TransparentShaderClass object. TransparentShaderClass* m_TransparentShader;
18 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
}; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" Initialize the two models and the new TransparentShaderClass to null in the class constructor. GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model1 = 0; m_Model2 = 0; m_TextureShader = 0; m_TransparentShader = 0; }
GraphicsClass::~GraphicsClass() { }
19 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
{ return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } Create and initialize the first model which will be a square with a dirt texture on it. // Create the first model object. m_Model1 = new ModelClass; if(!m_Model1) { return false; } // Initialize the first model object. result = m_Model1->Initialize(m_D3D->GetDevice(), L"../Engine/data/dirt01.dds", "../Engine/data/square.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the first model object.", L"Error", MB_OK); return false; } Create and initialize the second model which will be a square with a stone texture on it. // Create the second model object. m_Model2 = new ModelClass; if(!m_Model2) { return false; }
20 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
// Initialize the second model object. result = m_Model2->Initialize(m_D3D->GetDevice(), L"../Engine/data/stone01.dds", "../Engine/data/square.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the second model object.", L"Error", MB_OK); return false; } // Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } Create and initialize the new TransparentShaderClass object. // Create the transparent shader object. m_TransparentShader = new TransparentShaderClass; if(!m_TransparentShader) { return false; } // Initialize the transparent shader object. result = m_TransparentShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the transparent shader object.", L"Error", MB_OK); return false; } return true; } Release the new TransparentShaderClass object and two model objects in the Shutdown function.
21 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
void GraphicsClass::Shutdown() { // Release the transparent shader object. if(m_TransparentShader) { m_TransparentShader->Shutdown(); delete m_TransparentShader; m_TransparentShader = 0; } // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } // Release the second model object. if(m_Model2) { m_Model2->Shutdown(); delete m_Model2; m_Model2 = 0; } // Release the first model object. if(m_Model1) { m_Model1->Shutdown(); delete m_Model1; m_Model1 = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown();
22 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
bool GraphicsClass::Frame() { // Set the position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -5.0f); return true; }
bool GraphicsClass::Render() { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; bool result; float blendAmount; Setup a blend variable that will be sent into the transparent shader. // Set the blending amount to 50%. blendAmount = 0.5f; // Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); Render the first square model with the dirt texture in the center of the screen. Use just the regular texture shader for this. // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model1->Render(m_D3D->GetDeviceContext()); // Render the model with the texture shader.
23 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model1->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model1->GetTexture()); if(!result) { return false; } Render the second square model with the stone texture slightly to the right and above the previous square texture. Use the new TransparentShaderClass object with the blendAmount input to render the square stone texture with transparency so we can see through it. Also turn on the alpha blending from the D3DClass before rendering and then turn it off after rendering. // Translate to the right by one unit and towards the camera by one unit. D3DXMatrixTranslation(&worldMatrix, 1.0f, 0.0f, -1.0f); // Turn on alpha blending for the transparency to work. m_D3D->TurnOnAlphaBlending(); // Put the second square model on the graphics pipeline. m_Model2->Render(m_D3D->GetDeviceContext()); // Render the second square model with the stone texture and use the 50% blending amount for transparency. result = m_TransparentShader->Render(m_D3D->GetDeviceContext(), m_Model2->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model2->GetTexture(), blendAmount); if(!result) { return false; } // Turn off alpha blending. m_D3D->TurnOffAlphaBlending(); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
With transparency we can create see through textures and models which allows us to create a number of different effects.
24 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut26.html
To Do Exercises
1. Recompile the and run the program. Press escape to quit. 2. Change the value of the blendAmount in the GraphicsRender function to see different amounts of transparency.
Source Code
Visual Studio 2008 Project: dx11tut26.zip Source Only: dx11src26.zip Executable Only: dx11exe26.zip
25 of 25
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
Now that we have a reflection matrix we can use it to render the scene from the reflection camera view point instead of the regular camera view point. And when we render from the reflection view point we will render that data to a texture instead of the back buffer. This will give us the reflection of our scene on a texture. The final step is to make a second render pass of our scene but when we do so we use the reflection render to texture and blend it with the floor. This will be done using the reflection matrix and the normal view matrix to project the texture accurately onto the floor geometry.
Framework
The frame work has been updated to include a new class called ReflectionShaderClass for this tutorial. This class is the same as the TextureShaderClass except it handles a reflection view matrix and a reflection texture for interfacing with the new HLSL reflection shader code.
1 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
We will start the code section of the tutorial by examining the HLSL reflection shader code first:
Reflection.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: reflection.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; We add a new constant buffer to hold the reflection matrix. cbuffer ReflectionBuffer { matrix reflectionMatrix; };
//////////////
2 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; The PixleInputType now has a 4 float texture coordinate variable called reflectionPosition that will be used to hold the projected reflection texture input position. struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float4 reflectionPosition : TEXCOORD1; };
//////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType ReflectionVertexShader(VertexInputType input) { PixelInputType output; matrix reflectProjectWorld;
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; The first change to the vertex shader is that we create a matrix for transforming the input position values into the projected reflection position. This matrix is a combination of the reflection matrix, the projection matrix, and the world matrix. // Create the reflection projection world matrix. reflectProjectWorld = mul(reflectionMatrix, projectionMatrix); reflectProjectWorld = mul(worldMatrix, reflectProjectWorld);
3 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
Now transform the input position into the projected reflection position. These transformed position coordinates will be used in the pixel shader to derive where to map our projected reflection texture to. // Calculate the input position against the reflectProjectWorld matrix. output.reflectionPosition = mul(input.position, reflectProjectWorld); return output; }
Reflection.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: reflection.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType; We add a new texture variable for the scene reflection render to texture. Texture2D reflectionTexture;
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float4 reflectionPosition : TEXCOORD1; };
4 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// Sample the texture pixel at this location. textureColor = shaderTexture.Sample(SampleType, input.tex); The input reflection position homogenous coordinates need to be converted to proper texture coordinates. To do so first divide by the W coordinate. This leaves us with tu and tv coordinates in the -1 to +1 range, to fix it to map to a 0 to +1 range just divide by 2 and add 0.5. // Calculate the projected reflection texture coordinates. reflectTexCoord.x = input.reflectionPosition.x / input.reflectionPosition.w / 2.0f + 0.5f; reflectTexCoord.y = -input.reflectionPosition.y / input.reflectionPosition.w / 2.0f + 0.5f; Now when we sample from the reflection texture we use the projected reflection coordinates that have been converted to get the right reflection pixel for this projected reflection position. // Sample the texture pixel from the reflection texture using the projected texture coordinates. reflectionColor = reflectionTexture.Sample(SampleType, reflectTexCoord); Finally we blend the texture from the floor with the reflection texture to create the effect of the reflected cube on the floor. Here we use a linear interpolation between the two textures with a factor of 0.15. You can change this to a different blend equation or change the factor amount for a different or stronger effect. // Do a linear interpolation between the two textures for a blend effect. color = lerp(textureColor, reflectionColor, 0.15f); return color; }
Reflectionshaderclass.h
The ReflectionShaderClass is the same as the TextureShaderClass except that it also handles a reflection view matrix buffer and a reflection texture. //////////////////////////////////////////////////////////////////////////////// // Filename: reflectionshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _REFLECTIONSHADERCLASS_H_ #define _REFLECTIONSHADERCLASS_H_
5 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: ReflectionShaderClass //////////////////////////////////////////////////////////////////////////////// class ReflectionShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; This is the structure for the reflection view matrix dynamic constant buffer. struct ReflectionBufferType { D3DXMATRIX reflectionMatrix; }; public: ReflectionShaderClass(); ReflectionShaderClass(const ReflectionShaderClass&); ~ReflectionShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, D3DXMATRIX); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
6 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, D3DXMATRIX); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; This is the new buffer for the reflection view matrix. ID3D11Buffer* m_reflectionBuffer; }; #endif
Reflectionshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: reflectionshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "reflectionshaderclass.h"
ReflectionShaderClass::ReflectionShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; m_sampleState = 0; Initialize the reflection constant buffer to null in the class constructor. m_reflectionBuffer = 0; }
7 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
{ }
ReflectionShaderClass::~ReflectionShaderClass() { }
bool ReflectionShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; Load the new reflection HLSL shader files. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/reflection.vs", L"../Engine/reflection.ps"); if(!result) { return false; } return true; }
void ReflectionShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes as input the new reflection texture and reflection matrix. These are then set in the shader before rendering. bool ReflectionShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* reflectionTexture, D3DXMATRIX reflectionMatrix) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, reflectionTexture,
8 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
reflectionMatrix); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool ReflectionShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC reflectionBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load the new reflection vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ReflectionVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else
9 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
{ MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Load the new reflection pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ReflectionPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description.
10 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the matrix dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
11 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
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, &m_sampleState); if(FAILED(result)) { return false; } Here we setup the new reflection matrix dynamic constant buffer. // Setup the description of the reflection dynamic constant buffer that is in the vertex shader. reflectionBufferDesc.Usage = D3D11_USAGE_DYNAMIC; reflectionBufferDesc.ByteWidth = sizeof(ReflectionBufferType); reflectionBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; reflectionBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; reflectionBufferDesc.MiscFlags = 0; reflectionBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&reflectionBufferDesc, NULL, &m_reflectionBuffer); if(FAILED(result)) { return false; } return true;
12 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
void ReflectionShaderClass::ShutdownShader() { We release the new reflection buffer here in the ShutdownShader function. // Release the reflection constant buffer. if(m_reflectionBuffer) { m_reflectionBuffer->Release(); m_reflectionBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader)
13 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
void ReflectionShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
14 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
bool ReflectionShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* reflectionTexture, D3DXMATRIX reflectionMatrix) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; ReflectionBufferType* dataPtr2;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); Transpose the reflection matrix first as DirectX 11 requires all input matrices to be transposed for memory alignment reasons. // Transpose the relfection matrix to prepare it for the shader. D3DXMatrixTranspose(&reflectionMatrix, &reflectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the matrix constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the matrix constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
15 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
Lock the reflection buffer and copy the reflection matrix into it. After that unlock it and set it in the vertex shader. // Lock the reflection constant buffer so it can be written to. result = deviceContext->Map(m_reflectionBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the matrix constant buffer. dataPtr2 = (ReflectionBufferType*)mappedResource.pData; // Copy the matrix into the reflection constant buffer. dataPtr2->reflectionMatrix = reflectionMatrix; // Unlock the reflection constant buffer. deviceContext->Unmap(m_reflectionBuffer, 0); // Set the position of the reflection constant buffer in the vertex shader. bufferNumber = 1; // Now set the reflection constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_reflectionBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); Set the reflection texture as the second texture inside the pixel shader. // Set the reflection texture resource in the pixel shader. deviceContext->PSSetShaderResources(1, 1, &reflectionTexture); return true; }
void ReflectionShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0);
16 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Cameraclass.h
The CameraClass has been slightly modified to handle planar reflections. //////////////////////////////////////////////////////////////////////////////// // Filename: cameraclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _CAMERACLASS_H_ #define _CAMERACLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: CameraClass //////////////////////////////////////////////////////////////////////////////// class CameraClass { public: CameraClass(); CameraClass(const CameraClass&); ~CameraClass(); void SetPosition(float, float, float); void SetRotation(float, float, float); void Render(); void GetViewMatrix(D3DXMATRIX&);
17 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
We add two new functions for reflection. One is to build the reflection matrix and the other is to retrieve the reflection matrix. void RenderReflection(float); D3DXMATRIX GetReflectionViewMatrix(); private: D3DXMATRIX m_viewMatrix; float m_positionX, m_positionY, m_positionZ; float m_rotationX, m_rotationY, m_rotationZ; Here is the new reflection matrix. D3DXMATRIX m_reflectionViewMatrix; }; #endif
Cameraclass.cpp
I will just cover the new functions since the older CameraClass functions have not changed. //////////////////////////////////////////////////////////////////////////////// // Filename: cameraclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "cameraclass.h" The new RenderReflection function builds a reflection view matrix the same way as the regular Render function builds a view matrix. The only difference is that we take as input the height of the object that will act as the Y axis plane and then we use that height to invert the position.y variable for reflection. This will build the reflection view matrix that we can then use in the shader. Note that this function only works for the Y axis plane. void CameraClass::RenderReflection(float height) { D3DXVECTOR3 up, position, lookAt; float radians;
// Setup the vector that points upwards. up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; // Setup the position of the camera in the world.
18 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// For planar reflection invert the Y position of the camera. position.x = m_positionX; position.y = -m_positionY + (height * 2.0f); position.z = m_positionZ; // Calculate the rotation in radians. radians = m_rotationY * 0.0174532925f; // Setup where the camera is looking. lookAt.x = sinf(radians) + m_positionX; lookAt.y = position.y; lookAt.z = cosf(radians) + m_positionZ; // Create the view matrix from the three vectors. D3DXMatrixLookAtLH(&m_reflectionViewMatrix, &position, &lookAt, &up); return; } GetReflectionViewMatrix returns the reflection view matrix to calling functions. D3DXMATRIX CameraClass::GetReflectionViewMatrix() { return m_reflectionViewMatrix; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
19 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "textureshaderclass.h" #include "rendertextureclass.h" We include the new ReflectionShaderClass header file. #include "reflectionshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); bool Render(); private: bool RenderToTexture(); bool RenderScene(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; TextureShaderClass* m_TextureShader; We will be using a render to texture object in this tutorial. RenderTextureClass* m_RenderTexture;
20 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
We will create a second model object for the floor. ModelClass* m_FloorModel; Create the new reflection shader object. ReflectionShaderClass* m_ReflectionShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_TextureShader = 0; Initialize the render to texture, floor model, and reflection shader objects to null in the class constructor. m_RenderTexture = 0; m_FloorModel = 0; m_ReflectionShader = 0; }
GraphicsClass::~GraphicsClass() { }
21 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds", "../Engine/data/cube.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the texture shader object.
22 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } Create and initialize the render to texture object. // Create the render to texture object. m_RenderTexture = new RenderTextureClass; if(!m_RenderTexture) { return false; } // Initialize the render to texture object. result = m_RenderTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight); if(!result) { return false; } Create and initialize the blue floor model object. // Create the floor model object. m_FloorModel = new ModelClass; if(!m_FloorModel) { return false; } // Initialize the floor model object. result = m_FloorModel->Initialize(m_D3D->GetDevice(), L"../Engine/data/blue01.dds", "../Engine/data/floor.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the floor model object.", L"Error", MB_OK); return false;
23 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
} Create and initialize the new reflection shader object. // Create the reflection shader object. m_ReflectionShader = new ReflectionShaderClass; if(!m_ReflectionShader) { return false; } // Initialize the reflection shader object. result = m_ReflectionShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the reflection shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { Release the render to texture, floor model, and reflection shader objects in the Shutdown function. // Release the reflection shader object. if(m_ReflectionShader) { m_ReflectionShader->Shutdown(); delete m_ReflectionShader; m_ReflectionShader = 0; } // Release the floor model object. if(m_FloorModel) { m_FloorModel->Shutdown(); delete m_FloorModel; m_FloorModel = 0; } // Release the render to texture object.
24 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
if(m_RenderTexture) { m_RenderTexture->Shutdown(); delete m_RenderTexture; m_RenderTexture = 0; } // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
25 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
bool GraphicsClass::Render() { bool result; First render the scene as a reflection to a texture. // Render the entire scene as a reflection to the texture first. result = RenderToTexture(); if(!result) { return false; } Then render the scene normally and use the reflection texture to blend on the floor and create the reflection effect. // Render the scene as normal to the back buffer. result = RenderScene(); if(!result) { return false; } return true; }
bool GraphicsClass::RenderToTexture() { D3DXMATRIX worldMatrix, reflectionViewMatrix, projectionMatrix; static float rotation = 0.0f;
// Set the render target to be the render to texture. m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView()); // Clear the render to texture. m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView(), 0.0f, 0.0f, 0.0f, 1.0f); Before rendering the scene to a texture we need to create the reflection matrix using the position of the floor (-1.5) as the height input variable.
26 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// Use the camera to calculate the reflection matrix. m_Camera->RenderReflection(-1.5f); Now render the scene as normal but use the reflection matrix instead of the normal view matrix. Also there is no need to render the floor as we only need to render what will be reflected. // Get the camera reflection view matrix instead of the normal view matrix. reflectionViewMatrix = m_Camera->GetReflectionViewMatrix(); // Get the world and projection matrices. m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the model using the texture shader and the reflection view matrix. m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, reflectionViewMatrix, projectionMatrix, m_Model->GetTexture()); // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget();
return true; }
bool GraphicsClass::RenderScene() { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, reflectionMatrix; bool result; static float rotation = 0.0f;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
27 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
// Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } // Multiply the world matrix by the rotation. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the model with the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; } // Get the world matrix again and translate down for the floor model to render underneath the cube. m_D3D->GetWorldMatrix(worldMatrix); D3DXMatrixTranslation(&worldMatrix, 0.0f, -1.5f, 0.0f); // Get the camera reflection view matrix. reflectionMatrix = m_Camera->GetReflectionViewMatrix(); // Put the floor model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_FloorModel->Render(m_D3D->GetDeviceContext()); When we render the floor it will require the normal matrices and textures as well as the new reflection view matrix and reflection render to texture. // Render the floor model using the reflection shader, reflection texture, and reflection view matrix. result = m_ReflectionShader->Render(m_D3D->GetDeviceContext(), m_FloorModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_FloorModel->GetTexture(), m_RenderTexture->GetShaderResourceView(),
28 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
reflectionMatrix); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
We now have a planar reflection effect that can be used to mirror objects and blend them as reflections into other objects.
To Do Exercises
29 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut27.html
1. Recompile and run the program. You should get a spinning cube that is reflected by the blue floor object. 2. Change the blending factor and try a different blending equation to produce a different effect. 3. Change the planar reflection to the Z plane and move/rotate the floor object to create a upright mirror effect.
Source Code
Visual Studio 2008 Project: dx11tut27.zip Source Only: dx11src27.zip Executable Only: dx11exe27.zip
30 of 30
3/8/2013 12:41 PM
http://www.rastertek.com/dx11tut28.html
Framework
The frame work has a new FadeShaderClass for handling the shading of the fade in. We have also brought back the BitmapClass which has been modified to work with the FadeShaderClass and RenderTextureClass. The TimerClass is also used in this tutorial to time the fade in.
Fade.vs
1 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
The fade HLSL shaders are just a modified version of the texture shaders. //////////////////////////////////////////////////////////////////////////////// // Filename: fade.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f;
2 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Fade.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: fade.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType; We add a new buffer called FadeBuffer to control the color level of the texture by using the fadeAmount variable. This value will be incremented each frame to make the image brighter and brighter creating the fade in effect. The value range is between 0.0 (0%) and 1.0 (100%). The padding variable is just to ensure the buffer is a multiple of 16. cbuffer FadeBuffer { float fadeAmount; float3 padding; };
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0;
3 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
};
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 FadePixelShader(PixelInputType input) : SV_TARGET { float4 color;
// Sample the texture pixel at this location. color = shaderTexture.Sample(SampleType, input.tex); Here is where we use the fadeAmount to control the output color of all pixels from the texture to be set to a certain brightness. // Reduce the color brightness to the current fade percentage. color = color * fadeAmount; return color; }
Fadeshaderclass.h
The FadeShaderClass is just the TextureShaderClass modified to accommodate texture fading. //////////////////////////////////////////////////////////////////////////////// // Filename: fadeshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _FADESHADERCLASS_H_ #define _FADESHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
4 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
//////////////////////////////////////////////////////////////////////////////// // Class name: FadeShaderClass //////////////////////////////////////////////////////////////////////////////// class FadeShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; This is the new structure to match the fade constant buffer in the pixel shader. struct FadeBufferType { float fadeAmount; D3DXVECTOR3 padding; }; public: FadeShaderClass(); FadeShaderClass(const FadeShaderClass&); ~FadeShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState;
5 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
This is the new fade amount buffer that is used to control the brightness of all the pixels from the texture. ID3D11Buffer* m_fadeBuffer; }; #endif
Fadeshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: fadeshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "fadeshaderclass.h"
FadeShaderClass::FadeShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; m_sampleState = 0; Initialize the new buffer to null in the class constructor. m_fadeBuffer = 0; }
FadeShaderClass::~FadeShaderClass() { }
6 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
Load in the new fade.vs and fade.ps HLSL shader code. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/fade.vs", L"../Engine/fade.ps"); if(!result) { return false; } return true; }
void FadeShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes in a new float variable called fadeAmount. This variable contains the current percentage of brightness the texture should be drawn at. It will be set in the shader first before rendering the texture. bool FadeShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float fadeAmount) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, fadeAmount); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
7 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
{ HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC fadeBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load the new fade vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "FadeVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Load the new fade pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "FadePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) {
8 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
9 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the matrix dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); 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;
10 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
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, &m_sampleState); if(FAILED(result)) { return false; } Setup a buffer that will hold the fadeAmount variable so we can interface with the pixel shader fade buffer. // Setup the description of the fade dynamic constant buffer that is in the vertex shader. fadeBufferDesc.Usage = D3D11_USAGE_DYNAMIC; fadeBufferDesc.ByteWidth = sizeof(FadeBufferType); fadeBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; fadeBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; fadeBufferDesc.MiscFlags = 0; fadeBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the pixel shader constant buffer from within this class. result = device->CreateBuffer(&fadeBufferDesc, NULL, &m_fadeBuffer); if(FAILED(result)) { return false; } return true; }
void FadeShaderClass::ShutdownShader() { Release the new fade buffer in the ShutdownShader function. // Release the fade constant buffer. if(m_fadeBuffer) { m_fadeBuffer->Release(); m_fadeBuffer = 0; }
11 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void FadeShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
12 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool FadeShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float fadeAmount) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; FadeBufferType* dataPtr2;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
13 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the matrix constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the matrix constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); This is where we set the fadeAmount value inside the pixel shader. We first lock the fade buffer and then copy the updated fadeAmount value into it. After that we unlock the fade buffer and then set it in the pixel shader using the PSSetConstantBuffers function. // Lock the fade constant buffer so it can be written to. result = deviceContext->Map(m_fadeBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the fade constant buffer. dataPtr2 = (FadeBufferType*)mappedResource.pData; // Copy the fade amount into the fade constant buffer. dataPtr2->fadeAmount = fadeAmount; dataPtr2->padding = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
14 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Unlock the fade constant buffer. deviceContext->Unmap(m_fadeBuffer, 0); // Set the position of the fade constant buffer in the pixel shader. bufferNumber = 0; // Now set the fade constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_fadeBuffer); return true; }
void FadeShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Bitmapclass.h
The BitmapClass is the exact same as before except that it has been modified to not have any texture information. The FadeShaderClass will be supplied with a texture from the RenderTextureClass object instead of using the BitmapClass to supply the texture. However since we want to treat the render to texture like a bitmap and draw it 2D on the screen we use this modified BitmapClass to build the buffers and render the geometry before calling the fade shader. //////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _BITMAPCLASS_H_ #define _BITMAPCLASS_H_
15 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
//////////////////////////////////////////////////////////////////////////////// // Class name: BitmapClass //////////////////////////////////////////////////////////////////////////////// class BitmapClass { private: struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; public: BitmapClass(); BitmapClass(const BitmapClass&); ~BitmapClass(); bool Initialize(ID3D11Device*, int, int, int, int); void Shutdown(); bool Render(ID3D11DeviceContext*, int, int); int GetIndexCount(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); bool UpdateBuffers(ID3D11DeviceContext*, int, int); void RenderBuffers(ID3D11DeviceContext*); bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; int m_screenWidth, m_screenHeight;
16 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
Bitmapclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "bitmapclass.h"
BitmapClass::~BitmapClass() { }
bool BitmapClass::Initialize(ID3D11Device* device, int screenWidth, int screenHeight, int bitmapWidth, int bitmapHeight) { bool result;
// Store the screen size. m_screenWidth = screenWidth; m_screenHeight = screenHeight; // Store the size in pixels that this bitmap should be rendered at. m_bitmapWidth = bitmapWidth;
17 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
m_bitmapHeight = bitmapHeight; // Initialize the previous rendering position to -1. m_previousPosX = -1; m_previousPosY = -1; // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } return true; }
void BitmapClass::Shutdown() { // Shutdown the vertex and index buffers. ShutdownBuffers(); return; }
// Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen. result = UpdateBuffers(deviceContext, positionX, positionY); if(!result) { return false; } // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return true; }
18 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
bool BitmapClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i;
// Set the number of vertices in the vertex array. m_vertexCount = 6; // Set the number of indices in the index array. m_indexCount = m_vertexCount; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } // Initialize vertex array to zeros at first. memset(vertices, 0, (sizeof(VertexType) * m_vertexCount)); // Load the index array with data. for(i=0; i<m_indexCount; i++) { indices[i] = i; }
19 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices;
20 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
void BitmapClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; }
bool BitmapClass::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY) { float left, right, top, bottom; VertexType* vertices; D3D11_MAPPED_SUBRESOURCE mappedResource; VertexType* verticesPtr; HRESULT result;
// If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it // currently has the correct parameters. if((positionX == m_previousPosX) && (positionY == m_previousPosY)) { return true; } // If it has changed then update the position it is being rendered to. m_previousPosX = positionX; m_previousPosY = positionY;
21 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Calculate the screen coordinates of the left side of the bitmap. left = (float)((m_screenWidth / 2) * -1) + (float)positionX; // Calculate the screen coordinates of the right side of the bitmap. right = left + (float)m_bitmapWidth; // Calculate the screen coordinates of the top of the bitmap. top = (float)(m_screenHeight / 2) - (float)positionY; // Calculate the screen coordinates of the bottom of the bitmap. bottom = top - (float)m_bitmapHeight; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Load the vertex array with data. // First triangle. vertices[0].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[1].texture = D3DXVECTOR2(1.0f, 1.0f); vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f); // Bottom left. vertices[2].texture = D3DXVECTOR2(0.0f, 1.0f); // Second triangle. vertices[3].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[4].position = D3DXVECTOR3(right, top, 0.0f); // Top right. vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f); vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[5].texture = D3DXVECTOR2(1.0f, 1.0f); // Lock the vertex buffer so it can be written to. result = deviceContext->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) {
22 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
return false; } // Get a pointer to the data in the vertex buffer. verticesPtr = (VertexType*)mappedResource.pData; // Copy the data into the vertex buffer. memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount)); // Unlock the vertex buffer. deviceContext->Unmap(m_vertexBuffer, 0); // Release the vertex array as it is no longer needed. delete [] vertices; vertices = 0; return true; }
// Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
23 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "textureshaderclass.h" #include "rendertextureclass.h" #include "bitmapclass.h" We include the new FadeShaderClass header file here. #include "fadeshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND);
24 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
void Shutdown(); bool Frame(float); bool Render(); private: bool RenderToTexture(float); bool RenderFadingScene(); bool RenderNormalScene(float); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; TextureShaderClass* m_TextureShader; RenderTextureClass* m_RenderTexture; BitmapClass* m_Bitmap; We have new variables to maintain information about the fade in effect. float m_fadeInTime, m_accumulatedTime, m_fadePercentage; bool m_fadeDone; We create a new FadeShaderClass object here. FadeShaderClass* m_FadeShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
25 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
m_TextureShader = 0; m_RenderTexture = 0; m_Bitmap = 0; Initialize the new FadeShaderClass object to null in the class constructor. m_FadeShader = 0; }
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false;
26 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
} // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds", "../Engine/data/cube.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } Create and initialize the render to texture object here. // Create the render to texture object. m_RenderTexture = new RenderTextureClass; if(!m_RenderTexture) { return false; } // Initialize the render to texture object. result = m_RenderTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight); if(!result) {
27 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
return false; } Create and initialize the bitmap object here. Set it to the size of the screen since it will be rendered full screen. // Create the bitmap object. m_Bitmap = new BitmapClass; if(!m_Bitmap) { return false; } // Initialize the bitmap object. result = m_Bitmap->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, screenWidth, screenHeight); if(!result) { MessageBox(hwnd, L"Could not initialize the bitmap object.", L"Error", MB_OK); return false; } Setup the fade in variables here. // Set the fade in time to 3000 milliseconds. m_fadeInTime = 3000.0f; // Initialize the accumulated time to zero milliseconds. m_accumulatedTime = 0; // Initialize the fade percentage to zero at first so the scene is black. m_fadePercentage = 0; // Set the fading in effect to not done. m_fadeDone = false; Create and initialize the fade shader class object here. // Create the fade shader object. m_FadeShader = new FadeShaderClass; if(!m_FadeShader) { return false; } // Initialize the fade shader object. result = m_FadeShader->Initialize(m_D3D->GetDevice(), hwnd);
28 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
if(!result) { MessageBox(hwnd, L"Could not initialize the fade shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { Release the new FadeShaderClass object in the Shutdown function. // Release the fade shader object. if(m_FadeShader) { m_FadeShader->Shutdown(); delete m_FadeShader; m_FadeShader = 0; } // Release the bitmap object. if(m_Bitmap) { m_Bitmap->Shutdown(); delete m_Bitmap; m_Bitmap = 0; } // Release the render to texture object. if(m_RenderTexture) { m_RenderTexture->Shutdown(); delete m_RenderTexture; m_RenderTexture = 0; } // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0;
29 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
} // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } The Frame function handles the fade in process. Each frame the frame time is passed into the Frame function and that is used to calculate the percentage of how much the texture should be faded in at this time. If the accumulated frame time exceeds the fade in time then we set the m_fadeDone variable to true so that we are no longer fading in and instead are rendering the scene normally. bool GraphicsClass::Frame(float frameTime) { if(!m_fadeDone) { // Update the accumulated time with the extra frame time addition. m_accumulatedTime += frameTime; // While the time goes on increase the fade in amount by the time that is passing each frame. if(m_accumulatedTime < m_fadeInTime) { // Calculate the percentage that the screen should be faded in based on the accumulated time. m_fadePercentage = m_accumulatedTime / m_fadeInTime; }
30 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
else { // If the fade in time is complete then turn off the fade effect and render the scene normally. m_fadeDone = true; // Set the percentage to 100%. m_fadePercentage = 1.0f; } } // Set the position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); return true; } The Render function handles the rotation of the cube and switches between rendering to texture and fading in or rendering normally if fading in is complete. bool GraphicsClass::Render() { bool result; static float rotation = 0.0f;
// Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } if(m_fadeDone) { // If fading in is complete then render the scene normally using the back buffer. RenderNormalScene(rotation); } else { // If fading in is not complete then render the scene to a texture and fade that texture in. result = RenderToTexture(rotation); if(!result) { return false; }
31 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
result = RenderFadingScene(); if(!result) { return false; } } return true; } The RenderToTexture function renders the 3D rotating cube scene to a texture instead of the back buffer. bool GraphicsClass::RenderToTexture(float rotation) { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; bool result;
// Set the render target to be the render to texture. m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView()); // Clear the render to texture. m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView(), 0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Multiply the world matrix by the rotation. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the model with the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; }
32 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget(); return true; } The RenderFadingScene function uses the render to texture of the 3D rotating cube scene and draws it as a 2D bitmap to the screen using the FadeShaderClass object. The FadeShaderClass object takes in the m_fadePercentage value to determine what percentage of brightness the texture should be at this frame which creates the fade in effect. bool GraphicsClass::RenderFadingScene() { D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix; bool result;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and ortho matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Put the bitmap vertex and index buffers on the graphics pipeline to prepare them for drawing. result = m_Bitmap->Render(m_D3D->GetDeviceContext(), 0, 0); if(!result) { return false; } // Render the bitmap using the fade shader. result = m_FadeShader->Render(m_D3D->GetDeviceContext(), m_Bitmap->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_RenderTexture->GetShaderResourceView(), m_fadePercentage); if(!result) { return false; }
33 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
// Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; } If we are no longer fading in the scene then we switch to using this function to draw the scene normally and remove the cost of using render to texture. bool GraphicsClass::RenderNormalScene(float rotation) { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; bool result;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Multiply the world matrix by the rotation. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the model with the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene();
34 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
return true; }
Summary
Using render to texture we can now fade in and fade out our 3D scenes. This effect can be extended to render the new and old scene at the same time to create more interesting transition effects.
To Do Exercises
1. Recompile and run the program. The cube show slowly fade in over a 5 second period and then start rendering normally. Press escape to quit. 2. Change the speed at which the scene fades in. 3. Change the color the back buffer is cleared to in the render to texture so you can see the exact moment the application switches to rendering normally. 4. Change the code to fade the scene out after fading it in.
Source Code
Visual Studio 2008 Project: dx11tut28.zip
35 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut28.html
36 of 36
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut32.html
Shader Algorithm
Step 1: Render the scene that is behind the glass to a texture, this is called the refraction. Step 2: Project the refraction texture onto the glass surface. Step 3: Perturb the texture coordinates of the refraction texture using a normal map to simulate light traveling through glass. Step 4: Combine the perturbed refraction texture with a glass color texture for the final result. We will now examine how to implement each step for both glass and ice.
Glass
So first we need to render our entire scene that is viewable behind the glass to a texture. And then we project that render to texture onto the glass surface so it appears that the glass is just a see through view of the scene although it is really a 2D texture rendered onto two triangles. We use render to texture and texture projection to do this which was covered in previous tutorials. To simplify the example instead of having a complicated scene with numerous objects we will just say that our scene is a single square with a texture on it. So
1 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
rendering the scene to texture and then projecting it onto the glass model produces the following refraction result:
If the scene were more complex your window would actually become invisible and everything would look the same. The reason being is that if the texture is perfectly projected it would just cover the same 3D scene section with a 2D texture of the same scene making the resulting glass model a perfectly clear see through glass with no way to differentiate it from the 3D scene itself. To even determine what is your glass model and what is the scene you will need to dim or brighten the glass texture to see that it actually is still there for debugging purposes. Now that the scene is projected onto a texture you need a normal map so you can eventually perturb the refraction texture to make it look like it is behind glass. We will use the following normal map which will give a stripped faceted look to the glass:
Now that we have a normal map we can use each individual pixel in the normal map as a look up for how to modify what pixel in the refraction texture is sampled.
2 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
This allows us to sample the refraction texture slightly above, beside, and below to simulate light not traveling straight through but instead being bent slightly such as it is in glass. The scale of light being bent is controlled by the refractionScale variable which we set fairly low for glass, in this example it was set to 0.01. Note that this is entirely dependent on the normal map used as the normals can vary little or greatly in the normal map which prevents us from really having a scale value that will always work. So now if we sample the refraction texture using the normal map texture as a lookup with the scale being 0.01 we get the following image:
The basic effect is mostly complete now. However most glass has a tint or color associated with it and sometimes other markings. For the glass in this example we will use the following color texture:
We take the color texture and the perturbed refraction and combine them to get the final glass effect:
3 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
Ice
Ice works exactly the same as glass with just different inputs into the shader. To start with we have the same scene of the textured square projected onto the ice surface model:
However with ice we want a different look to the final surface so we will use a different color texture:
4 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
Also the normal map will need to be different to simulate all the tiny bumps all over the surface with ice. Fortunately the color texture has just the right amount of noise in it to be used to make a ice normal map. Simply take the color texture above and use the Nivida normal map filter in Photoshop with a Scale of 5 and it creates the following normal map:
Now if we use that normal map and a stronger refractionScale such as 0.1 for ice (instead of how we used 0.01 for glass) we get the following heavily perturbed refraction image:
5 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
Finally if we combine the perturbed refraction texture with the ice color texture the resulting image is very realistic:
One final comment before we get into the code is that when you see these shaders working on surfaces that have motion behind them (such as a spinning cube behind the glass or ice) they look incredibly real. Make sure you at least run the executable for this tutorial to see what I'm talking about.
Framework
The frame work for this tutorial is similar to the previous tutorials. The only new class added is the GlassShaderClass which handles the glass and ice shading. The
6 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
RenderTextureClass is used in this tutorial for rendering the 3D scene to a texture. Also the TextureShaderClass is used to render the spinning cube model for the regular scene that will be behind the glass object.
We will start the code section by examining the HLSL code for the glass shader.
Glass.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: glass.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
7 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; The PixelInputType structure has a new refractionPosition variable for the refraction vertex coordinates that will be passed into the pixel shader. struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float4 refractionPosition : TEXCOORD1; };
//////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType GlassVertexShader(VertexInputType input) { PixelInputType output; matrix viewProjectWorld;
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; Create the matrix used for transforming the input vertex coordinates to the projected coordinates. // Create the view projection world matrix for refraction. viewProjectWorld = mul(viewMatrix, projectionMatrix); viewProjectWorld = mul(worldMatrix, viewProjectWorld); Transform the input vertex coordinates to the projected values and pass it into the pixel shader. // Calculate the input position against the viewProjectWorld matrix.
8 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
Glass.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: glass.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// SamplerState SampleType; The glass shader uses three different textures. The colorTexture is the basic surface color used for the glass. The normalTexture is the normal map look up table containing all the normal vectors. And finally the refractionTexture contains the 3D scene that is behind the glass rendered to a 2D texture. You will also notice I have used the direct register assignments. This helps clarify which texture is bound to which register instead of relying on the order they are placed in the HLSL file. Texture2D colorTexture : register(t0); Texture2D normalTexture : register(t1); Texture2D refractionTexture : register(t2); The GlassBuffer is used for setting the refractionScale. The refractionScale variable is used for scaling the amount of perturbation to the refraction texture. This is generally low for glass and higher for ice. cbuffer GlassBuffer { float refractionScale; float3 padding; };
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0;
9 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 GlassPixelShader(PixelInputType input) : SV_TARGET { float2 refractTexCoord; float4 normalMap; float3 normal; float4 refractionColor; float4 textureColor; float4 color; First convert the input projected homogenous coordinates (-1, +1) to (0, 1) texture coordinates. // Calculate the projected refraction texture coordinates. refractTexCoord.x = input.refractionPosition.x / input.refractionPosition.w / 2.0f + 0.5f; refractTexCoord.y = -input.refractionPosition.y / input.refractionPosition.w / 2.0f + 0.5f; Next sample the normal map and move it from (0, 1) texture coordinates to (-1, 1) coordinates. // Sample the normal from the normal map texture. normalMap = normalTexture.Sample(SampleType, input.tex); // Expand the range of the normal from (0,1) to (-1,+1). normal = (normalMap.xyz * 2.0f) - 1.0f; Now perturb the refraction texture sampling location by the normals that were calculated. Also multiply the normal by the refraction scale to increase or decrease the perturbation. // Re-position the texture coordinate sampling position by the normal map value to simulate light distortion through glass. refractTexCoord = refractTexCoord + (normal.xy * refractionScale); Next sample the refraction texture using the perturbed coordinates and sample the color texture using the normal input texture coordinates. // Sample the texture pixel from the refraction texture using the perturbed texture coordinates. refractionColor = refractionTexture.Sample(SampleType, refractTexCoord); // Sample the texture pixel from the glass color texture. textureColor = colorTexture.Sample(SampleType, input.tex); Finally combine the refraction and color texture for the final result.
10 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Evenly combine the glass color and refraction value for the final color. color = lerp(refractionColor, textureColor, 0.5f); return color; }
Glassshaderclass.h
The GlassShaderClass is based on the TextureShaderClass with slight changes for glass shading. //////////////////////////////////////////////////////////////////////////////// // Filename: glassshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GLASSSHADERCLASS_H_ #define _GLASSSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: GlassShaderClass //////////////////////////////////////////////////////////////////////////////// class GlassShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; We have a new structure type used for setting the refraction scale in the constant buffer inside the pixel shader.
11 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
public: GlassShaderClass(); GlassShaderClass(const GlassShaderClass&); ~GlassShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, float); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer; The glass shader needs a refraction scale value which the m_glassBuffer pointer provides an interface to. ID3D11Buffer* m_glassBuffer; }; #endif
Glassshaderclass.cpp
12 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
GlassShaderClass::GlassShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_sampleState = 0; m_matrixBuffer = 0; Initialize the glass buffer to null in the class constructor. m_glassBuffer = 0; }
GlassShaderClass::~GlassShaderClass() { }
bool GlassShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; Initialize the glass shader with the new glass.vs and glass.ps HLSL files. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/glass.vs", L"../Engine/glass.ps"); if(!result) { return false; } return true; }
13 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
void GlassShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes as input the color texture, normal map texture, refraction texture, and refraction scale value. These values are set in the shader first using the SetShaderParameters function before the rendering occurs in the RenderShader function which is called afterward. bool GlassShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* colorTexture, ID3D11ShaderResourceView* normalTexture, ID3D11ShaderResourceView* refractionTexture, float refractionScale) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, colorTexture, normalTexture, refractionTexture, refractionScale); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool GlassShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc;
14 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load the glass vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "GlassVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Load the glass pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "GlassPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else {
15 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
16 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // 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, &m_sampleState); if(FAILED(result)) { return false; } // Setup the description of the matrix dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
17 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
if(FAILED(result)) { return false; } Setup the description and create the glass buffer which will be used to set the refraction scale in the pixel shader. // Setup the description of the glass dynamic constant buffer that is in the pixel shader. glassBufferDesc.Usage = D3D11_USAGE_DYNAMIC; glassBufferDesc.ByteWidth = sizeof(GlassBufferType); glassBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; glassBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; glassBufferDesc.MiscFlags = 0; glassBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the pixel shader constant buffer from within this class. result = device->CreateBuffer(&glassBufferDesc, NULL, &m_glassBuffer); if(FAILED(result)) { return false; } return true; }
void GlassShaderClass::ShutdownShader() { Release the new glass buffer in the ShutdownShader function. // Release the glass constant buffer. if(m_glassBuffer) { m_glassBuffer->Release(); m_glassBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; }
18 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void GlassShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize();
19 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool GlassShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* colorTexture, ID3D11ShaderResourceView* normalTexture, ID3D11ShaderResourceView* refractionTexture, float refractionScale) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; GlassBufferType* dataPtr2; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false;
20 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
} // Get a pointer to the data in the matrix constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the matrix constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); The color, normal, and refraction textures are set in the pixel shader here. // Set the three shader texture resources in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &colorTexture); deviceContext->PSSetShaderResources(1, 1, &normalTexture); deviceContext->PSSetShaderResources(2, 1, &refractionTexture); The glass buffer is locked and then the refractionScale value is copied into the glass buffer and then set in the pixel shader. // Lock the glass constant buffer so it can be written to. result = deviceContext->Map(m_glassBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the glass constant buffer. dataPtr2 = (GlassBufferType*)mappedResource.pData; // Copy the variables into the glass constant buffer. dataPtr2->refractionScale = refractionScale; dataPtr2->padding = D3DXVECTOR3(0.0f, 0.0f, 0.0f); // Unlock the glass constant buffer. deviceContext->Unmap(m_glassBuffer, 0);
21 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Set the position of the glass constant buffer in the pixel shader. bufferNumber = 0; // Now set the glass constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_glassBuffer); return true; }
void GlassShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true;
22 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "rendertextureclass.h" #include "textureshaderclass.h" The new GlassShaderClass header file is included now. #include "glassshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool RenderToTexture(float); bool Render(float); private: D3DClass* m_D3D; CameraClass* m_Camera; We create a model for the spinning cube and the glass window. ModelClass* m_Model; ModelClass* m_WindowModel;
23 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
We need a render to texture object to render the spinning cube part of the scene. RenderTextureClass* m_RenderTexture; The texture shader is used to render the normal scene. The glass shader is used to render the glass window model. TextureShaderClass* m_TextureShader; GlassShaderClass* m_GlassShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_WindowModel = 0; m_RenderTexture = 0; m_TextureShader = 0; The new GlassShaderClass object is initialized to null in the class constructor. m_GlassShader = 0; }
GraphicsClass::~GraphicsClass() {
24 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } Create a model for the cube that will be spinning behind the glass window. It has a normal map associated with it but is not used so you can ignore the last parameter of the Initialize. I did this just to make the function generic. // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds", L"../Engine/data/bump03.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
25 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
return false; } Create a model for the glass window. It uses the square .obj model since the window will just be two triangles that make up a square. It also uses a texture called glass01.dds for the glass color and a normal map called bump03.dds for the perturbation of the glass refraction. // Create the window model object. m_WindowModel = new ModelClass; if(!m_WindowModel) { return false; } // Initialize the window model object. result = m_WindowModel->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/glass01.dds", L"../Engine/data/bump03.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the window model object.", L"Error", MB_OK); return false; } The render to texture object will be used to render the refraction of the scene to a texture and then passed into the glass shader as input. // Create the render to texture object. m_RenderTexture = new RenderTextureClass; if(!m_RenderTexture) { return false; } // Initialize the render to texture object. result = m_RenderTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight); if(!result) { return false; } The texture shader is used to render the spinning cube. // Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; }
26 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } This is where the new glass shader is created and initialized. // Create the glass shader object. m_GlassShader = new GlassShaderClass; if(!m_GlassShader) { return false; } // Initialize the glass shader object. result = m_GlassShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the glass shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { The new glass shader is released here in the Shutdown function. // Release the glass shader object. if(m_GlassShader) { m_GlassShader->Shutdown(); delete m_GlassShader; m_GlassShader = 0; } // Release the texture shader object. if(m_TextureShader)
27 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
{ m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } // Release the render to texture object. if(m_RenderTexture) { m_RenderTexture->Shutdown(); delete m_RenderTexture; m_RenderTexture = 0; } // Release the window model object. if(m_WindowModel) { m_WindowModel->Shutdown(); delete m_WindowModel; m_WindowModel = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the Direct3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; }
28 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
return; }
bool GraphicsClass::Frame() { static float rotation = 0.0f; bool result; We update the rotation of the cube each frame and send the same value into both the RenderToTexture and Render function to keep the rotation in sync. // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } The position of the camera is set here also. // Set the position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); First we render the 3D scene to a texture so the glass shader will have a refraction texture as input. // Render the scene to texture first. result = RenderToTexture(rotation); if(!result) { return false; } Then we render the scene again normally and render the glass over top of it with the perturbed and colored refraction texture rendered on the glass model. // Render the scene. result = Render(rotation); if(!result) { return false; } return true; } The RenderToTexture function just renders the 3D spinning cube scene to a texture.
29 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
// Set the render target to be the render to texture. m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView()); // Clear the render to texture. m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView(), 0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Multiply the world matrix by the rotation. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the cube model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the cube model using the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture if(!result) { return false; } // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget(); return true; }
bool GraphicsClass::Render(float rotation) { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; float refractionScale; bool result;
30 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
First set the refraction scale to modify how much perturbation occurs in the glass. // Set the refraction scale for the glass shader. refractionScale = 0.01f; // Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); Then render the 3D spinning cube scene as normal. // Multiply the world matrix by the rotation. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the cube model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); // Render the cube model using the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; } // Reset the world matrix. m_D3D->GetWorldMatrix(worldMatrix); Now render the window model using the glass shader with the color texture, normal map, refraction render to texture, and refraction scale as input. // Translate to back where the window model will be rendered. D3DXMatrixTranslation(&worldMatrix, 0.0f, 0.0f, -1.5f); // Put the window model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_WindowModel->Render(m_D3D->GetDeviceContext()); // Render the window model using the glass shader. result = m_GlassShader->Render(m_D3D->GetDeviceContext(), m_WindowModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
31 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
m_WindowModel->GetTexture(), m_WindowModel->GetNormalMap(), m_RenderTexture->GetShaderResourceView(), refractionScale); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
We can now render both glass and ice effects through the use of refraction and a normal map for perturbation.
32 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
33 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut32.html
To Do Exercises
1. Recompile and run the program. You should get a spinning cube behind green perturbed glass. Press escape to quit. 2. To see the ice effect change the following function in GraphicsClass::Initialize from: result = m_WindowModel->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/glass01.dds", L"../Engine/data/bump03.dds"); To: result = m_WindowModel->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/ice01.dds", L"../Engine/data/icebump01.dds"); And change the refractionScale to 0.1f and move the camera closer: m_Camera->SetPosition(0.0f, 0.0f, -5.0f); refractionScale = 0.1f; Now recompile and run the program with those three changes to see the ice effect. 3. Change the value of the refractionScale to see how it affects the perturbation. 4. Modify the combination of the color texture and the perturbed refraction texture in the pixel shader to get different output results. 5. Make your own glass color texture and normal map and get your own personal glass shader effect to work (also modify the refractionScale so it looks right for your normal map).
Source Code
Visual Studio 2008 Project: dx11tut32.zip Source Only: dx11src32.zip Executable Only: dx11exe32.zip
34 of 34
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut33.html
These noise textures can be procedurally generated with several different graphics manipulation programs. The key is to produce one that has noise properties specific to a good looking fire. The second texture needed for fire effect will be a texture composed of fire colored noise and a flame outline. For example the following texture is composed of a texture that uses perlin noise with fire colors and a texture of a small flame. If you look closely at the middle bottom part of the texture you can see the umbra and penumbra of the flame:
And finally you will need an alpha texture for transparency of the flame so that the final shape is very close to that of a small flame. This can be rough as the perturbation will take care of making it take shape of a good flame:
1 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
Now that we have the three textures required for the fire effect we can explain how the shader works. The first thing we do is take the noise texture and create three separate textures from it. The three separate textures are all based on the original noise texture except that they are scaled differently. These scales are called octaves as they are simply repeated tiles of the original noise texture to create numerous more detailed noise textures. The following three noise textures are scaled (tiled) by 1, 2, and 3:
We are also going to scroll all three noise textures upwards to create an upward moving noise which will correlate with a fire that burns upwards. The scroll speed will be set differently for all three so that each moves at a different rate. We will eventually combine the three noise textures so having them move at different rates adds dimension to the fire.
2 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
The next step is to convert the three noise textures from the (0, 1) range into the (-1, +1) range. This has the effect of removing some of the noise and creating noise that looks closer to fire. For example the first noise texture now looks like the following:
With all three noise textures in the (-1, +1) range we can now add distortion to each texture along the x and y coordinates. The distortion modifies the noise textures by scaling down the noise similar to how the refraction scale in the water, glass, and ice shaders did so. Once each noise texture is distorted we can then combine all three noise textures to produce a final noise texture. Note that the distortion here is only applied to the x and y coordinates since we will be using them as a look up table for the x and y texture sampling just like we do with normal maps. The z coordinate is ignored as it has no use when sampling textures in 2D. Remember also that each frame the three noise textures are scrolling upwards at different rates so combining them creates an almost organized flowing noise that looks like fire. Now the next very important step is to perturb the original fire color texture. In other shaders such as water, glass, and ice we usually use a normal map at this point to perturb the texture sampling coordinates. However in fire we use the noise as our perturbed texture sampling coordinates. But before we do that we want to also perturb the noise itself. We will use a distortion scale and bias to distort the noise higher at the top of the texture and less at the bottom of the texture to create a solid flame at the base and flame licks at the top. In other words we perturb the noise along the Y axis using an increased amount of distortion as we go up the noise texture from the bottom. We now use the perturbed final noise texture as our look up table for texture sampling coordinates that will be used to sample the fire color texture. Note that we use Clamp instead of Wrap for the fire color texture or we will get flame licks wrapping around to the bottom which would ruin the look. The fire color texture sampled using the perturbed noise now looks like the following:
Now that we have an animated burning square that looks fairly realistic we need a way of shaping it into more of a flame. To do so we turn on blending and use the alpha texture we created earlier. The trick is to sample the alpha texture using the same perturbed noise to make the alpha look like a burning flame. We also need to use Clamp instead of Wrap for sampling to prevent the flames from wrapping around to the bottom. When we do so we get the following result from the sampled perturbed alpha:
3 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
To complete the effect we set the perturbed alpha value to be the alpha channel of the perturbed fire color texture and the blending takes care of the rest:
Framework
The frame work has one new class called FireShaderClass which is just the TextureShaderClass updated for the fire effect.
4 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
We will start the code section by examining the HLSL fire shader.
Fire.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: fire.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; For the vertex shader we create a new constant buffer that will contain the values needed for calculations each frame. The first variable called frameTime is updated each frame so the shader has access to an incremental time that is used for scrolling the different noise textures. The second variable scrollSpeeds is a 3 float array that contains three different scrolling speeds. The x value is the scroll speed for the first noise texture. The y value is the scroll speed for the second noise texture. And the z value is the scroll speed for the third noise texture. The third variable scales is a 3 float array that contains three different scales (or octaves) for the three different noise textures. The x, y, and z values of scales is generally set to 1, 2, and 3. This will make the first noise texture a single tile. It also makes the second noise texture tiled twice in both directions. And finally it makes the third noise texture tiled three times in both directions. The last variable is called padding. It is a single float that is used to make the NoiseBuffer a size that is divisible by 16. Padding will generally just be set to 0.0f. cbuffer NoiseBuffer { float frameTime; float3 scrollSpeeds; float3 scales; float padding; };
5 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; The PixelInputType structure has been changed to take three different texture coordinates. We use it for sampling the same noise texture in three different ways to basically create three different noise textures from one. struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float2 texCoords1 : TEXCOORD1; float2 texCoords2 : TEXCOORD2; float2 texCoords3 : TEXCOORD3; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; Here is where we create three different texture sampling values so that the same noise texture can be used to create three different noise textures. For each texture coordinate we first scale it by the scale value which then tiles the texture a number of times depending on the value in the scales array. After that we scroll the three different y coordinates upwards using the frame time and the value in the scrollSpeeds array. The scroll speed for all three will be different which gives the fire dimension. // Compute texture coordinates for first noise texture using the first scale and upward scrolling speed values. output.texCoords1 = (input.tex * scales.x);
6 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
output.texCoords1.y = output.texCoords1.y + (frameTime * scrollSpeeds.x); // Compute texture coordinates for second noise texture using the second scale and upward scrolling speed values. output.texCoords2 = (input.tex * scales.y); output.texCoords2.y = output.texCoords2.y + (frameTime * scrollSpeeds.y); // Compute texture coordinates for third noise texture using the third scale and upward scrolling speed values. output.texCoords3 = (input.tex * scales.z); output.texCoords3.y = output.texCoords3.y + (frameTime * scrollSpeeds.z); return output; }
Fire.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: fire.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// The three textures for the fire effect are the fire color texture, the noise texture, and the alpha texture. Texture2D fireTexture : register(t0); Texture2D noiseTexture : register(t1); Texture2D alphaTexture : register(t2); SamplerState SampleType; We add a second sample state that uses Clamp. The Wrap used in the first sample state would cause the fire to wrap around which ruins the effect. SamplerState SampleType2; The pixel shader has a constant buffer called DistorionBuffer which contains the values needed by the pixel shader to create the fire effect. The three distortion arrays in the buffer contain a x and y value for distorting the three different noise textures by individual x and y parameters. The distortion scale and bias values in the DistortionBuffer are used for perturbing the final combined noise texture to make it take the shape of a flame. cbuffer DistortionBuffer { float2 distortion1;
7 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float2 texCoords1 : TEXCOORD1; float2 texCoords2 : TEXCOORD2; float2 texCoords3 : TEXCOORD3; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 FirePixelShader(PixelInputType input) : SV_TARGET { float4 noise1; float4 noise2; float4 noise3; float4 finalNoise; float perturb; float2 noiseCoords; float4 fireColor; float4 alphaColor; First create three different noise values by sampling the noise texture three different ways. Afterward move the texture pixel value into the (-1, +1) range. // Sample the same noise texture using the three different texture coordinates to get three different noise scales. noise1 = noiseTexture.Sample(SampleType, input.texCoords1); noise2 = noiseTexture.Sample(SampleType, input.texCoords2); noise3 = noiseTexture.Sample(SampleType, input.texCoords3); // Move the noise from the (0, 1) range to the (-1, +1) range. noise1 = (noise1 - 0.5f) * 2.0f; noise2 = (noise2 - 0.5f) * 2.0f; noise3 = (noise3 - 0.5f) * 2.0f;
8 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
Now scale down the x and y sampling coordinates by the distortion amount. After they are distorted all three texture values are combined into a single value which represents the final noise value for this pixel. // Distort the three noise x and y coordinates by the three different distortion x and y values. noise1.xy = noise1.xy * distortion1.xy; noise2.xy = noise2.xy * distortion2.xy; noise3.xy = noise3.xy * distortion3.xy; // Combine all three distorted noise results into a single noise result. finalNoise = noise1 + noise2 + noise3; We now perturb the final noise result to create a fire look to the overall noise texture. Note that we perturb it more at the top and less as it moves to the bottom. This creates a flickering flame at the top and as it progresses downwards it creates a more solid flame base. // Perturb the input texture Y coordinates by the distortion scale and bias values. // The perturbation gets stronger as you move up the texture which creates the flame flickering at the top effect. perturb = ((1.0f - input.tex.y) * distortionScale) + distortionBias; // Now create the perturbed and distorted texture sampling coordinates that will be used to sample the fire color texture. noiseCoords.xy = (finalNoise.xy * perturb) + input.tex.xy; Sample both the fire color texture and the alpha texture by the perturbed noise sampling coordinates to create the fire effect. // Sample the color from the fire texture using the perturbed and distorted texture sampling coordinates. // Use the clamping sample state instead of the wrap sample state to prevent flames wrapping around. fireColor = fireTexture.Sample(SampleType2, noiseCoords.xy); // Sample the alpha value from the alpha texture using the perturbed and distorted texture sampling coordinates. // This will be used for transparency of the fire. // Use the clamping sample state instead of the wrap sample state to prevent flames wrapping around. alphaColor = alphaTexture.Sample(SampleType2, noiseCoords.xy); Combine the alpha and the fire color to create the transparent blended final fire effect. // Set the alpha blending of the fire to the perturbed and distored alpha texture value. fireColor.a = alphaColor; return fireColor; }
Fireshaderclass.h
9 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
The FireShaderClass is just the TextureShaderClass modified for the fire effect. //////////////////////////////////////////////////////////////////////////////// // Filename: fireshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _FIRESHADERCLASS_H_ #define _FIRESHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: FireShaderClass //////////////////////////////////////////////////////////////////////////////// class FireShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; The vertex shader has a constant buffer with variables for calculating noise so we need to create a structure that mirrors it so that we can set those values. This structure contains the frame speed, the three different scroll speeds, and the three different noise scales. The structure also has a padding variable that is used to make the structure a multiple of 16 which is a requirement for when the noise buffer is created. struct NoiseBufferType { float frameTime; D3DXVECTOR3 scrollSpeeds; D3DXVECTOR3 scales; float padding; }; The pixel shader also has a constant buffer with variables used for distorting the noise values to create the fire effect. This new structure is used in conjunction with
10 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
a buffer to set the values in the pixel shader. The structure contains the three distortion arrays and the distortion scale and bias. struct DistortionBufferType { D3DXVECTOR2 distortion1; D3DXVECTOR2 distortion2; D3DXVECTOR2 distortion3; float distortionScale; float distortionBias; }; public: FireShaderClass(); FireShaderClass(const FireShaderClass&); ~FireShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, float, D3DXVECTOR3, D3DXVECTOR3, D3DXVECTOR2, D3DXVECTOR2, D3DXVECTOR2, float, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, float, D3DXVECTOR3, D3DXVECTOR3, D3DXVECTOR2, D3DXVECTOR2, D3DXVECTOR2, float, float); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; We have a new buffer for the noise constant buffer in the vertex shader. ID3D11Buffer* m_noiseBuffer; ID3D11SamplerState* m_sampleState; There is a new sampler state which will use Clamp instead of Wrap for the fire effect. ID3D11SamplerState* m_sampleState2;
11 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
Also there is a new distortion buffer for the distortion constant buffer in the pixel shader. ID3D11Buffer* m_distortionBuffer; }; #endif
Fireshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: fireshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "fireshaderclass.h" All the private pointers in the class are initialized to null in the class constructor. FireShaderClass::FireShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; m_noiseBuffer = 0; m_sampleState = 0; m_sampleState2 = 0; m_distortionBuffer = 0; }
FireShaderClass::~FireShaderClass() { }
12 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
The fire.vs and fire.ps HLSL files are loaded here. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/fire.vs", L"../Engine/fire.ps"); if(!result) { return false; } return true; }
void FireShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function takes in all the numerous input variables that are used to render and tweak the look of the fire. They are first set in the shader using the SetShaderParameters function. Once all the values are set the shader is used for rendering by then calling the RenderShader function. bool FireShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* fireTexture, ID3D11ShaderResourceView* noiseTexture, ID3D11ShaderResourceView* alphaTexture, float frameTime, D3DXVECTOR3 scrollSpeeds, D3DXVECTOR3 scales, D3DXVECTOR2 distortion1, D3DXVECTOR2 distortion2, D3DXVECTOR2 distortion3, float distortionScale, float distortionBias) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, fireTexture, noiseTexture, alphaTexture, frameTime, scrollSpeeds, scales, distortion1, distortion2, distortion3, distortionScale, distortionBias); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount);
13 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
return true; }
bool FireShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_BUFFER_DESC noiseBufferDesc; D3D11_SAMPLER_DESC samplerDesc; D3D11_SAMPLER_DESC samplerDesc2; D3D11_BUFFER_DESC distortionBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Load the fire vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "FireVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; }
14 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
Load the fire pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "FirePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
15 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the matrix buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } Setup and create the new noise buffer.
16 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
// Setup the description of the dynamic noise constant buffer that is in the vertex shader. noiseBufferDesc.Usage = D3D11_USAGE_DYNAMIC; noiseBufferDesc.ByteWidth = sizeof(NoiseBufferType); noiseBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; noiseBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; noiseBufferDesc.MiscFlags = 0; noiseBufferDesc.StructureByteStride = 0; // Create the noise buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&noiseBufferDesc, NULL, &m_noiseBuffer); 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, &m_sampleState); if(FAILED(result)) { return false; } Setup and create the new sampler state that uses CLAMP instead of WRAP. // Create a second texture sampler state description for a Clamp sampler. samplerDesc2.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc2.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc2.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc2.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc2.MipLODBias = 0.0f;
17 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
samplerDesc2.MaxAnisotropy = 1; samplerDesc2.ComparisonFunc = D3D11_COMPARISON_ALWAYS; samplerDesc2.BorderColor[0] = 0; samplerDesc2.BorderColor[1] = 0; samplerDesc2.BorderColor[2] = 0; samplerDesc2.BorderColor[3] = 0; samplerDesc2.MinLOD = 0; samplerDesc2.MaxLOD = D3D11_FLOAT32_MAX; // Create the texture sampler state. result = device->CreateSamplerState(&samplerDesc2, &m_sampleState2); if(FAILED(result)) { return false; } Setup and create the new distortion buffer. // Setup the description of the dynamic distortion constant buffer that is in the pixel shader. distortionBufferDesc.Usage = D3D11_USAGE_DYNAMIC; distortionBufferDesc.ByteWidth = sizeof(DistortionBufferType); distortionBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; distortionBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; distortionBufferDesc.MiscFlags = 0; distortionBufferDesc.StructureByteStride = 0; // Create the distortion buffer pointer so we can access the pixel shader constant buffer from within this class. result = device->CreateBuffer(&distortionBufferDesc, NULL, &m_distortionBuffer); if(FAILED(result)) { return false; } return true; }
void FireShaderClass::ShutdownShader() { The ShutdownShader function releases all the pointers that were used to access values inside the fire shader. // Release the distortion constant buffer. if(m_distortionBuffer) {
18 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
m_distortionBuffer->Release(); m_distortionBuffer = 0; } // Release the second sampler state. if(m_sampleState2) { m_sampleState2->Release(); m_sampleState2 = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the noise constant buffer. if(m_noiseBuffer) { m_noiseBuffer->Release(); m_noiseBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; }
19 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
void FireShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return;
20 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
bool FireShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* fireTexture, ID3D11ShaderResourceView* noiseTexture, ID3D11ShaderResourceView* alphaTexture, float frameTime, D3DXVECTOR3 scrollSpeeds, D3DXVECTOR3 scales, D3DXVECTOR2 distortion1, D3DXVECTOR2 distortion2, D3DXVECTOR2 distortion3, float distortionScale, float distortionBias) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; NoiseBufferType* dataPtr2; DistortionBufferType* dataPtr3; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); Set the matrix buffer in the vertex shader as usual. // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the matrix constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the matrix constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0;
21 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
// Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); Set the new noise buffer in the vertex shader. // Lock the noise constant buffer so it can be written to. result = deviceContext->Map(m_noiseBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the noise constant buffer. dataPtr2 = (NoiseBufferType*)mappedResource.pData; // Copy the data into the noise constant buffer. dataPtr2->frameTime = frameTime; dataPtr2->scrollSpeeds = scrollSpeeds; dataPtr2->scales = scales; dataPtr2->padding = 0.0f; // Unlock the noise constant buffer. deviceContext->Unmap(m_noiseBuffer, 0); // Set the position of the noise constant buffer in the vertex shader. bufferNumber = 1; // Now set the noise constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_noiseBuffer); Set the three textures in the pixel shader. // Set the three shader texture resources in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &fireTexture); deviceContext->PSSetShaderResources(1, 1, &noiseTexture); deviceContext->PSSetShaderResources(2, 1, &alphaTexture); Set the new distortion buffer in the pixel shader. // Lock the distortion constant buffer so it can be written to. result = deviceContext->Map(m_distortionBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false;
22 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
} // Get a pointer to the data in the distortion constant buffer. dataPtr3 = (DistortionBufferType*)mappedResource.pData; // Copy the data into the distortion constant buffer. dataPtr3->distortion1 = distortion1; dataPtr3->distortion2 = distortion2; dataPtr3->distortion3 = distortion3; dataPtr3->distortionScale = distortionScale; dataPtr3->distortionBias = distortionBias; // Unlock the distortion constant buffer. deviceContext->Unmap(m_distortionBuffer, 0); // Set the position of the distortion constant buffer in the pixel shader. bufferNumber = 0; // Now set the distortion constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_distortionBuffer); return true; }
void FireShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler states in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); Set the new sampler state that use CLAMP in the pixel shader. deviceContext->PSSetSamplers(1, 1, &m_sampleState2); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return;
23 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" We add the header for the new FireShaderClass. #include "fireshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass();
24 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; The private FireShaderClass object is added here also. FireShaderClass* m_FireShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; Initialize the new fire shader object to null in the class constructor. m_FireShader = 0; }
25 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } Load a square model for the fire. Also load the three textures that will be used to create the fire effect for this model. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/fire01.dds",
26 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
L"../Engine/data/noise01.dds", L"../Engine/data/alpha01.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } Create and initialize the new fire shader object. // Create the fire shader object. m_FireShader = new FireShaderClass; if(!m_FireShader) { return false; } // Initialize the fire shader object. result = m_FireShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the fire shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { Shutdown and release the new fire shader object in the Shutdown function. // Release the fire shader object. if(m_FireShader) { m_FireShader->Shutdown(); delete m_FireShader; m_FireShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown();
27 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the Direct3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
// Set the position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Render the scene. result = Render(); if(!result) { return false; } return true; }
28 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
bool result; D3DXVECTOR3 scrollSpeeds, scales; D3DXVECTOR2 distortion1, distortion2, distortion3; float distortionScale, distortionBias; static float frameTime = 0.0f; Each frame increment the time. This is used to scroll the three different noise textures in the shader. Note that if you don't lock the FPS to 60 then you will need to determine the difference of time each frame and update a timer to keep the fire burning at a consistent speed regardless of the FPS. // Increment the frame time counter. frameTime += 0.01f; if(frameTime > 1000.0f) { frameTime = 0.0f; } Set the three scroll speeds, scales, and distortion values for the three different noise textures. // Set the three scrolling speeds for the three different noise textures. scrollSpeeds = D3DXVECTOR3(1.3f, 2.1f, 2.3f); // Set the three scales which will be used to create the three different noise octave textures. scales = D3DXVECTOR3(1.0f, 2.0f, 3.0f); // Set the three different x and y distortion factors for the three different noise textures. distortion1 = D3DXVECTOR2(0.1f, 0.2f); distortion2 = D3DXVECTOR2(0.1f, 0.3f); distortion3 = D3DXVECTOR2(0.1f, 0.1f); Set the bias and scale that are used for perturbing the noise texture into a flame form. // The the scale and bias of the texture coordinate sampling perturbation. distortionScale = 0.8f; distortionBias = 0.5f; // Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix);
29 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
This shader requires blending as we use a perturbed alpha texture for sampling and creating see through parts of the final fire effect. // Turn on alpha blending for the fire transparency. m_D3D->TurnOnAlphaBlending(); Put the square model on the graphics pipeline. // Put the square model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); Render the square model using the flame shader. // Render the square model using the fire shader. result = m_FireShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture1(), m_Model->GetTexture2(), m_Model->GetTexture3(), frameTime, scrollSpeeds, scales, distortion1, distortion2, distortion3, distortionScale, distortionBias); if(!result) { return false; } // Turn off alpha blending. m_D3D->TurnOffAlphaBlending(); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
The fire shader produces an incredibly realistic fire effect. It is also highly customizable giving it the ability to produce almost any type of fire flame by just modifying one or more of the many tweakable shader variables. The flame will still need to be bill boarded or rendered a couple times at different angles in the same location to be used realistically in a 3D setting.
30 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut33.html
To Do Exercises
1. Recompile and run the program. You should get an animated fire effect. Press escape to quit. 2. Modify the many different shader values to see the different effects they produce. Start with scale and bias. You may also want to comment out certain parts of the fire shader to see the effect they have on the different textures. 3. Create your own noise texture and see how it changes the fire. 4. Create your own alpha texture to modify the overall shape of the flame.
Source Code
Visual Studio 2008 Project: dx11tut33.zip Source Only: dx11src33.zip Executable Only: dx11exe33.zip
31 of 31
3/8/2013 12:42 PM
http://www.rastertek.com/dx11tut35.html
Also if we want to look at it from a 3D scene perspective take for example the following scene:
1 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
Now if we just render the objects with their depth value as their color you will see the close cube object has a decent range of color and the sphere is almost completely white with little detail:
The reason the depth buffer is setup this way is because the majority of applications want a lot of precise detail up close and are not as concerned about precision with distant objects. If distance is important in your application then you may run into issues with distant objects that are close together overlapping causing pixels to
2 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
flicker (also called Z fighting). There are a number of well known solutions to this issue but it comes at a cost depending on your implementation. In this tutorial we are going to look at just rendering a simple 10 float unit plane and coloring it based on the depth information. We will color the portion that is closest to the screen as red, then the next small portion as green, and then finally the remainder to the far clip plane as blue. The reason we are doing this is to show you a way of performing detail calculations with depth. This will allow you to extend the technique to do things such as bump mapping things that are close and then as they get distant you can move to just regular diffuse lighting. Also note that depth buffers can be used for so much more, one example being projective shadow mapping, but we are going to just start with the basics.
Framework
The frame work for this tutorial has just the basics with a new class called DepthShaderClass which will handle the depth shading.
We will start the code section of the tutorial by examining the HLSL depth shader first.
Depth.vs
//////////////////////////////////////////////////////////////////////////////// // Filename: depth.vs //////////////////////////////////////////////////////////////////////////////// The depth vertex shader only requires the matrix buffer, no other globals are needed. ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix;
3 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// For input into the vertex shader we only require position. Texturing, normals, and anything else is not required since we are only planning to color pixels based on depth. struct VertexInputType { float4 position : POSITION; }; For input into the pixel shader we will need the vertex position in homogeneous clip space stored in the position variable as usual. However since the SV_POSITION semantic offsets the position by 0.5 we will need a second set of coordinates called depthPosition that are not modified so that we can perform depth calculations. struct PixelInputType { float4 position : SV_POSITION; float4 depthPosition : TEXTURE0; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); As described in the pixel shader input we store a second pair of position coordinates that will not be offset which will be used for depth calculations.
4 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
// Store the position value in a second input value for depth value calculations. output.depthPosition = output.position; return output; }
Depth.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: depth.ps ////////////////////////////////////////////////////////////////////////////////
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float4 depthPosition : TEXTURE0; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 DepthPixelShader(PixelInputType input) : SV_TARGET { float depthValue; float4 color; First we get the depth value for this pixel, note that it is stored as z/w so we perform the necessary division to get the depth. // Get the depth value of the pixel by dividing the Z pixel depth by the homogeneous W coordinate. depthValue = input.depthPosition.z / input.depthPosition.w; The following section is where we determine how to color the pixel. Remember the diagram at the top of the tutorial which describes how the first 10% of the depth buffer contains 90% of the float values. In this tutorial we will color that entire section as red. Then following that we will color a very tiny portion of the depth buffer green, notice it is only 0.025% in terms of precision but it takes up a large section close to the near plane. This green section helps you understand how quickly the precision falls off. The remaining section of the depth buffer is colored blue. // First 10% of the depth buffer color red.
5 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
if(depthValue < 0.9f) { color = float4(1.0, 0.0f, 0.0f, 1.0f); } // The next 0.025% portion of the depth buffer color green. if(depthValue > 0.9f) { color = float4(0.0, 1.0f, 0.0f, 1.0f); } // The remainder of the depth buffer color blue. if(depthValue > 0.925f) { color = float4(0.0, 0.0f, 1.0f, 1.0f); } return color; }
Depthshaderclass.h
The DepthShaderClass is a very simple shader class that is similar to the first color shader we looked at when we first started examining HLSL. //////////////////////////////////////////////////////////////////////////////// // Filename: depthshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _DEPTHSHADERCLASS_H_ #define _DEPTHSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
////////////////////////////////////////////////////////////////////////////////
6 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
// Class name: DepthShaderClass //////////////////////////////////////////////////////////////////////////////// class DepthShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: DepthShaderClass(); DepthShaderClass(const DepthShaderClass&); ~DepthShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; We only require the matrix constant buffer. ID3D11Buffer* m_matrixBuffer; }; #endif
Depthshaderclass.cpp
7 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
//////////////////////////////////////////////////////////////////////////////// // Filename: depthshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "depthshaderclass.h" The class constructor initializes all the private pointers to null. DepthShaderClass::DepthShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; }
DepthShaderClass::~DepthShaderClass() { }
bool DepthShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; We load the depth.fx HLSL shader here. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/depth.vs", L"../Engine/depth.ps"); if(!result) { return false; } return true; }
void DepthShaderClass::Shutdown() {
8 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
// Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } Once again this shader is fairly simple and only requires the three regular matrices as input. bool DepthShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool DepthShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; We only need a single element in our polygonLayout description. D3D11_INPUT_ELEMENT_DESC polygonLayout[1]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0;
9 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
pixelShaderBuffer = 0; Load in the depth vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "DepthVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Load in the depth pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "DepthPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer.
10 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } Notice the layout only uses position to match the input to the HLSL shader. The VertexType in the ModelClass will need to be modified to match this also. // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
11 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } return true; }
void DepthShaderClass::ShutdownShader() { // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; }
12 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
return; }
void DepthShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; } Only the three regular matrices are needed as global inputs to the shader. bool DepthShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectio { HRESULT result;
13 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); return true; }
void DepthShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0);
14 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" We include the new header for the DepthShaderClass. #include "depthshaderclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; We have changed the near and far plane values to reduce how much precision is usually lost by the larger values. const float SCREEN_DEPTH = 100.0f; const float SCREEN_NEAR = 1.0f;
////////////////////////////////////////////////////////////////////////////////
15 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
// Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; We now have a private object for the new DepthShaderClass. DepthShaderClass* m_DepthShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
16 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } Set the camera up a bit to view the plane that will be rendered.
17 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
// Set the initial position of the camera. m_Camera->SetPosition(0.0f, 2.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } Load the floor.txt model in. // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/floor.txt"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } Load the floor.txt model in. // Create the depth shader object. m_DepthShader = new DepthShaderClass; if(!m_DepthShader) { return false; } // Initialize the depth shader object. result = m_DepthShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the depth shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { Release the new DepthShaderClass object in the Shutdown function.
18 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
// Release the depth shader object. if(m_DepthShader) { m_DepthShader->Shutdown(); delete m_DepthShader; m_DepthShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
19 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
} return true; }
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); Render the floor model using the new depth shader. // Render the model using the depth shader. result = m_DepthShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
20 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut35.html
We can now render the floor using color values to represent depth ranges. Also remember that the depth buffer can be used for many other purposes.
To Do Exercises
1. Recompile and run the program, you should see a floor rendered with three colored depth values. 2. Modify the ranges in the pixel shader to see the range output it changes. 3. Add a fourth color between the green and blue specifying a very small range. 4. Have the pixel shader return the depth value instead of the color. 5. Modify the near and far plane in the GraphicsClass.h file to see the effects it has on the precision.
Source Code
Visual Studio 2010 Project: dx11tut35.zip Source Only: dx11src35.zip Executable Only: dx11exe35.zip
21 of 21
3/8/2013 12:39 PM
http://www.rastertek.com/dx11tut37.html
Modelclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_
1 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
{ private: struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; We add a new structure that will hold the instance information. In this tutorial we are modifying the position of each instance of the triangle so we use a position vector. But note that it could be anything else you want to modify for each instance such as color, size, rotation, and so forth. You can modify multiple things at once for each instance also. struct InstanceType { D3DXVECTOR3 position; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D11Device*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); We have two new functions for getting the vertex and instance counts. We also removed the helper function which previously returned the index count as the instance count has replaced that. int GetVertexCount(); int GetInstanceCount(); ID3D11ShaderResourceView* GetTexture(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer* m_vertexBuffer; The ModelClass now has an instance buffer instead of an index buffer. All buffers in DirectX 11 are generic so it uses the ID3D11Buffer type.
2 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
ID3D11Buffer* m_instanceBuffer; int m_vertexCount; The index count has been replaced with the instance count. int m_instanceCount; TextureClass* m_Texture; }; #endif
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
ModelClass::ModelClass() { m_vertexBuffer = 0; Initialize the new instance buffer to null in the class constructor. m_instanceBuffer = 0; m_Texture = 0; }
ModelClass::~ModelClass() { }
3 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
bool result;
// Initialize the vertex and instance buffers. result = InitializeBuffers(device); if(!result) { return false; } // Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; }
void ModelClass::Shutdown() { // Release the model texture. ReleaseTexture(); // Shutdown the vertex and instance buffers. ShutdownBuffers(); return; }
void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // Put the vertex and instance buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return; } These are the two new helper functions which return the vertex and instance counts. int ModelClass::GetVertexCount() {
4 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
return m_vertexCount; }
bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; InstanceType* instances; D3D11_BUFFER_DESC vertexBufferDesc, instanceBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, instanceData; HRESULT result; We will start by manually setting up the vertex buffer that holds the triangle as usual, however there will be no index buffer setup with it this time. // Set the number of vertices in the vertex array. m_vertexCount = 3; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left. vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f); vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle. vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);
5 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Release the vertex array now that the vertex buffer has been created and loaded. delete [] vertices; vertices = 0; We will now setup the new instance buffer. We start by first setting the number of instances of the triangle that will need to be rendered. For this tutorial I have manually set it to 4 so that we will have four triangles rendered on the screen. // Set the number of instances in the array. m_instanceCount = 4; Next we create a temporary instance array using the instance count. Note we use the InstanceType structure for the array type which is defined in the ModelClass header file. // Create the instance array. instances = new InstanceType[m_instanceCount]; if(!instances) { return false; } Now here is where we setup the different positions for each instance of the triangle. I have set four different x, y, z positions for each triangle. Note that this is where you could set color, scaling, different texture coordinates, and so forth. An instance can be modified in any way you want it to be. For this tutorial I used position as it is easy to see visually which helps understand how instancing works.
6 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Load the instance array with data. instances[0].position = D3DXVECTOR3(-1.5f, -1.5f, 5.0f); instances[1].position = D3DXVECTOR3(-1.5f, 1.5f, 5.0f); instances[2].position = D3DXVECTOR3( 1.5f, -1.5f, 5.0f); instances[3].position = D3DXVECTOR3( 1.5f, 1.5f, 5.0f); The instance buffer description is setup exactly the same as a vertex buffer description. // Set up the description of the instance buffer. instanceBufferDesc.Usage = D3D11_USAGE_DEFAULT; instanceBufferDesc.ByteWidth = sizeof(InstanceType) * m_instanceCount; instanceBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; instanceBufferDesc.CPUAccessFlags = 0; instanceBufferDesc.MiscFlags = 0; instanceBufferDesc.StructureByteStride = 0; Just like the vertex buffer we get the pointer to the instance array and then create the instance buffer. Once the instance buffer is created we can release the temporary instance array since the data from the array has been copied into the instance buffer. // Give the subresource structure a pointer to the instance data. instanceData.pSysMem = instances; instanceData.SysMemPitch = 0; instanceData.SysMemSlicePitch = 0; // Create the instance buffer. result = device->CreateBuffer(&instanceBufferDesc, &instanceData, &m_instanceBuffer); if(FAILED(result)) { return false; } // Release the instance array now that the instance buffer has been created and loaded. delete [] instances; instances = 0; return true; }
void ModelClass::ShutdownBuffers() { Release the new instance buffer in the ShutdownBuffers function. // Release the instance buffer.
7 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
if(m_instanceBuffer) { m_instanceBuffer->Release(); m_instanceBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; }
void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { unsigned int strides[2]; unsigned int offsets[2]; ID3D11Buffer* bufferPointers[2]; The instance buffer is just a second vertex buffer containing different information so it is set on the device at the same time using the same call as the vertex buffer. So instead of how we previously sent in a single stride, offset, and buffer we now send an array of strides, offsets, and buffers to the IASetVertexBuffers call. First we set the two strides to the size of the VertexType and InstanceType. // Set the buffer strides. strides[0] = sizeof(VertexType); strides[1] = sizeof(InstanceType); We then set the offsets for both the vertex and instance buffer. // Set the buffer offsets. offsets[0] = 0; offsets[1] = 0; Next we create an array that holds the pointers to the vertex buffer and the instance buffer. // Set the array of pointers to the vertex and instance buffers. bufferPointers[0] = m_vertexBuffer; bufferPointers[1] = m_instanceBuffer; Finally we set both the vertex buffer and the instance buffer on the device context in the same call.
8 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 2, bufferPointers, strides, offsets); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
// Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result) { return false; } return true; }
void ModelClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
9 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
Textureshaderclass.h
The TextureShaderClass has been modified also to handle setting up instancing for the shader. //////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTURESHADERCLASS_H_ #define _TEXTURESHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: TextureShaderClass //////////////////////////////////////////////////////////////////////////////// class TextureShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: TextureShaderClass(); TextureShaderClass(const TextureShaderClass&); ~TextureShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*);
10 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); void RenderShader(ID3D11DeviceContext*, int, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; }; #endif
Textureshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "textureshaderclass.h"
11 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
TextureShaderClass::~TextureShaderClass() { }
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/texture.vs", L"../Engine/texture.ps"); if(!result) { return false; } return true; }
void TextureShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; } The Render function now takes as input a vertex count and an instance count instead of the old index count. bool TextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int vertexCount, int instanceCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture); if(!result) { return false; } // Now render the prepared buffers with the shader.
12 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
bool TextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL);
13 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
14 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; For instancing we are going to add a third element to the layout. As we plan to position the four instanced triangles in different positions in 3D space we need to send the shader an extra position vector composed of three floats representing x, y, and z. Therefore the format is set to DXGI_FORMAT_R32G32B32_FLOAT. The semantic name is set to TEXCOORD as we are just using a generic semantic. Note that we have to set the semantic index to 1 in the layout since there is already a TEXCOORD for the texture coordinates using slot 0. Now for the instancing specific stuff we set the InputSlotClass to D3D11_INPUT_PER_INSTANCE_DATA which indicates that this is instanced data. Secondly the InstanceDataStepRate is now used and we set the step rate to 1 so that it will draw one instance before stepping forward a unit in the instance data. Note also that this is the first unit in the instance buffer so we set the AlignedByteOffset to 0 again since we are not aligning to the vertex buffer with the instance data. polygonLayout[2].SemanticName = "TEXCOORD"; polygonLayout[2].SemanticIndex = 1; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 1; polygonLayout[2].AlignedByteOffset = 0; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA; polygonLayout[2].InstanceDataStepRate = 1; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0;
15 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); 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, &m_sampleState); if(FAILED(result)) { return false; } return true; }
void TextureShaderClass::ShutdownShader() { // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer)
16 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
{ m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void TextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt");
17 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool TextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData;
18 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); return true; } The RenderShader function is different in two ways. First is that it takes as input the vertex and instance count instead of how it used to take in just an index count. Secondly it uses the DrawInstanced function to draw the triangles instead of using the DrawIndexed function. void TextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int vertexCount, int instanceCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawInstanced(vertexCount, instanceCount, 0, 0); return; }
Texture.vs
19 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
The vertex shader has now been modified to use instancing. //////////////////////////////////////////////////////////////////////////////// // Filename: texture.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// The VertexInputType structure now has the third element which will hold the instanced input position data. struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 instancePosition : TEXCOORD1; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
20 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; Here is where we use the instanced position information to modify the position of each triangle we are drawing. // Update the position of the vertices based on the data for this particular instance. input.position.x += input.instancePosition.x; input.position.y += input.instancePosition.y; input.position.z += input.instancePosition.z; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Texture.ps
The pixel shader has not changed for this tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: texture.ps ////////////////////////////////////////////////////////////////////////////////
21 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 TexturePixelShader(PixelInputType input) : SV_TARGET { float4 textureColor;
// Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); return textureColor; }
Graphicsclass.h
The GraphicsClass header hasn't changed for this tutorial. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "textureshaderclass.h"
///////////// // GLOBALS //
22 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; TextureShaderClass* m_TextureShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass()
23 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; }
24 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0;
25 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
} // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
// Render the graphics scene. result = Render(); if(!result) { return false; } return true; }
26 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDeviceContext()); The Render function for the shader now requires the vertex and instance count from the model object. // Render the model using the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetVertexCount(), m_Model->GetInstanceCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
With instancing we can now use just a single copy of geometry and render it multiple times with each instance having its own unique rendering properties.
27 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut37.html
To Do Exercises
1. Recompile and run the program. You should see four triangles drawn using just a single triangle model. 2. Modify the position information to place the four triangles differently. 3. Add a fifth instance of the triangle. 4. Modify the color information also for each instance.
Source Code
Source Code and Data Files: dx11src37.zip Executable: dx11exe37.zip
28 of 28
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut43.html
Then whenever we find that the viewing frustum has intersected with another 3D object we would draw a pixel from the texture that matches the projected location. For example if we projected a texture onto a blue plane we would end up rendering the texture inside the green boundary area that intersects with the blue 3D plane:
1 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
For this tutorial we will start with the following 3D scene of a cube sitting on a plane:
Then we will use the following texture as the texture we want to project onto our 3D scene:
2 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
Then we will setup the view point from where we want to project and also the location to where we want to project. The "from" location will be called the view point, and the "to" location will be called the look at point. In this example our from location (view point) will be behind the camera to the upper right corner of the scene. And we will set the location to project to (the look at point) as the center of the scene. We then setup a view and projection matrix with these parameters and then render the scene projecting the texture onto it. This gives us the following result:
We will start the code section of the tutorial by looking at the projection shader HLSL code first.
3 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
Projection.vs
The code for the projection shaders is the directional light shaders modified to handle projected texturing. //////////////////////////////////////////////////////////////////////////////// // Filename: projection.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; We will require a separate view matrix and projection matrix for the view point that we want to project the texture from. matrix viewMatrix2; matrix projectionMatrix2; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; The viewPosition is the position of the vertice as viewed by the location where we are going to the project the texture from. float4 viewPosition : TEXCOORD1;
4 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
};
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); Calculate the position of the vertice from the view point of where we are projecting the texture from. Note we can use the regular world matrix but we use the second pair of view and projection matrices. // Store the position of the vertice as viewed by the projection view point in a separate variable. output.viewPosition = mul(input.position, worldMatrix); output.viewPosition = mul(output.viewPosition, viewMatrix2); output.viewPosition = mul(output.viewPosition, projectionMatrix2); // Store the texture coordinates for the pixel shader. output.tex = input.tex; // Calculate the normal vector against the world matrix only. output.normal = mul(input.normal, (float3x3)worldMatrix); // Normalize the normal vector. output.normal = normalize(output.normal); return output; }
Projection.ps
////////////////////////////////////////////////////////////////////////////////
5 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
////////////// // TEXTURES // ////////////// Texture2D shaderTexture : register(t0); The projectionTexture is the texture that we will be projecting onto the scene. Texture2D projectionTexture : register(t1);
////////////////////// // CONSTANT BUFFERS // ////////////////////// cbuffer LightBuffer { float4 ambientColor; float4 diffuseColor; float3 lightDirection; float padding; };
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float4 viewPosition : TEXCOORD1; };
////////////////////////////////////////////////////////////////////////////////
6 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
// Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 ProjectionPixelShader(PixelInputType input) : SV_TARGET { float4 color; float3 lightDir; float lightIntensity; float4 textureColor; float2 projectTexCoord; float4 projectionColor; Perform regular directional lighting and texturing as usual. // Set the default output color to the ambient light value for all pixels. color = ambientColor; // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel. lightIntensity = saturate(dot(input.normal, lightDir)); if(lightIntensity > 0.0f) { // Determine the light color based on the diffuse color and the amount of light intensity. color += (diffuseColor * lightIntensity); } // Saturate the light color. color = saturate(color); // Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); // Combine the light color and the texture color. color = color * textureColor; Now we calculate the projection coordinates for sampling the projected texture from. These coordinates are the position the vertex is being viewed from by the location of the projection view point. The coordinates are translated into 2D screen coordinates and moved into the 0.0f to 1.0f range from the -0.5f to +0.5f range. // Calculate the projected texture coordinates. projectTexCoord.x = input.viewPosition.x / input.viewPosition.w / 2.0f + 0.5f; projectTexCoord.y = -input.viewPosition.y / input.viewPosition.w / 2.0f + 0.5f; Next we need to check if the coordinates are in the 0.0f to 1.0f range. If they are not in that range then this pixel is not in the projection area so it is just illuminated
7 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
and textured as normal. However if it is inside the 0.0f to 1.0f range then this pixel is inside the projected texture area and we need to apply the projected texture to the output pixel. // Determine if the projected coordinates are in the 0 to 1 range. If it is then this pixel is inside the projected view port. if((saturate(projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y)) { Sample the projection texture using the projected texture coordinates and then set the output pixel color to be the projected texture color. // Sample the color value from the projection texture using the sampler at the projected texture coordinate location. projectionColor = projectionTexture.Sample(SampleType, projectTexCoord); // Set the output color of this pixel to the projection texture overriding the regular color value. color = projectionColor; } return color; }
Projectionshaderclass.h
The ProjectionShaderClass is just the LightShaderClass rewritten to handle texture projection. //////////////////////////////////////////////////////////////////////////////// // Filename: projectionshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _PROJECTIONSHADERCLASS_H_ #define _PROJECTIONSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
8 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
//////////////////////////////////////////////////////////////////////////////// class ProjectionShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; We will require a view matrix and a projection matrix from the view point of the projection to perform projected texturing. D3DXMATRIX view2; D3DXMATRIX projection2; }; struct LightBufferType { D3DXVECTOR4 ambientColor; D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float padding; }; public: ProjectionShaderClass(); ProjectionShaderClass(const ProjectionShaderClass&); ~ProjectionShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR4, D3DXVECTOR4, D3DX D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR4, D3DXVECT D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader;
9 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer; ID3D11Buffer* m_lightBuffer; }; #endif
Projectionshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: projectionshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "projectionshaderclass.h"
ProjectionShaderClass::~ProjectionShaderClass() { }
10 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
Load the projection HLSL files. // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/projection.vs", L"../Engine/projection.ps"); if(!result) { return false; } return true; }
void ProjectionShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
bool ProjectionShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 lightDirection, D3DXMATRIX viewMatrix2, D3DXMATRIX projectionMatrix2, ID3D11ShaderResourceView* projectionTexture) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, ambientColor, diffuseColor, lightDirection, viewMatrix2, projectionMatrix2, projectionTexture); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
11 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
bool ProjectionShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_BUFFER_DESC lightBufferDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; Compile the projection vertex shader. // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ProjectionVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } Compile the projection pixel shader. // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ProjectionPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result))
12 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
{ // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0;
13 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
polygonLayout[2].SemanticName = "NORMAL"; polygonLayout[2].SemanticIndex = 0; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 0; polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // 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, &m_sampleState); if(FAILED(result))
14 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
{ return false; } // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } // Setup the description of the light dynamic constant buffer that is in the pixel shader. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the pixel shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; } return true; }
void ProjectionShaderClass::ShutdownShader() { // Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0;
15 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
} // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void ProjectionShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i;
16 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool ProjectionShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 lightDirection, D3DXMATRIX viewMatrix2, D3DXMATRIX projectionMatrix2, ID3D11ShaderResourceView* projectionTexture) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; unsigned int bufferNumber; MatrixBufferType* dataPtr; LightBufferType* dataPtr2;
17 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); Transpose the second view and projection matrices. D3DXMatrixTranspose(&viewMatrix2, &viewMatrix2); D3DXMatrixTranspose(&projectionMatrix2, &projectionMatrix2); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; Set the two new matrices in the shader. dataPtr->view2 = viewMatrix2; dataPtr->projection2 = projectionMatrix2; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; }
18 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
// Get a pointer to the data in the constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData; // Copy the lighting variables into the constant buffer. dataPtr2->ambientColor = ambientColor; dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; dataPtr2->padding = 0.0f; // Unlock the constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); Set the projection texture in the pixel shader. deviceContext->PSSetShaderResources(1, 1, &projectionTexture); return true; }
void ProjectionShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return;
19 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
Viewpointclass.h
The ViewPointClass encapsulates the view point that we will be looking at the scene from in terms of projecting a texture. It is similar to the LightClass or CameraClass that it has a position in the 3D world and it has a position that it is looking at in the 3D world. With the position variables and projection variables set it can then generate a view matrix and a projection matrix to represent the view point in the 3D world. Those two matrices can then be sent into the shader to be used to project the texture onto the 3D scene. //////////////////////////////////////////////////////////////////////////////// // Filename: viewpointclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _VIEWPOINTCLASS_H_ #define _VIEWPOINTCLASS_H_
//////////////////////////////////////////////////////////////////////////////// // Class name: ViewPointClass //////////////////////////////////////////////////////////////////////////////// class ViewPointClass { public: ViewPointClass(); ViewPointClass(const ViewPointClass&); ~ViewPointClass(); void SetPosition(float, float, float); void SetLookAt(float, float, float); void SetProjectionParameters(float, float, float, float); void GenerateViewMatrix(); void GenerateProjectionMatrix(); void GetViewMatrix(D3DXMATRIX&); void GetProjectionMatrix(D3DXMATRIX&);
20 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
private: D3DXVECTOR3 m_position, m_lookAt; D3DXMATRIX m_viewMatrix, m_projectionMatrix; float m_fieldOfView, m_aspectRatio, m_nearPlane, m_farPlane; }; #endif
Viewpointclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: viewpointclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "viewpointclass.h"
ViewPointClass::ViewPointClass() { }
ViewPointClass::~ViewPointClass() { }
21 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
void ViewPointClass::SetProjectionParameters(float fieldOfView, float aspectRatio, float nearPlane, float farPlane) { m_fieldOfView = fieldOfView; m_aspectRatio = aspectRatio; m_nearPlane = nearPlane; m_farPlane = farPlane; return; }
// Setup the vector that points upwards. up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; // Create the view matrix from the three vectors. D3DXMatrixLookAtLH(&m_viewMatrix, &m_position, &m_lookAt, &up); return; }
void ViewPointClass::GenerateProjectionMatrix() { // Create the projection matrix for the view point. D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, m_fieldOfView, m_aspectRatio, m_nearPlane, m_farPlane); return; }
22 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "lightclass.h" Include the headers required for projecting a texture onto the scene. #include "projectionshaderclass.h" #include "textureclass.h" #include "viewpointclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 100.0f; const float SCREEN_NEAR = 1.0f;
23 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
//////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass *m_GroundModel, *m_CubeModel; LightClass* m_Light; Define the class pointer variables that will be used for projecting the texture. ProjectionShaderClass* m_ProjectionShader; TextureClass* m_ProjectionTexture; ViewPointClass* m_ViewPoint; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
24 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
m_GroundModel = 0; m_CubeModel = 0; m_Light = 0; Initialize the three pointers to null in the class constructor. m_ProjectionShader = 0; m_ProjectionTexture = 0; m_ViewPoint = 0; }
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera)
25 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
{ return false; } // Set the initial position and rotation of the camera. m_Camera->SetPosition(0.0f, 7.0f, -10.0f); m_Camera->SetRotation(35.0f, 0.0f, 0.0f); // Create the ground model object. m_GroundModel = new ModelClass; if(!m_GroundModel) { return false; } // Initialize the ground model object. result = m_GroundModel->Initialize(m_D3D->GetDevice(), "../Engine/data/floor.txt", L"../Engine/data/stone.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the ground model object.", L"Error", MB_OK); return false; }
// Create the cube model object. m_CubeModel = new ModelClass; if(!m_CubeModel) { return false; } // Initialize the cube model object. result = m_CubeModel->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the cube model object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; }
26 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
// Initialize the light object. m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f); m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, -0.75f, 0.5f); Create and initialize the projection shader object. // Create the projection shader object. m_ProjectionShader = new ProjectionShaderClass; if(!m_ProjectionShader) { return false; } // Initialize the projection shader object. result = m_ProjectionShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the projection shader object.", L"Error", MB_OK); return false; } Create and initialize the DirectX 11 texture that we will be projecting onto the 3D scene. // Create the projection texture object. m_ProjectionTexture = new TextureClass; if(!m_ProjectionTexture) { return false; } // Initialize the projection texture object. result = m_ProjectionTexture->Initialize(m_D3D->GetDevice(), L"../Engine/data/dx11.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the projection texture object.", L"Error", MB_OK); return false; } Create and initialize the view point object that will be used as the location to project the texture from. When setting the projection parameters here I have set it up to create a square viewing frustum. // Create the view point object. m_ViewPoint = new ViewPointClass;
27 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
if(!m_ViewPoint) { return false; } // Initialize the view point object. m_ViewPoint->SetPosition(2.0f, 5.0f, -2.0f); m_ViewPoint->SetLookAt(0.0f, 0.0f, 0.0f); m_ViewPoint->SetProjectionParameters((float)(D3DX_PI / 2.0f), 1.0f, 0.1f, 100.0f); m_ViewPoint->GenerateViewMatrix(); m_ViewPoint->GenerateProjectionMatrix(); return true; }
void GraphicsClass::Shutdown() { We release the three texture projection related objects in the Shutdown function. // Release the view point object. if(m_ViewPoint) { delete m_ViewPoint; m_ViewPoint = 0; } // Release the projection texture object. if(m_ProjectionTexture) { m_ProjectionTexture->Shutdown(); delete m_ProjectionTexture; m_ProjectionTexture = 0; } // Release the projection shader object. if(m_ProjectionShader) { m_ProjectionShader->Shutdown(); delete m_ProjectionShader; m_ProjectionShader = 0; } // Release the light object.
28 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
if(m_Light) { delete m_Light; m_Light = 0; } // Release the cube model object. if(m_CubeModel) { m_CubeModel->Shutdown(); delete m_CubeModel; m_CubeModel = 0; } // Release the ground model object. if(m_GroundModel) { m_GroundModel->Shutdown(); delete m_GroundModel; m_GroundModel = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
29 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
// Render the graphics scene. result = Render(); if(!result) { return false; } return true; }
bool GraphicsClass::Render() { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; D3DXMATRIX viewMatrix2, projectionMatrix2; bool result;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); Get the second view and projection matrix from the view point object. These two matrices will be used for projecting the texture. // Get the view and projection matrices from the view point object. m_ViewPoint->GetViewMatrix(viewMatrix2); m_ViewPoint->GetProjectionMatrix(projectionMatrix2); Now render the two models using the projection shader to project the texture onto them as well as light and texture them. // Setup the translation for the ground model. D3DXMatrixTranslation(&worldMatrix, 0.0f, 1.0f, 0.0f); // Render the ground model using the projection shader. m_GroundModel->Render(m_D3D->GetDeviceContext()); result = m_ProjectionShader->Render(m_D3D->GetDeviceContext(), m_GroundModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_GroundModel->GetTexture(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), m_Light->GetDirection(),
30 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
viewMatrix2, projectionMatrix2, m_ProjectionTexture->GetTexture()); if(!result) { return false; } // Reset the world matrix and setup the translation for the cube model. m_D3D->GetWorldMatrix(worldMatrix); D3DXMatrixTranslation(&worldMatrix, 0.0f, 2.0f, 0.0f); // Render the cube model using the projection shader. m_CubeModel->Render(m_D3D->GetDeviceContext()); result = m_ProjectionShader->Render(m_D3D->GetDeviceContext(), m_CubeModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_CubeModel->GetTexture(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), m_Light->GetDirection(), viewMatrix2, projectionMatrix2, m_ProjectionTexture->GetTexture()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
We can now project 2D textures onto 3D scenes from any defined view point.
31 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut43.html
To Do Exercises
1. Compile and run the program. You will see a 3D scene with a 2D texture projected onto it. 2. Change the texture that is being projected onto the scene. 3. Set the cube to rotate to see the effect of the projected texture on it. 4. Modify the location of the view point. 5. Modify the projection parameters of the view point to create a different shaped projection.
Source Code
Source Code and Data Files: dx11src43.zip Executable: dx11exe43.zip
32 of 32
3/8/2013 12:45 PM
http://www.rastertek.com/dx11tut45.html
Shadermanagerclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: shadermanagerclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _SHADERMANAGERCLASS_H_ #define _SHADERMANAGERCLASS_H_
1 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
/////////////////////// #include "d3dclass.h" Here is where we include each shader class header file that we want to use in the application. #include "textureshaderclass.h" #include "lightshaderclass.h" #include "bumpmapshaderclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: ShaderManagerClass //////////////////////////////////////////////////////////////////////////////// class ShaderManagerClass { public: ShaderManagerClass(); ShaderManagerClass(const ShaderManagerClass&); ~ShaderManagerClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); We create a single render function for each shader that the ShaderManagerClass handles. bool RenderTextureShader(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); bool RenderLightShader(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4, D3DXVECTOR3, D3DXVECTOR4, float); bool RenderBumpMapShader(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4); private: The ShaderManagerClass contains a private class object for each shader type the application will be using. TextureShaderClass* m_TextureShader; LightShaderClass* m_LightShader; BumpMapShaderClass* m_BumpMapShader; }; #endif
2 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
Shadermanagerclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: shadermanagerclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "shadermanagerclass.h"
ShaderManagerClass::ShaderManagerClass() { Initialize the private shader class objects to null in the class constructor. m_TextureShader = 0; m_LightShader = 0; m_BumpMapShader = 0; }
ShaderManagerClass::~ShaderManagerClass() { }
bool ShaderManagerClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; Create and initialize the texture shader object. // Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; }
3 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
// Initialize the texture shader object. result = m_TextureShader->Initialize(device, hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } Create and initialize the light shader object. // Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) { return false; } // Initialize the light shader object. result = m_LightShader->Initialize(device, hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK); return false; } Create and initialize the bump map shader object. // Create the bump map shader object. m_BumpMapShader = new BumpMapShaderClass; if(!m_BumpMapShader) { return false; } // Initialize the bump map shader object. result = m_BumpMapShader->Initialize(device, hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the bump map shader object.", L"Error", MB_OK); return false; } return true; }
4 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
The Shutdown function releases all the shaders that were in use by the application. void ShaderManagerClass::Shutdown() { // Release the bump map shader object. if(m_BumpMapShader) { m_BumpMapShader->Shutdown(); delete m_BumpMapShader; m_BumpMapShader = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown(); delete m_LightShader; m_LightShader = 0; } // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } return; } Here is where we create a specialized render function for each shader type that ShaderManagerClass uses. In the application we can now just pass around a pointer to the ShaderManagerClass object and then make a call to one of the following functions to render any model with the desired shader. bool ShaderManagerClass::RenderTextureShader(ID3D11DeviceContext* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRI ID3D11ShaderResourceView* texture) { bool result;
// Render the model using the texture shader. result = m_TextureShader->Render(device, indexCount, worldMatrix, viewMatrix, projectionMatrix, texture); if(!result) { return false;
5 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
} return true; }
bool ShaderManagerClass::RenderLightShader(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXM ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 ambient, D3DXVECTOR4 diffuse, D3DXVECTOR3 cameraPosition, D3DXVECTOR4 specular, float specularPower) { bool result;
// Render the model using the light shader. result = m_LightShader->Render(deviceContext, indexCount, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambient, diffuse, cameraPosition, specular, specularPower); if(!result) { return false; } return true; }
bool ShaderManagerClass::RenderBumpMapShader(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3 ID3D11ShaderResourceView* colorTexture, ID3D11ShaderResourceView* normalTexture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuse) { bool result;
// Render the model using the bump map shader. result = m_BumpMapShader->Render(deviceContext, indexCount, worldMatrix, viewMatrix, projectionMatrix, colorTexture, normalTexture, lightDirection, diffuse); if(!result) { return false; } return true; }
6 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" So now in the GraphicsClass we include just the header for the ShaderManagerClass instead of all the individual shader headers. #include "shadermanagerclass.h" #include "cameraclass.h" #include "lightclass.h" #include "modelclass.h" #include "bumpmodelclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown();
7 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
bool Frame(); private: bool Render(float); private: D3DClass* m_D3D; We create a private pointer to the ShaderManagerClass object here. ShaderManagerClass* m_ShaderManager; CameraClass* m_Camera; LightClass* m_Light; We will use three different cube models for the purposes of the tutorial. ModelClass* m_Model1; ModelClass* m_Model2; BumpModelClass* m_Model3; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
8 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } We create and initialize the ShaderManagerClass object here. // Create the shader manager object. m_ShaderManager = new ShaderManagerClass; if(!m_ShaderManager) { return false; } // Initialize the shader manager object. result = m_ShaderManager->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the shader manager object.", L"Error", MB_OK);
9 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } // Initialize the light object. m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f); m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f); m_Light->SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetSpecularPower(64.0f); Load the three different cube models with their associated textures. This way we have three different cubes that we can each render with a different shader using the ShaderManagerClass. // Create the model object. m_Model1 = new ModelClass; if(!m_Model1) { return false; } // Initialize the model object. result = m_Model1->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/marble.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the first model object.", L"Error", MB_OK); return false; }
10 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
// Create the second model object. m_Model2 = new ModelClass; if(!m_Model2) { return false; } // Initialize the second model object. result = m_Model2->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/metal.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the second model object.", L"Error", MB_OK); return false; } // Create the third bump model object for models with normal maps and related vectors. m_Model3 = new BumpModelClass; if(!m_Model3) { return false; } // Initialize the bump model object. result = m_Model3->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/stone.dds", L"../Engine/data/normal.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the third model object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { // Release the model objects. if(m_Model1) { m_Model1->Shutdown(); delete m_Model1; m_Model1 = 0; }
11 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
if(m_Model2) { m_Model2->Shutdown(); delete m_Model2; m_Model2 = 0; } if(m_Model3) { m_Model3->Shutdown(); delete m_Model3; m_Model3 = 0; } // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the shader manager object. if(m_ShaderManager) { m_ShaderManager->Shutdown(); delete m_ShaderManager; m_ShaderManager = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return;
12 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
// Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene. result = Render(rotation); if(!result) { return false; } return true; }
bool GraphicsClass::Render(float rotation) { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, translateMatrix; bool result;
// Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // Setup the rotation and translation of the first model.
13 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
D3DXMatrixRotationY(&worldMatrix, rotation); D3DXMatrixTranslation(&translateMatrix, -3.5f, 0.0f, 0.0f); D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translateMatrix); Render the first cube model using the texture shader from the ShaderManagerClass object. // Render the first model using the texture shader. m_Model1->Render(m_D3D->GetDeviceContext()); result = m_ShaderManager->RenderTextureShader(m_D3D->GetDeviceContext(), m_Model1->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model1->GetTexture()); if(!result) { return false; } // Setup the rotation and translation of the second model. m_D3D->GetWorldMatrix(worldMatrix); D3DXMatrixRotationY(&worldMatrix, rotation); D3DXMatrixTranslation(&translateMatrix, 0.0f, 0.0f, 0.0f); D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translateMatrix); Render the second cube model using the light shader from the ShaderManagerClass object. // Render the second model using the light shader. m_Model2->Render(m_D3D->GetDeviceContext()); result = m_ShaderManager->RenderLightShader(m_D3D->GetDeviceContext(), m_Model2->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model2->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), m_Camera->GetPosition(), m_Light->GetSpecularColor(), m_Light->GetSpecularPower()); if(!result) { return false; } // Setup the rotation and translation of the third model. m_D3D->GetWorldMatrix(worldMatrix); D3DXMatrixRotationY(&worldMatrix, rotation); D3DXMatrixTranslation(&translateMatrix, 3.5f, 0.0f, 0.0f); D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translateMatrix); Render the third cube model using the bump map shader from the ShaderManagerClass object. // Render the third model using the bump map shader. m_Model3->Render(m_D3D->GetDeviceContext()); result = m_ShaderManager->RenderBumpMapShader(m_D3D->GetDeviceContext(), m_Model3->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model3->GetColorTexture(), m_Model3->GetNormalMapTexture(), m_Light->GetDirection(),
14 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
m_Light->GetDiffuseColor()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
Now we have a single class which encapsulates all the shading functionality that our graphics application will need.
To Do Exercises
1. Compile and run the code. You should see three cubes each shaded with a different shader. Press escape to quit. 2. Add another shader to the ShaderManagerClass and render a fourth cube using that shader. 3. Add some intelligence to the ShaderManagerClass to load and unload shaders on demand.
Source Code
Source Code and Data Files: dx11src45.zip
15 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut45.html
Executable: dx11exe45.zip
16 of 16
3/8/2013 12:43 PM
http://www.rastertek.com/dx11tut46.html
Now say I want to apply glow to just the border of the button and not the text. Controlling where we apply glow is very important as we generally don't want everything glowing. To do so I will create a black and white glow map indicating where I want the glow effect to be applied. This is similar to making an alpha map for transparency. In the case of the menu button and just applying glow to the border I would create a glow map that looks like the following:
Now that we have a glow map bitmap and the menu button bitmap we can start the glow effect. The first step is that we render just the portion of the menu button bitmap that we want the glow applied to onto a render to texture. We do the render by creating a glow map shader which takes the two bitmaps and renders the pixels from the color bitmap on the render to texture object only if there is a corresponding non-black pixel in the glow map. This way our glow map basically becomes a transparency mask allowing just the border of the menu button to be rendered to the render to texture object. With the border rendered to the render to texture object we can proceed to the next important step of applying the blur. We will down sample the texture, blur it horizontally, then blur it vertically, and then up sample it. This was done in the DirectX blur tutorial on this website so we won't cover those blur details in this tutorial. Once the render to texture has been blurred we then brighten the glow by multiplying the color by a glow strength value. The following image I used a glow strength of 3.0:
Now that we have the glow render to texture we will apply it to the final scene. We first need to render all our user interface elements as we would normally without the glow to their own render to texture. We then take the resulting render to texture object and the blurred render to texture object and combine the two textures in a glow shader. All the glow shader does is it adds the to textures together and then draws it to the back buffer. The following image is the result:
1 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
Also remember that 3D objects are done in the exact same way. Render the portions of the 3D object using the glow map to a render to texture, blur it, and combine it with the regular 3D scene using the glow shader. For this tutorial the following steps and the related render to texture objects for each step will be the following: 1. Render the glow maps to a render to texture object (output is m_RenderTexture). 2. Down sample the render to texture of the glow maps to a render to texture object that is half the size (new output is m_DownSampleTexure). 3. Perform a horizontal blur on the output of the previous step (new output is m_HorizontalBlurTexture). 4. Perform a vertical blur on the horizontal blur texture (new output is m_VerticalBlurTexture). 5. Up sample the vertical blurred texture to regular screen size again (new output is m_UpSampleTexure). 6. Now render the user interface elements to render to texture (output will be m_RenderTexture). 7. Combine the up sampled texture and the render texture of the user interface elements using the glow shader and write it to the back buffer. For the code section of this tutorial we won't be discussing anything related to blur as it was already covered in the blur tutorial. We will start by looking at the modified BitmapClass.
Bitmapclass.h
The BitmapClass has been modified and now has a glow map texture associated with it. //////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _BITMAPCLASS_H_ #define _BITMAPCLASS_H_
2 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
//////////////////////////////////////////////////////////////////////////////// // Class name: BitmapClass //////////////////////////////////////////////////////////////////////////////// class BitmapClass { private: struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; public: BitmapClass(); BitmapClass(const BitmapClass&); ~BitmapClass(); bool Initialize(ID3D11Device*, int, int, WCHAR*, WCHAR*, int, int); void Shutdown(); bool Render(ID3D11DeviceContext*, int, int); int GetIndexCount(); ID3D11ShaderResourceView* GetTexture(); ID3D11ShaderResourceView* GetGlowMap(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); bool UpdateBuffers(ID3D11DeviceContext*, int, int); void RenderBuffers(ID3D11DeviceContext*); bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*); void ReleaseTextures(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; TextureClass* m_Texture; TextureClass* m_GlowMap; int m_screenWidth, m_screenHeight; int m_bitmapWidth, m_bitmapHeight;
3 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
Bitmapclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "bitmapclass.h"
BitmapClass::~BitmapClass() { }
bool BitmapClass::Initialize(ID3D11Device* device, int screenWidth, int screenHeight, WCHAR* textureFilename, WCHAR* glowMapFilename, int bitmapWidth, int bitm { bool result;
// Store the screen size. m_screenWidth = screenWidth; m_screenHeight = screenHeight; // Store the size in pixels that this bitmap should be rendered at.
4 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
m_bitmapWidth = bitmapWidth; m_bitmapHeight = bitmapHeight; // Initialize the previous rendering position to negative one. m_previousPosX = -1; m_previousPosY = -1; // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } // Load the textures for this bitmap. result = LoadTextures(device, textureFilename, glowMapFilename); if(!result) { return false; } return true; }
void BitmapClass::Shutdown() { // Release the bitmap textures. ReleaseTextures(); // Shutdown the vertex and index buffers. ShutdownBuffers(); return; }
// Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen. result = UpdateBuffers(deviceContext, positionX, positionY); if(!result)
5 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
{ return false; } // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return true; }
bool BitmapClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i;
// Set the number of vertices in the vertex array. m_vertexCount = 6; // Set the number of indices in the index array. m_indexCount = m_vertexCount;
6 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } // Initialize vertex array to zeros at first. memset(vertices, 0, (sizeof(VertexType) * m_vertexCount)); // Load the index array with data. for(i=0; i<m_indexCount; i++) { indices[i] = i; } // Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
7 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
void BitmapClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; }
8 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
return; }
bool BitmapClass::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY) { float left, right, top, bottom; VertexType* vertices; D3D11_MAPPED_SUBRESOURCE mappedResource; VertexType* verticesPtr; HRESULT result;
// If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it // currently has the correct parameters. if((positionX == m_previousPosX) && (positionY == m_previousPosY)) { return true; } // If it has changed then update the position it is being rendered to. m_previousPosX = positionX; m_previousPosY = positionY; // Calculate the screen coordinates of the left side of the bitmap. left = (float)((m_screenWidth / 2) * -1) + (float)positionX; // Calculate the screen coordinates of the right side of the bitmap. right = left + (float)m_bitmapWidth; // Calculate the screen coordinates of the top of the bitmap. top = (float)(m_screenHeight / 2) - (float)positionY; // Calculate the screen coordinates of the bottom of the bitmap. bottom = top - (float)m_bitmapHeight; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Load the vertex array with data. // First triangle.
9 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
vertices[0].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[1].texture = D3DXVECTOR2(1.0f, 1.0f); vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f); // Bottom left. vertices[2].texture = D3DXVECTOR2(0.0f, 1.0f); // Second triangle. vertices[3].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[4].position = D3DXVECTOR3(right, top, 0.0f); // Top right. vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f); vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[5].texture = D3DXVECTOR2(1.0f, 1.0f); // Lock the vertex buffer so it can be written to. result = deviceContext->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the vertex buffer. verticesPtr = (VertexType*)mappedResource.pData; // Copy the data into the vertex buffer. memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount)); // Unlock the vertex buffer. deviceContext->Unmap(m_vertexBuffer, 0); // Release the vertex array as it is no longer needed. delete [] vertices; vertices = 0; return true; }
10 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
// Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result) { return false; } // Create the glow map texture object. m_GlowMap = new TextureClass; if(!m_GlowMap) { return false;
11 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
} // Initialize the glow map texture object. result = m_GlowMap->Initialize(device, glowMapFilename); if(!result) { return false; } return true; }
void BitmapClass::ReleaseTextures() { // Release the texture objects. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } if(m_GlowMap) { m_GlowMap->Shutdown(); delete m_GlowMap; m_GlowMap = 0; } return; }
Glowmap.vs
The purpose of the glow map shader is to render just the glow map portions of the color bitmap onto a render to texture so that it can be further processed (blurred). The code here for the HLSL glow map shader is just the texture shader HLSL code modified to handle glow map rendering. //////////////////////////////////////////////////////////////////////////////// // Filename: glowmap.vs ////////////////////////////////////////////////////////////////////////////////
12 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader.
13 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
Glowmap.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: glowmap.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// Texture2D shaderTexture : register(t0); We add a texture resource for the glow map. Texture2D glowMapTexture : register(t1); SamplerState SampleType;
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 GlowMapPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float4 glowMap; float4 color;
14 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
First sample both the bitmap texture and the glow map texture. // Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); // Sample the glow map. glowMap = glowMapTexture.Sample(SampleType, input.tex); Now return just the portions of the color texture that aligns with the white pixels in the glow map. In other words black is transparent, and anything else in the bitmap that isn't black gets drawn. // If the glow map is black then return just black. Otherwise if the glow map has color then return the color from the texture. if((glowMap.r == 0.0f) && (glowMap.g == 0.0f) && (glowMap.b == 0.0f)) { color = float4(0.0f, 0.0f, 0.0f, 1.0f); } else { color = textureColor; } return color; }
Glowmapshaderclass.h
The GlowMapShaderClass is just the TextureShaderClass modified to handle an extra texture (glow map). //////////////////////////////////////////////////////////////////////////////// // Filename: glowmapshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GLOWMAPSHADERCLASS_H_ #define _GLOWMAPSHADERCLASS_H_
15 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
//////////////////////////////////////////////////////////////////////////////// // Class name: GlowMapShaderClass //////////////////////////////////////////////////////////////////////////////// class GlowMapShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: GlowMapShaderClass(); GlowMapShaderClass(const GlowMapShaderClass&); ~GlowMapShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView* void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState; }; #endif
16 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
Glowmapshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: glowmapshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "glowmapshaderclass.h"
GlowMapShaderClass::~GlowMapShaderClass() { }
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/glowmap.vs", L"../Engine/glowmap.ps"); if(!result) { return false; } return true; }
17 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
void GlowMapShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
bool GlowMapShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* glowMap) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, glowMap); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool GlowMapShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc;
18 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
vertexShaderBuffer = 0; pixelShaderBuffer = 0; // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "GlowMapVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "GlowMapPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) {
19 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0;
20 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } // Create a texture sampler state description. samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; 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, &m_sampleState); if(FAILED(result)) { return false; } return true; }
21 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
{ m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void GlowMapShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout;
22 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool GlowMapShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* glowMap) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result))
23 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
{ return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Set shader texture resources in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); deviceContext->PSSetShaderResources(1, 1, &glowMap); return true; }
void GlowMapShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return;
24 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
Glow.vs
The purpose of the glow shader is to combine the blurred glow map texture with the actual regular color rendering of the scene. The regular scene is rendered to the input colorTexture, and the glow (blurred glow map) part of the scene has been rendered to the input glowTexture. The shader then multiplies the glowTexture by the glowStrength value to increase the glow. After that it adds the glow texture with the color texture to get the final pixel result. //////////////////////////////////////////////////////////////////////////////// // Filename: glow.vs ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
25 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; return output; }
Glow.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: glow.ps ////////////////////////////////////////////////////////////////////////////////
///////////// // GLOBALS // ///////////// The render to texture of the regular scene is sent into the shader as colorTexture. Texture2D colorTexture : register(t0); The blurred glow map render to texture is sent into the shader as glowTexture. Texture2D glowTexture : register(t1); SamplerState SampleType; The glowStrength variable in the GlowBuffer is for increasing the blurred glow map texture so that the glow has a stronger effect.
26 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 GlowPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float4 glowColor; float4 color; Sample the regular scene texture and the glow map texture. // Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = colorTexture.Sample(SampleType, input.tex); // Sample the glow texture. glowColor = glowTexture.Sample(SampleType, input.tex); Add the two textures together to create the final glow. The glow map texture is also multiplied by the glow strength to increase the appearance of the glow effect. // Add the texture color to the glow color multiplied by the glow stength. color = saturate(textureColor + (glowColor * glowStrength)); return color; }
27 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
Glowshaderclass.h
The GlowShaderClass is just the TextureShaderClass modified to handle glow related variables. //////////////////////////////////////////////////////////////////////////////// // Filename: glowshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GLOWSHADERCLASS_H_ #define _GLOWSHADERCLASS_H_
////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std;
//////////////////////////////////////////////////////////////////////////////// // Class name: GlowShaderClass //////////////////////////////////////////////////////////////////////////////// class GlowShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; struct GlowBufferType { float glowStrength; D3DXVECTOR3 padding; }; public: GlowShaderClass(); GlowShaderClass(const GlowShaderClass&); ~GlowShaderClass();
28 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, float); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, ID3D11ShaderResourceView* void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11Buffer* m_glowBuffer; ID3D11SamplerState* m_sampleState; }; #endif
Glowshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: glowshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "glowshaderclass.h"
29 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
GlowShaderClass::~GlowShaderClass() { }
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/glow.vs", L"../Engine/glow.ps"); if(!result) { return false; } return true; }
void GlowShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
bool GlowShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* colorTexture, ID3D11ShaderResourceView* glowTexture, float glowStrength) { bool result;
// Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, colorTexture, glowTexture, glowStrength);
30 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
bool GlowShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_BUFFER_DESC glowBufferDesc; D3D11_SAMPLER_DESC samplerDesc;
// Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "GlowVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); }
31 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
return false; } // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "GlowPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0;
32 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); 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. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } // Setup the description of the dynamic glow constant buffer that is in the pixel shader. glowBufferDesc.Usage = D3D11_USAGE_DYNAMIC; glowBufferDesc.ByteWidth = sizeof(GlowBufferType);
33 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
glowBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; glowBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; glowBufferDesc.MiscFlags = 0; glowBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the pixel shader constant buffer from within this class. result = device->CreateBuffer(&glowBufferDesc, NULL, &m_glowBuffer); 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, &m_sampleState); if(FAILED(result)) { return false; } return true; }
34 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
} // Release the glow constant buffer. if(m_glowBuffer) { m_glowBuffer->Release(); m_glowBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
void GlowShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i;
35 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
ofstream fout;
// Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
bool GlowShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* colorTexture, ID3D11ShaderResourceView* glowTexture, float glowStrength) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; GlowBufferType* dataPtr2; unsigned int bufferNumber;
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
36 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_glowBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr2 = (GlowBufferType*)mappedResource.pData; // Copy the data into the constant buffer. dataPtr2->glowStrength = glowStrength; dataPtr2->padding = D3DXVECTOR3(0.0f, 0.0f, 0.0f); // Unlock the constant buffer. deviceContext->Unmap(m_glowBuffer, 0); // Set the position of the constant buffer in the pixel shader.
37 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
bufferNumber = 0; // Now set the constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_glowBuffer); // Set shader texture resources in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &colorTexture); deviceContext->PSSetShaderResources(1, 1, &glowTexture); return true; }
void GlowShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Graphicsclass.h
The GraphicsClass is the same as the blur tutorial except that we add shaders for rendering both the glow and the glow map. //////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_
38 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "textureshaderclass.h" #include "bitmapclass.h" #include "rendertextureclass.h" #include "orthowindowclass.h" #include "horizontalblurshaderclass.h" #include "verticalblurshaderclass.h" #include "glowmapshaderclass.h" #include "glowshaderclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f;
//////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); bool RenderGlowMapToTexture(); bool DownSampleTexture(); bool RenderHorizontalBlurToTexture(); bool RenderVerticalBlurToTexture(); bool UpSampleTexture();
39 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
bool RenderUIElementsToTexture(); bool RenderGlowScene(); private: D3DClass* m_D3D; CameraClass* m_Camera; TextureShaderClass* m_TextureShader; BitmapClass* m_Bitmap; RenderTextureClass *m_RenderTexture, *m_DownSampleTexure, *m_HorizontalBlurTexture, *m_VerticalBlurTexture, *m_UpSampleTexure; OrthoWindowClass *m_SmallWindow, *m_FullScreenWindow; HorizontalBlurShaderClass* m_HorizontalBlurShader; VerticalBlurShaderClass* m_VerticalBlurShader; GlowMapShaderClass* m_GlowMapShader; GlowShaderClass* m_GlowShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_TextureShader = 0; m_Bitmap = 0; m_RenderTexture = 0; m_DownSampleTexure = 0; m_SmallWindow = 0; m_HorizontalBlurTexture = 0; m_HorizontalBlurShader = 0; m_VerticalBlurTexture = 0; m_VerticalBlurShader = 0; m_UpSampleTexure = 0; m_FullScreenWindow = 0; m_GlowMapShader = 0;
40 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
m_GlowShader = 0; }
GraphicsClass::~GraphicsClass() { }
// Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the texture shader object.
41 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; } // Create the bitmap object. m_Bitmap = new BitmapClass; if(!m_Bitmap) { return false; } // Initialize the bitmap object. result = m_Bitmap->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, L"../Engine/data/test.dds", L"../Engine/data/glowmap.dds", 256, 32); if(!result) { MessageBox(hwnd, L"Could not initialize the bitmap object.", L"Error", MB_OK); return false; } // Create the render to texture object. m_RenderTexture = new RenderTextureClass; if(!m_RenderTexture) { return false; } // Initialize the render to texture object. result = m_RenderTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize the render to texture object.", L"Error", MB_OK); return false; }
42 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Create the down sample render to texture object. m_DownSampleTexure = new RenderTextureClass; if(!m_DownSampleTexure) { return false; } // Initialize the down sample render to texture object. result = m_DownSampleTexure->Initialize(m_D3D->GetDevice(), (screenWidth / 2), (screenHeight / 2), SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize the down sample render to texture object.", L"Error", MB_OK); return false; } // Create the small ortho window object. m_SmallWindow = new OrthoWindowClass; if(!m_SmallWindow) { return false; } // Initialize the small ortho window object. result = m_SmallWindow->Initialize(m_D3D->GetDevice(), (screenWidth / 2), (screenHeight / 2)); if(!result) { MessageBox(hwnd, L"Could not initialize the small ortho window object.", L"Error", MB_OK); return false; } // Create the horizontal blur render to texture object. m_HorizontalBlurTexture = new RenderTextureClass; if(!m_HorizontalBlurTexture) { return false; } // Initialize the horizontal blur render to texture object. result = m_HorizontalBlurTexture->Initialize(m_D3D->GetDevice(), (screenWidth / 2), (screenHeight / 2), SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize the horizontal blur render to texture object.", L"Error", MB_OK); return false; }
43 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Create the horizontal blur shader object. m_HorizontalBlurShader = new HorizontalBlurShaderClass; if(!m_HorizontalBlurShader) { return false; } // Initialize the horizontal blur shader object. result = m_HorizontalBlurShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the horizontal blur shader object.", L"Error", MB_OK); return false; } // Create the vertical blur render to texture object. m_VerticalBlurTexture = new RenderTextureClass; if(!m_VerticalBlurTexture) { return false; } // Initialize the vertical blur render to texture object. result = m_VerticalBlurTexture->Initialize(m_D3D->GetDevice(), (screenWidth / 2), (screenHeight / 2), SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize the vertical blur render to texture object.", L"Error", MB_OK); return false; } // Create the vertical blur shader object. m_VerticalBlurShader = new VerticalBlurShaderClass; if(!m_VerticalBlurShader) { return false; } // Initialize the vertical blur shader object. result = m_VerticalBlurShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the vertical blur shader object.", L"Error", MB_OK); return false; }
44 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Create the up sample render to texture object. m_UpSampleTexure = new RenderTextureClass; if(!m_UpSampleTexure) { return false; } // Initialize the up sample render to texture object. result = m_UpSampleTexure->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize the up sample render to texture object.", L"Error", MB_OK); return false; } // Create the full screen ortho window object. m_FullScreenWindow = new OrthoWindowClass; if(!m_FullScreenWindow) { return false; } // Initialize the full screen ortho window object. result = m_FullScreenWindow->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight); if(!result) { MessageBox(hwnd, L"Could not initialize the full screen ortho window object.", L"Error", MB_OK); return false; } // Create the glow map shader object. m_GlowMapShader = new GlowMapShaderClass; if(!m_GlowMapShader) { return false; } // Initialize the glow map shader object. result = m_GlowMapShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the glow map shader object.", L"Error", MB_OK); return false; }
45 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Create the glow shader object. m_GlowShader = new GlowShaderClass; if(!m_GlowShader) { return false; } // Initialize the glow shader object. result = m_GlowShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the glow shader object.", L"Error", MB_OK); return false; } return true; }
void GraphicsClass::Shutdown() { // Release the glow shader object. if(m_GlowShader) { m_GlowShader->Shutdown(); delete m_GlowShader; m_GlowShader = 0; } // Release the glow map shader object. if(m_GlowMapShader) { m_GlowMapShader->Shutdown(); delete m_GlowMapShader; m_GlowMapShader = 0; } // Release the full screen ortho window object. if(m_FullScreenWindow) { m_FullScreenWindow->Shutdown(); delete m_FullScreenWindow; m_FullScreenWindow = 0; }
46 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Release the up sample render to texture object. if(m_UpSampleTexure) { m_UpSampleTexure->Shutdown(); delete m_UpSampleTexure; m_UpSampleTexure = 0; } // Release the vertical blur shader object. if(m_VerticalBlurShader) { m_VerticalBlurShader->Shutdown(); delete m_VerticalBlurShader; m_VerticalBlurShader = 0; } // Release the vertical blur render to texture object. if(m_VerticalBlurTexture) { m_VerticalBlurTexture->Shutdown(); delete m_VerticalBlurTexture; m_VerticalBlurTexture = 0; } // Release the horizontal blur shader object. if(m_HorizontalBlurShader) { m_HorizontalBlurShader->Shutdown(); delete m_HorizontalBlurShader; m_HorizontalBlurShader = 0; } // Release the horizontal blur render to texture object. if(m_HorizontalBlurTexture) { m_HorizontalBlurTexture->Shutdown(); delete m_HorizontalBlurTexture; m_HorizontalBlurTexture = 0; } // Release the small ortho window object. if(m_SmallWindow) { m_SmallWindow->Shutdown(); delete m_SmallWindow;
47 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
m_SmallWindow = 0; } // Release the down sample render to texture object. if(m_DownSampleTexure) { m_DownSampleTexure->Shutdown(); delete m_DownSampleTexure; m_DownSampleTexure = 0; } // Release the render to texture object. if(m_RenderTexture) { m_RenderTexture->Shutdown(); delete m_RenderTexture; m_RenderTexture = 0; } // Release the bitmap object. if(m_Bitmap) { m_Bitmap->Shutdown(); delete m_Bitmap; m_Bitmap = 0; } // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) {
48 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Render the graphics scene. result = Render(); if(!result) { return false; } return true; } The Render function calls the different steps required to produce the glow. It is similar to the blur tutorial except that we start by rendering the glow map to a texture instead of the 3D scene. After that we do the blurring as normal. Once the blur is complete we render the UI elements to another render to texture and combine it with the blurred texture for the final result. bool GraphicsClass::Render() { bool result;
// First render the glow maps to a render texture. result = RenderGlowMapToTexture(); if(!result) { return false; } // Next down sample the render texture to a smaller sized texture. result = DownSampleTexture(); if(!result) { return false;
49 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
} // Perform a horizontal blur on the down sampled render texture. result = RenderHorizontalBlurToTexture(); if(!result) { return false; } // Now perform a vertical blur on the horizontal blur render texture. result = RenderVerticalBlurToTexture(); if(!result) { return false; } // Up sample the final blurred render texture to screen size again. result = UpSampleTexture(); if(!result) { return false; } // Render the regular UI elements to a full screen texture. result = RenderUIElementsToTexture(); if(!result) { return false; } // Render the final scene combining the UI elements with the glowing UI elements. RenderGlowScene(); return true; } This function is what renders the glow map portions of the color bitmap onto the render to texture object. bool GraphicsClass::RenderGlowMapToTexture() { D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix; bool result;
50 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext()); // Clear the render to texture. m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and projection matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Put the bitmap vertex and index buffers on the graphics pipeline to prepare them for drawing. result = m_Bitmap->Render(m_D3D->GetDeviceContext(), 100, 100); if(!result) { return false; } // Render the bitmap using the glow map shader. m_GlowMapShader->Render(m_D3D->GetDeviceContext(), m_Bitmap->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_Bitmap->GetTexture(), m_Bitmap->GetGlowMap()); // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget(); // Reset the viewport back to the original. m_D3D->ResetViewport(); return true; }
51 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Set the render target to be the render to texture. m_DownSampleTexure->SetRenderTarget(m_D3D->GetDeviceContext()); // Clear the render to texture. m_DownSampleTexure->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 1.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world and view matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); // Get the ortho matrix from the render to texture since texture has different dimensions being that it is smaller. m_DownSampleTexure->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Put the small ortho window vertex and index buffers on the graphics pipeline to prepare them for drawing. m_SmallWindow->Render(m_D3D->GetDeviceContext()); // Render the small ortho window using the texture shader and the render to texture of the scene as the texture resource. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_SmallWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_RenderTexture->GetShaderResourceView()); if(!result) { return false; } // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget(); // Reset the viewport back to the original. m_D3D->ResetViewport(); return true; }
bool GraphicsClass::RenderHorizontalBlurToTexture()
52 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Set the render target to be the render to texture. m_HorizontalBlurTexture->SetRenderTarget(m_D3D->GetDeviceContext()); // Clear the render to texture. m_HorizontalBlurTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world and view matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); // Get the ortho matrix from the render to texture since texture has different dimensions. m_HorizontalBlurTexture->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Store the screen width in a float that will be used in the horizontal blur shader. screenSizeX = (float)m_HorizontalBlurTexture->GetTextureWidth(); // Put the small ortho window vertex and index buffers on the graphics pipeline to prepare them for drawing. m_SmallWindow->Render(m_D3D->GetDeviceContext()); // Render the small ortho window using the horizontal blur shader and the down sampled render to texture resource. result = m_HorizontalBlurShader->Render(m_D3D->GetDeviceContext(), m_SmallWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_DownSampleTexure->GetShaderResourceView(), screenSizeX); if(!result) { return false; } // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget();
53 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
bool GraphicsClass::RenderVerticalBlurToTexture() { D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix; float screenSizeY; bool result;
// Set the render target to be the render to texture. m_VerticalBlurTexture->SetRenderTarget(m_D3D->GetDeviceContext()); // Clear the render to texture. m_VerticalBlurTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world and view matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); // Get the ortho matrix from the render to texture since texture has different dimensions. m_VerticalBlurTexture->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Store the screen height in a float that will be used in the vertical blur shader. screenSizeY = (float)m_VerticalBlurTexture->GetTextureHeight(); // Put the small ortho window vertex and index buffers on the graphics pipeline to prepare them for drawing. m_SmallWindow->Render(m_D3D->GetDeviceContext()); // Render the small ortho window using the vertical blur shader and the horizontal blurred render to texture resource. result = m_VerticalBlurShader->Render(m_D3D->GetDeviceContext(), m_SmallWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_HorizontalBlurTexture->GetShaderResourceView(), screenSizeY); if(!result) { return false;
54 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
} // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget(); // Reset the viewport back to the original. m_D3D->ResetViewport(); return true; }
// Set the render target to be the render to texture. m_UpSampleTexure->SetRenderTarget(m_D3D->GetDeviceContext()); // Clear the render to texture. m_UpSampleTexure->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world and view matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); // Get the ortho matrix from the render to texture since texture has different dimensions. m_UpSampleTexure->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Put the full screen ortho window vertex and index buffers on the graphics pipeline to prepare them for drawing. m_FullScreenWindow->Render(m_D3D->GetDeviceContext()); // Render the full screen ortho window using the texture shader and the small sized final blurred render to texture resource. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_FullScreenWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix,
55 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
m_VerticalBlurTexture->GetShaderResourceView()); if(!result) { return false; } // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget(); // Reset the viewport back to the original. m_D3D->ResetViewport(); return true; } This next function is what renders the user interface elements to their own render to texture object. bool GraphicsClass::RenderUIElementsToTexture() { D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix; bool result;
// Set the render target to be the render to texture. m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext()); // Clear the render to texture. m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and ortho matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff(); // Put the bitmap vertex and index buffers on the graphics pipeline to prepare them for drawing. result = m_Bitmap->Render(m_D3D->GetDeviceContext(), 100, 100);
56 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
if(!result) { return false; } // Render the bitmap using the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Bitmap->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_Bitmap->GetTexture()); if(!result) { return false; } // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Reset the render target back to the original back buffer and not the render to texture anymore. m_D3D->SetBackBufferRenderTarget(); // Reset the viewport back to the original. m_D3D->ResetViewport(); return true; } And then this final function is what combines the user interface elements texture with the blurred glow map texture to produce the final glow result. bool GraphicsClass::RenderGlowScene() { D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix;
// Clear the buffers to begin the scene. m_D3D->BeginScene(1.0f, 0.0f, 0.0f, 0.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, and ortho matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetOrthoMatrix(orthoMatrix); // Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff();
57 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
// Put the full screen ortho window vertex and index buffers on the graphics pipeline to prepare them for drawing. m_FullScreenWindow->Render(m_D3D->GetDeviceContext()); // Render the full screen ortho window using the texture shader and the full screen sized blurred render to texture resource. m_GlowShader->Render(m_D3D->GetDeviceContext(), m_FullScreenWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_RenderTexture->GetShaderResourceView(), m_UpSampleTexure->GetShaderResourceView(), 3.0f); // Turn the Z buffer back on now that all 2D rendering has completed. m_D3D->TurnZBufferOn(); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
Summary
We can now apply selective glow to our scene using glow maps.
To Do Exercises
1. Compile and run the program. You should see a glow applied to the bitmap object. Press escape to quit. 2. Modify the glow strength to see the effect it has. 3. Create a different glow map to make only the text glow. 4. Add glow to a 3D scene (such as the spinning cube from the other tutorials). 5. Use an additional texture such as noise or a gradient and animate the glow by translating the noise or gradient texture and using it as intensity for the glow.
Source Code
58 of 59
3/8/2013 12:47 PM
http://www.rastertek.com/dx11tut46.html
59 of 59
3/8/2013 12:47 PM