3

编辑 1:

显然,我已经开始以不正确的方式在 WPF 环境中实现 3D 渲染。Ofc 在下面有我的问题的解决方案,但我建议阅读 Sheridan 答案的更新并使用他的建议来实现这一点。它不仅安全,而且性能更好。虽然理解起来有点复杂,但是一旦理解了,就可以开始在WPF中渲染多个3D应用了。感谢您的帮助谢里登!


问题 ;

我是 WPF 的新手,我想用 WPF 设计一个连续的渲染(比如在游戏应用程序中)。我正在使用多线程来提供更好的 UI 控件(开始/停止按钮 fe)。或者,由于使用无限循环来渲染 3D 世界,事件可能会被处理。

但是,我的问题是,在运行程序时,我得到一个Invalid operation was unhandled错误。问题是有一个对象是主线程的属性,因此新线程可能无法访问它。

从 XAML 文件,

<Grid>
    <!-- ui controls omitted ... -->
    <Viewport3D Name="myViewport" ClipToBounds="True">
        <!-- all inits, camera, pos, ... -->
    </Viewport3D>
</Grid>

在主班;

/// <summary>this method is done to render the 3D app in other thread.</summary>
private void Runtime(Viewport3D vp) {
    System.Diagnostics.Debug.WriteLine("runtime ");
    Render3D r3d = new Render3D(vp);
    // actual startup
    while (keepRunning) {
        r3d.Init3D();
    }
}

/// <summary>this method toggles the game runtime</summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StartOrStop(object sender, RoutedEventArgs e) {
    keepRunning = !keepRunning;
    if (keepRunning) {
        buttonStartStop.Content = "Stop";
        // thread
        t1 = new Thread( () => Runtime(myViewport) );
        t1.Start();
    }
    else {
        buttonStartStop.Content = "Start";
        t1.Abort();
    }
}

3DViewport 对象在 XAML 文件中初始化。这就是为什么我将它传递给新线程,它可以创建一个使用该 3DViewport 类的对象。

下面是该Render3D课程的示例。

// constructor
internal Render3D(Viewport3D v) {
    currViewport = v;
}

/// <summary>get called in loops to render gfx</summary>
internal void Init3D() {
    // clear rendered view
    ClearRenderWindow();
    // add landscape
    AddLandScape();
}

/// <summary>clear window to re-render gfx</summary>
private void ClearRenderWindow() {
    ModelVisual3D mv;

    // ***** error got caught here below ******
    for (int i = currViewport.Children.Count - 1; i >= 0; i--) {
        mv = (ModelVisual3D)currViewport.Children[i];
        if (mv.Content is DirectionalLight == false) currViewport.Children.Remove(mv);
    }
}

错误在currViewport.Children.Count方法中被捕获。如前所述,问题在于当前线程没有该对象的所有权。这是我在多线程体验中第一次面对这个问题。我已经四处寻找,但找不到解决方案。

有谁知道如何传递 Viewport3D 对象的所有权,或者一个好的解决方法?

4

2 回答 2

3

首先,我想说 WPF不是一个很好的框架来开发除了最简单的游戏之外的所有游戏......我建议使用像微软的XNA这样的游戏框架。

但是,如果您坚持使用 WPF,那么我想CompositionTarget.Rendering提请您注意该事件。它基本上使用主机的帧速率来渲染调节图形通道,避免使用计时器。

您还应该查看 MSDN 上的How to: Render on a Per Frame Interval Using CompositionTarget页面,以获取更多有用的信息和代码示例。

另外,请阅读“WPF Control Development Unleashed: Building Advanced User Experiences”一书中的摘录:

一些读者可能会认识到这种方法与 DirectX 等高端图形子系统之间的相似之处。不要将 CompositionTarget.Rendering 误认为是创建基于 WPF 的游戏图形引擎的良好注入点。高端图形和超高帧速率并不是 WPF 动画这一特定方面的目标。

与 DispatcherTimer 方法类似,基于 CompositionTarget.Rendering 的动画也没有时间限制。但是,这些事件与渲染线程同步,从而产生比 DispatcherTimer 更流畅的动画。也不需要启动和停止计时器,尽管您可能必须分离和附加事件处理程序以提高性能。

更新>>>

发现这只是一个课程项目,我会忽略我之前的评论和你到目前为止的代码示例。不要在已有渲染系统的情况下尝试创建新的渲染系统。相反,您应该遵循以下方法:

创建实现INotifyPropertyChanged接口并具有X、、YDirectionVector(可能是Size结构)公共属性的数据对象。

添加一个Move方法(或Swim您的类的方法),您可以在其中根据属性的值Fish更新数据对象X和属性。YDirectionVector

ListBox控件添加到您的 UI。

创建一个集合属性来保存您的数据对象、添加项目并将集合绑定到该ListBox.ItemsSource属性。

创建 aDataTemplate来定义您的Fish对象的外观...您可以使用Path该类来绘制它们,甚至可以使用 aRotateTransform来旋转它们(可以从DirectionVector属性中计算角度)。在 中DataTemplate,您可以将XY属性绑定到 `Margin' 属性。

最后,添加一个无限循环(可能带有中断选项),并在该循环中遍历数据对象的集合并调用Move()每个对象。这将更新数据对象在ListBox.

于 2013-08-21T11:34:19.837 回答
3

作为一般规则,唯一可以更改 WPF 中线程忠诚度的对象是那些派生自Freezable. (例如,Model3D是可冻结的,因此,像Light和这样的东西也是如此GeometryModel3D。)

直接参与可视化树的元素不是从Freezable. 它们源自 Visual(通常,但不总是,通过FrameworkElement)。因此,视觉元素永远与您创建它们的线程相关联。Freezables 通常是描述性的项目,告诉可视化树元素做什么。例如,画笔(无论是实心、渐变填充、图像画笔还是其他)都是可冻结的,但要使用画笔做某事,您需要将其用作某些视觉元素的属性(即不是可冻结的),Fill例如一个Rectangle

所以Model3D属于这一类——它是对 3D 模型的描述,但它实际上并不知道如何渲染自己。您将此描述提供给一些知道如何呈现模型的视觉元素(例如Viewport3D)。

因此可以Model3D在工作线程上构建,然后将其传递给 UI 线程。

但是,您只能从某个线程开始使用可冻结对象,而不是在您通过调用Freeze. 顾名思义,这可以防止进一步修改。一旦可冻结对象被冻结,它就不再与任何特定线程相关联,因此您可以从任何您喜欢的线程中使用它。

这里的预期使用模型是:

  1. 在工作线程上构建一些复杂的东西
  2. 冻结它
  3. 将它附加到知道如何在 UI 线程中呈现它的东西上

如果您想要构建一个Model3D需要很长时间才能构建的复杂系统,并且您不希望在此过程中使应用程序无响应,这可能是合适的。

但是,如果您需要随着时间的推移对模型进行修改,这将没有任何用处。如果这就是您所需要的(听起来确实如此),那么您往往别无选择,只能在 UI 线程上创建模型 - 如果您创建了一个您从未真正冻结的可冻结对象(因为您需要能够更改它)那么你必须在渲染它的同一个线程上创建它。当您想要更新模型时,您需要确保在 UI 线程上完成更新,或者您可以使用数据绑定,它能够处理任何线程上的更改通知事件,并将这些事件编组到 UI 线程你。

但是,我想知道您是否真的需要多线程。你给出的理由为

提供更好的 UI 控制(开始/停止按钮 fe)。

这并不是使用单独线程的真正理由。没有什么可以阻止 UI 线程对模型执行更新并响应 UI 输入。您只需要确保更新模型的代码定期将控制权返回给事件循环。

使用单独线程的唯一原因是,如果确定模型更新内容的计算成本很高。例如,如果您编写的代码对某个进程执行复杂且高度详细的模拟,然后呈现结果,则在工作线程上执行计算以使 UI 保持响应可能是有意义的。但即便如此,一旦这些计算完成,您需要确保基于这些计算结果对模型所做的更新是在 UI 线程上完成的,而不是在工作线程上完成的。

可能值得考虑是否可以每次都构建一个新模型。如果您放弃旧模型并立即用新构建的模型替换它,用户可能实际上不会注意到。这可以让你在工作线程上构建整个模型,因为你可以冻结它。如果你每次都建立一个新模型,冻结是安全的,因为任何时候你想改变一些东西,你只需要建立一个新模型而不是更新旧模型。

另一个变体是拥有一个主要由冻结部分组成的模型,其中包含一些未冻结的顶级元素。

于 2013-08-21T13:45:04.687 回答