private void SetupOutlineRenderingStep() { _solidColorEffectWithOutline = new SolidColorEffect(); _solidColorEffectWithOutline.Color = Colors.Orange.ToColor4(); _solidColorEffectWithOutline.OverrideModelColor = true; _solidColorEffectWithOutline.OutlineThickness = 0; _solidColorEffectWithOutline.WriteMaxDepthValue = false; _solidColorEffectWithOutline.InitializeResources(MainDXViewportView.DXScene.DXDevice); _disposables.Add(_solidColorEffectWithOutline); _backgroundColor = new Color4(1, 1, 1, 0); var stencilSetToOneDescription = new DepthStencilStateDescription() { IsDepthEnabled = true, DepthWriteMask = DepthWriteMask.All, DepthComparison = Comparison.LessEqual, IsStencilEnabled = true, StencilReadMask = 0xFF, StencilWriteMask = 0xFF, FrontFace = { Comparison = Comparison.Always, DepthFailOperation = StencilOperation.Keep, FailOperation = StencilOperation.Keep, PassOperation = StencilOperation.Replace // The value that is set as reference is set in the ContextStateManger when _deviceContext.OutputMerger.SetDepthStencilState is called - the value is set to 1. } }; stencilSetToOneDescription.BackFace = stencilSetToOneDescription.FrontFace; _stencilSetToOneDepthStencilState = new DepthStencilState(MainDXViewportView.DXScene.Device, stencilSetToOneDescription); if (MainDXViewportView.DXScene.DXDevice.IsDebugDevice) { _stencilSetToOneDepthStencilState.DebugName = "StencilSetToOneDepthStencilState"; } _disposables.Add(_stencilSetToOneDepthStencilState); var renderWhenStencilIsNotOneDescription = new DepthStencilStateDescription() { IsDepthEnabled = true, DepthWriteMask = DepthWriteMask.All, DepthComparison = Comparison.LessEqual, IsStencilEnabled = true, StencilReadMask = 0xFF, StencilWriteMask = 0xFF, FrontFace = { Comparison = Comparison.Greater, // render only when 1 is greater then stencil value DepthFailOperation = StencilOperation.Keep, FailOperation = StencilOperation.Keep, PassOperation = StencilOperation.Keep } }; renderWhenStencilIsNotOneDescription.BackFace = stencilSetToOneDescription.FrontFace; _renderWhenStencilIsNotOneState = new DepthStencilState(MainDXViewportView.DXScene.Device, renderWhenStencilIsNotOneDescription); if (MainDXViewportView.DXScene.DXDevice.IsDebugDevice) { _renderWhenStencilIsNotOneState.DebugName = "RenderWhenStencilIsNotOneState"; } _disposables.Add(_renderWhenStencilIsNotOneState); var renderingStepsGroup = new RenderingStepsGroup("Render outline group", MainDXViewportView.DXScene.RenderingSteps); renderingStepsGroup.BeforeRunningStep += (object sender, DirectX.RenderingEventArgs args) => { var renderingContext = args.RenderingContext; // Set new back buffer where we will render outline objects SetupOutlineBackBuffers(renderingContext); // Set new DepthStencilState that will also set stencil value to 1 for each rendered pixel renderingContext.ContextStatesManager.DepthStencilState = _stencilSetToOneDepthStencilState; _addOutlineRenderingStep.SourceTexture = _outlineShaderResourceView; }; renderingStepsGroup.AfterRunningStep += (object sender, DirectX.RenderingEventArgs args) => { var renderingContext = args.RenderingContext; // Reset the saved back buffer RestoreBackBuffers(renderingContext); }; // The first step in rendering outlines is to render selected objects with the selected solid color. // This is done with creating a custom RenderObjectsRenderingStep // and overriding the OverrideEffect to use SolidColorEffect and _stencilSetToOneDepthStencilState. // We also render only the selected objects - this is done with using FilterObjectsFunction. var renderOutlinesObjectsRenderingStep = new RenderObjectsRenderingStep("Render outlined objects") { OverrideEffect = _solidColorEffectWithOutline, OverrideDepthStencilState = _stencilSetToOneDepthStencilState, FilterObjectsFunction = delegate(RenderablePrimitiveBase objectToRender) { // IMPORTANT: // This delegate is highly performance critical because it is called for each object in each frame. // Therefore do not access any WPF's DependencyProperties there. var wpfGeometryModel3DNode = objectToRender.OriginalObject as WpfGeometryModel3DNode; if (wpfGeometryModel3DNode == null) { return(false); } // NOTE: // Here we do a simple check for object name in a List<string>. // If you have a lot of selected objects, use HashSet<string> instead. // // The reason why the check is done by the object name is that this way // we can simply connect the WPF object (that is selected) and the SceneNode object that is created from the WPF object. // So if we define the name for the WPF objects, then the SceneNode objects that are created from them will also have the same name. // // But if it is not possible to name WPF objects of if you have duplicate names, // then you will need to store SceneNode objects in HashSet instead of object names. // // To use this we need to get the SceneNode instances that are created from WPF objects. // One option is to call MainDXViewportView.GetSceneNodeForWpfObject(wpfObject) for each WPF object (this must be called after the SceneNodes are initialized - for example in DXSceneInitialized). // Another option is to wait until SceneNodes are created from WPF objects (in DXSceneInitialized event handler or if the scene was already created after calling MainDXViewportView.Update()). // Then go through all SceneNodes in the hierarchy (you can use MainDXViewportView.DXScene.RootNode.ForEachChildNode method) // and for each WpfGeometryModel3DNode gets its GeometryModel3D and build a Dictionary with WPF (GeometryModel3D) and SceneNode (WpfGeometryModel3DNode) objects. // Then when you select for which WPF objects you want to draw outline, create a HashSet<WpfGeometryModel3DNode> with list of WpfGeometryModel3DNode objects. return(_selectedObjectNames.Contains(wpfGeometryModel3DNode.Name)); } }; renderingStepsGroup.Children.Add(renderOutlinesObjectsRenderingStep); // Add two ExpandPostProcess that will make the outline bigger int outlineWidth = (int)OutlineSizeSlider.Value; _horizontalExpandPostProcess = new Ab3d.DirectX.PostProcessing.ExpandPostProcess(isVerticalRenderingPass: false, expansionWidth: outlineWidth, backgroundColor: _backgroundColor); _verticalExpandPostProcess = new Ab3d.DirectX.PostProcessing.ExpandPostProcess(isVerticalRenderingPass: true, expansionWidth: outlineWidth, backgroundColor: _backgroundColor); _disposables.Add(_horizontalExpandPostProcess); _disposables.Add(_verticalExpandPostProcess); var expandPostProcesses = new List <PostProcessBase>(); expandPostProcesses.Add(_horizontalExpandPostProcess); expandPostProcesses.Add(_verticalExpandPostProcess); // We could also blur the outline to make it bigger, but Expand creates better results //var horizontalBlurPostProcess = new Ab3d.DirectX.PostProcessing.SimpleBlurPostProcess(isVerticalBlur: false, filterWidth: 5); //var verticalBlurPostProcess = new Ab3d.DirectX.PostProcessing.SimpleBlurPostProcess(isVerticalBlur: true, filterWidth: 5); //horizontalBlurPostProcess.InitializeResources(MainDXViewportView.DXScene.DXDevice); //verticalBlurPostProcess.InitializeResources(MainDXViewportView.DXScene.DXDevice); //blurPostProcesses.Add(horizontalBlurPostProcess); //blurPostProcesses.Add(verticalBlurPostProcess); _expandObjectsPostProcessesRenderingSteps = new RenderPostProcessingRenderingStep("Expand objects rendering step", expandPostProcesses); renderingStepsGroup.Children.Add(_expandObjectsPostProcessesRenderingSteps); MainDXViewportView.DXScene.RenderingSteps.AddAfter(MainDXViewportView.DXScene.DefaultInitializeRenderingStep, renderingStepsGroup); _addOutlineRenderingStep = new RenderTextureRenderingStep(RenderTextureRenderingStep.TextureChannelsCount.FourChannels, "Render outline over 3D scene") { Offsets = new Vector4(0, 0, 0, 0), // preserve original colors Factors = new Vector4(1, 1, 1, 1), TargetViewport = new ViewportF(0, 0, 1f, 1f), // render to full screen CustomBlendState = MainDXViewportView.DXScene.DXDevice.CommonStates.NonPremultipliedAlphaBlend, // alpha blend CustomDepthStencilState = _renderWhenStencilIsNotOneState, // only render when stencil value is less then 1 (not where the objects are rendered) }; _addOutlineRenderingStep.BeforeRunningStep += delegate(object sender, DirectX.RenderingEventArgs args) { var renderingContext = args.RenderingContext; renderingContext.SetBackBuffer(renderingContext.CurrentBackBuffer, renderingContext.CurrentBackBufferDescription, renderingContext.CurrentRenderTargetView, _outlineDepthStencilView, false); //renderingContext.DeviceContext.OutputMerger.SetTargets(_outlineDepthStencilView, renderingContext.CurrentRenderTargetView); }; MainDXViewportView.DXScene.RenderingSteps.AddBefore(MainDXViewportView.DXScene.DefaultCompleteRenderingStep, _addOutlineRenderingStep); }
private void UpdatePostProcesses() { if (MainDXViewportView.DXScene == null) // If not yet initialized or if using WPF 3D { return; } // Because we have created the PostProcess objects here, we also need to dipose them DisposeCreatedPostProcesses(); MainDXViewportView.DXScene.PostProcesses.Clear(); if (ToonCheckBox.IsChecked ?? false) { var toonShadingPostProcess = new ToonShadingPostProcess(); MainDXViewportView.DXScene.PostProcesses.Add(toonShadingPostProcess); _createdPostProcesses.Add(toonShadingPostProcess); } if (BlackAndWhiteCheckBox.IsChecked ?? false) { var blackAndWhitePostProcess = new BlackAndWhitePostProcess(); MainDXViewportView.DXScene.PostProcesses.Add(blackAndWhitePostProcess); _createdPostProcesses.Add(blackAndWhitePostProcess); } if (SimpleBlurCheckBox.IsChecked ?? false) { // Blur is done in two passes - one horizontal and one vertical _simpleBlurHorizontalBlurPostProcess = new Ab3d.DirectX.PostProcessing.SimpleBlurPostProcess(isVerticalBlur: false, filterWidth: 5); _simpleBlurVerticalBlurPostProcess = new Ab3d.DirectX.PostProcessing.SimpleBlurPostProcess(isVerticalBlur: true, filterWidth: 5); UpdateSimpleBlurParameters(); MainDXViewportView.DXScene.PostProcesses.Add(_simpleBlurHorizontalBlurPostProcess); MainDXViewportView.DXScene.PostProcesses.Add(_simpleBlurVerticalBlurPostProcess); _createdPostProcesses.Add(_simpleBlurHorizontalBlurPostProcess); _createdPostProcesses.Add(_simpleBlurVerticalBlurPostProcess); } if (GaussianBlurCheckBox.IsChecked ?? false) { // Blur is done in two passes - one horizontal and one vertical _gaussianHorizontalBlurPostProcess = new Ab3d.DirectX.PostProcessing.GaussianBlurPostProcess(isVerticalBlur: false, blurStandardDeviation: 2.0f); _gaussianVerticalBlurPostProcess = new Ab3d.DirectX.PostProcessing.GaussianBlurPostProcess(isVerticalBlur: true, blurStandardDeviation: 2.0f); UpdateGaussianBlurParameters(); MainDXViewportView.DXScene.PostProcesses.Add(_gaussianHorizontalBlurPostProcess); MainDXViewportView.DXScene.PostProcesses.Add(_gaussianVerticalBlurPostProcess); _createdPostProcesses.Add(_gaussianHorizontalBlurPostProcess); _createdPostProcesses.Add(_gaussianVerticalBlurPostProcess); } if (ExpandCheckBox.IsChecked ?? false) { int expansionWidth = (int)ExpansionWidthSlider.Value; var backgroundColor = MainDXViewportView.DXScene.BackgroundColor; // Note that you should not use MainDXViewportView.BackgroundColor because it is not yet alpha premultiplied - for example (1,1,1,0) is used instead of (0,0,0,0). _expandHorizontalPostProcess = new Ab3d.DirectX.PostProcessing.ExpandPostProcess(false, expansionWidth, backgroundColor); _expandVerticalPostProcess = new Ab3d.DirectX.PostProcessing.ExpandPostProcess(true, expansionWidth, backgroundColor); if (FixExpansionColorCheckBox.IsChecked ?? false) { // With Offsets and Factors we can adjust the colors of the effect. // Offsets are added to each color and then the color is multiplied by Factors. // // The following values render expansion in red color _expandHorizontalPostProcess.Offsets = new Vector4(1, 0, 0, 1); // RGBA values: add 1 to red and alpha _expandHorizontalPostProcess.Factors = new Vector4(1, 0, 0, 1); // RGBA values: set green and blur to 0 _expandVerticalPostProcess.Offsets = new Vector4(1, 0, 0, 1); _expandVerticalPostProcess.Factors = new Vector4(1, 0, 0, 1); } UpdateExpandParameters(); MainDXViewportView.DXScene.PostProcesses.Add(_expandHorizontalPostProcess); MainDXViewportView.DXScene.PostProcesses.Add(_expandVerticalPostProcess); _createdPostProcesses.Add(_expandHorizontalPostProcess); _createdPostProcesses.Add(_expandVerticalPostProcess); } if (EdgeDetectionCheckBox.IsChecked ?? false) { _edgeDetectionPostProcess = new Ab3d.DirectX.PostProcessing.SoberEdgeDetectionPostProcess(); UpdateEdgeDetectionParameters(); MainDXViewportView.DXScene.PostProcesses.Add(_edgeDetectionPostProcess); _createdPostProcesses.Add(_edgeDetectionPostProcess); } }
private void SetupExpandPostProcessOutlines() { var dxScene = MainDXViewportView.DXScene; _blackOutlineEffect = EnsureSolidColorEffect(); if (_blackOutlineEffect == null) { return; } // Reset values that may be changed when using SolidColorEffectWithOutlines _blackOutlineEffect.DepthBias = 0; _blackOutlineEffect.OverrideRasterizerState = null; _blackOutlineEffect.OutlineThickness = 0; _blackOutlineEffect.WriteMaxDepthValue = true; _renderObjectOutlinesRenderingStep = EnsureRenderObjectsRenderingStep(dxScene); if (!dxScene.RenderingSteps.Contains(_renderObjectOutlinesRenderingStep)) { dxScene.RenderingSteps.AddBefore(dxScene.DefaultRenderObjectsRenderingStep, _renderObjectOutlinesRenderingStep); } int outlineWidth = (int)OutlineWidthComboBox.SelectedItem; if (_prepareExpandObjectsPostProcessingRenderingStep == null) { // Expand post process is done in two passes (one horizontal and one vertical) _horizontalExpandPostProcess = new Ab3d.DirectX.PostProcessing.ExpandPostProcess(isVerticalRenderingPass: false, expansionWidth: outlineWidth, backgroundColor: dxScene.BackgroundColor); _verticalExpandPostProcess = new Ab3d.DirectX.PostProcessing.ExpandPostProcess(isVerticalRenderingPass: true, expansionWidth: outlineWidth, backgroundColor: dxScene.BackgroundColor); _disposables.Add(_horizontalExpandPostProcess); _disposables.Add(_verticalExpandPostProcess); var expandPostProcesses = new PostProcessBase[] { _horizontalExpandPostProcess, _verticalExpandPostProcess }; // To execute the post processes we need to rendering steps: // 1) PreparePostProcessingRenderingStep that creates required RenderTargets and ShaderResourceViews and sets that to the RenderPostProcessingRenderingStep // 2) RenderPostProcessingRenderingStep that actually executed all the post-processes // Because we will execute post-processes before the standard scene rendering, // we also need to make sure that the Destination buffer is correctly set (see _prepareExandObjectsPostProcessingRenderingStep.BeforeRunningStep) // and that the DepthStencilView is reset after the post-processes are rendered (see _expandObjectsPostProcessesRenderingSteps.AfterRunningStep). // First create the RenderPostProcessingRenderingStep because it is needed in the constructor of the PreparePostProcessingRenderingStep _expandObjectsPostProcessesRenderingSteps = new RenderPostProcessingRenderingStep("Expand objects rendering step", expandPostProcesses); _expandObjectsPostProcessesRenderingSteps.AfterRunningStep += delegate(object sender, RenderingEventArgs args) { // Post-processes are usually executed at the end of rendering process and work on 2D textures so they do not require DepthStencil. // The CurrentBackBuffer / Description and RenderTargetView are already correct because they are sent by PreparePostProcessingRenderingStep, // but we need to set the _savedDepthStencilView and SupersamplingCount. args.RenderingContext.SetBackBuffer(args.RenderingContext.CurrentBackBuffer, args.RenderingContext.CurrentBackBufferDescription, args.RenderingContext.CurrentRenderTargetView, _savedDepthStencilView, dxScene.SupersamplingCount, bindNewRenderTargetsToDeviceContext: true); }; _disposables.Add(_expandObjectsPostProcessesRenderingSteps); _prepareExpandObjectsPostProcessingRenderingStep = new PreparePostProcessingRenderingStep(_expandObjectsPostProcessesRenderingSteps, "Prepare expand post process"); _prepareExpandObjectsPostProcessingRenderingStep.BeforeRunningStep += delegate(object sender, RenderingEventArgs args) { // Because after the post-processes are executed we will continue with rendering the scene, // we need to set the DestinationBackBuffer (there will be the final result of the post-processes) // to the currently used BackBuffer. If this is not done, then RenderingContext.FinalBackBuffer is used as destination back buffer. _prepareExpandObjectsPostProcessingRenderingStep.SetCustomDestinationBackBuffer(args.RenderingContext.CurrentBackBuffer, args.RenderingContext.CurrentBackBufferDescription, args.RenderingContext.CurrentRenderTargetView); // Save CurrentDepthStencilView _savedDepthStencilView = args.RenderingContext.CurrentDepthStencilView; }; _disposables.Add(_prepareExpandObjectsPostProcessingRenderingStep); } else { _horizontalExpandPostProcess.ExpansionWidth = outlineWidth; _verticalExpandPostProcess.ExpansionWidth = outlineWidth; } if (!dxScene.RenderingSteps.Contains(_prepareExpandObjectsPostProcessingRenderingStep)) { dxScene.RenderingSteps.AddAfter(_renderObjectOutlinesRenderingStep, _prepareExpandObjectsPostProcessingRenderingStep); dxScene.RenderingSteps.AddAfter(_prepareExpandObjectsPostProcessingRenderingStep, _expandObjectsPostProcessesRenderingSteps); } }