private void Build() { var triangles = _bvhData.scene.triangles; var vetices = _bvhData.scene.vertices; var rootSpec = NodeSpec.New(); rootSpec.numRef = triangles.Count; // 遍历所有图元(引用),计算根节点的包围盒 for (int i = 0; i < rootSpec.numRef; i++) { var pRef = PrimitiveRef.New(); pRef.triangleIdx = i; // 计算单个图元的包围盒 for (int j = 0; j < 3; j++) { pRef.bounds.Union(vetices[triangles[i][j]]); } rootSpec.bounds.Union(pRef.bounds); _refStack.Add(pRef); } // 最小重叠面积,只有重叠面积大于这个值时才考虑进行spatial split _minOverlap = rootSpec.bounds.Area * SPLIT_ALPHA; // 递归创建BVH _bvhData.root = BuildNodeRecursively(rootSpec, 0); Debug.Log("Build Completely."); }
// 不能直接new NodeSpec,必须调用此方法,否则Bounds初始化成员都是0 public static NodeSpec New() { NodeSpec spec = new NodeSpec(); spec.bounds = AABB.New(); return(spec); }
private BVHNode BuildNodeRecursively(NodeSpec spec, int depth) { // 节点只有一个图元的时候没必要再继续分割 if (spec.numRef <= MIN_LEAF_SIZE || depth >= MAX_DEPTH) { return(CreatLeaf(spec)); } // 挑选使用object split还是spatial split float leafSAH = spec.bounds.Area * spec.numRef; float nodeSAH = spec.bounds.Area * 0.125f;//spec.Bounds.Area * 2; // 节点遍历的固定开销,2是个经验值(不一定是最好的) ObjectSplit objectSplit = FindObjectSplit(spec, nodeSAH); SpatialSplit spatialSplit = SpatialSplit.New(); if (depth < MAX_SPATIAL_DEPTH) { var overlap = objectSplit.leftBounds; overlap.Intersect(objectSplit.rightBounds); if (overlap.Area >= _minOverlap) { spatialSplit = FindSpatialSplit(spec, nodeSAH); } } // 叶节点胜出,不论是Object还是Spatial slpit,分割后的 float minSAH = Mathf.Min(Mathf.Min(leafSAH, objectSplit.sah), spatialSplit.sah); if (minSAH == leafSAH && spec.numRef <= MAX_LEAF_SIZE) { return(CreatLeaf(spec)); } // spatial split胜出,尝试执行spatial split NodeSpec left = NodeSpec.New(); NodeSpec right = NodeSpec.New(); if (minSAH == spatialSplit.sah) { PerformSpatialSplit(ref left, ref right, spec, spatialSplit); } // objcet split胜出,或spatial split并未取得实质性进展,执行object split if (left.numRef == 0 || right.numRef == 0) { PerformObjectSplit(ref left, ref right, spec, objectSplit); } _numDuplicates += left.numRef + right.numRef - spec.numRef; // 由于后文取下标的方式,一定是先右后左 var rightNode = BuildNodeRecursively(right, depth + 1); var leftNode = BuildNodeRecursively(left, depth + 1); return(new InnerNode(spec.bounds, leftNode, rightNode)); }
BVHNode CreatLeaf(NodeSpec spec) { for (int i = 0; i < spec.numRef; i++) { var end = _refStack.Count - 1; var pRef = _refStack[end]; _bvhData.triIndices.Add(pRef.triangleIdx); _refStack.RemoveAt(end); } return(new LeafNode(spec.bounds, _bvhData.triIndices.Count - spec.numRef, _bvhData.triIndices.Count)); }
private void PerformObjectSplit(ref NodeSpec left, ref NodeSpec right, NodeSpec spec, ObjectSplit split) { int refIdx = _refStack.Count - spec.numRef; _refComparer.sortDim = split.dim; _refStack.Sort(refIdx, spec.numRef, _refComparer); left.numRef = split.numLeft; left.bounds = split.leftBounds; right.numRef = spec.numRef - split.numLeft; right.bounds = split.rightBounds; }
private ObjectSplit FindObjectSplit(NodeSpec spec, float nodeSAH) { ObjectSplit split = ObjectSplit.New(); int refIdx = _refStack.Count - spec.numRef; // CreateLeaf以后_refStack发生了变化 for (byte dim = 0; dim < 3; dim++) { _refComparer.sortDim = dim; _refStack.Sort(refIdx, spec.numRef, _refComparer); // 从右到左,记录每一种可能的分割后,处在“右边”包围盒的 AABB rightBounds = AABB.New(); for (int i = spec.numRef - 1; i > 0; i--) { rightBounds.Union(_refStack[refIdx + i].bounds); _rightBounds[i - 1] = rightBounds; // 每一个都记录下来,后面才能比较 } // 从左到右尝试分割,比较计算得到最佳SAH AABB leftBounds = AABB.New(); for (int i = 1; i < spec.numRef; i++) { leftBounds.Union(_refStack[refIdx + i - 1].bounds); float sah = nodeSAH + leftBounds.Area * i /*左边有i个图元*/ + _rightBounds[i - 1].Area * (spec.numRef - i); if (sah < split.sah) { split.sah = sah; split.dim = dim; split.numLeft = i; split.leftBounds = leftBounds; split.rightBounds = _rightBounds[i - 1]; } } } return(split); }
/// <summary> /// 给定分割平面后划分当前节点下的所有图元 /// </summary> private void PerformSpatialSplit(ref NodeSpec left, ref NodeSpec right, NodeSpec spec, SpatialSplit split) { // 划分在左侧: [leftStart, leftEnd] // 被分割的: [leftEnd, rightStart] // 划分在右侧: [rightStart, refs.Count] var refs = _refStack; int leftStart = refs.Count - spec.numRef; int leftEnd = leftStart; int rightStart = refs.Count; left.bounds = right.bounds = AABB.New(); // 处理完全只在分割平面某一侧的图元 for (int i = leftEnd; i < rightStart; i++) { // 完全在左边的往左边放 if (refs[i].bounds.max[split.dim] <= split.pos) { left.bounds.Union(refs[i].bounds); refs.Swap(i, leftEnd++); } // 完全在右边的往右边放 else if (refs[i].bounds.min[split.dim] >= split.pos) { right.bounds.Union(refs[i].bounds); refs.Swap(i--, --rightStart); } } // 处理被分割平面分开了的图元,可能被划分在左侧或右侧,或者同时划分在两侧 while (leftEnd < rightStart) { // 初步分割引用 PrimitiveRef lref, rref; SplitReference(out lref, out rref, refs[leftEnd], split.dim, split.pos); AABB lub = left.bounds; // 不分割,完全划分到【左】侧时的包围盒,left unsplit bounds AABB rub = right.bounds; // 不分割,完全划分到【右】侧时的包围盒 AABB ldb = left.bounds; // 分割时划分到【左】侧的包围盒,left duplicate bounds AABB rdb = right.bounds; // 分割时划分到【右】侧的包围盒 lub.Union(refs[leftEnd].bounds); rub.Union(refs[leftEnd].bounds); ldb.Union(lref.bounds); rdb.Union(rref.bounds); float lac = leftEnd - leftStart; float rac = refs.Count - rightStart; float lbc = leftEnd - leftStart + 1; float rbc = refs.Count - rightStart + 1; float unsplitLeftSAH = lub.Area * lbc + right.bounds.Area * rac; float unsplitRightSAH = left.bounds.Area * lac + rub.Area * rbc; float duplicateSAH = ldb.Area * lbc + rdb.Area * rbc; float minSAH = Mathf.Min(Mathf.Min(unsplitLeftSAH, unsplitRightSAH), duplicateSAH); // 整个图元划分到左侧 if (minSAH == unsplitLeftSAH) { left.bounds = lub; leftEnd++; } // 整个图元划分到右侧 else if (minSAH == unsplitRightSAH) { right.bounds = rub; refs.Swap(leftEnd, --rightStart); } // 同时划分到两侧 else { left.bounds = ldb; right.bounds = rdb; refs[leftEnd++] = lref; refs.Add(rref); } } left.numRef = leftEnd - leftStart; right.numRef = refs.Count - rightStart; }
private SpatialSplit FindSpatialSplit(NodeSpec spec, float nodeSAH) { // _bins变量每一次分割都被复用 var origin = spec.bounds.min; var binSize = (spec.bounds.max - origin) / N_SPATIAL_BINS; var invBinSize = new Vector3(1f / binSize.x, 1f / binSize.y, 1f / binSize.z); for (int dim = 0; dim < 3; dim++) { for (int i = 0; i < N_SPATIAL_BINS; i++) { _bins[dim, i] = SpatialBin.New(); } } // 把图元分配到3个维度的bin中 for (int refIdx = _refStack.Count - spec.numRef; refIdx < _refStack.Count; refIdx++) { var pRef = _refStack[refIdx]; // ....Vector3Int.FloorToInt 误用了 celling...查半天。。。。 var firstBin = MathUtils.ClampV3Int(Vector3Int.FloorToInt((pRef.bounds.min - origin).Multiply(invBinSize)), Vector3Int.zero, new Vector3Int(N_SPATIAL_BINS - 1, N_SPATIAL_BINS - 1, N_SPATIAL_BINS - 1)); var lastBin = MathUtils.ClampV3Int(Vector3Int.FloorToInt((pRef.bounds.max - origin).Multiply(invBinSize)), firstBin, new Vector3Int(N_SPATIAL_BINS - 1, N_SPATIAL_BINS - 1, N_SPATIAL_BINS - 1)); for (int dim = 0; dim < 3; dim++) { var curRef = pRef; // 从左到右分割,curRef并不更新图元索引,只更新包围盒 for (int i = firstBin[dim]; i < lastBin[dim]; i++) { PrimitiveRef leftRef, rightRef; SplitReference(out leftRef, out rightRef, curRef, dim, origin[dim] + binSize[dim] * (i + 1)); _bins[dim, i].bounds.Union(leftRef.bounds); curRef = rightRef; } _bins[dim, lastBin[dim]].bounds.Union(curRef.bounds); // 分割后图元最右边的包围盒也算进来 // 只对分割后图元所在的第一个和最后一个bin添加图元引用计数 _bins[dim, firstBin[dim]].enter++; _bins[dim, lastBin[dim]].exit++; } } // 根据分割好的bins,来选择最佳分割平面,跟FindObjectSplit类似,只不过是以bin为单位,而不是图元 SpatialSplit split = SpatialSplit.New(); for (byte dim = 0; dim < 3; dim++) { // 从右到左,记录每一种可能的分割后,处在“右边”包围盒的 AABB rightBounds = AABB.New(); for (int i = N_SPATIAL_BINS - 1; i > 0; i--) { rightBounds.Union(_bins[dim, i].bounds); _rightBounds[i - 1] = rightBounds; //_rightBounds用来临时记录右边包围盒的,被复用 } AABB leftBounds = AABB.New(); int leftNum = 0; int rightNum = spec.numRef; for (int i = 1; i < N_SPATIAL_BINS; i++) { leftBounds.Union(_bins[dim, i - 1].bounds); leftNum += _bins[dim, i - 1].enter; rightNum -= _bins[dim, i - 1].exit; float sah = nodeSAH + leftBounds.Area * leftNum + _rightBounds[i - 1].Area * rightNum; if (sah < split.sah) { split.sah = sah; split.dim = dim; split.pos = origin[dim] + binSize[dim] * i; } } } return(split); }