public override void Run() { #region Create renderers // Note: the renderers take care of creating their own // device resources and listen for DeviceManager.OnInitialize var particleSystem = ToDispose(new ParticleRenderer()); particleSystem.Initialize(this); var totalParticles = 100000; particleSystem.Constants.DomainBoundsMax = new Vector3(20, 20, 20); particleSystem.Constants.DomainBoundsMin = new Vector3(-20, 0, -20); particleSystem.Constants.ForceDirection = -Vector3.UnitY; particleSystem.Constants.ForceStrength = 1.8f; particleSystem.Constants.Radius = 0.05f; float particleLifetime = 13f; particleSystem.InitializeParticles(totalParticles, particleLifetime); // Create a axis-grid renderer var axisGrid = ToDispose(new AxisGridRenderer()); axisGrid.Initialize(this); // Create and initialize the mesh renderer var loadedMesh = Common.Mesh.LoadFromFile("cartoon_village.cmo"); List <MeshRenderer> meshes = new List <MeshRenderer>(); meshes.AddRange((from mesh in loadedMesh select ToDispose(new MeshRenderer(mesh)))); foreach (var m in meshes) { m.Initialize(this); m.World = Matrix.Identity; } // Set the first animation as the current animation and start clock foreach (var m in meshes) { if (m.Mesh.Animations != null && m.Mesh.Animations.Any()) { m.CurrentAnimation = m.Mesh.Animations.First().Value; } m.Clock.Start(); } // Create and initialize a Direct2D FPS text renderer var fps = ToDispose(new Common.FpsRenderer("Calibri", Color.CornflowerBlue, new Point(8, 8), 16)); fps.Initialize(this); // Create and initialize a general purpose Direct2D text renderer // This will display some instructions and the current view and rotation offsets var textRenderer = ToDispose(new Common.TextRenderer("Calibri", Color.CornflowerBlue, new Point(8, 40), 12)); textRenderer.Initialize(this); #endregion // Initialize the world matrix var worldMatrix = Matrix.Identity; // Set the camera position slightly behind (z) var cameraPosition = new Vector3(0, 1, 20); var cameraTarget = Vector3.Zero; // Looking at the origin 0,0,0 var cameraUp = Vector3.UnitY; // Y+ is Up // Prepare matrices // Create the view matrix from our camera position, look target and up direction var viewMatrix = Matrix.LookAtRH(cameraPosition, cameraTarget, cameraUp); viewMatrix.TranslationVector += new Vector3(0, -0.98f, 0); // Create the projection matrix /* FoV 60degrees = Pi/3 radians */ // Aspect ratio (based on window size), Near clip, Far clip var projectionMatrix = Matrix.PerspectiveFovRH((float)Math.PI / 3f, Width / (float)Height, 0.1f, 100f); // Maintain the correct aspect ratio on resize Window.Resize += (s, e) => { projectionMatrix = Matrix.PerspectiveFovRH((float)Math.PI / 3f, Width / (float)Height, 0.1f, 100f); }; bool paused = false; var simTime = new System.Diagnostics.Stopwatch(); simTime.Start(); List <string> particleShaders = new List <string>(); //particleShaders.Add("CS"); particleShaders.Add("Snowfall"); particleShaders.Add("Waves"); particleShaders.Add("Sweeping"); int particleIndx = 0; #region Rotation and window event handlers // Create a rotation vector to keep track of the rotation // around each of the axes var rotation = new Vector3(0.0f, 0.0f, 0.0f); // We will call this action to update text // for the text renderer Action updateText = () => { textRenderer.Text = String.Format("Rotation ({0}) (Up/Down Left/Right Wheel+-)\nView ({1}) (A/D, W/S, Shift+Wheel+-)" //+ "\nPress 1,2,3,4,5,6,7,8 to switch shaders" + "\nTime: {2:0.00} (P to toggle, R to reset scene)" + "\nParticles = {3:#0} (+/- to change)" //+ "\nPress Z to show/hide depth buffer - Press F to toggle wireframe" //+ "\nPress 1-8 to switch shaders" , rotation, viewMatrix.TranslationVector, simTime.Elapsed.TotalSeconds, totalParticles ); }; Dictionary <Keys, bool> keyToggles = new Dictionary <Keys, bool>(); keyToggles[Keys.Z] = false; keyToggles[Keys.F] = false; keyToggles[Keys.I] = true; // Support keyboard/mouse input to rotate or move camera view var moveFactor = 0.02f; // how much to change on each keypress var shiftKey = false; var ctrlKey = false; var background = new Color(30, 30, 34); Vector2 rightMouseClick = new Vector2(0, 0); Window.KeyDown += (s, e) => { var context = DeviceManager.Direct3DContext; shiftKey = e.Shift; ctrlKey = e.Control; switch (e.KeyCode) { // WASD -> pans view case Keys.A: viewMatrix.TranslationVector += new Vector3(moveFactor * 12, 0f, 0f); break; case Keys.D: viewMatrix.TranslationVector -= new Vector3(moveFactor * 12, 0f, 0f); break; case Keys.S: if (shiftKey) { viewMatrix.TranslationVector += new Vector3(0f, moveFactor * 12, 0f); } else { viewMatrix.TranslationVector -= new Vector3(0f, 0f, 1) * moveFactor * 12; } break; case Keys.W: if (shiftKey) { viewMatrix.TranslationVector -= new Vector3(0f, moveFactor * 12, 0f); } else { viewMatrix.TranslationVector += new Vector3(0f, 0f, 1) * moveFactor * 12; } break; // Up/Down and Left/Right - rotates around X / Y respectively // (Mouse wheel rotates around Z) case Keys.Down: worldMatrix *= Matrix.RotationX(moveFactor); rotation += new Vector3(moveFactor, 0f, 0f); break; case Keys.Up: worldMatrix *= Matrix.RotationX(-moveFactor); rotation -= new Vector3(moveFactor, 0f, 0f); break; case Keys.Left: worldMatrix *= Matrix.RotationY(moveFactor); rotation += new Vector3(0f, moveFactor, 0f); break; case Keys.Right: worldMatrix *= Matrix.RotationY(-moveFactor); rotation -= new Vector3(0f, moveFactor, 0f); break; case Keys.T: fps.Show = !fps.Show; textRenderer.Show = !textRenderer.Show; break; case Keys.B: if (background == Color.White) { background = new Color(30, 30, 34); } else { background = Color.White; } break; case Keys.G: axisGrid.Show = !axisGrid.Show; break; case Keys.P: paused = !paused; if (paused) { simTime.Stop(); } else { simTime.Start(); } // Pause or resume mesh animation meshes.ForEach(m => { if (m.Clock.IsRunning) { m.Clock.Stop(); } else { m.Clock.Start(); } }); updateText(); break; case Keys.X: // To test for correct resource recreation // Simulate device reset or lost. System.Diagnostics.Debug.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()); DeviceManager.Initialize(DeviceManager.Dpi); System.Diagnostics.Debug.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()); break; case Keys.Z: keyToggles[Keys.Z] = !keyToggles[Keys.Z]; if (keyToggles[Keys.Z]) { context.PixelShader.Set(depthPixelShader); } else { context.PixelShader.Set(pixelShader); } break; case Keys.F: keyToggles[Keys.F] = !keyToggles[Keys.F]; RasterizerStateDescription rasterDesc; if (context.Rasterizer.State != null) { rasterDesc = context.Rasterizer.State.Description; } else { rasterDesc = new RasterizerStateDescription() { CullMode = CullMode.None, FillMode = FillMode.Solid } }; if (keyToggles[Keys.F]) { rasterDesc.FillMode = FillMode.Wireframe; context.Rasterizer.State = ToDispose(new RasterizerState(context.Device, rasterDesc)); } else { rasterDesc.FillMode = FillMode.Solid; context.Rasterizer.State = ToDispose(new RasterizerState(context.Device, rasterDesc)); } break; case Keys.R: // TODO: reset particles renderer if (simTime.IsRunning) { simTime.Restart(); } else { simTime.Reset(); } break; case Keys.D1: context.PixelShader.Set(pixelShader); break; case Keys.D2: context.PixelShader.Set(lambertShader); break; case Keys.D3: context.PixelShader.Set(phongShader); break; case Keys.D4: context.PixelShader.Set(blinnPhongShader); break; case Keys.Add: var increaseParticles = 10000; if (shiftKey) { increaseParticles *= 10; } totalParticles += increaseParticles; particleSystem.InitializeParticles(totalParticles, particleLifetime); updateText(); break; case Keys.Subtract: var decreaseParticles = 10000; if (shiftKey) { decreaseParticles *= 10; } totalParticles -= decreaseParticles; totalParticles = Math.Max(totalParticles, 10000); particleSystem.InitializeParticles(totalParticles, particleLifetime); updateText(); break; case Keys.Enter: particleSystem.SwitchTexture(); break; case Keys.Back: if (shiftKey) { particleSystem.UseLightenBlend = !particleSystem.UseLightenBlend; } else { particleIndx++; particleIndx = particleIndx % particleShaders.Count; } break; case Keys.I: keyToggles[Keys.I] = !keyToggles[Keys.I]; break; } updateText(); }; Window.KeyUp += (s, e) => { // Clear the shift/ctrl keys so they aren't sticky if (e.KeyCode == Keys.ShiftKey) { shiftKey = false; } if (e.KeyCode == Keys.ControlKey) { ctrlKey = false; } }; Window.MouseWheel += (s, e) => { if (shiftKey) { // Zoom in/out viewMatrix.TranslationVector += new Vector3(0f, 0f, (e.Delta / 120f) * moveFactor * 2); } else { // rotate around Z-axis viewMatrix *= Matrix.RotationZ((e.Delta / 120f) * moveFactor); rotation += new Vector3(0f, 0f, (e.Delta / 120f) * moveFactor); } updateText(); }; var lastX = 0; var lastY = 0; Window.MouseDown += (s, e) => { if (e.Button == MouseButtons.Left) { lastX = e.X; lastY = e.Y; } else if (e.Button == MouseButtons.Right) { // Move the mouse cursor coordinates into the -1 to +1 range. float pointX = ((2.0f * (float)e.X) / (float)Window.ClientSize.Width) - 1.0f; float pointY = (((2.0f * (float)e.Y) / (float)Window.ClientSize.Height) - 1.0f) * -1.0f; rightMouseClick = new Vector2(pointX, pointY); var inverseWorldViewProj = Matrix.Invert(Matrix.Multiply(worldMatrix, Matrix.Multiply(viewMatrix, projectionMatrix))); var far = Vector3.TransformCoordinate(new Vector3(rightMouseClick, 1f), inverseWorldViewProj); var near = Vector3.TransformCoordinate(new Vector3(rightMouseClick, 0f), inverseWorldViewProj); //attractor.Z = 0; particleSystem.Constants.Attractor = Vector3.Normalize(far - near) * 50; particleSystem.UpdateConstants(); } }; Window.MouseMove += (s, e) => { if (e.Button == MouseButtons.Left) { var yRotate = lastX - e.X; var xRotate = lastY - e.Y; lastY = e.Y; lastX = e.X; // Mouse move changes // Rotate view (i.e. camera) //viewMatrix *= Matrix.RotationX(xRotate * moveFactor); //viewMatrix *= Matrix.RotationY(yRotate * moveFactor); // Rotate around origin var backup = viewMatrix.TranslationVector; viewMatrix.TranslationVector = Vector3.Zero; viewMatrix *= Matrix.RotationX(xRotate * moveFactor); viewMatrix.TranslationVector = backup; worldMatrix *= Matrix.RotationY(yRotate * moveFactor); updateText(); } }; // Display instructions with initial values updateText(); #endregion var clock = new System.Diagnostics.Stopwatch(); clock.Start(); long elapsed = 0; long frames = 0; long elapsedShader = 0; float elapsedSinceGenerator = 0; #region Render loop // Create and run the render loop RenderLoop.Run(Window, () => { // Start of frame: // Retrieve immediate context var context = DeviceManager.Direct3DContext; // Clear depth stencil view context.ClearDepthStencilView(DepthStencilView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0); // Clear render target view context.ClearRenderTargetView(RenderTargetView, background); // Create viewProjection matrix var viewProjection = Matrix.Multiply(viewMatrix, projectionMatrix); // Extract camera position from view var camPosition = Matrix.Transpose(Matrix.Invert(viewMatrix)).Column4; cameraPosition = new Vector3(camPosition.X, camPosition.Y, camPosition.Z); var perFrame = new ConstantBuffers.PerFrame(); perFrame.Light.Color = new Color(1f, 1f, 1f, 1.0f); var lightDir = Vector3.Transform(new Vector3(1f, -1f, -1f), worldMatrix); perFrame.Light.Direction = new Vector3(lightDir.X, lightDir.Y, lightDir.Z);// new Vector3(Vector3.Transform(new Vector3(1f, -1f, 1f), worldMatrix * Matrix.RotationAxis(Vector3.UnitY, time)).ToArray().Take(3).ToArray()); perFrame.CameraPosition = cameraPosition; context.UpdateSubresource(ref perFrame, perFrameBuffer); // Render each object var perMaterial = new ConstantBuffers.PerMaterial(); perMaterial.Ambient = new Color4(0.2f); perMaterial.Diffuse = Color.White; perMaterial.Emissive = new Color4(0); perMaterial.Specular = Color.White; perMaterial.SpecularPower = 20f; perMaterial.HasTexture = 0; perMaterial.UVTransform = Matrix.Identity; context.UpdateSubresource(ref perMaterial, perMaterialBuffer); var perObject = new ConstantBuffers.PerObject(); // MESH if (particleIndx == 0) { foreach (var m in meshes) { perObject.World = m.World * worldMatrix; perObject.WorldInverseTranspose = Matrix.Transpose(Matrix.Invert(perObject.World)); perObject.WorldViewProjection = perObject.World * viewProjection; perObject.Transpose(); context.UpdateSubresource(ref perObject, perObjectBuffer); // Provide the material constant buffer to the mesh renderer m.PerMaterialBuffer = perMaterialBuffer; m.PerArmatureBuffer = perArmatureBuffer; m.Render(); } } // AXIS GRID using (var prevPixelShader = context.PixelShader.Get()) { perMaterial.HasTexture = 0; perMaterial.UVTransform = Matrix.Identity; context.UpdateSubresource(ref perMaterial, perMaterialBuffer); context.PixelShader.Set(pixelShader); perObject.World = worldMatrix; perObject.WorldInverseTranspose = Matrix.Transpose(Matrix.Invert(perObject.World)); perObject.WorldViewProjection = perObject.World * viewProjection; perObject.Transpose(); context.UpdateSubresource(ref perObject, perObjectBuffer); axisGrid.Render(); context.PixelShader.Set(prevPixelShader); } #region Update particle system // 1. Run Compute Shader to update particles if (simTime.IsRunning) { particleSystem.Frame.FrameTime = ((float)simTime.Elapsed.TotalSeconds - particleSystem.Frame.Time); particleSystem.Frame.Time = (float)simTime.Elapsed.TotalSeconds; particleSystem.Update("Generator", particleShaders[particleIndx]); } // 2. Render the particles clock.Restart(); particleSystem.Instanced = keyToggles[Keys.I]; particleSystem.Render(); clock.Stop(); // Keep track of how long the dispatch calls are taking elapsed += particleSystem.LastDispatchTicks; elapsedShader += clock.ElapsedTicks; frames++; // Output core particle render statistics if (frames % 250 == 0) { textRenderer.Text = string.Format("Particle: (CS:{0:F6} ms, Render:{1:F6} ms)\nCompute Shader: {4} (Backspace to cycle)\n{2} (I to toggle)\n{3} (Shift-Backspace to toggle)\nToggle texture <Enter>", (double)elapsed / (double)frames / System.Diagnostics.Stopwatch.Frequency * 1000.0, (double)elapsedShader / (double)frames / System.Diagnostics.Stopwatch.Frequency * 1000.0, keyToggles[Keys.I] ? "Instanced Vertex Shader" : "Geometry Shader", particleSystem.UseLightenBlend ? "Lighten Blend" : "Darken Blend", particleShaders[particleIndx]); } else if (frames > 250) { frames = 0; elapsed = 0; elapsedShader = 0; } #endregion // Render FPS fps.Render(); // Render instructions + position changes textRenderer.Render(); // Present the frame Present(); }); #endregion }
protected override void DoRender() { // Calculate elapsed seconds var time = clock.ElapsedMilliseconds / 1000.0f; // Retrieve device context var context = this.DeviceManager.Direct3DContext; // Calculate skin matrices for each bone ConstantBuffers.PerArmature skinMatrices = new ConstantBuffers.PerArmature(); if (mesh.Bones != null) { // Retrieve each bone's local transform for (var i = 0; i < mesh.Bones.Count; i++) { skinMatrices.Bones[i] = mesh.Bones[i].BoneLocalTransform; } // Load bone transforms from animation frames if (CurrentAnimation.HasValue) { // Keep track of the last key-frame used for each bone Mesh.Keyframe?[] lastKeyForBones = new Mesh.Keyframe?[mesh.Bones.Count]; // Keep track of whether a bone has been interpolated bool[] lerpedBones = new bool[mesh.Bones.Count]; for (var i = 0; i < CurrentAnimation.Value.Keyframes.Count; i++) { // Retrieve current key-frame var frame = CurrentAnimation.Value.Keyframes[i]; // If the current frame is not in the future if (frame.Time <= time) { // Keep track of last key-frame for bone lastKeyForBones[frame.BoneIndex] = frame; // Retrieve transform from current key-frame skinMatrices.Bones[frame.BoneIndex] = frame.Transform; } // Frame is in the future, check if we should interpolate else { // Only interpolate a bone's key-frames ONCE if (!lerpedBones[frame.BoneIndex]) { // Retrieve the previous key-frame if exists Mesh.Keyframe prevFrame; if (lastKeyForBones[frame.BoneIndex] != null) prevFrame = lastKeyForBones[frame.BoneIndex].Value; else continue; // nothing to interpolate // Make sure we only interpolate with // one future frame for this bone lerpedBones[frame.BoneIndex] = true; // Calculate time difference between frames var frameLength = frame.Time - prevFrame.Time; var timeDiff = time - prevFrame.Time; var amount = timeDiff / frameLength; // Interpolation using Lerp on scale and translation, and Slerp on Rotation (Quaternion) Vector3 t1, t2; // Translation Quaternion q1, q2;// Rotation float s1, s2; // Scale // Decompose the previous key-frame's transform prevFrame.Transform.DecomposeUniformScale(out s1, out q1, out t1); // Decompose the current key-frame's transform frame.Transform.DecomposeUniformScale(out s2, out q2, out t2); // Perform interpolation and reconstitute matrix skinMatrices.Bones[frame.BoneIndex] = Matrix.Scaling(MathUtil.Lerp(s1, s2, amount)) * Matrix.RotationQuaternion(Quaternion.Slerp(q1, q2, amount)) * Matrix.Translation(Vector3.Lerp(t1, t2, amount)); } } } } // Apply parent bone transforms // We assume here that the first bone has no parent // and that each parent bone appears before children for (var i = 1; i < mesh.Bones.Count; i++) { var bone = mesh.Bones[i]; if (bone.ParentIndex > -1) { var parentTransform = skinMatrices.Bones[bone.ParentIndex]; skinMatrices.Bones[i] = (skinMatrices.Bones[i] * parentTransform); } } // Change the bone transform from rest pose space into bone space (using the inverse of the bind/rest pose) for (var i = 0; i < mesh.Bones.Count; i++) { skinMatrices.Bones[i] = Matrix.Transpose(mesh.Bones[i].InvBindPose * skinMatrices.Bones[i]); } // Check need to loop animation if (!PlayOnce && CurrentAnimation.HasValue && CurrentAnimation.Value.EndTime <= time) { this.Clock.Restart(); } } // Update the constant buffer with the skin matrices for each bone context.UpdateSubresource(skinMatrices.Bones, PerArmatureBuffer); // Draw sub-meshes grouped by material for (var mIndx = 0; mIndx < mesh.Materials.Count; mIndx++) { // Retrieve sub meshes for this material var subMeshesForMaterial = (from sm in mesh.SubMeshes where sm.MaterialIndex == mIndx select sm).ToArray(); // If the material buffer is available and there are submeshes // using the material update the PerMaterialBuffer if (PerMaterialBuffer != null && subMeshesForMaterial.Length > 0) { // update the PerMaterialBuffer constant buffer var material = new ConstantBuffers.PerMaterial() { Ambient = new Color4(mesh.Materials[mIndx].Ambient), Diffuse = new Color4(mesh.Materials[mIndx].Diffuse), Emissive = new Color4(mesh.Materials[mIndx].Emissive), Specular = new Color4(mesh.Materials[mIndx].Specular), SpecularPower = mesh.Materials[mIndx].SpecularPower, UVTransform = mesh.Materials[mIndx].UVTransform, }; int texIndxOffset = mIndx * Common.Mesh.MaxTextures; material.HasTexture = (uint)(textureViews[texIndxOffset] != null ? 1 : 0); // 0=false material.HasNormalMap = (uint)(EnableNormalMap && textureViews[texIndxOffset+1] != null ? 1 : 0); // 0=false // Bind textures to the pixel shader context.PixelShader.SetShaderResources(0, textureViews.GetRange(texIndxOffset, Common.Mesh.MaxTextures).ToArray()); // Set texture sampler state context.PixelShader.SetSampler(0, samplerState); // Update material buffer context.UpdateSubresource(ref material, PerMaterialBuffer); } // For each sub-mesh foreach (var subMesh in subMeshesForMaterial) { // Ensure the vertex buffer and index buffers are in range if (subMesh.VertexBufferIndex < vertexBuffers.Count && subMesh.IndexBufferIndex < indexBuffers.Count) { // Retrieve and set the vertex and index buffers var vertexBuffer = vertexBuffers[(int)subMesh.VertexBufferIndex]; context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, Utilities.SizeOf<Vertex>(), 0)); context.InputAssembler.SetIndexBuffer(indexBuffers[(int)subMesh.IndexBufferIndex], Format.R16_UInt, 0); // Set topology context.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList; } // Draw the sub-mesh (includes Primitive count which we multiply by 3) // The submesh also includes a start index into the vertex buffer context.DrawIndexed((int)subMesh.PrimCount * 3, (int)subMesh.StartIndex, 0); } } // If there are no materials if (mesh.Materials.Count == 0) { foreach (var subMesh in mesh.SubMeshes) { // Ensure the vertex buffer and index buffers are in range if (subMesh.VertexBufferIndex < vertexBuffers.Count && subMesh.IndexBufferIndex < indexBuffers.Count) { // Retrieve and set the vertex and index buffers var vertexBuffer = vertexBuffers[(int)subMesh.VertexBufferIndex]; context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, Utilities.SizeOf<Vertex>(), 0)); context.InputAssembler.SetIndexBuffer(indexBuffers[(int)subMesh.IndexBufferIndex], Format.R16_UInt, 0); // Set topology context.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList; } // Draw the sub-mesh (includes Primitive count which we multiply by 3) // The submesh also includes a start index into the vertex buffer context.DrawIndexed((int)subMesh.PrimCount * 3, (int)subMesh.StartIndex, 0); } } }
protected override void DoRender() { // Calculate elapsed seconds var time = clock.ElapsedMilliseconds / 1000.0f; // Retrieve device context var context = this.DeviceManager.Direct3DContext; // Calculate skin matrices for each bone ConstantBuffers.PerArmature skinMatrices = new ConstantBuffers.PerArmature(); if (mesh.Bones != null) { // Retrieve each bone's local transform for (var i = 0; i < mesh.Bones.Count; i++) { skinMatrices.Bones[i] = mesh.Bones[i].BoneLocalTransform; } // Load bone transforms from animation frames if (CurrentAnimation.HasValue) { // Keep track of the last key-frame used for each bone Mesh.Keyframe?[] lastKeyForBones = new Mesh.Keyframe?[mesh.Bones.Count]; // Keep track of whether a bone has been interpolated bool[] lerpedBones = new bool[mesh.Bones.Count]; for (var i = 0; i < CurrentAnimation.Value.Keyframes.Count; i++) { // Retrieve current key-frame var frame = CurrentAnimation.Value.Keyframes[i]; // If the current frame is not in the future if (frame.Time <= time) { // Keep track of last key-frame for bone lastKeyForBones[frame.BoneIndex] = frame; // Retrieve transform from current key-frame skinMatrices.Bones[frame.BoneIndex] = frame.Transform; } // Frame is in the future, check if we should interpolate else { // Only interpolate a bone's key-frames ONCE if (!lerpedBones[frame.BoneIndex]) { // Retrieve the previous key-frame if exists Mesh.Keyframe prevFrame; if (lastKeyForBones[frame.BoneIndex] != null) { prevFrame = lastKeyForBones[frame.BoneIndex].Value; } else { continue; // nothing to interpolate } // Make sure we only interpolate with // one future frame for this bone lerpedBones[frame.BoneIndex] = true; // Calculate time difference between frames var frameLength = frame.Time - prevFrame.Time; var timeDiff = time - prevFrame.Time; var amount = timeDiff / frameLength; // Interpolation using Lerp on scale and translation, and Slerp on Rotation (Quaternion) Vector3 t1, t2; // Translation Quaternion q1, q2; // Rotation float s1, s2; // Scale // Decompose the previous key-frame's transform prevFrame.Transform.DecomposeUniformScale(out s1, out q1, out t1); // Decompose the current key-frame's transform frame.Transform.DecomposeUniformScale(out s2, out q2, out t2); // Perform interpolation and reconstitute matrix skinMatrices.Bones[frame.BoneIndex] = Matrix.Scaling(MathUtil.Lerp(s1, s2, amount)) * Matrix.RotationQuaternion(Quaternion.Slerp(q1, q2, amount)) * Matrix.Translation(Vector3.Lerp(t1, t2, amount)); } } } } // Apply parent bone transforms // We assume here that the first bone has no parent // and that each parent bone appears before children for (var i = 1; i < mesh.Bones.Count; i++) { var bone = mesh.Bones[i]; if (bone.ParentIndex > -1) { var parentTransform = skinMatrices.Bones[bone.ParentIndex]; skinMatrices.Bones[i] = (skinMatrices.Bones[i] * parentTransform); } } // Change the bone transform from rest pose space into bone space (using the inverse of the bind/rest pose) for (var i = 0; i < mesh.Bones.Count; i++) { skinMatrices.Bones[i] = Matrix.Transpose(mesh.Bones[i].InvBindPose * skinMatrices.Bones[i]); } // Check need to loop animation if (!PlayOnce && CurrentAnimation.HasValue && CurrentAnimation.Value.EndTime <= time) { this.Clock.Restart(); } } // Update the constant buffer with the skin matrices for each bone context.UpdateSubresource(skinMatrices.Bones, PerArmatureBuffer); // Draw sub-meshes grouped by material for (var mIndx = 0; mIndx < mesh.Materials.Count; mIndx++) { // Retrieve sub meshes for this material var subMeshesForMaterial = (from sm in mesh.SubMeshes where sm.MaterialIndex == mIndx select sm).ToArray(); // If the material buffer is available and there are submeshes // using the material update the PerMaterialBuffer if (PerMaterialBuffer != null && subMeshesForMaterial.Length > 0) { // update the PerMaterialBuffer constant buffer var material = new ConstantBuffers.PerMaterial() { Ambient = new Color4(mesh.Materials[mIndx].Ambient), Diffuse = new Color4(mesh.Materials[mIndx].Diffuse), Emissive = new Color4(mesh.Materials[mIndx].Emissive), Specular = new Color4(mesh.Materials[mIndx].Specular), SpecularPower = mesh.Materials[mIndx].SpecularPower, UVTransform = mesh.Materials[mIndx].UVTransform, }; int texIndxOffset = mIndx * Common.Mesh.MaxTextures; material.HasTexture = (uint)(textureViews[texIndxOffset] != null ? 1 : 0); // 0=false material.HasNormalMap = (uint)(EnableNormalMap && textureViews[texIndxOffset + 1] != null ? 1 : 0); // 0=false // Bind textures to the pixel shader context.PixelShader.SetShaderResources(0, textureViews.GetRange(texIndxOffset, Common.Mesh.MaxTextures).ToArray()); // Set texture sampler state context.PixelShader.SetSampler(0, samplerState); // Update material buffer context.UpdateSubresource(ref material, PerMaterialBuffer); } // For each sub-mesh foreach (var subMesh in subMeshesForMaterial) { // Ensure the vertex buffer and index buffers are in range if (subMesh.VertexBufferIndex < vertexBuffers.Count && subMesh.IndexBufferIndex < indexBuffers.Count) { // Retrieve and set the vertex and index buffers var vertexBuffer = vertexBuffers[(int)subMesh.VertexBufferIndex]; context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, Utilities.SizeOf <Vertex>(), 0)); context.InputAssembler.SetIndexBuffer(indexBuffers[(int)subMesh.IndexBufferIndex], Format.R16_UInt, 0); // Set topology context.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList; } // Draw the sub-mesh (includes Primitive count which we multiply by 3) // The submesh also includes a start index into the vertex buffer context.DrawIndexed((int)subMesh.PrimCount * 3, (int)subMesh.StartIndex, 0); } } // If there are no materials if (mesh.Materials.Count == 0) { foreach (var subMesh in mesh.SubMeshes) { // Ensure the vertex buffer and index buffers are in range if (subMesh.VertexBufferIndex < vertexBuffers.Count && subMesh.IndexBufferIndex < indexBuffers.Count) { // Retrieve and set the vertex and index buffers var vertexBuffer = vertexBuffers[(int)subMesh.VertexBufferIndex]; context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, Utilities.SizeOf <Vertex>(), 0)); context.InputAssembler.SetIndexBuffer(indexBuffers[(int)subMesh.IndexBufferIndex], Format.R16_UInt, 0); // Set topology context.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList; } // Draw the sub-mesh (includes Primitive count which we multiply by 3) // The submesh also includes a start index into the vertex buffer context.DrawIndexed((int)subMesh.PrimCount * 3, (int)subMesh.StartIndex, 0); } } }
public override void Run() { #region Create renderers // Note: the renderers take care of creating their own // device resources and listen for DeviceManager.OnInitialize var particleSystem = ToDispose(new ParticleRenderer()); particleSystem.Initialize(this); var totalParticles = 100000; particleSystem.Constants.DomainBoundsMax = new Vector3(20, 20, 20); particleSystem.Constants.DomainBoundsMin = new Vector3(-20, 0, -20); particleSystem.Constants.ForceDirection = -Vector3.UnitY; particleSystem.Constants.ForceStrength = 1.8f; particleSystem.Constants.Radius = 0.05f; float particleLifetime = 13f; particleSystem.InitializeParticles(totalParticles, particleLifetime); // Create a axis-grid renderer var axisGrid = ToDispose(new AxisGridRenderer()); axisGrid.Initialize(this); // Create and initialize the mesh renderer var loadedMesh = Common.Mesh.LoadFromFile("cartoon_village.cmo"); List<MeshRenderer> meshes = new List<MeshRenderer>(); meshes.AddRange((from mesh in loadedMesh select ToDispose(new MeshRenderer(mesh)))); foreach (var m in meshes) { m.Initialize(this); m.World = Matrix.Identity; } // Set the first animation as the current animation and start clock foreach (var m in meshes) { if (m.Mesh.Animations != null && m.Mesh.Animations.Any()) m.CurrentAnimation = m.Mesh.Animations.First().Value; m.Clock.Start(); } // Create and initialize a Direct2D FPS text renderer var fps = ToDispose(new Common.FpsRenderer("Calibri", Color.CornflowerBlue, new Point(8, 8), 16)); fps.Initialize(this); // Create and initialize a general purpose Direct2D text renderer // This will display some instructions and the current view and rotation offsets var textRenderer = ToDispose(new Common.TextRenderer("Calibri", Color.CornflowerBlue, new Point(8, 40), 12)); textRenderer.Initialize(this); #endregion // Initialize the world matrix var worldMatrix = Matrix.Identity; // Set the camera position slightly behind (z) var cameraPosition = new Vector3(0, 1, 20); var cameraTarget = Vector3.Zero; // Looking at the origin 0,0,0 var cameraUp = Vector3.UnitY; // Y+ is Up // Prepare matrices // Create the view matrix from our camera position, look target and up direction var viewMatrix = Matrix.LookAtRH(cameraPosition, cameraTarget, cameraUp); viewMatrix.TranslationVector += new Vector3(0, -0.98f, 0); // Create the projection matrix /* FoV 60degrees = Pi/3 radians */ // Aspect ratio (based on window size), Near clip, Far clip var projectionMatrix = Matrix.PerspectiveFovRH((float)Math.PI / 3f, Width / (float)Height, 0.1f, 100f); // Maintain the correct aspect ratio on resize Window.Resize += (s, e) => { projectionMatrix = Matrix.PerspectiveFovRH((float)Math.PI / 3f, Width / (float)Height, 0.1f, 100f); }; bool paused = false; var simTime = new System.Diagnostics.Stopwatch(); simTime.Start(); List<string> particleShaders = new List<string>(); //particleShaders.Add("CS"); particleShaders.Add("Snowfall"); particleShaders.Add("Waves"); particleShaders.Add("Sweeping"); int particleIndx = 0; #region Rotation and window event handlers // Create a rotation vector to keep track of the rotation // around each of the axes var rotation = new Vector3(0.0f, 0.0f, 0.0f); // We will call this action to update text // for the text renderer Action updateText = () => { textRenderer.Text = String.Format("Rotation ({0}) (Up/Down Left/Right Wheel+-)\nView ({1}) (A/D, W/S, Shift+Wheel+-)" //+ "\nPress 1,2,3,4,5,6,7,8 to switch shaders" + "\nTime: {2:0.00} (P to toggle, R to reset scene)" + "\nParticles = {3:#0} (+/- to change)" //+ "\nPress Z to show/hide depth buffer - Press F to toggle wireframe" //+ "\nPress 1-8 to switch shaders" , rotation, viewMatrix.TranslationVector, simTime.Elapsed.TotalSeconds, totalParticles ); }; Dictionary<Keys, bool> keyToggles = new Dictionary<Keys, bool>(); keyToggles[Keys.Z] = false; keyToggles[Keys.F] = false; keyToggles[Keys.I] = true; // Support keyboard/mouse input to rotate or move camera view var moveFactor = 0.02f; // how much to change on each keypress var shiftKey = false; var ctrlKey = false; var background = new Color(30, 30, 34); Vector2 rightMouseClick = new Vector2(0, 0); Window.KeyDown += (s, e) => { var context = DeviceManager.Direct3DContext; shiftKey = e.Shift; ctrlKey = e.Control; switch (e.KeyCode) { // WASD -> pans view case Keys.A: viewMatrix.TranslationVector += new Vector3(moveFactor * 12, 0f, 0f); break; case Keys.D: viewMatrix.TranslationVector -= new Vector3(moveFactor * 12, 0f, 0f); break; case Keys.S: if (shiftKey) viewMatrix.TranslationVector += new Vector3(0f, moveFactor * 12, 0f); else viewMatrix.TranslationVector -= new Vector3(0f, 0f, 1) * moveFactor * 12; break; case Keys.W: if (shiftKey) viewMatrix.TranslationVector -= new Vector3(0f, moveFactor * 12, 0f); else viewMatrix.TranslationVector += new Vector3(0f, 0f, 1) * moveFactor * 12; break; // Up/Down and Left/Right - rotates around X / Y respectively // (Mouse wheel rotates around Z) case Keys.Down: worldMatrix *= Matrix.RotationX(moveFactor); rotation += new Vector3(moveFactor, 0f, 0f); break; case Keys.Up: worldMatrix *= Matrix.RotationX(-moveFactor); rotation -= new Vector3(moveFactor, 0f, 0f); break; case Keys.Left: worldMatrix *= Matrix.RotationY(moveFactor); rotation += new Vector3(0f, moveFactor, 0f); break; case Keys.Right: worldMatrix *= Matrix.RotationY(-moveFactor); rotation -= new Vector3(0f, moveFactor, 0f); break; case Keys.T: fps.Show = !fps.Show; textRenderer.Show = !textRenderer.Show; break; case Keys.B: if (background == Color.White) { background = new Color(30, 30, 34); } else { background = Color.White; } break; case Keys.G: axisGrid.Show = !axisGrid.Show; break; case Keys.P: paused = !paused; if (paused) simTime.Stop(); else simTime.Start(); // Pause or resume mesh animation meshes.ForEach(m => { if (m.Clock.IsRunning) m.Clock.Stop(); else m.Clock.Start(); }); updateText(); break; case Keys.X: // To test for correct resource recreation // Simulate device reset or lost. System.Diagnostics.Debug.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()); DeviceManager.Initialize(DeviceManager.Dpi); System.Diagnostics.Debug.WriteLine(SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()); break; case Keys.Z: keyToggles[Keys.Z] = !keyToggles[Keys.Z]; if (keyToggles[Keys.Z]) { context.PixelShader.Set(depthPixelShader); } else { context.PixelShader.Set(pixelShader); } break; case Keys.F: keyToggles[Keys.F] = !keyToggles[Keys.F]; RasterizerStateDescription rasterDesc; if (context.Rasterizer.State != null) rasterDesc = context.Rasterizer.State.Description; else rasterDesc = new RasterizerStateDescription() { CullMode = CullMode.None, FillMode = FillMode.Solid }; if (keyToggles[Keys.F]) { rasterDesc.FillMode = FillMode.Wireframe; context.Rasterizer.State = ToDispose(new RasterizerState(context.Device, rasterDesc)); } else { rasterDesc.FillMode = FillMode.Solid; context.Rasterizer.State = ToDispose(new RasterizerState(context.Device, rasterDesc)); } break; case Keys.R: // TODO: reset particles renderer if (simTime.IsRunning) simTime.Restart(); else simTime.Reset(); break; case Keys.D1: context.PixelShader.Set(pixelShader); break; case Keys.D2: context.PixelShader.Set(lambertShader); break; case Keys.D3: context.PixelShader.Set(phongShader); break; case Keys.D4: context.PixelShader.Set(blinnPhongShader); break; case Keys.Add: var increaseParticles = 10000; if (shiftKey) increaseParticles *= 10; totalParticles += increaseParticles; particleSystem.InitializeParticles(totalParticles, particleLifetime); updateText(); break; case Keys.Subtract: var decreaseParticles = 10000; if (shiftKey) decreaseParticles *= 10; totalParticles -= decreaseParticles; totalParticles = Math.Max(totalParticles, 10000); particleSystem.InitializeParticles(totalParticles, particleLifetime); updateText(); break; case Keys.Enter: particleSystem.SwitchTexture(); break; case Keys.Back: if (shiftKey) { particleSystem.UseLightenBlend = !particleSystem.UseLightenBlend; } else { particleIndx++; particleIndx = particleIndx % particleShaders.Count; } break; case Keys.I: keyToggles[Keys.I] = !keyToggles[Keys.I]; break; } updateText(); }; Window.KeyUp += (s, e) => { // Clear the shift/ctrl keys so they aren't sticky if (e.KeyCode == Keys.ShiftKey) shiftKey = false; if (e.KeyCode == Keys.ControlKey) ctrlKey = false; }; Window.MouseWheel += (s, e) => { if (shiftKey) { // Zoom in/out viewMatrix.TranslationVector += new Vector3(0f, 0f, (e.Delta / 120f) * moveFactor * 2); } else { // rotate around Z-axis viewMatrix *= Matrix.RotationZ((e.Delta / 120f) * moveFactor); rotation += new Vector3(0f, 0f, (e.Delta / 120f) * moveFactor); } updateText(); }; var lastX = 0; var lastY = 0; Window.MouseDown += (s, e) => { if (e.Button == MouseButtons.Left) { lastX = e.X; lastY = e.Y; } else if (e.Button == MouseButtons.Right) { // Move the mouse cursor coordinates into the -1 to +1 range. float pointX = ((2.0f * (float)e.X) / (float)Window.ClientSize.Width) - 1.0f; float pointY = (((2.0f * (float)e.Y) / (float)Window.ClientSize.Height) - 1.0f) * -1.0f; rightMouseClick = new Vector2(pointX, pointY); var inverseViewProj = Matrix.Invert(Matrix.Multiply(viewMatrix, projectionMatrix)); var far = Vector3.TransformCoordinate(new Vector3(rightMouseClick, 1f), inverseViewProj); var near = Vector3.TransformCoordinate(new Vector3(rightMouseClick, 0f), inverseViewProj); //attractor.Z = 0; particleSystem.Constants.Attractor = Vector3.Normalize(far - near) * 50; particleSystem.UpdateConstants(); } }; Window.MouseMove += (s, e) => { if (e.Button == MouseButtons.Left) { var yRotate = lastX - e.X; var xRotate = lastY - e.Y; lastY = e.Y; lastX = e.X; // Mouse move changes viewMatrix *= Matrix.RotationX(-xRotate * moveFactor); viewMatrix *= Matrix.RotationY(-yRotate * moveFactor); updateText(); } }; // Display instructions with initial values updateText(); #endregion var clock = new System.Diagnostics.Stopwatch(); clock.Start(); long elapsed = 0; long frames = 0; long elapsedShader = 0; float elapsedSinceGenerator = 0; #region Render loop // Create and run the render loop RenderLoop.Run(Window, () => { // Start of frame: // Retrieve immediate context var context = DeviceManager.Direct3DContext; // Clear depth stencil view context.ClearDepthStencilView(DepthStencilView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0); // Clear render target view context.ClearRenderTargetView(RenderTargetView, background); // Create viewProjection matrix var viewProjection = Matrix.Multiply(viewMatrix, projectionMatrix); // Extract camera position from view var camPosition = Matrix.Transpose(Matrix.Invert(viewMatrix)).Column4; cameraPosition = new Vector3(camPosition.X, camPosition.Y, camPosition.Z); var perFrame = new ConstantBuffers.PerFrame(); perFrame.Light.Color = new Color(1f, 1f, 1f, 1.0f); var lightDir = Vector3.Transform(new Vector3(1f, -1f, -1f), worldMatrix); perFrame.Light.Direction = new Vector3(lightDir.X, lightDir.Y, lightDir.Z);// new Vector3(Vector3.Transform(new Vector3(1f, -1f, 1f), worldMatrix * Matrix.RotationAxis(Vector3.UnitY, time)).ToArray().Take(3).ToArray()); perFrame.CameraPosition = cameraPosition; context.UpdateSubresource(ref perFrame, perFrameBuffer); // Render each object var perMaterial = new ConstantBuffers.PerMaterial(); perMaterial.Ambient = new Color4(0.2f); perMaterial.Diffuse = Color.White; perMaterial.Emissive = new Color4(0); perMaterial.Specular = Color.White; perMaterial.SpecularPower = 20f; perMaterial.HasTexture = 0; perMaterial.UVTransform = Matrix.Identity; context.UpdateSubresource(ref perMaterial, perMaterialBuffer); var perObject = new ConstantBuffers.PerObject(); // MESH if (particleIndx == 0) { foreach (var m in meshes) { perObject.World = m.World * worldMatrix; perObject.WorldInverseTranspose = Matrix.Transpose(Matrix.Invert(perObject.World)); perObject.WorldViewProjection = perObject.World * viewProjection; perObject.Transpose(); context.UpdateSubresource(ref perObject, perObjectBuffer); // Provide the material constant buffer to the mesh renderer m.PerMaterialBuffer = perMaterialBuffer; m.PerArmatureBuffer = perArmatureBuffer; m.Render(); } } // AXIS GRID using (var prevPixelShader = context.PixelShader.Get()) { perMaterial.HasTexture = 0; perMaterial.UVTransform = Matrix.Identity; context.UpdateSubresource(ref perMaterial, perMaterialBuffer); context.PixelShader.Set(pixelShader); perObject.World = worldMatrix; perObject.WorldInverseTranspose = Matrix.Transpose(Matrix.Invert(perObject.World)); perObject.WorldViewProjection = perObject.World * viewProjection; perObject.Transpose(); context.UpdateSubresource(ref perObject, perObjectBuffer); axisGrid.Render(); context.PixelShader.Set(prevPixelShader); } #region Update particle system // 1. Run Compute Shader to update particles if (simTime.IsRunning) { particleSystem.Frame.FrameTime = ((float)simTime.Elapsed.TotalSeconds - particleSystem.Frame.Time); particleSystem.Frame.Time = (float)simTime.Elapsed.TotalSeconds; particleSystem.Update("Generator", particleShaders[particleIndx]); } // 2. Render the particles clock.Restart(); particleSystem.Instanced = keyToggles[Keys.I]; particleSystem.Render(); clock.Stop(); // Keep track of how long the dispatch calls are taking elapsed += particleSystem.LastDispatchTicks; elapsedShader += clock.ElapsedTicks; frames++; // Output core particle render statistics if (frames % 250 == 0) { textRenderer.Text = string.Format("Particle: (CS:{0:F6} ms, Render:{1:F6} ms)\nCompute Shader: {4} (Backspace to cycle)\n{2} (I to toggle)\n{3} (Shift-Backspace to toggle)\nToggle texture <Enter>", (double)elapsed / (double)frames / System.Diagnostics.Stopwatch.Frequency * 1000.0, (double)elapsedShader / (double)frames / System.Diagnostics.Stopwatch.Frequency * 1000.0, keyToggles[Keys.I] ? "Instanced Vertex Shader" : "Geometry Shader", particleSystem.UseLightenBlend ? "Lighten Blend" : "Darken Blend", particleShaders[particleIndx]); } else if (frames > 250) { frames = 0; elapsed = 0; elapsedShader = 0; } #endregion // Render FPS fps.Render(); // Render instructions + position changes textRenderer.Render(); // Present the frame Present(); }); #endregion }