如何并行化 3d 模型创建?
How to parallelize 3d model creation?
这里有点特殊情况,因为我使用的是一个名为 helix-toolkit 的库,但请耐心等待。
我想在我的代码中使用 backgroundworker 并行创建模型对象。
我知道多线程和处理 UI 元素存在一个大问题,但也许有某种解决方法。
这是我的代码的粗略结构:
我在其中创建 Backgroundwoker 的第一个文件,拆分了创建 Geometry3D 对象的工作量,最后调用 SetModelGeometry 将几何体绑定到视口。第二个文件显示绑定是如何完成的。
MainWindow.xaml.cs
private void Draw_Building()
{
_worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
_worker.DoWork += Draw_Building_DoWork;
_worker.ProgressChanged += DrawBuilding_ProgressChanged;
_worker.RunWorkerCompleted += DrawBuilding_RunWorkerCompleted;
_worker.RunWorkerAsync(10000);
}
private void Draw_Building_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
// returns a list containing Geometry3D objects ( created by meshBuilder.ToMeshGeometry3D() )
Geometryhandler.Draw_Building(sender, e);
}
private void DrawBuilding_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
StatusProgressBar.Value = e.ProgressPercentage;
var i = (int)e.UserState;
var actualComponent = MyBuildingComponents.First(c => c.Id == i);
LblStatusbarInfo.Text = "Currently meshing element #" + actualComponent.Globalid + " (" +
actualComponent.Objectname + ")";
StatusProgressbarMsg.Text = "Meshing (" + e.ProgressPercentage + " %)";
}
private void DrawBuilding_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StatusProgressbarMsg.Text = "-";
StatusProgressBar.Value = 0;
LblStatusbarInfo.Text = "Meshing completed.";
Geometry = e.Result as List<MeshIdandGeometry>;
// creates MeshGeometryModel3D objects and binds them to the viewport using the List of Geometries
MainViewModel.SetModelGeometry(Geometry);
}
MainViewModel.cs
public void SetModelGeometry(List<MeshIdandGeometry> geometry)
{
MyModelGeometry = new Element3DCollection();
if (geometry != null)
{
foreach (var mygeometry in geometry)
{
var s = new MeshGeometryModel3D
{
Geometry = mygeometry.Geometry,
Material = mygeometry.Material,
};
this.MyModelGeometry.Add(s);
s.Attach(MyModelViewport.RenderHost);
}
}
this.OnPropertyChanged("MyModelGeometry");
}
我目前的问题是以下错误消息:
The calling thread cannot access this object because a different
thread owns it.
试图将 ModelGeometry
附加到视口时在 SetModelGeometry
函数中抛出。
我猜编译器是在抱怨几何图形是在不同的线程中创建的,他现在无法访问这些线程。
有没有不破坏DrawBuilding函数并行执行的workaround/solution?
编辑:
编辑 2:发布了错误版本的 Draw_Building
方法
Geometryhandler
中的Draw_Building
方法:
public void Draw_Building(object sender, DoWorkEventArgs e)
{
var geometry = new List<MeshIdandGeometry>();
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(@"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(@"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
var max = _mainWindow.MyBuildingComponents.Count;
var i = 1;
// Loop over building components
foreach (var component in _mainWindow.MyBuildingComponents)
{
//if (i == 5) break;
var component1 = component;
var componentTriangles = _mainWindow.MyTriangles.Where(triangle => triangle.ComponentId == component1.Id);
var meshBuilder = new MeshBuilder(true, true, true);
// Loop over triangles in building element
foreach (var triangle in componentTriangles)
{
var triangle1 = triangle;
var p1 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId1);
var p2 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId2);
var p3 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId3);
if (p1 != null && p2 != null && p3 != null)
{
//meshBuilder.AddTriangle(new Vector3((float)p1.X, (float)p1.Y, (float)p1.Z),
// new Vector3((float)p2.X, (float)p2.Y, (float)p2.Z),
// new Vector3((float)p3.X, (float)p3.Y, (float)p3.Z));
// coordination are switched to match the coordinate system in SharpDX viewport
meshBuilder.AddTriangle(new Vector3(-(float)p1.Y, (float)p1.Z, -(float)p1.X),
new Vector3(-(float)p2.Y, (float)p2.Z, -(float)p2.X),
new Vector3(-(float)p3.Y, (float)p3.Z, -(float)p3.X));
}
}
var mesh = meshBuilder.ToMeshGeometry3D();
var meshandtriangle = new MeshIdandGeometry
{
Id = component1.Id,
Geometry = mesh,
Material = standardMaterial,
};
geometry.Add(meshandtriangle);
i++;
var progressPercentage = Convert.ToInt32(((double)i / max) * 100);
var backgroundWorker = sender as BackgroundWorker;
backgroundWorker?.ReportProgress(progressPercentage, component1.Id);
}
e.Result = geometry;
}
非常感谢@egse 找到解决方案。
导致问题的部分代码:
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(@"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(@"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
基本上,上述代码的问题在于 material 是作为后台工作程序范围内的局部变量创建的。当 UI 线程尝试输入 material 对象时,这会导致所有权问题。
这个问题的解决方案是确保 material 是由 UI 线程创建的(例如,在这种情况下,Geometryhandler
的构造函数)
TL;DR:不要在 UI.
之外的另一个线程中创建从 DependencyObject 继承的 类 实例
这里有点特殊情况,因为我使用的是一个名为 helix-toolkit 的库,但请耐心等待。
我想在我的代码中使用 backgroundworker 并行创建模型对象。
我知道多线程和处理 UI 元素存在一个大问题,但也许有某种解决方法。
这是我的代码的粗略结构:
我在其中创建 Backgroundwoker 的第一个文件,拆分了创建 Geometry3D 对象的工作量,最后调用 SetModelGeometry 将几何体绑定到视口。第二个文件显示绑定是如何完成的。
MainWindow.xaml.cs
private void Draw_Building()
{
_worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
_worker.DoWork += Draw_Building_DoWork;
_worker.ProgressChanged += DrawBuilding_ProgressChanged;
_worker.RunWorkerCompleted += DrawBuilding_RunWorkerCompleted;
_worker.RunWorkerAsync(10000);
}
private void Draw_Building_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
// returns a list containing Geometry3D objects ( created by meshBuilder.ToMeshGeometry3D() )
Geometryhandler.Draw_Building(sender, e);
}
private void DrawBuilding_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
StatusProgressBar.Value = e.ProgressPercentage;
var i = (int)e.UserState;
var actualComponent = MyBuildingComponents.First(c => c.Id == i);
LblStatusbarInfo.Text = "Currently meshing element #" + actualComponent.Globalid + " (" +
actualComponent.Objectname + ")";
StatusProgressbarMsg.Text = "Meshing (" + e.ProgressPercentage + " %)";
}
private void DrawBuilding_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StatusProgressbarMsg.Text = "-";
StatusProgressBar.Value = 0;
LblStatusbarInfo.Text = "Meshing completed.";
Geometry = e.Result as List<MeshIdandGeometry>;
// creates MeshGeometryModel3D objects and binds them to the viewport using the List of Geometries
MainViewModel.SetModelGeometry(Geometry);
}
MainViewModel.cs
public void SetModelGeometry(List<MeshIdandGeometry> geometry)
{
MyModelGeometry = new Element3DCollection();
if (geometry != null)
{
foreach (var mygeometry in geometry)
{
var s = new MeshGeometryModel3D
{
Geometry = mygeometry.Geometry,
Material = mygeometry.Material,
};
this.MyModelGeometry.Add(s);
s.Attach(MyModelViewport.RenderHost);
}
}
this.OnPropertyChanged("MyModelGeometry");
}
我目前的问题是以下错误消息:
The calling thread cannot access this object because a different thread owns it.
试图将 ModelGeometry
附加到视口时在 SetModelGeometry
函数中抛出。
我猜编译器是在抱怨几何图形是在不同的线程中创建的,他现在无法访问这些线程。
有没有不破坏DrawBuilding函数并行执行的workaround/solution?
编辑:
编辑 2:发布了错误版本的 Draw_Building
方法
Geometryhandler
中的Draw_Building
方法:
public void Draw_Building(object sender, DoWorkEventArgs e)
{
var geometry = new List<MeshIdandGeometry>();
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(@"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(@"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
var max = _mainWindow.MyBuildingComponents.Count;
var i = 1;
// Loop over building components
foreach (var component in _mainWindow.MyBuildingComponents)
{
//if (i == 5) break;
var component1 = component;
var componentTriangles = _mainWindow.MyTriangles.Where(triangle => triangle.ComponentId == component1.Id);
var meshBuilder = new MeshBuilder(true, true, true);
// Loop over triangles in building element
foreach (var triangle in componentTriangles)
{
var triangle1 = triangle;
var p1 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId1);
var p2 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId2);
var p3 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId3);
if (p1 != null && p2 != null && p3 != null)
{
//meshBuilder.AddTriangle(new Vector3((float)p1.X, (float)p1.Y, (float)p1.Z),
// new Vector3((float)p2.X, (float)p2.Y, (float)p2.Z),
// new Vector3((float)p3.X, (float)p3.Y, (float)p3.Z));
// coordination are switched to match the coordinate system in SharpDX viewport
meshBuilder.AddTriangle(new Vector3(-(float)p1.Y, (float)p1.Z, -(float)p1.X),
new Vector3(-(float)p2.Y, (float)p2.Z, -(float)p2.X),
new Vector3(-(float)p3.Y, (float)p3.Z, -(float)p3.X));
}
}
var mesh = meshBuilder.ToMeshGeometry3D();
var meshandtriangle = new MeshIdandGeometry
{
Id = component1.Id,
Geometry = mesh,
Material = standardMaterial,
};
geometry.Add(meshandtriangle);
i++;
var progressPercentage = Convert.ToInt32(((double)i / max) * 100);
var backgroundWorker = sender as BackgroundWorker;
backgroundWorker?.ReportProgress(progressPercentage, component1.Id);
}
e.Result = geometry;
}
非常感谢@egse 找到解决方案。
导致问题的部分代码:
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(@"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(@"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
基本上,上述代码的问题在于 material 是作为后台工作程序范围内的局部变量创建的。当 UI 线程尝试输入 material 对象时,这会导致所有权问题。
这个问题的解决方案是确保 material 是由 UI 线程创建的(例如,在这种情况下,Geometryhandler
的构造函数)
TL;DR:不要在 UI.
之外的另一个线程中创建从 DependencyObject 继承的 类 实例