编辑1:
显然,我已经开始以不正确的方式在WPF环境中实现3D渲染。 Ofc我的问题在下面有一个解决方案,但我建议阅读Sheridan的答案的更新,并使用他的建议来实现这一目标。它不仅安全,而且性能更好。虽然理解它有点复杂,但是一旦理解了它,就可以开始在WPF中渲染多个3D应用程序了。 谢谢你的帮助Sheridan!
问题;
我是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对象的所有权,还是一个好的解决方法?
答案 0 :(得分:3)
首先,我想说WPF 不是开发除最简单游戏之外的所有游戏的好框架......我建议使用微软的XNA之类的游戏框架。
但是,如果您坚持使用WPF,那么我想提请您注意CompositionTarget.Rendering
事件。它主要使用主机的帧速率来渲染图形通道,从而避免使用定时器。
您还应该查看MSDN上的How to: Render on a Per Frame Interval Using CompositionTarget页面,以获取更多有用的信息和代码示例。
此外,请阅读“WPF Control Development Unleashed:构建高级用户体验”一书中的摘录:
有些读者可能会认识到这种方法与高端图形之间的相似性 像DirectX这样的子系统。不要错误使用CompositionTarget.Rendering进行良好的注入 指向创建基于WPF的游戏图形引擎。高端显卡和超高清 帧速率不是WPF动画这一特定方面的目标。
与DispatcherTimer方法类似,基于CompositionTarget.Rendering的动画 也没有时间限制。但是,这些事件与生成的渲染线程同步 比DispatcherTimer更流畅的动画。也没有必要开始和 停止计时器,虽然您可能需要分离并附加事件处理程序以进行改进 性能
更新&gt;&gt;&gt;
我发现这只是一个课程项目,到目前为止我会忽略我之前的评论和你的代码示例。当已经存在时,不要尝试创建新的渲染系统。相反,你应该遵循这种方法:
创建实现INotifyPropertyChanged
界面的数据对象,并拥有X
,Y
和DirectionVector
(可能是Size
结构)公共属性。< / p>
为您的Move
类添加Swim
方法(或Fish
方法),在其中更新数据对象的X
和Y
属性,具体取决于DirectionVector
属性的值。
向您的用户界面添加ListBox
控件。
创建一个集合属性来保存数据对象,添加项目并将集合绑定到ListBox.ItemsSource
属性。
创建DataTemplate
以定义Fish
个对象的外观......您可以使用Path
类来绘制它们,甚至使用RotateTransform
来旋转它们(角度可以从DirectionVector
属性计算)。在DataTemplate
中,您可以将X
和Y
属性绑定到“边距”属性。
最后,添加一个无限循环(可能带有一个break out选项)并在该循环中迭代数据对象的集合并在每个循环上调用Move()
。这将更新数据对象在ListBox
。
答案 1 :(得分:3)
作为一般规则,唯一可以改变WPF中线程效忠的对象是那些派生自Freezable
的对象。 (例如,Model3D
是可冻结的,因此,Light
和GeometryModel3D
之类的内容。)
直接参与可视树的元素不是来自Freezable
。它们来自Visual(通常,但并非总是如此,通过FrameworkElement
)。因此,可视元素永远与您创建它们的线程相关联。 Freezables通常是描述性项目,告诉可视树元素该做什么。例如,画笔(无论是实体,渐变填充,图像画笔等等)都是freezables,但是要用画笔做某事,你需要将它用作某些视觉元素的属性(即不可冻结的东西),例如{ {1}}的{1}}。
所以Fill
属于这个类别 - 它是3D模型的描述,但它实际上并不知道如何渲染自己。您将此描述提供给一些知道如何呈现模型的视觉元素(例如Rectangle
)。
因此可以在工作线程上构建Model3D
,然后将其传递给UI线程。
但是,您只能通过调用Viewport3D
来冻结它后,从某个线程开始使用freezable对象,而不是创建它的那个线程。顾名思义,这可以防止进一步修改。冻结freezable后,它不再与任何特定线程相关联,因此您可以从任何您喜欢的线程中使用它。
此处的预期用法模型是:
如果你想构建一个复杂的Model3D
,这可能是合适的,这个复杂的Freeze
需要花费很长时间来构建,并且你不想让应用程序在发生这种情况时没有响应。
但是,如果您需要随着时间的推移可以修改模型,这没有任何用处。如果这就是你需要的东西(听起来就是这样)那么你往往别无选择,只能在UI线程上创建模型 - 如果你创建一个你从未实际冻结的freezable(因为你需要能够改变它)那么你必须在将渲染它的同一个线程上创建它。当您想要更新模型时,您需要确保在UI线程上完成更新,或者您可以使用数据绑定,它能够处理任何线程上的更改通知事件,并且它将编组那些到UI线程的你。
但是,我想知道你是否真的需要多线程。你给出了原因
提供更好的UI控制(开始/停止按钮fe)。
这不是使用单独线程的真正原因。没有什么能阻止UI线程执行模型更新并响应UI输入。您只需要确保定期更新模型的代码将控制权返回给事件循环。
使用单独线程的唯一原因是,确定模型更新应该是什么的计算在计算上是昂贵的。例如,如果您编写的代码执行某些进程的复杂且高度详细的模拟,然后呈现结果,则在工作线程上执行计算以使UI保持响应可能是有意义的。但即便如此,一旦完成这些计算,您需要确保根据这些计算结果对模型所做的更新是在UI线程上完成的,而不是工作线程。
值得考虑的是,您是否可以逐步建立新模型。如果您丢弃旧模型并立即将其替换为新构建的模型,用户可能实际上并未注意到。这可以使您在工作线程上构建整个模型,因为您可以将其冻结。如果你每次都建立一个新的模型,那么冻结就是安全的,因为只要你想改变一些东西,你只需要建立一个新模型而不是更新旧模型。
另一种变化是拥有一个主要由冷冻碎片组成的模型,由一些未冻结的顶级元素包含。