This example demonstrates the use of callbacks for handling user input. It renders two objects: A rotating cube and a billboard which shows a 2D mouse cursor.
The billboard mouse cursor replaces the native mouse cursor whenever the mouse position is over the visible part of the target window.
The cube can be rotated by moving the mouse when the left mouse button is pressed. For this purpose, the mouse_motion callback is used, which reports raw, high precision relative mouse movement to the application.
Here is the vertex shader for rendering the cube (cube.vsh)
// Copyright Florian Winter 2010 - 2010. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // Per-scene constants cbuffer PerScene { // Model-view-projection matric float4x4 model_view_projection_matrix; } struct Output { float4 position : SV_POSITION; float4 world_position : WORLDPOS; }; // Main shader function Output main(float4 position : POSITION) { // Transform position using model-view-projection matric Output output; output.position = mul(position, model_view_projection_matrix); output.world_position = position; return output; }
Here is the pixel shader for rendering the cube (cube.psh)
// Copyright Florian Winter 2010 - 2010. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) float4 main(float4 position : SV_POSITION, float4 world_position : WORLDPOS) : SV_Target { return world_position; }
Here is the vertex shader for rendering the billboard (billboard.vsh)
// Copyright Florian Winter 2010 - 2010. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // Per-scene constants cbuffer PerScene { // Model-view-projection matric float4x4 model_view_projection_matrix; } struct Output { float4 position : SV_POSITION; float4 model_position : MODELPOS; }; // Main shader function Output main(float4 position : POSITION) { // Transform position using model-view-projection matric Output output; output.position = mul(position, model_view_projection_matrix); output.model_position = position / 16; return output; }
Here is the pixel shader for rendering the billboard (billboard.psh)
// Copyright Florian Winter 2010 - 2010. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // This shader draws a mouse cursor as a simple transparent star pattern. float4 main(float4 position : SV_POSITION, float4 model_position : MODELPOS) : SV_Target { // Get polar coordinates float angle = atan2(model_position.y, model_position.x); float distance = length(model_position); // Compute color float factor = 0.5 + 0.25 * cos(angle * 5); return clamp(1 - distance, 0, 1) * factor; }
Here is the source code:
// Copyright Florian Winter 2010 - 2010. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include <tk11/tk11.hpp> #include <boost/bind.hpp> #include <D3DX10Math.h> using namespace tk11; // 2D mouse cursor. class Billboard { public: // Object initialization. // Here we create resources, such as textures, buffers and shaders. void init(ID3D11Device_ptr device) { // Load shaders vertex_shader_code = load_compiled_shader("billboard.Vso"); pixel_shader_code = load_compiled_shader("billboard.Pso"); // Create vertex buffer Vertex vertices[4]; float w = 16; float h = 16; vertices[0].position = D3DXVECTOR2(-w, h); vertices[1].position = D3DXVECTOR2(-w, -h); vertices[2].position = D3DXVECTOR2(w, h); vertices[3].position = D3DXVECTOR2(w, -h); vertex_buffer = create_vertex_buffer(device, vertices, 4); // Set up input element descriptions D3D11_INPUT_ELEMENT_DESC elements[1] = { make_vector_input_element<float, 2>("POSITION") }; // Create input layout input_layout = create_input_layout(device, elements, 1, vertex_shader_code); // Create vertex shader vertex_shader = create_vertex_shader(device, vertex_shader_code); // Create vertex shader constant buffer vertex_shader_cbuffer = create_typed_constant_buffer<VS_Constants>(device); // Create pixel shader pixel_shader = create_pixel_shader(device, pixel_shader_code); // Fill in blend state description D3D11_BLEND_DESC desc; desc.AlphaToCoverageEnable = FALSE; desc.IndependentBlendEnable = FALSE; desc.RenderTarget[0].BlendEnable = TRUE; desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE; desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; // Create blend state blend_state = create_blend_state(device, desc); } // Mouse pointer movement void mouse_move(int x, int y) { // Setup model matrix D3DXMatrixTranslation(&model_view_matrix, FLOAT(x), FLOAT(y), 0); } // Rendering. // Here we render the object to the main render target (the back buffer) void render(ID3D11DeviceContext_ptr context, const D3DXMATRIX& projection_matrix) { // Compute model-view-projection matrix // by coposing model-view matrix and projection matrix VS_Constants vs_constants; D3DXMatrixMultiply(&vs_constants.model_view_projection_matrix, &model_view_matrix, &projection_matrix); // A D3DXMATRIX must be transposed before it can be used by a shader D3DXMatrixTranspose(&vs_constants.model_view_projection_matrix, &vs_constants.model_view_projection_matrix); // Update vertex shader constant buffer typed_update_constant_buffer(context, vertex_shader_cbuffer, vs_constants); // Bind shaders context->VSSetShader(vertex_shader.get(), 0, 0); context->PSSetShader(pixel_shader.get(), 0, 0); // Bind constant buffers ID3D11Buffer* vs_cbuffers[1] = { vertex_shader_cbuffer.get() }; context->VSSetConstantBuffers(0, 1, vs_cbuffers); // Bind vertex buffer ID3D11Buffer* vb_buffers[1] = { vertex_buffer.get() }; UINT vb_strides[1] = { sizeof(Vertex) }; UINT vb_offsets[1] = { 0 }; context->IASetVertexBuffers(0, 1, vb_buffers, vb_strides, vb_offsets); // Bind input layout context->IASetInputLayout(input_layout.get()); // Set primitive topology context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); // Set blend state FLOAT blend_factors[4] = { 0, 0, 0, 0 }; context->OMSetBlendState(blend_state.get(), blend_factors, 0xffffffff); // Draw two triangle strips context->Draw(4, 0); } private: // Vertex structure struct Vertex { // Position D3DXVECTOR2 position; }; // Constants of the vertex shader struct VS_Constants { // Transformation matrix D3DXMATRIX model_view_projection_matrix; }; // Model matrix D3DXMATRIX model_view_matrix; // Vertex shader code ID3D10Blob_ptr vertex_shader_code; // Pixel shader code ID3D10Blob_ptr pixel_shader_code; // Vertex buffer ID3D11Buffer_ptr vertex_buffer; // Input layout ID3D11InputLayout_ptr input_layout; // Vertex shader ID3D11VertexShader_ptr vertex_shader; // Vertex shader constant buffer ID3D11Buffer_ptr vertex_shader_cbuffer; // Pixel shader ID3D11PixelShader_ptr pixel_shader; // Blend state ID3D11BlendState_ptr blend_state; }; // Rotating cube class Cube { public: Cube() : angle1(0) , angle2(0) , torque1(0) , torque2(0) , view_distance(5) , last_time(seconds(0)) {} // Object initialization. // Here we create resources, such as textures, buffers and shaders. void init(ID3D11Device_ptr device) { // Load shaders vertex_shader_code = load_compiled_shader("cube.Vso"); pixel_shader_code = load_compiled_shader("cube.Pso"); // Create vertex buffer Vertex vertices[8]; vertices[0].position = D3DXVECTOR3(-1, -1, -1); vertices[1].position = D3DXVECTOR3(1, -1, -1); vertices[2].position = D3DXVECTOR3(-1, 1, -1); vertices[3].position = D3DXVECTOR3(1, 1, -1); vertices[4].position = D3DXVECTOR3(-1, -1, 1); vertices[5].position = D3DXVECTOR3(1, -1, 1); vertices[6].position = D3DXVECTOR3(-1, 1, 1); vertices[7].position = D3DXVECTOR3(1, 1, 1); vertex_buffer = create_vertex_buffer(device, vertices, 8); // Create index buffer int indices[16] = { 4, 6, 0, 2, 1, 3, 5, 7, 3, 2, 7, 6, 5, 4, 1, 0 }; index_buffer = create_index_buffer(device, indices, 16); // Set up input element descriptions D3D11_INPUT_ELEMENT_DESC elements[1] = { make_vector_input_element<float, 3>("POSITION") }; // Create input layout input_layout = create_input_layout(device, elements, 1, vertex_shader_code); // Create vertex shader vertex_shader = create_vertex_shader(device, vertex_shader_code); // Create vertex shader constant buffer vertex_shader_cbuffer = create_typed_constant_buffer<VS_Constants>(device); // Create pixel shader pixel_shader = create_pixel_shader(device, pixel_shader_code); } // Object update. // Here we update all variables that change over time. void update(const Duration& elapsed_time) { // Get relative elapsed time Duration delta_time(elapsed_time - last_time); last_time = elapsed_time; // Get relative elapsed time in seconds float t = time_div_float<float>(delta_time, seconds(1)); // Update rotation angle1 += torque1; angle2 += torque2; torque1 *= powf(0.3f, t); torque2 *= powf(0.3f, t); // Normalize angles to keep them numerically stable const float pi = 3.1415926535f; angle1 = fmodf(angle1, 2 * pi); angle2 = fmodf(angle2, 2 * pi); // Update rotation matrix update_matrix(); } // Relative high-precision mouse motion. void mouse_motion(int delta_x, int delta_y) { const float pi = 3.1415926535f; // Update angles based on mouse motion torque1 += float(delta_x) * 2 * pi * 0.0001f; torque2 += float(delta_y) * 2 * pi * 0.0001f; } // Mouse wheel has been turned void mouse_wheel(int delta, int x, int y) { // Adjust view distance view_distance *= powf(1.1f, float(delta)); } // Rendering. // Here we render the object to the main render target (the back buffer) void render(ID3D11DeviceContext_ptr context, const D3DXMATRIX& projection_matrix) { // Compute model-view-projection matrix // by coposing model-view matrix and projection matrix VS_Constants vs_constants; D3DXMatrixMultiply(&vs_constants.model_view_projection_matrix, &model_view_matrix, &projection_matrix); // A D3DXMATRIX must be transposed before it can be used by a shader D3DXMatrixTranspose(&vs_constants.model_view_projection_matrix, &vs_constants.model_view_projection_matrix); // Update vertex shader constant buffer typed_update_constant_buffer(context, vertex_shader_cbuffer, vs_constants); // Bind shaders context->VSSetShader(vertex_shader.get(), 0, 0); context->PSSetShader(pixel_shader.get(), 0, 0); // Bind constant buffers ID3D11Buffer* vs_cbuffers[1] = { vertex_shader_cbuffer.get() }; context->VSSetConstantBuffers(0, 1, vs_cbuffers); // Bind vertex buffer ID3D11Buffer* vb_buffers[1] = { vertex_buffer.get() }; UINT vb_strides[1] = { sizeof(Vertex) }; UINT vb_offsets[1] = { 0 }; context->IASetVertexBuffers(0, 1, vb_buffers, vb_strides, vb_offsets); // Bind index buffer context->IASetIndexBuffer(index_buffer.get(), DXGI_FORMAT_R32_UINT, 0); // Bind input layout context->IASetInputLayout(input_layout.get()); // Set primitive topology context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); // Draw two triangle strips context->DrawIndexed(8, 0, 0); context->DrawIndexed(8, 8, 0); } private: // A vertex of the cube struct Vertex { // Position D3DXVECTOR3 position; }; // Constants of the vertex shader struct VS_Constants { // Transformation matrix D3DXMATRIX model_view_projection_matrix; }; // Model-view matrix D3DXMATRIX model_view_matrix; // Vertex shader code ID3D10Blob_ptr vertex_shader_code; // Pixel shader code ID3D10Blob_ptr pixel_shader_code; // Vertex buffer ID3D11Buffer_ptr vertex_buffer; // Index buffer ID3D11Buffer_ptr index_buffer; // Input layout ID3D11InputLayout_ptr input_layout; // Vertex shader ID3D11VertexShader_ptr vertex_shader; // Vertex shader constant buffer ID3D11Buffer_ptr vertex_shader_cbuffer; // Pixel shader ID3D11PixelShader_ptr pixel_shader; // Orientation angles of cube float angle1; float angle2; // Momentum float torque1; float torque2; // Distance of the viewer to the object float view_distance; // Last update time Duration last_time; // Update the model-view matrix based on current parameters void update_matrix() { // Setup camera matrix D3DXMATRIX view_matrix; D3DXVECTOR3 eye(0, 0, view_distance); D3DXVECTOR3 at(0, 0, 0); D3DXVECTOR3 up(0, 1, 0); D3DXMatrixLookAtLH(&view_matrix, &eye, &at, &up); // Setup model matrix D3DXMATRIX model_matrix; D3DXMATRIX rot1; D3DXMATRIX rot2; D3DXVECTOR3 axis1(1,0,0); D3DXVECTOR3 axis2(0,1,0); D3DXMatrixRotationAxis(&rot1, &axis1, angle1); D3DXMatrixRotationAxis(&rot2, &axis2, angle2); D3DXMatrixMultiply(&model_matrix, &rot1, &rot2); // Combine model and view matrix D3DXMatrixMultiply(&model_view_matrix, &model_matrix, &view_matrix); } }; // Application class // Holds all resources owned by the application and makes them accessible by // callbacks. class Application { public: // Initialize and run application // Creates the framework and the window, then starts the main loop. Application() : window(framework, get_windows_parameters()) , cursor_in_window(false) , left_button_down(false) { framework.run(); } private: // Perspective projection matrix. // This projection matrix is used for drawing 3D objects. D3DXMATRIX perspective_matrix; // Orthogonal projection matrix // This projection matrix is used for drawing HUD objects. D3DXMATRIX ortho_matrix; // Rotating cube object Cube cube; // Billboard object Billboard billboard; // The Tk11 framework Framework framework; // The main rendering window Window window; // Mouse cursor is inside window bool cursor_in_window; // Left mouse button is down bool left_button_down; // Resize callback. // Here we update all variables which depend on the size of the target window, // such as projection matrices and viewport parameters. void resize(unsigned int x_resolution, unsigned int y_resolution, bool fullscreen, ID3D11Device_ptr device, IDXGISwapChain_ptr swap_chain, ID3D11Texture2D_ptr back_buffer) { // Setup projection matrix float aspect = float(x_resolution) / float(y_resolution); float fovy = 3.1415926535f / 3; float znear = 2; float zfar = 100; D3DXMatrixPerspectiveFovLH(&perspective_matrix, fovy, aspect, znear, zfar); // Prepare orthogonal projection matrix D3DXMatrixOrthoOffCenterLH(&ortho_matrix, 0, FLOAT(x_resolution), FLOAT(y_resolution), 0, 0, 1); } // Scene initialization callback. // Here we create resources, such as textures, buffers and shaders. void init(ID3D11Device_ptr device) { // Initialize objects cube.init(device); billboard.init(device); } // Scene update callback. // Here we update all variables that change over time. void update(const Duration& elapsed_time) { // Update objects cube.update(elapsed_time); } // Scene rendering callback. // Here we render the scene to the main render target (the back buffer) void render(ID3D11DeviceContext_ptr context, ID3D11RenderTargetView_ptr render_target) { // Clear render target view float clear_color_rgba[] = { 0, 0, 0, 0 }; context->ClearRenderTargetView(render_target.get(), clear_color_rgba); // Render 3D objects cube.render(context, perspective_matrix); // Render the cursor if it is inside the window, // and the left button is not down. if(cursor_in_window && !left_button_down) { billboard.render(context, ortho_matrix); } } // Mouse pointer movement callback. // This callback is invoked when the mouse pointer has been moved over the // target window. void mouse_move(int x, int y) { // Update the position of the billboard which shows the mouse cursor billboard.mouse_move(x, y); } // Callback: Mouse pointer has entered the window. void mouse_enter() { // Rendered cursor is now visible cursor_in_window = true; } // Callback: Mouse pointer has left the window. void mouse_leave() { // Rendered cursor is now invisible cursor_in_window = false; } // Callback: A mouse button has been pressed. void mouse_down(Mouse_Button button, int x, int y) { std::cout << "down button=" << button << ", x=" << x << ", y=" << y << std::endl; // Update left button state. // If the left burron is down, raw mouse motion rotates the cube. if(button == left_button) { left_button_down = true; } } // Callback: A mouse button has been released. void mouse_up(Mouse_Button button, int x, int y) { std::cout << "up button=" << button << ", x=" << x << ", y=" << y << std::endl; // Update left button state. // If the left burron is down, raw mouse motion rotates the cube. if(button == left_button) { left_button_down = false; } } // Callback: Mouse wheel has been turned void mouse_wheel(int delta, int x, int y) { // Forward event to the cube object. // The mouse wheel controls the view distance. cube.mouse_wheel(delta, x, y); } // Callback: Relative high-precision mouse motion. void mouse_motion(int delta_x, int delta_y) { // If left button is down, forward the event to the cube to rotate it. if(left_button_down) { cube.mouse_motion(delta_x, delta_y); } } // Callback: A key has been pressed void key_down(unsigned int key) { std::cout << "DOWN: " << key << std::endl; // Handle some keys if(key == VK_ESCAPE) { // Quit application framework.quit(); } } // Callback: A key has been released void key_up(unsigned int key) { std::cout << "UP: " << key << std::endl; } // Callback: A key has been repeated void key_repeat(unsigned int key) { std::cout << "REPEAT: " << key << std::endl; } // Callback: A character has been tyed void char_input(unsigned int char_) { std::wcout << static_cast<wchar_t>(char_) << std::endl; } // Get window parameters // Returns a Window_Parameters structure which defines all parameters for // creating the Direct3D window, including callbacks. Window_Parameters get_windows_parameters() { using boost::bind; // Framework parameters Window_Parameters parameters; // Set windowed mode with 800x600 resolution parameters.x_resolution = 800; parameters.y_resolution = 600; parameters.fullscreen = false; // Hide the Windows mouse cursor when it is over the window. // We want to render our own cursor. parameters.cursor_mode = hide_cursor; // Window and rendering callbacks parameters.resize_callback = bind(&Application::resize, this, _1, _2, _3, _4, _5, _6); parameters.init_callback = bind(&Application::init, this, _1); parameters.update_callback = bind(&Application::update, this, _1); parameters.render_callback = bind(&Application::render, this, _1, _2); // Mouse calbacks parameters.mouse_move_callback = bind(&Application::mouse_move, this, _1, _2); parameters.mouse_enter_callback = bind(&Application::mouse_enter, this); parameters.mouse_leave_callback = bind(&Application::mouse_leave, this); parameters.mouse_down_callback = bind(&Application::mouse_down, this, _1, _2, _3); parameters.mouse_up_callback = bind(&Application::mouse_up, this, _1, _2, _3); parameters.mouse_wheel_callback = bind(&Application::mouse_wheel, this, _1, _2, _3); parameters.mouse_motion_callback = bind(&Application::mouse_motion, this, _1, _2); // Keyboard callbacks parameters.key_down_callback = bind(&Application::key_down, this, _1); parameters.key_up_callback = bind(&Application::key_up, this, _1); parameters.key_repeat_callback = bind(&Application::key_repeat, this, _1); parameters.char_input_callback = bind(&Application::char_input, this, _1); return parameters; } }; int main() { try { // Crate and run application Application app; return 0; } catch(const boost::exception& e) { // Show an error dialog with diagnostic information about the exception // which occurred. // (Only for debugging. In a real application, you may want to translate // exceptions into more readable error messages). show_error_dialog(diagnostic_information(e)); return 1; } catch(const std::exception& e) { // Show an error dialog with the name of the exception. // (Only for debugging. In a real application, you may want to translate // exceptions into more readable error messages). show_error_dialog(e.what()); return 1; } catch(...) { // Show an error dialog. // (Only for debugging. In a real application, you may want to translate // exceptions into more readable error messages). show_error_dialog("Unknown exception"); return 1; } }