Java软光栅渲染器-背面消隐

目标

  • 将三角网格的背面(看不见的部分)剔除掉。

参考:从零实现3D图像引擎:(14)背面消隐的三大陷阱

实现

变换到观察空间

首先,进行背面消隐时,先要使用 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程序就可以了。

结果如下:

总结

目标达成。