2023-04-12------------------视锥体剔除

119 阅读3分钟

效果

1681463666145-20230412_23183200_00_00-00_00_300.gif

一、首先实现面与包围球的关系计算

1.1、三点定义一个平面,并实现点到平面的距离计算

import { BufferGeometry } from "./BufferGeometry.js";
import * as Vector3 from '../Math/vec3.js';

export default class PlaneGeometry extends BufferGeometry {
  /**
   * 顺时针排序的三个点
   * @param {*} position0 
   * @param {*} position1 
   * @param {*} position2 
   */
  constructor(position0, position1, position2) {
    super()
    this._firstPosition = position0
    this._normal = this.computeNormal(position0, position1, position2)
  }

  /**
   * 根据三点坐标获取归一化的法向量
   * @param {*} position0 
   * @param {*} position1 
   * @param {*} position2 
   * @returns 
   */
  computeNormal(position0, position1, position2) {
    const vector1 = Vector3.fromValues(position1[0] - position0[0], position1[1] - position0[1], position1[2] - position0[2])
    const vector2 = Vector3.fromValues(position2[0] - position1[0], position2[1] - position1[1], position2[2] - position1[2])
    const normalVector = Vector3.create()
    Vector3.cross(normalVector, vector1, vector2)
    const normalizeNormal = Vector3.create()
    Vector3.normalize(normalizeNormal, normalVector)
    return normalizeNormal
  }

  /**
   * 获取法向
   */
  get normal() {
    return this._normal
  }

  /**
   * 获取面上一点
   * @returns 
   */
  getFirstPosition() {
    return this._firstPosition
  }

  /**
   * 获取某一点到面的距离,正值在法向量指向的一侧,负值不在
   * @param {*} point 
   */
  getDistanceToPoint(point) {
    //先计算平面上一点到该点向量
    const planePoint2Point = Vector3.create()
    Vector3.subtract(planePoint2Point, point, this._firstPosition)
    //归一化
    const normalizeVec = Vector3.create()
    Vector3.normalize(normalizeVec, planePoint2Point)
    //计算点积
    const cosTheta = Vector3.dot(normalizeVec, this._normal)
    //计算距离
    const distance = Vector3.length(planePoint2Point) * cosTheta
    return distance
  }
}

1.2、在每一帧中,获取视锥体的世界坐标

 this._positions = [
      0, 0, 0,//眼睛位置
      rightNear, topNear, -this._near,//近平面右上坐标 3
      rightNear, bottomNear, -this._near,//近平面右下坐标 6
      leftNear, topNear, -this._near,//近平面左上坐标 9
      leftNear, bottomNear, -this._near,//近平面左下坐标 12
      rightFar, topFar, -this._far,//远平面右上 15
      rightFar, bottomFar, -this._far,//远平面右下 18
      leftFar, topFar, -this._far,//远平面左上 21
      leftFar, bottomFar, -this._far,//远平面左下 24
    ]


/**
   * 转换到世界坐标下
   * @param {*} positions 
   * @returns 
   */
  getWorldPosition() {
    const positions = this._positions
    const worldPositions = []
    //转为世界坐标
    for (let i = 0; i < positions.length; i += 3) {
      const vector = { elements: [positions[i], positions[i + 1], positions[i + 2], 1.0] }
      const worldVector = this._camera.inverseViewMatrix.multiplyVector4(vector)
      worldPositions[i] = worldVector.elements[0]
      worldPositions[i + 1] = worldVector.elements[1]
      worldPositions[i + 2] = worldVector.elements[2]
    }
    return worldPositions
  }

1.2、每一帧下根据世界坐标计算视锥体六个面

/**
   * 获取近平面
   * @returns 
   */
  getNearPlane() {
    const pointNearRightTop = Vector3.fromValues(this._worldPositions[3], this._worldPositions[4], this._worldPositions[5])
    const pointNearRightBottom = Vector3.fromValues(this._worldPositions[6], this._worldPositions[7], this._worldPositions[8])
    const pointNearLeftBottom = Vector3.fromValues(this._worldPositions[12], this._worldPositions[13], this._worldPositions[14])
    const nearPlane = new PlaneGeometry(pointNearRightTop, pointNearRightBottom, pointNearLeftBottom)
    return nearPlane
  }

  /**
   * 获取左平面
   * @returns 
   */
  getLeftPlane() {
    const pointFarLeftTop = Vector3.fromValues(this._worldPositions[21], this._worldPositions[22], this._worldPositions[23])
    const pointNearLeftTop = Vector3.fromValues(this._worldPositions[9], this._worldPositions[10], this._worldPositions[11])
    const pointNearLeftBottom = Vector3.fromValues(this._worldPositions[12], this._worldPositions[13], this._worldPositions[14])
    const leftPlane = new PlaneGeometry(pointFarLeftTop, pointNearLeftTop, pointNearLeftBottom)
    return leftPlane
  }

  /**
   * 获取右平面
   * @returns 
   */
  getRightPlane() {
    const pointFarRightTop = Vector3.fromValues(this._worldPositions[15], this._worldPositions[16], this._worldPositions[17])
    const pointFarRightBottom = Vector3.fromValues(this._worldPositions[18], this._worldPositions[19], this._worldPositions[20])
    const pointNearRightBottom = Vector3.fromValues(this._worldPositions[6], this._worldPositions[7], this._worldPositions[8])
    const rightPlane = new PlaneGeometry(pointFarRightTop, pointFarRightBottom, pointNearRightBottom)
    return rightPlane
  }


  /**
   * 获取上平面
   * @returns 
   */
  getTopPlane() {
    const pointNearLeftTop = Vector3.fromValues(this._worldPositions[9], this._worldPositions[10], this._worldPositions[11])
    const pointFarLeftTop = Vector3.fromValues(this._worldPositions[21], this._worldPositions[22], this._worldPositions[23])

    const pointFarRightTop = Vector3.fromValues(this._worldPositions[15], this._worldPositions[16], this._worldPositions[17])
    const topPlane = new PlaneGeometry(pointNearLeftTop, pointFarLeftTop, pointFarRightTop)
    return topPlane
  }

  /**
   * 获取下平面
   * @returns 
   */
  getBottomPlane() {
    const pointNearRightBottom = Vector3.fromValues(this._worldPositions[6], this._worldPositions[7], this._worldPositions[8])
    const pointFarRightBottom = Vector3.fromValues(this._worldPositions[18], this._worldPositions[19], this._worldPositions[20])

    const pointFarLeftBottom = Vector3.fromValues(this._worldPositions[24], this._worldPositions[25], this._worldPositions[26])
    const bottomPlane = new PlaneGeometry(pointNearRightBottom, pointFarRightBottom, pointFarLeftBottom)
    return bottomPlane
  }

  /**
   * 获取远平面
   */
  getFarPlane() {
    const pointFarRightBottom = Vector3.fromValues(this._worldPositions[18], this._worldPositions[19], this._worldPositions[20])
    const pointFarRightTop = Vector3.fromValues(this._worldPositions[15], this._worldPositions[16], this._worldPositions[17])

    const pointFarLeftTop = Vector3.fromValues(this._worldPositions[21], this._worldPositions[22], this._worldPositions[23])
    const farPlane = new PlaneGeometry(pointFarRightBottom, pointFarRightTop, pointFarLeftTop)
    return farPlane
  }

1.4、计算每一个面与包围球是否相交

/**
   * 根据包围球判断是否剔除mesh
   * @param {*} sphere 
   * @returns 
   */
  isCulledMeshBySphere(sphere) {
    let culled = false
    const radius = sphere.radius
    const center = sphere.center
    const refCenter = this._camera.refCenter
    const distPoint = Vector3.fromValues(center[0] - refCenter[0], center[1] - refCenter[1], center[2] - refCenter[2])

    const frustGeometry = this
    //当distance为负值,并且距离大于半径时剔除
    const nearPlane = frustGeometry.getNearPlane()
    const distanceNear = nearPlane.getDistanceToPoint(distPoint)
    if (distanceNear < -radius) {
      console.log('近平面剔除')
      culled = true
    }

    const leftPlane = frustGeometry.getLeftPlane()
    const distanceLeft = leftPlane.getDistanceToPoint(distPoint)
    if (distanceLeft < -radius) {
      console.log('左平面剔除')
      culled = true
    }

    const rightPlane = frustGeometry.getRightPlane()
    const distanceRight = rightPlane.getDistanceToPoint(distPoint)
    if (distanceRight < -radius) {
      console.log('右平面剔除')
      culled = true
    }

    const farPlane = frustGeometry.getFarPlane()
    const distanceFar = farPlane.getDistanceToPoint(distPoint)
    if (distanceFar < -radius) {
      console.log('远平面剔除')
      culled = true
    }

    const topPlane = frustGeometry.getTopPlane()
    const distanceTop = topPlane.getDistanceToPoint(distPoint)
    if (distanceTop < -radius) {
      console.log('上平面剔除')
      culled = true
    }


    const bottomPlane = frustGeometry.getBottomPlane()
    const distanceBottom = bottomPlane.getDistanceToPoint(distPoint)
    if (distanceBottom < -radius) {
      console.log('下平面剔除')
      culled = true
    }
    return culled
  }