🎨 引言
在 3D 图形开发中,给立方体的每个面设置不同颜色是一个非常经典且实用的小项目。它不仅帮助我们理解 3D 网格构造、材质绑定和光照渲染 的基本原理,还为我们后续学习更复杂的模型操作打下基础。
在这篇博客中,我们将使用 Helix Toolkit(WPF SharpDX 版本),通过 两种方法 来实现一个“六面异色立方体”,并详细分析它们的优缺点与适用场景。
效果演示
🧱 方法一:自动解析网格 —— CreateColoredBox
🔧 核心思想
- 使用
MeshBuilder.AddBox(...)
创建一个标准立方体。 - 遍历所有三角形索引,每6个三角形组成一个完整的面。
- 计算每个面的中心点,判断其属于哪个方向(前/后/左/右/上/下)。
- 动态为每个面创建子网格,并分配不同的材质颜色。
✅ 示例代码
private Model3DGroup CreateColoredBox(
double width, double height, double depth,
Brush frontBrush, Brush backBrush,
Brush leftBrush, Brush rightBrush,
Brush topBrush, Brush bottomBrush)
{
var modelGroup = new Model3DGroup();
var meshBuilder = new MeshBuilder();
meshBuilder.AddBox(new Point3D(0, 0, 0), width, height, depth);
var mesh = meshBuilder.ToMesh();
for (int i = 0; i < mesh.TriangleIndices.Count; i += 6)
{
// 计算当前面的中心点
var center = new Point3D();
for (int j = 0; j < 6; j++)
{
var idx = mesh.TriangleIndices[i + j];
center.X += mesh.Positions[idx].X;
center.Y += mesh.Positions[idx].Y;
center.Z += mesh.Positions[idx].Z;
}
center.X /= 6;
center.Y /= 6;
center.Z /= 6;
// 判断面方向
Brush brush;
if (Math.Abs(center.Z - depth / 2) < 0.001) brush = frontBrush;
else if (Math.Abs(center.Z + depth / 2) < 0.001) brush = backBrush;
else if (Math.Abs(center.X - width / 2) < 0.001) brush = rightBrush;
else if (Math.Abs(center.X + width / 2) < 0.001) brush = leftBrush;
else if (Math.Abs(center.Y - height / 2) < 0.001) brush = topBrush;
else if (Math.Abs(center.Y + height / 2) < 0.001) brush = bottomBrush;
else brush = Brushes.Gray;
// 创建子网格
var subMesh = new MeshGeometry3D();
for (int j = 0; j < 6; j++)
{
var idx = mesh.TriangleIndices[i + j];
subMesh.Positions.Add(mesh.Positions[idx]);
subMesh.TextureCoordinates.Add(mesh.TextureCoordinates[idx]);
subMesh.TriangleIndices.Add(j);
}
// 重新计算法线
subMesh.Normals = ComputeNormals(subMesh);
var material = new DiffuseMaterial(brush);
modelGroup.Children.Add(new GeometryModel3D(subMesh, material)
{
BackMaterial = material
});
}
return modelGroup;
}
private Vector3DCollection ComputeNormals(MeshGeometry3D mesh)
{
var normals = new Vector3DCollection();
for (int i = 0; i < mesh.TriangleIndices.Count; i += 3)
{
var p0 = mesh.Positions[mesh.TriangleIndices[i]];
var p1 = mesh.Positions[mesh.TriangleIndices[i + 1]];
var p2 = mesh.Positions[mesh.TriangleIndices[i + 2]];
var normal = Vector3D.CrossProduct(p1 - p0, p2 - p0);
normal.Normalize();
for (int j = 0; j < 3; j++)
normals.Add(normal);
}
return normals;
}
📌 优点
- 可拓展性强,适用于从 OBJ 模型中提取不同区域进行着色。
- 不依赖顶点顺序,适合自动化处理。
⚠️ 注意事项
- 需手动计算法线以确保光照正确。
- 需要添加
BackMaterial
以保证背面可见。
🧩 方法二:手动定义顶点 —— CreateColoredBoxNew
🔧 核心思想
- 手动定义立方体的 8 个顶点坐标。
- 为每个面指定四个顶点(顺时针排列),构建四边形。
- 使用
AddQuad(...)
添加每个面并绑定材质。
✅ 示例代码
private Model3DGroup CreateColoredBoxNew(
double width, double height, double depth,
Brush frontBrush, Brush backBrush,
Brush leftBrush, Brush rightBrush,
Brush topBrush, Brush bottomBrush)
{
var modelGroup = new Model3DGroup();
var points = GetCubeVertices(width, height, depth);
AddFace(modelGroup, new[] { points[0], points[1], points[2], points[3] }, frontBrush); // 前面
AddFace(modelGroup, new[] { points[5], points[4], points[7], points[6] }, backBrush); // 后面
AddFace(modelGroup, new[] { points[4], points[0], points[3], points[7] }, leftBrush); // 左面
AddFace(modelGroup, new[] { points[1], points[5], points[6], points[2] }, rightBrush); // 右面
AddFace(modelGroup, new[] { points[3], points[2], points[6], points[7] }, topBrush); // 上面
AddFace(modelGroup, new[] { points[4], points[5], points[1], points[0] }, bottomBrush);// 下面
return modelGroup;
}
private Point3D[] GetCubeVertices(double width, double height, double depth)
{
double x = width / 2, y = height / 2, z = depth / 2;
return new Point3D[]
{
new Point3D(-x, -y, z), // 0: 左前上
new Point3D(x, -y, z), // 1: 右前上
new Point3D(x, y, z), // 2: 右前下
new Point3D(-x, y, z), // 3: 左前下
new Point3D(-x, -y, -z), // 4: 左后上
new Point3D(x, -y, -z), // 5: 右后上
new Point3D(x, y, -z), // 6: 右后下
new Point3D(-x, y, -z) // 7: 左后下
};
}
private void AddFace(Model3DGroup group, Point3D[] points, Brush brush)
{
var meshBuilder = new MeshBuilder();
meshBuilder.AddQuad(points[0], points[1], points[2], points[3]); // 四边形
var geometry = meshBuilder.ToMesh();
var material = new DiffuseMaterial(brush);
group.Children.Add(new GeometryModel3D(geometry, material)
{
BackMaterial = material
});
}
📌 优点
- 结构清晰,适合初学者理解 3D 网格构造。
- 易于控制每个面的方向和材质。
- 适合教学或快速原型设计。
⚠️ 注意事项
- 顶点顺序必须是顺时针排列,否则可能被剔除。
- 面数较多时管理复杂,不适合大规模模型。
📊 对比总结
特性 | CreateColoredBox | CreateColoredBoxNew |
---|---|---|
实现难度 | 中等(需理解三角面片) | 简单(手动定义顶点) |
可读性 | 较低 | 高 |
可扩展性 | 强(可应用于任意模型) | 弱(仅适用于规则几何体) |
是否需要手动计算法线 | 是 | 否(由 AddQuad 自动生成) |
是否推荐初学者使用 | ❌ | ✅ |
🧪 实际效果预览
运行程序后,你将看到一个立方体:
面 | 颜色 |
---|---|
前面 | Red |
后面 | Green |
左面 | Blue |
右面 | Yellow |
上面 | Purple |
下面 | Orange |
无论你旋转视角还是缩放视图,六个面都始终可见,颜色准确无误。
🧰 XAML 配置建议
确保你的 MainWindow.xaml
包含以下内容:
<Window x:Class="Load3DModelWithMaterialUseHelix.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="http://helix-toolkit.org/wpf"
Title="六面异色立方体" Height="600" Width="800">
<Grid>
<hc:HelixViewport3D x:Name="helixViewport" ZoomExtentsWhenLoaded="True">
<hc:DefaultLights />
</hc:HelixViewport3D>
</Grid>
</Window>
🎯 进阶方向
如果你对这个项目感兴趣,可以继续尝试以下功能:
- 【封装类】将这两种方法封装成
ColoredBoxVisual3D
类供复用 - 【交互增强】点击某个面高亮或弹出信息
- 【动态更新】运行时修改某个面的颜色
- 【纹理贴图】为每个面加载图片资源
- 【多面体扩展】构造金字塔、五棱柱等复杂几何体
📌 GitHub 示例源码:
CSDN下载
📬 订阅更新:关注我,获取更多 C#/WPF/3D 开发实战文章