一、坐标系
-
WPF 的 3D 坐标系原点通常位于对象中心,X 轴向右,Y 轴向上,Z 轴朝向观察者(右手定则)。
正四面体旋转以原点为中心,绕Z 轴自动旋转。
-
视点坐标:
位置(Position)0,0,50、观察方向(LookDirection)0,0,-1 和向上方向(UpDirection)0,1,0
二、问题
问题:3D正四面体自动旋转,有时候会只看到两个分离的面,有时候什么都看不到,全白色。
三、解析
- 这个问题是 WPF 3D 的背面剔除(Back-face culling) 机制导致的。
在 WPF 中,默认只会渲染法线朝向相机的三角形面。
而三角形面的朝向由顶点索引顺序决定(右手定则,逆时针为正面)。如果三角形面的顶点顺序反了,会导致它们的法线指向了立方体内部(很多是指向原点)。
法线朝向相机(即指向外边的)能够正确显示,指向内部的不会显示。三角形不渲染,所以看到的是纯白色背景。
因为 Viewport3D 默认背景为白色(或网格背景为白色),当所有三角形都被剔除、没有任何像素被绘制时,就直接透出白色背景了。
四、计算
-
顶点:(0,0,0), (10,10,0), (10,0,10) , (0,10,10)
面1: (0,0,0), (10,10,0), (10,0,10) 索引 0,1,2
面2: (0,0,0), (10,10,0), (0,10,10) 索引 0,1,2
面3: (0,0,0), (10,0,10), (0,10,10) 索引 0,1,2
面4: (10,0,10), (0,10,10), (10,10,0) 索引 0,1,2
另外三个顶点是 (10,10,0), (10,0,10), (0,10,10)。这个四面体不是以原点为中心,而是以原点为一个顶点。那么背面剔除的问题可能表现在某些角度出现空白?需要检查每个面的法线方向是否朝外。由于所有面都包含原点,且其他顶点都在正象限,法线方向可能需要计算。
-
通常为了清晰显示立体感,我们只渲染正面,但需要正确顺序。
对于以原点为顶点的四面体,我们可以选择从外部看每个面的逆时针顺序。
更简单的方法是将所有三角形索引改为按顺时针从外部看?
但标准是逆时针为正面。我们可以计算每个面的法线,如果指向内部,则交换最后两个索引。
-
面1: A,B,C:
A=(0,0,0), B=(10,10,0), C=(10,0,10)。
向量AB=(10,10,0), AC=(10,0,10)。法线为: 叉积 AB × AC = (10,10,0) × (10,0,10) = (100, -100, -100),指向 (1,-1,-1) 方向,这大致指向外部吗?
这个四面体位于第一象限,A在原点,外部应该是指向远离原点的方向?但这个四面体不在原点中心,其外部方向对于每个面是不同的。
我们通常希望模型围绕原点旋转时,外部法线指向外。但该四面体所有顶点都在x>=0,y>=0,z>=0区域,其重心在 (5,5,5) 附近。对于面A,B,C,外部应该是远离重心方向。
重心G=( (0+10+10+0)/4, (0+10+0+10)/4, (0+0+10+10)/4 ) = (5,5,5)。
面ABC的中心是 ( (0+10+10)/3, (0+10+0)/3, (0+0+10)/3 ) ≈ (6.67, 3.33, 3.33)。
从重心到面中心的方向向量是 (1.67, -1.67, -1.67),即大致方向 (1,-1,-1)。
而我们的法线 (1,-1,-1) 正好指向外部,所以该顺序 (A,B,C) 是正确的。所以面1的顺序 0,1,2 是正确的。
-
面2: A,B,D。
A=(0,0,0), B=(10,10,0), D=(0,10,10)。
向量AB=(10,10,0), AD=(0,10,10)。
叉积 AB×AD = (10 * 10-0 * 10, 0 * 0-10 * 10, 10 *1 0-10 * 0) = (100, -100, 100) 即 (1,-1,1)。
面2中心 ( (0+10+0)/3, (0+10+10)/3, (0+0+10)/3 ) = (3.33, 6.67, 3.33)。
重心到面中心向量: (5-3.33, 5-6.67, 5-3.33) = (1.67, -1.67, 1.67) 即 (1,-1,1)。
法线 (1,-1,1) 也是外部,所以顺序 0,1,2 也是正确的。
-
面3: A,C,D。
A=(0,0,0), C=(10,0,10), D=(0,10,10)。
向量AC=(10,0,10), AD=(0,10,10)。
叉积 AC×AD = (0 * 10-10 * 10, 10 * 0-10 * 10, 10 * 10-0 * 0) = (-100, -100, 100) 即 (-1,-1,1)。
面3中心 ( (0+10+0)/3, (0+0+10)/3, (0+10+10)/3 ) = (3.33, 3.33, 6.67)。
重心到面中心向量:(5-3.33, 5-3.33, 5-6.67) = (1.67, 1.67, -1.67) 即 (1,1,-1)。
我们的法线是 (-1,-1,1),正好相反,是向内。所以顺序 0,1,2 是反的,应该改为 0,2,1 才使得法线向外。
-
面4: B,C,D。
B=(10,10,0), C=(10,0,10), D=(0,10,10)。
向量BC=(0,-10,10), BD=(-10,0,10)。
叉积 BC×BD = ((-10) * 10 - 10 * 0, 10 * (-10) - 0 * 10, 0 * 0 - (-10) * (-10)) = (-100, -100, -100) 即 (-1,-1,-1)。
面4中心 ( (10+10+0)/3, (10+0+10)/3, (0+10+10)/3 ) = (6.67, 6.67, 6.67)。
重心到面中心向量:(5-6.67, 5-6.67, 5-6.67) = (-1.67, -1.67, -1.67) 即 (-1,-1,-1)。
法线 (-1,-1,-1) 是外部,所以顺序 0,1,2 正确。
-
因此,只有第三个面(A,C,D)需要改为 0,2,1,而其他三个面保持 0,1,2。
五、问题判断
- WPF 3D 默认只渲染法线朝向相机的三角形(逆时针顺序为正面)。程序的四个面都使用了 TriangleIndices="0,1,2",但经过计算,第三个面(A,C,D)的法线指向内部,而不是朝外。因此:
- 当相机从某个方向看时,如果所有面都背对相机,就什么都看不见(全白色);
- 当相机转到特定角度,只有个别面的法线指向相机,所以只能看到一两个面。
六、改正
第三个面(A,C,D)需要改为 0,2,1,而其他三个面保持 0,1,2。
七、补充

- 绕 X 轴旋转 160°(接近 180°)相当于把原本在 Y 轴正方向的东西翻到了后面。
红色原本在视角的正前方(原始状态下只有它可见),但转 160° 后它正好翻到了背面。
绿色和黄色原本藏在背面,现在随着旋转被翻到了正面。
WPF 3D 默认只渲染法线朝向相机的三角形来看,红色不渲染,所以看见了藏在背面的绿色和黄色。