本文告诉大家如何在 WPF 使用 SharpDX 做绘制,本文是入门级博客

本文是一个系列

先介绍一下 SharpDx ,一个底层封装的 DirectX 库,支持 AnyCpu ,支持 Direct3D9, Direct3D11, Direct3D12,Direct2D1。支持 win32 程序和商店程序。

环境

需要 .NET 4.5 和以上的环境才可以使用。

安装

首先安装 SharpDX 的库,需要安装下面几个库

创建工厂

使用 SharpDX 和 DirectX 一样,开始都需要创建工厂,然后创建RenderTarget,之后才可以显示基础图形。

先引用命名

using D2D = SharpDX.Direct2D1;
using WIC = SharpDX.WIC;
using DW = SharpDX.DirectWrite;
using DXGI = SharpDX.DXGI;

需要在 Loaded 之后添加代码

                var factory = new D2D.Factory();

创建 RenderTarget

创建 RenderTarget 可以尝试 WindowRenderTarget ,因为是入门博客,我不告诉大家如何使用其他几个 RenderTarget ,如果想知道,请自己多去看博客。

创建 WindowRenderTarget 需要参数 RenderTargetProperties ,HwndRenderTargetProperties。所以需要先创建这两个。

创建 RenderTargetProperties 需要参数 PixelFormat ,请看下面

                var pixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Straight);

                var renderTargetProperties = new D2D.RenderTargetProperties(D2D.RenderTargetType.Default, pixelFormat,
                    96, 96, D2D.RenderTargetUsage.None, D2D.FeatureLevel.Level_DEFAULT);

RenderTargetProperties 需要的参数是 RenderTargetType ,PixelFormat,dpiX,dpiY,RenderTargetUsage,FeatureLevel,参数大家看命名就知道是做什么的,在这里就不告诉大家。

创建 HwndRenderTargetProperties 请看下面代码

                var hwndRenderTargetProperties = new D2D.HwndRenderTargetProperties();
                hwndRenderTargetProperties.Hwnd = new WindowInteropHelper(this).Handle;

现在尝试创建 RenderTarget 请看代码

               var renderTarget = new D2D.WindowRenderTarget(factory, renderTargetProperties, hwndRenderTargetProperties);

因为需要拿到 RenderTarget 进行画基础图形,一般把 RenderTarget 放在字段。

        public MainWindow()
        {
            InitializeComponent();

            Loaded += (s, e) =>
            {
                var factory = new D2D.Factory();

                var pixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Straight);

                var hwndRenderTargetProperties = new D2D.HwndRenderTargetProperties();
                hwndRenderTargetProperties.Hwnd = new WindowInteropHelper(this).Handle;
                hwndRenderTargetProperties.Hwnd = new WindowInteropHelper(this).Handle;
                hwndRenderTargetProperties.PixelSize = new Size2((int)ActualWidth, (int)ActualHeight);

                var renderTargetProperties = new D2D.RenderTargetProperties(D2D.RenderTargetType.Default, pixelFormat,
                    96, 96, D2D.RenderTargetUsage.None, D2D.FeatureLevel.Level_DEFAULT);

                _renderTarget = new D2D.WindowRenderTarget(factory, renderTargetProperties, hwndRenderTargetProperties);
            };
        }

        private D2D.RenderTarget _renderTarget;

这里的 PixelFormat 使用 B8G8R8A8_UNorm 的意思是每个元素包含4个8位无符号分量,分量的取值范围在[0,1]区间内的浮点数,因为不是任何类型的数据都能存储到纹理中的,纹理只支持特定格式的数据存储。这里的 BGRA 的意思分别是 蓝色(Blue)、绿色(Green)、红色(Red)和 alpha(透明度),其他可以选的格式

  • DXGI_FORMAT_R32G32B32_FLOAT:每个元素包含3个32位浮点分量。
  • DXGI_FORMAT_R16G16B16A16_UNORM:每个元素包含4个16位分量,分量的取值范围在[0,1]区间内。
  • DXGI_FORMAT_R32G32_UINT:每个元素包含两个32位无符号整数分量。
  • DXGI_FORMAT_R8G8B8A8_UNORM:每个元素包含4个8位无符号分量,分量的取值范围在[0,1]区间内的浮点数。
  • DXGI_FORMAT_R8G8B8A8_SNORM:每个元素包含4个8位有符号分量,分量的取值范围在[−1,1] 区间内的浮点数。
  • DXGI_FORMAT_R8G8B8A8_SINT:每个元素包含4个8位有符号整数分量,分量的取值范围在[−128, 127] 区间内的整数。
  • DXGI_FORMAT_R8G8B8A8_UINT:每个元素包含4个8位无符号整数分量,分量的取值范围在[0, 255]区间内的整数

更多概念请看DirectX11 Direct3D基本概念 - CSDN博客

画圈

现在就和 WPF 使用 Direct2D1 画图入门 差不多方法来画圈,如何可以画出来,那么就是成功使用 SharpDX 。不要问我为什么用画圈来判断是否可以使用 SharpDX,因为在所有基础的 draw 只有椭圆最耗性能。

还是使用 CompositionTarget 来知道什么时候刷新,在函数添加下面代码

            CompositionTarget.Rendering += CompositionTarget_Rendering;

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
        }

因为画椭圆需要三个参数,第一个是 D2D.Ellipse ,第二个是 Brush ,第三个是线条宽度。

因为 Brush 需要使用刚才的工厂创建,如果不使用工厂创建会异常

先创建 SolidColorBrush 然后创建 D2D.Ellipse

            var ellipse = new D2D.Ellipse(new RawVector2(100,100),10,10 );

            var brush = new D2D.SolidColorBrush(_renderTarget, new RawColor4(1, 0, 0, 1));

Ellipse 的三个参数是圆心和两个半径,上面是画出半径是 10 的圆。

RawColor4 就是 rgba ,颜色是从 0 到 1 ,对应 WPF 的 RGB 从 0 到 255 ,所以需要转换。

准备好几个参数,可以尝试画出来,在画之前需要使用 BeginDraw 。为什么需要调用这个函数,因为实际上调用 Draw 是不会立刻画出来,而是创建绘制命令,如果渲染是 CPU 渲染,那么就会根据命令让 CPU 在内存渲染。如果渲染 GPU 渲染,就会把命令发到 GPU ,让他渲染。

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            var ellipse = new D2D.Ellipse(new RawVector2(100, 100), 10, 10);

            var brush = new D2D.SolidColorBrush(_renderTarget, new RawColor4(1, 0, 0, 1));
            _renderTarget.BeginDraw();

            _renderTarget.DrawEllipse(ellipse, brush, 1);

            _renderTarget.EndDraw();
        }

重新告诉大家如何画出。首先拿到窗口,在 WPF 能创建的 WindowRenderTarget 最简单是拿到窗口。因为通过几个属性设置如何渲染,在哪渲染,所以还需要多使用几个属性才可以创建 D2D.WindowRenderTarget 。因为需要一个时机对 WindowRenderTarget 画出,所以我就使用 CompositionTarget 对他进行画出。

上面很多参数都没有详细说明,具体请看这位大佬的博客

简化一下的全部代码如下

using PInvoke;

using SharpDX;
using SharpDX.Direct2D1;

using System.Windows;
using System.Windows.Interop;
using SharpDX.Mathematics.Interop;
using D2D = SharpDX.Direct2D1;
using DXGI = SharpDX.DXGI;
using System.Windows.Media;
using System.Reflection;

namespace LifafaheqearNearkairliraywal;

public class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
        Application application = new Application();
        application.Startup += (s, e) =>
        {
            application.MainWindow.Show();
        };

        Window window = new Window();
        D2DRender render = new D2DRender();
        window.Loaded += (s, e) =>
        {
            render.Init(window);
        };
        
        application.MainWindow=window;
        application.Run();
    }
}

class D2DRender
{
    public void Init(Window window)
    {
        _window = window;

        var factory = new D2D.Factory();

        var pixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Ignore);

        var renderTargetProperties = new D2D.RenderTargetProperties
        (
              // 默认的行为就是尝试使用硬件优先,否则再使用软件
              D2D.RenderTargetType.Default,
              // 像素格式,对于当前大多数显卡来说,选择 B8G8R8A8 是完全能支持的
              // 而且也方便和其他框架,如 WPF 交互
              pixelFormat,
              dpiX: 96,
              dpiY: 96,
              D2D.RenderTargetUsage.None,
              D2D.FeatureLevel.Level_DEFAULT
        );
        var hwndRenderTargetProperties = new D2D.HwndRenderTargetProperties();
        hwndRenderTargetProperties.Hwnd = new WindowInteropHelper(window).Handle;
        ActualWidth = (int)window.ActualWidth;
        ActualHeight = (int)window.ActualHeight;
        hwndRenderTargetProperties.PixelSize = new Size2(ActualWidth, ActualHeight);

        var renderTarget = new D2D.WindowRenderTarget(factory, renderTargetProperties, hwndRenderTargetProperties);
        _renderTarget = renderTarget;

        window.SizeChanged -= Window_SizeChanged;
        window.SizeChanged += Window_SizeChanged;

        AddRendering();
    }

    private int ActualWidth { set; get; }
    private int ActualHeight { set; get; }

    private void AddRendering()
    {
        CompositionTarget.Rendering -= CompositionTarget_Rendering;
        CompositionTarget.Rendering += CompositionTarget_Rendering;
    }

    public void CompositionTarget_Rendering(object? sender, EventArgs e)
    {
        Render();
    }

    private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(_window);
        ArgumentNullException.ThrowIfNull(_renderTarget);

        var window = _window;

        ActualWidth = (int) window.ActualWidth;
        ActualHeight = (int) window.ActualHeight;

        _renderTarget.Resize(new Size2(ActualWidth, ActualHeight));
    }

    public void Render()
    {
        var renderTarget = _renderTarget;
        if (renderTarget == null)
        {
            throw new InvalidOperationException();
        }

        renderTarget.BeginDraw();

        renderTarget.Clear(new RawColor4(0.2f,0.5f,0.5f,1));

        var width = Random.Shared.Next(100, 200);
        var height = width;
        var maxWidth = ActualWidth - width;
        var maxHeight = ActualHeight - height;

        var x = Random.Shared.Next(width, maxWidth);
        var y = Random.Shared.Next(height, maxHeight);

        var ellipse = new D2D.Ellipse(new RawVector2(x, y), width, height);

        using var brush = new D2D.SolidColorBrush(_renderTarget, new RawColor4(1, 0, 0, 1));

        renderTarget.FillEllipse(ellipse, brush);

        renderTarget.EndDraw();
    }

    private D2D.WindowRenderTarget? _renderTarget;
    private Window? _window;
}

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin a582f328a967af32635cba6d08720341431c9c24

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin a582f328a967af32635cba6d08720341431c9c24

获取代码之后,进入 LifafaheqearNearkairliraywal 文件夹


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/WPF-%E4%BD%BF%E7%94%A8-SharpDX.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

微软最具价值专家


无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号

欢迎进入 Eleven 老师组建的 .NET 社区

以上广告全是友情推广,无盈利