目标
- 将三角网格的背面(看不见的部分)剔除掉。
实现
变换到观察空间
首先,进行背面消隐时,先要使用 WorldMatrix 和 ViewMatrix 将三维向量转换到观察空间。此时摄像机的位置即是原点,三角形的顶点向量就等同于观察的方向向量。
修改一下Mesh类的render方法:
/**
* 渲染3D场景
*
* @param imageRaster
* @param camera
*/
public void render(ImageRaster imageRaster, Camera camera) {
// 世界变换矩阵
Matrix4f worldMat = transform.toTransformMatrix();
// 观察-投影变换矩阵...
// 模型-观察变换矩阵
Matrix4f mv = camera.getViewMatrix().mult(worldMat);
// 模型-观察-投影变换矩阵...
// 视口变换矩阵...
// 用于保存变换后的向量坐标...
// 遍历所有三角形
for (int i = 0; i < indexes.length - 2; i += 3) {
int a = indexes[i];
int b = indexes[i + 1];
int c = indexes[i + 2];
Vector3f va = positions[a];
Vector3f vb = positions[b];
Vector3f vc = positions[c];
// 在摄像机空间进行背面消隐
if (cullBackFace(mv.mult(va), mv.mult(vb), mv.mult(vc)))
continue;
// 使用齐次坐标计算顶点...
// 模型-观察-透视 变换...
// 透视除法...
// 把顶点位置修正到屏幕空间...
// 画三角形...
}
}
判断是否为背面
A、B、C三个向量是按照顶点索引的顺序传入的,根据这三个向量,就可以计算出表面法线的方向。
faceNormal = (B-A) × (C-B)
此时,A、B、C任意一点的坐标,都等同于观察方向向量。假设选取OC点为观察方向,通过点乘就能够得知 faceNormal 和 OC 的夹角。
faceNormal dot C
若是点乘的结果大于0,就说明faceNormal和观察方向的夹角小于90°,是背朝摄像机的,这个面应该被剔除。
若是点乘的结果等于0,则说明faceNormal和视线垂直,这个面和视线完全平行,也是看不见的,应该被剔除。
代码如下:
/**
* 剔除背面
*
* @param a
* @param b
* @param c
* @return
*/
protected boolean cullBackFace(Vector3f a, Vector3f b, Vector3f c) {
// 计算ab向量
Vector3f ab = b.subtract(a, a);
// 计算bc向量
Vector3f bc = c.subtract(b, b);
// 计算表面法线
Vector3f faceNormal = ab.crossLocal(bc);
return faceNormal.dot(c) >= 0;
}
测试用例
并不需要重新写一个测试用例,运行Test3DView程序就可以了。
结果如下:
总结
目标达成。