// --------------------------------------------------------------------------- // // @file TwSimpleDX11.cpp // // @brief Example that uses AntTweakBar with DirectX11. // // It draws a Menger sponge, aka Sierpinski cube: // http://en.wikipedia.org/wiki/Menger_sponge . // // Cubes shading is augmented with some simple ambient occlusion // applied by subdividing each cube face into a 3x3 grid. // AntTweakBar is used to add some interacitve controls. // // Note that most of the code here is related to DirectX and // Menger sponge generation. AntTweakBar calls are localized // in the WinMain function. // // AntTweakBar: http://anttweakbar.sourceforge.net/doc // DirectX: http://msdn.microsoft.com/directx // // @author Philippe Decaudin // // --------------------------------------------------------------------------- #include #include #include #include #include "d3d10vs2003.h" // workaround to include D3D10.h and D3D11.h with VS2003 #include // Shaders: // Vertex and pixel shaders are defined in TwSimpleDX11.hlsl and are compiled // in a pre-build step using the fxc.exe compiler (from the DirectX SDK Aug'09 or later). // Pre-build commands are: // fxc /T vs_4_0_level_9_1 /E MainVS /Fh $(IntDir)\TwSimpleDX11_VS.h TwSimpleDX11.hlsl // fxc /T ps_4_0_level_9_1 /E MainPS /Fh $(IntDir)\TwSimpleDX11_PS.h TwSimpleDX11.hlsl // #include "TwSimpleDX11_VS.h" #include "TwSimpleDX11_PS.h" // D3D objects ID3D11Device * g_D3DDev = NULL; ID3D11DeviceContext * g_D3DDevCtx = NULL; IDXGISwapChain * g_SwapChain = NULL; DXGI_SWAP_CHAIN_DESC g_SwapChainDesc; ID3D11RenderTargetView *g_RenderTargetView = NULL; ID3D11DepthStencilView *g_DepthStencilView = NULL; D3D11_TEXTURE2D_DESC g_DepthStencilDesc; ID3D11VertexShader * g_VertexShader = NULL; ID3D11PixelShader * g_PixelShader = NULL; ID3D11InputLayout * g_InputLayout = NULL; ID3D11Buffer * g_VertexBuffer = NULL; ID3D11Buffer * g_IndexBuffer = NULL; ID3D11BlendState * g_BlendState = NULL; ID3D11DepthStencilState*g_DepthStencilState = NULL; ID3D11RasterizerState * g_RasterState = NULL; ID3D11Buffer * g_ConstantBuffer = NULL; // Geometry data structures and objects struct Vector3 { float v[3]; static Vector3 ZERO; }; Vector3 Vector3::ZERO = { 0, 0, 0 }; struct Matrix4x4 { float m[4][4]; static Matrix4x4 IDENTITY; }; Matrix4x4 Matrix4x4::IDENTITY = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; struct Quaternion { float q[4]; static Quaternion IDENTITY; }; Quaternion Quaternion::IDENTITY = { 0, 0, 0, 1 }; const float FLOAT_PI = 3.14159265f; struct Vertex { Vector3 Position; Vector3 Normal; unsigned int AmbientColor; }; struct ShaderConstants { Matrix4x4 WorldViewProj; Matrix4x4 WorldNorm; Vector3 LightDir; float LightCoeff; }; // Each cube face is split into a 3x3 grid const int CUBE_FACE_VERTEX_COUNT = 4 * 4; // 16 vertices per face const int CUBE_FACE_TRIANGLE_COUNT = 2 * 3 * 3; // 18 triangles to be drawn for each face // Faces color of the sponge wrt to recursion level const unsigned int COLORS[] = { 0xffffffff, 0xff007fff, 0xff7fff00, 0xffff007f, 0xff0000ff, 0xff00ff00, 0xffff0000 }; // Scene globals Quaternion g_SpongeRotation; // model rotation, set by InitScene int g_SpongeLevel = 2; // number of recursions bool g_SpongeAO = true; // apply ambient occlusion unsigned int g_SpongeIndicesCount = 0; // set by BuildSponge Vector3 g_LightDir = {-0.5f, -0.2f, 1}; // light direction vector float g_CamDistance = 0.7f; // camera distance float g_BackgroundColor[] = {0, 0, 0.5f, 1}; // background color bool g_Animate = true; // enable animation float g_AnimationSpeed = 0.2f; // animation speed // Some math operators and functions. // They are defined here to avoid linking with D3DX. Vector3 operator+(const Vector3& a, const Vector3& b) { Vector3 out; out.v[0] = a.v[0] + b.v[0]; out.v[1] = a.v[1] + b.v[1]; out.v[2] = a.v[2] + b.v[2]; return out; } Vector3 operator*(float s, const Vector3& a) { Vector3 out; out.v[0] = s * a.v[0]; out.v[1] = s * a.v[1]; out.v[2] = s * a.v[2]; return out; } float Length(const Vector3& a) { return sqrt(a.v[0]*a.v[0] + a.v[1]*a.v[1] + a.v[2]*a.v[2]); } Matrix4x4 Projection(float fov, float aspectRatio, float zNear, float zFar) // Left-handed projection { Matrix4x4 out; float yScale = 1.0f / tan(fov / 2.0f); float xScale = yScale / aspectRatio; out.m[0][0] = xScale; out.m[0][1] = out.m[0][2] = out.m[0][3] = 0; out.m[1][1] = yScale; out.m[1][0] = out.m[1][2] = out.m[1][3] = 0; out.m[2][0] = out.m[2][1] = 0; out.m[2][2] = zFar / (zFar - zNear); out.m[2][3] = 1; out.m[3][0] = out.m[3][1] = out.m[3][3] = 0; out.m[3][2] = - zNear * zFar / (zFar - zNear); return out; } Matrix4x4 Translation(const Vector3& t) { Matrix4x4 out(Matrix4x4::IDENTITY); out.m[3][0] = t.v[0]; out.m[3][1] = t.v[1]; out.m[3][2] = t.v[2]; return out; } Matrix4x4 Scale(float s) { Matrix4x4 out(Matrix4x4::IDENTITY); out.m[0][0] = out.m[1][1] = out.m[2][2] = s; return out; } Matrix4x4 operator*(const Matrix4x4& a, const Matrix4x4& b) { Matrix4x4 out(Matrix4x4::IDENTITY); int i, j; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) out.m[i][j] = a.m[i][0]*b.m[0][j] + a.m[i][1]*b.m[1][j] + a.m[i][2]*b.m[2][j] + a.m[i][3]*b.m[3][j]; return out; } Vector3 operator*(const Vector3& p, const Matrix4x4& a) { Vector3 out; float rw = 1.f / (p.v[0]*a.m[0][3] + p.v[1]*a.m[1][3] + p.v[2]*a.m[2][3] + a.m[3][3]); out.v[0] = rw * (p.v[0]*a.m[0][0] + p.v[1]*a.m[1][0] + p.v[2]*a.m[2][0] + a.m[3][0]); out.v[1] = rw * (p.v[0]*a.m[0][1] + p.v[1]*a.m[1][1] + p.v[2]*a.m[2][1] + a.m[3][1]); out.v[2] = rw * (p.v[0]*a.m[0][2] + p.v[1]*a.m[1][2] + p.v[2]*a.m[2][2] + a.m[3][2]); return out; } Quaternion RotationFromAxisAngle(const Vector3& axis, float angle) { Quaternion out; float norm = Length(axis); float sina2 = sin(0.5f * angle); out.q[0] = sina2 * axis.v[0] / norm; out.q[1] = sina2 * axis.v[1] / norm; out.q[2] = sina2 * axis.v[2] / norm; out.q[3] = cos(0.5f * angle); return out; } void AxisAngleFromRotation(Vector3& outAxis, float& outAngle, const Quaternion& quat) { float sina2 = sqrt(quat.q[0]*quat.q[0] + quat.q[1]*quat.q[1] + quat.q[2]*quat.q[2]); outAngle = 2.0f * atan2(sina2, quat.q[3]); float r = (sina2 > 0) ? (1.0f / sina2) : 0; outAxis.v[0] = r * quat.q[0]; outAxis.v[1] = r * quat.q[1]; outAxis.v[2] = r * quat.q[2]; } Matrix4x4 QuaternionToMatrix(const Quaternion& quat) { Matrix4x4 out; float yy2 = 2.0f * quat.q[1] * quat.q[1]; float xy2 = 2.0f * quat.q[0] * quat.q[1]; float xz2 = 2.0f * quat.q[0] * quat.q[2]; float yz2 = 2.0f * quat.q[1] * quat.q[2]; float zz2 = 2.0f * quat.q[2] * quat.q[2]; float wz2 = 2.0f * quat.q[3] * quat.q[2]; float wy2 = 2.0f * quat.q[3] * quat.q[1]; float wx2 = 2.0f * quat.q[3] * quat.q[0]; float xx2 = 2.0f * quat.q[0] * quat.q[0]; out.m[0][0] = - yy2 - zz2 + 1.0f; out.m[0][1] = xy2 + wz2; out.m[0][2] = xz2 - wy2; out.m[0][3] = 0; out.m[1][0] = xy2 - wz2; out.m[1][1] = - xx2 - zz2 + 1.0f; out.m[1][2] = yz2 + wx2; out.m[1][3] = 0; out.m[2][0] = xz2 + wy2; out.m[2][1] = yz2 - wx2; out.m[2][2] = - xx2 - yy2 + 1.0f; out.m[2][3] = 0; out.m[3][0] = out.m[3][1] = out.m[3][2] = 0; out.m[3][3] = 1; return out; } // Forward declarations HRESULT InitDevice(HWND wnd); HRESULT InitScene(); void Cleanup(); LRESULT CALLBACK MessageProc(HWND, UINT, WPARAM, LPARAM); void Anim(); void Render(); HRESULT BuildSponge(int levelMax, bool aoEnabled); // Callback function called by AntTweakBar to set the sponge recursion level void TW_CALL SetSpongeLevelCB(const void *value, void * /*clientData*/) { g_SpongeLevel = *static_cast(value); BuildSponge(g_SpongeLevel, g_SpongeAO); } // Callback function called by AntTweakBar to get the sponge recursion level void TW_CALL GetSpongeLevelCB(void *value, void * /*clientData*/) { *static_cast(value) = g_SpongeLevel; } // Callback function called by AntTweakBar to enable/disable ambient occlusion void TW_CALL SetSpongeAOCB(const void *value, void * /*clientData*/) { g_SpongeAO = *static_cast(value); BuildSponge(g_SpongeLevel, g_SpongeAO); } // Callback function called by AntTweakBar to get ambient occlusion state void TW_CALL GetSpongeAOCB(void *value, void * /*clientData*/) { *static_cast(value) = g_SpongeAO; } // Main int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int cmdShow) { // Register our window class WNDCLASSEX wcex = { sizeof(WNDCLASSEX), CS_HREDRAW|CS_VREDRAW, MessageProc, 0L, 0L, instance, NULL, NULL, NULL, NULL, L"TwDX11", NULL }; RegisterClassEx(&wcex); // Create a window RECT rc = { 0, 0, 640, 480 }; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); HWND wnd = CreateWindow(L"TwDX11", L"AntTweakBar simple example using DirectX11", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right-rc.left, rc.bottom-rc.top, NULL, NULL, instance, NULL); if (!wnd) { MessageBox(NULL, L"Cannot create window", L"Error", MB_OK|MB_ICONERROR); return 0; } ShowWindow(wnd, cmdShow); UpdateWindow(wnd); // Initialize D3D11 if (FAILED(InitDevice(wnd))) { MessageBox(wnd, L"Cannot create D3D11 device", L"Error", MB_OK|MB_ICONERROR); Cleanup(); return 0; } // Initialize the 3D scene if (FAILED(InitScene())) { MessageBox(wnd, L"Scene initialization failed.", L"Error", MB_OK|MB_ICONERROR); Cleanup(); return 0; } // Initialize AntTweakBar if (!TwInit(TW_DIRECT3D11, g_D3DDev)) { MessageBoxA(wnd, TwGetLastError(), "AntTweakBar initialization failed", MB_OK|MB_ICONERROR); Cleanup(); return 0; } // Create a tweak bar TwBar *bar = TwNewBar("TweakBar"); TwDefine(" GLOBAL help='This example shows how to integrate AntTweakBar into a DirectX11 application.' "); // Message added to the help bar. int barSize[2] = {224, 320}; TwSetParam(bar, NULL, "size", TW_PARAM_INT32, 2, barSize); // Add variables to the tweak bar TwAddVarCB(bar, "Level", TW_TYPE_INT32, SetSpongeLevelCB, GetSpongeLevelCB, NULL, "min=0 max=3 group=Sponge keyincr=l keydecr=L"); TwAddVarCB(bar, "Ambient Occlusion", TW_TYPE_BOOLCPP, SetSpongeAOCB, GetSpongeAOCB, NULL, "group=Sponge key=o"); TwAddVarRW(bar, "Rotation", TW_TYPE_QUAT4F, &g_SpongeRotation, "opened=true axisz=-z group=Sponge"); TwAddVarRW(bar, "Animation", TW_TYPE_BOOLCPP, &g_Animate, "group=Sponge key=a"); TwAddVarRW(bar, "Animation speed", TW_TYPE_FLOAT, &g_AnimationSpeed, "min=-10 max=10 step=0.1 group=Sponge keyincr=+ keydecr=-"); TwAddVarRW(bar, "Light direction", TW_TYPE_DIR3F, &g_LightDir, "opened=true axisz=-z showval=false"); TwAddVarRW(bar, "Camera distance", TW_TYPE_FLOAT, &g_CamDistance, "min=0 max=4 step=0.01 keyincr=PGUP keydecr=PGDOWN"); TwAddVarRW(bar, "Background", TW_TYPE_COLOR4F, &g_BackgroundColor, "colormode=hls"); // Main message loop MSG msg = {0}; while (WM_QUIT != msg.message) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { Anim(); Render(); } } TwTerminate(); Cleanup(); return (int)msg.wParam; } // Create Direct3D device and swap chain HRESULT InitDevice(HWND wnd) { HRESULT hr = S_OK; // Get window size RECT rc; GetClientRect(wnd, &rc); UINT width = rc.right - rc.left; UINT height = rc.bottom - rc.top; // Create D3D11 device and swap chain UINT createDeviceFlags = 0; #ifdef _DEBUG createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif ZeroMemory(&g_SwapChainDesc, sizeof(g_SwapChainDesc)); g_SwapChainDesc.BufferCount = 1; g_SwapChainDesc.BufferDesc.Width = width; g_SwapChainDesc.BufferDesc.Height = height; g_SwapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; g_SwapChainDesc.BufferDesc.RefreshRate.Numerator = 0; g_SwapChainDesc.BufferDesc.RefreshRate.Denominator = 0; g_SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; g_SwapChainDesc.OutputWindow = wnd; g_SwapChainDesc.SampleDesc.Count = 4; g_SwapChainDesc.SampleDesc.Quality = 0; g_SwapChainDesc.Windowed = TRUE; g_SwapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // Try to create a hardware accelerated device with multisample antialiasing first hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, NULL, 0, D3D11_SDK_VERSION, &g_SwapChainDesc, &g_SwapChain, &g_D3DDev, NULL, &g_D3DDevCtx); if (FAILED(hr)) { // If failed, try without antialiasing g_SwapChainDesc.SampleDesc.Count = 1; hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, NULL, 0, D3D11_SDK_VERSION, &g_SwapChainDesc, &g_SwapChain, &g_D3DDev, NULL, &g_D3DDevCtx); if (FAILED(hr)) { // If failed, try to create a reference device hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_REFERENCE, NULL, createDeviceFlags, NULL, 0, D3D11_SDK_VERSION, &g_SwapChainDesc, &g_SwapChain, &g_D3DDev, NULL, &g_D3DDevCtx); if (SUCCEEDED(hr)) MessageBox(wnd, L"No DX11 hardware acceleration found.\nSwitching to REFERENCE driver (very slow).", L"Warning", MB_OK|MB_ICONWARNING); else return hr; } } // Create a render target and depth-stencil view ID3D11Texture2D *backBuffer = NULL, *dsBuffer = NULL; hr = g_SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBuffer); if (FAILED(hr)) return hr; hr = g_D3DDev->CreateRenderTargetView(backBuffer, NULL, &g_RenderTargetView); backBuffer->Release(); if (FAILED(hr)) return hr; g_DepthStencilDesc.Width = width; g_DepthStencilDesc.Height = height; g_DepthStencilDesc.MipLevels = 1; g_DepthStencilDesc.ArraySize = 1; g_DepthStencilDesc.Format = DXGI_FORMAT_D16_UNORM; g_DepthStencilDesc.SampleDesc = g_SwapChainDesc.SampleDesc; g_DepthStencilDesc.Usage = D3D11_USAGE_DEFAULT; g_DepthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; g_DepthStencilDesc.CPUAccessFlags = 0; g_DepthStencilDesc.MiscFlags = 0; hr = g_D3DDev->CreateTexture2D(&g_DepthStencilDesc, NULL, &dsBuffer); if (FAILED(hr)) return hr; hr = g_D3DDev->CreateDepthStencilView(dsBuffer, NULL, &g_DepthStencilView); dsBuffer->Release(); if (FAILED(hr)) return hr; g_D3DDevCtx->OMSetRenderTargets(1, &g_RenderTargetView, g_DepthStencilView); // Setup the viewport D3D11_VIEWPORT vp; vp.Width = (float)width; vp.Height = (float)height; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; g_D3DDevCtx->RSSetViewports(1, &vp); return S_OK; } // Initialize the 3D objects & shaders HRESULT InitScene() { HRESULT hr; // Define the input layout D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, offsetof(Vertex, Position), D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, offsetof(Vertex, Normal), D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(Vertex, AmbientColor), D3D11_INPUT_PER_VERTEX_DATA, 0 } }; hr = g_D3DDev->CreateInputLayout(layout, sizeof(layout)/sizeof(layout[0]), g_MainVS, sizeof(g_MainVS), &g_InputLayout); if (FAILED(hr)) return hr; // Set the input layout g_D3DDevCtx->IASetInputLayout(g_InputLayout); // Create shaders hr = g_D3DDev->CreateVertexShader(g_MainVS, sizeof(g_MainVS), NULL, &g_VertexShader); if (FAILED(hr)) return hr; hr = g_D3DDev->CreatePixelShader(g_MainPS, sizeof(g_MainPS), NULL, &g_PixelShader); if (FAILED(hr)) return hr; // Set shaders g_D3DDevCtx->VSSetShader(g_VertexShader, NULL, 0); g_D3DDevCtx->PSSetShader(g_PixelShader, NULL, 0); // Create vertex and index buffers hr = BuildSponge(g_SpongeLevel, g_SpongeAO); if (FAILED(hr)) return hr; // Create constant buffer D3D11_BUFFER_DESC bd; bd.Usage = D3D11_USAGE_DYNAMIC; bd.ByteWidth = sizeof(ShaderConstants); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; bd.MiscFlags = 0; bd.StructureByteStride = 0; hr = g_D3DDev->CreateBuffer(&bd, NULL, &g_ConstantBuffer); if (FAILED(hr)) return hr; // Blend state D3D11_BLEND_DESC bsd; bsd.AlphaToCoverageEnable = FALSE; bsd.IndependentBlendEnable = FALSE; for (int i = 0; i < 8; i++) { bsd.RenderTarget[i].BlendEnable = TRUE; bsd.RenderTarget[i].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; bsd.RenderTarget[i].SrcBlend = D3D11_BLEND_SRC_ALPHA; bsd.RenderTarget[i].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; bsd.RenderTarget[i].BlendOp = D3D11_BLEND_OP_ADD; bsd.RenderTarget[i].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA; bsd.RenderTarget[i].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; bsd.RenderTarget[i].BlendOpAlpha = D3D11_BLEND_OP_ADD; } g_D3DDev->CreateBlendState(&bsd, &g_BlendState); float blendFactors[4] = { 1, 1, 1, 1 }; g_D3DDevCtx->OMSetBlendState(g_BlendState, blendFactors, 0xffffffff); // Depth-stencil state D3D11_DEPTH_STENCILOP_DESC od; od.StencilFunc = D3D11_COMPARISON_ALWAYS; od.StencilFailOp = D3D11_STENCIL_OP_KEEP; od.StencilPassOp = D3D11_STENCIL_OP_KEEP; od.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; D3D11_DEPTH_STENCIL_DESC dsd; dsd.DepthEnable = TRUE; dsd.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; dsd.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; dsd.StencilEnable = FALSE; dsd.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK; dsd.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK; dsd.FrontFace = od; dsd.BackFace = od; g_D3DDev->CreateDepthStencilState(&dsd, &g_DepthStencilState); g_D3DDevCtx->OMSetDepthStencilState(g_DepthStencilState, 0); // Rasterizer state D3D11_RASTERIZER_DESC rs; ZeroMemory(&rs, sizeof(rs)); rs.FillMode = D3D11_FILL_SOLID; rs.CullMode = D3D11_CULL_NONE; rs.MultisampleEnable = (g_SwapChainDesc.SampleDesc.Count > 0); g_D3DDev->CreateRasterizerState(&rs, &g_RasterState); g_D3DDevCtx->RSSetState(g_RasterState); // Init model rotation Vector3 axis = {-1, 1, 0}; g_SpongeRotation = RotationFromAxisAngle(axis, FLOAT_PI/4); return S_OK; } // Clean up D3D objects void Cleanup() { #define RELEASE_CHECK(p) if (p) { ULONG rc = (p)->Release(); assert(rc == 0); (void)rc; (p) = NULL; } if (g_D3DDevCtx) g_D3DDevCtx->ClearState(); RELEASE_CHECK(g_BlendState); RELEASE_CHECK(g_DepthStencilState); RELEASE_CHECK(g_RasterState); RELEASE_CHECK(g_VertexBuffer); RELEASE_CHECK(g_IndexBuffer); RELEASE_CHECK(g_ConstantBuffer); RELEASE_CHECK(g_InputLayout); RELEASE_CHECK(g_VertexShader); RELEASE_CHECK(g_PixelShader); RELEASE_CHECK(g_RenderTargetView); RELEASE_CHECK(g_DepthStencilView); if (g_SwapChainDesc.Windowed) RELEASE_CHECK(g_SwapChain); RELEASE_CHECK(g_D3DDevCtx); RELEASE_CHECK(g_D3DDev); } // Called every time the application receives a message LRESULT CALLBACK MessageProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { // Send event message to AntTweakBar if (TwEventWin(wnd, message, wParam, lParam)) return 0; // Event has been handled by AntTweakBar switch (message) { case WM_PAINT: { PAINTSTRUCT ps; BeginPaint(wnd, &ps); EndPaint(wnd, &ps); return 0; } case WM_SIZE: // Window size has been changed if (g_D3DDev) // Resize D3D render target { // Release render target and depth-stencil view ID3D11RenderTargetView *nullRTV = NULL; g_D3DDevCtx->OMSetRenderTargets(1, &nullRTV, NULL); if (g_RenderTargetView) { g_RenderTargetView->Release(); g_RenderTargetView = NULL; } if (g_DepthStencilView) { g_DepthStencilView->Release(); g_DepthStencilView = NULL; } if (g_SwapChain) { // Resize swap chain g_SwapChainDesc.BufferDesc.Width = LOWORD(lParam); g_SwapChainDesc.BufferDesc.Height = HIWORD(lParam); g_SwapChain->ResizeBuffers(g_SwapChainDesc.BufferCount, g_SwapChainDesc.BufferDesc.Width, g_SwapChainDesc.BufferDesc.Height, g_SwapChainDesc.BufferDesc.Format, g_SwapChainDesc.Flags); // Re-create a render target and depth-stencil view ID3D11Texture2D *backBuffer = NULL, *dsBuffer = NULL; g_SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBuffer); g_D3DDev->CreateRenderTargetView(backBuffer, NULL, &g_RenderTargetView); backBuffer->Release(); g_DepthStencilDesc.Width = g_SwapChainDesc.BufferDesc.Width; g_DepthStencilDesc.Height = g_SwapChainDesc.BufferDesc.Height; g_D3DDev->CreateTexture2D(&g_DepthStencilDesc, NULL, &dsBuffer); g_D3DDev->CreateDepthStencilView(dsBuffer, NULL, &g_DepthStencilView); dsBuffer->Release(); g_D3DDevCtx->OMSetRenderTargets(1, &g_RenderTargetView, g_DepthStencilView); // Setup the viewport D3D11_VIEWPORT vp; vp.Width = (float)g_SwapChainDesc.BufferDesc.Width; vp.Height = (float)g_SwapChainDesc.BufferDesc.Height; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; g_D3DDevCtx->RSSetViewports(1, &vp); } // TwWindowSize has been called by TwEventWin, so it is not necessary to call it again here. } return 0; case WM_CHAR: if (wParam == VK_ESCAPE) PostQuitMessage(0); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(wnd, message, wParam, lParam); } } // Append vertices and indices of a cube to the index and vertex buffers. // The cube has gradient ambient-occlusion defined per edge. void AppendCubeToBuffers(std::vector& vertices, std::vector& indices, const Matrix4x4& xform, float aoRatio, const bool aoEdges[12], const unsigned int faceColors[6]) { unsigned int indicesOffset = (unsigned int)vertices.size(); // Fill vertex buffer // Cube faces and edges numbering: // __________ _____6____ // / /| /| /| // / 4 / |<2 10 5 9 | // /_________/ | /__|__2___/ 7 // | | 1| | |___4__|__| // 3>| 0 | / 3 / 1 / // | | / | 11 | 8 // |_________|/ |/____0___|/ // 5^ // Each face is split in a 3x3 grid, which gives 16 vertices per face and 3x3x2(=18) triangles per face. // Ambient occlusion color is set for each of these vertices wrt aoEdges flags. const float R = 0.5f; // unit cube radius // the 4 corner coordinates for each of the 6 faces const Vector3 A[6] = { {-R, -R, -R}, {+R, -R, -R}, {+R, -R, +R}, {-R, -R, +R}, {-R, +R, -R}, {-R, -R, -R} }; const Vector3 B[6] = { {+R, -R, -R}, {+R, -R, +R}, {-R, -R, +R}, {-R, -R, -R}, {+R, +R, -R}, {+R, -R, -R} }; const Vector3 C[6] = { {-R, +R, -R}, {+R, +R, -R}, {+R, +R, +R}, {-R, +R, +R}, {-R, +R, +R}, {-R, -R, +R} }; const Vector3 D[6] = { {+R, +R, -R}, {+R, +R, +R}, {-R, +R, +R}, {-R, +R, -R}, {+R, +R, +R}, {+R, -R, +R} }; // the 6 face normals const Vector3 N[6] = { { 0, 0, -1}, {+1, 0, 0}, { 0, 0, +1}, {-1, 0, 0}, { 0, +1, 0}, { 0, -1, 0} }; // association between edge indices and the 6 faces const int E[6][4] = { {0, 1, 2, 3}, {8, 7, 9, 1}, {4, 5, 6, 7}, {11, 3, 10, 5}, {2, 9, 6, 10}, {0, 8, 4, 11} }; int face, i, j; float u, v; bool ao; Vertex vertex; for (face = 0; face < 6; face++) for (j = 0; j < 4; j++) { v = (j == 1) ? aoRatio : ((j == 2) ? 1.0f - aoRatio : j/3); for (i = 0; i < 4; i++) { u = (i == 1) ? aoRatio : ((i == 2) ? 1.0f - aoRatio : i/3); vertex.Position = (1.0f - v) * ((1.0f - u) * A[face] + u * B[face]) + v * ((1.0f - u) * C[face] + u * D[face]); vertex.Position = vertex.Position * xform; vertex.Normal = N[face]; ao = (j == 0) && aoEdges[E[face][0]]; ao |= (i == 3) && aoEdges[E[face][1]]; ao |= (j == 3) && aoEdges[E[face][2]]; ao |= (i == 0) && aoEdges[E[face][3]]; #define DARKEN(r, s) ( (unsigned int)(float(r)*(s)) > 255 ? 255 : (unsigned int)(float(r)*(s)) ) #define DARKEN_COLOR(c, s) ( 0xff000000 | (DARKEN(((c)>>16)&0xff, s)<<16) | (DARKEN(((c)>>8)&0xff, s)<<8) | DARKEN((c)&0xff, s) ) vertex.AmbientColor = ao ? DARKEN_COLOR(faceColors[face], 0.75f) : faceColors[face]; vertices.push_back(vertex); } } // Fill index buffer // 3 indices per triangle, 2*3*3 triangles per faces, 6 faces. // Vertex index numbering of each face: // 12__13__14___15 // |'. | .'| .'| // 8__'9'_10'__11 // | .'| .'| .'| // 4'__5'__6'__7 // | .'| .'|'. | // 0'__1'__2__'3 const unsigned short I[CUBE_FACE_TRIANGLE_COUNT][3] = { {0, 5, 4}, {0, 1, 5}, {1, 6, 5}, {1, 2, 6}, {3, 6, 2}, {3, 7, 6}, {4, 9, 8}, {4, 5, 9}, {5, 10, 9}, {5, 6, 10}, {6, 11, 10}, {6, 7, 11}, {8, 9, 12}, {9, 13, 12}, {9, 14, 13}, {9, 10, 14}, {10, 15, 14}, {10, 11, 15} }; int tri; for (face = 0; face < 6; face++) for (tri = 0; tri < CUBE_FACE_TRIANGLE_COUNT; tri++) for (i = 0; i < 3; i++) indices.push_back(indicesOffset + I[tri][i] + 16*face); // 16 vertices per face } // Recursive function called to fill the vertex and index buffers with the cubes forming the Menger sponge. void FillSpongeBuffers(int level, int levelMax, std::vector& vertices, std::vector& indices, const Vector3& center, bool aoEnabled, const bool aoEdges[12], const unsigned int faceColors[6]) { float scale = pow(1.0f/3.0f, level); if (level == levelMax) { float aoRatio = pow(3.0f, level) * 0.02f; if (aoRatio > 0.4999f) aoRatio = 0.4999f; Matrix4x4 xform = Scale(scale) * Translation(center); AppendCubeToBuffers(vertices, indices, xform, aoRatio, aoEdges, faceColors); } else { // Local function which applies AO in one direction struct Local { static void ApplyAO(int i, int j, bool& e0, bool& e1, bool& e2, bool& e3) { if (i == -1 && j == 0) e0 = e1 = true; if (i == +1 && j <= 0) e1 = false; if (i == +1 && j >= 0) e0 = false; if (i == +1 && j == 0) e2 = e3 = true; if (i == -1 && j <= 0) e2 = false; if (i == -1 && j >= 0) e3 = false; if (j == -1 && i == 0) e1 = e2 = true; if (j == +1 && i <= 0) e1 = false; if (j == +1 && i >= 0) e2 = false; if (j == +1 && i == 0) e0 = e3 = true; if (j == -1 && i <= 0) e0 = false; if (j == -1 && i >= 0) e3 = false; } }; bool aoEdgesCopy[12]; unsigned int faceColorsCopy[6]; int i, j, k, l; for (i = -1; i <= 1; i++) for (j = -1; j <= 1; j++) for (k = -1; k <= 1; k++) if ( !( (i == 0 && j == 0) || (i == 0 && k == 0) || (j == 0 && k == 0) ) ) { float s = 1.0f/3.0f * scale; Vector3 t = { center.v[0] + s * i, center.v[1] + s * j, center.v[2] + s * k }; for (l = 0; l < 12; l++) aoEdgesCopy[l] = aoEdges[l]; if (aoEnabled) { Local::ApplyAO( i, j, aoEdgesCopy[8], aoEdgesCopy[9], aoEdgesCopy[10], aoEdgesCopy[11]); // z direction Local::ApplyAO( i, k, aoEdgesCopy[1], aoEdgesCopy[7], aoEdgesCopy[5], aoEdgesCopy[3] ); // y direction Local::ApplyAO(-k, j, aoEdgesCopy[0], aoEdgesCopy[2], aoEdgesCopy[6], aoEdgesCopy[4] ); // x direction } for (l = 0; l < 6; l++) faceColorsCopy[l] = faceColors[l]; if (k == +1) faceColorsCopy[0] = COLORS[level+1]; if (i == -1) faceColorsCopy[1] = COLORS[level+1]; if (k == -1) faceColorsCopy[2] = COLORS[level+1]; if (i == +1) faceColorsCopy[3] = COLORS[level+1]; if (j == -1) faceColorsCopy[4] = COLORS[level+1]; if (j == +1) faceColorsCopy[5] = COLORS[level+1]; FillSpongeBuffers(level + 1, levelMax, vertices, indices, t, aoEnabled, aoEdgesCopy, faceColorsCopy); } } } // Build sponge vertex and index buffers HRESULT BuildSponge(int levelMax, bool aoEnabled) { if (g_VertexBuffer) g_VertexBuffer->Release(); if (g_IndexBuffer) g_IndexBuffer->Release(); g_SpongeIndicesCount = 0; // Fill vertex and index memory buffers static std::vector vertices; static std::vector indices; vertices.clear(); indices.clear(); bool aoEdges[12] = { false, false, false, false, false, false, false, false, false, false, false, false }; unsigned int faceColors[6] = { COLORS[0], COLORS[0], COLORS[0], COLORS[0], COLORS[0], COLORS[0] }; FillSpongeBuffers(0, levelMax, vertices, indices, Vector3::ZERO, aoEnabled, aoEdges, faceColors); // Create vertex buffer D3D11_BUFFER_DESC bd; bd.Usage = D3D11_USAGE_IMMUTABLE; bd.ByteWidth = (UINT)vertices.size() * sizeof(Vertex); bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; bd.StructureByteStride = 0; D3D11_SUBRESOURCE_DATA initData; initData.pSysMem = &vertices[0]; initData.SysMemPitch = 0; initData.SysMemSlicePitch = 0; HRESULT hr = g_D3DDev->CreateBuffer(&bd, &initData, &g_VertexBuffer); if (FAILED(hr)) return hr; // Create index buffer bd.ByteWidth = (UINT)indices.size() * sizeof(unsigned int); bd.BindFlags = D3D11_BIND_INDEX_BUFFER; initData.pSysMem = &indices[0]; hr = g_D3DDev->CreateBuffer(&bd, &initData, &g_IndexBuffer); if (FAILED(hr)) { g_VertexBuffer->Release(); return hr; } g_SpongeIndicesCount = (unsigned int)indices.size(); return S_OK; } // Render the sponge void DrawSponge() { if (g_SpongeIndicesCount == 0) return; // Set vertex buffer UINT stride = sizeof(Vertex); UINT offset = 0; g_D3DDevCtx->IASetVertexBuffers(0, 1, &g_VertexBuffer, &stride, &offset); // Set index buffer g_D3DDevCtx->IASetIndexBuffer(g_IndexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set primitive topology g_D3DDevCtx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // Set shaders g_D3DDevCtx->VSSetShader(g_VertexShader, NULL, 0); g_D3DDevCtx->PSSetShader(g_PixelShader, NULL, 0); // Render the primitives g_D3DDevCtx->DrawIndexed(g_SpongeIndicesCount, 0, 0); } // Copy world/view/proj matrices and light parameters to shader constants void SetShaderConstants(const Matrix4x4& world, const Matrix4x4& view, const Matrix4x4& proj) { D3D11_MAPPED_SUBRESOURCE mappedResource; HRESULT hr = g_D3DDevCtx->Map(g_ConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if (SUCCEEDED(hr)) { ShaderConstants *cst = (ShaderConstants *)mappedResource.pData; cst->WorldViewProj = world * view * proj; cst->WorldNorm = world; cst->LightDir = (1.0f / Length(g_LightDir)) * g_LightDir; cst->LightCoeff = 0.85f; g_D3DDevCtx->Unmap(g_ConstantBuffer, 0); } g_D3DDevCtx->VSSetConstantBuffers(0, 1, &g_ConstantBuffer); } // Render a frame void Render() { // Clear the back buffer g_D3DDevCtx->ClearRenderTargetView(g_RenderTargetView, g_BackgroundColor); g_D3DDevCtx->ClearDepthStencilView(g_DepthStencilView, D3D11_CLEAR_DEPTH, 1, 0); // Set world/view/proj matrices and global shader constants float aspectRatio = (float)g_DepthStencilDesc.Width / g_DepthStencilDesc.Height; Matrix4x4 proj = Projection(FLOAT_PI/4, aspectRatio, 0.1f, 100.0f); float dist = g_CamDistance + 0.4f; Vector3 camPosInv = { dist * 0.3f, dist * 0.0f, dist * 2.0f }; Matrix4x4 view = Translation(camPosInv); Matrix4x4 world = QuaternionToMatrix(g_SpongeRotation); SetShaderConstants(world, view, proj); // Draw the sponge DrawSponge(); // Draw tweak bars TwDraw(); // Present the information rendered in the back buffer to the front buffer (the screen) g_SwapChain->Present(0, 0); } // Rotating sponge void Anim() { static DWORD s_PrevTick = GetTickCount(); DWORD tick = GetTickCount(); // msec float dt = float(tick - s_PrevTick) / 1000.0f; // sec if (g_Animate && dt > 0 && dt < 0.2f) { Vector3 axis = Vector3::ZERO; float angle = 0; AxisAngleFromRotation(axis, angle, g_SpongeRotation); if (Length(axis) < 1.0e-6f) axis.v[1] = 1; angle += g_AnimationSpeed * dt; if (angle >= 2.0f*FLOAT_PI) angle -= 2.0f*FLOAT_PI; else if (angle <= 0) angle += 2.0f*FLOAT_PI; g_SpongeRotation = RotationFromAxisAngle(axis, angle); } s_PrevTick = tick; }