原理:
通过一个简单的包围盒把物体包围起来,射线和场景中的物体求交之前,会先和这个包围盒进行求交,如果该射线没有碰到该包围盒,表明该直线一定不会和包围盒里的物体相交;如果该射线碰到该包围盒,那么再来计算射线是否和包围盒中的物体相交。
作用:
BVH可以使渲染器更加高效,值得注意的是盒子是可以重叠的。当然,盒子内也可以放其他的盒子,这样我们会得到一个树形结构。
怎样判断和盒子是否相交?
2D包围盒的边界是一条线,而3D包围盒的边界是一个面
射线可以看为是一个方程:P(t)=A+B*t
将这个公式实际应用
实现步骤:
1.修改Vector3D,Point3D中的代码,方便接下来的索引操作(对于索引器的操作:索引值为0返回x,值为1返回y,值为2返回z。)
public class Vector3D
{
public double[] data = new double[3];
public double this[int index]
{
get => data[index];
set => data[index] = value;
}
private double _x;
private double _y;
private double _z;
public double X
{
get => data[0];
set => data[0] = value;
}
public double Y
{
get => data[1];
set => data[1] = value;
}
public double Z
{
get => data[2];
set => data[2] = value;
}
//...省略其他代码...//
}
2.创建AABB包围盒的类
public class AABB
{
Vector3D _min;
Vector3D _max;
public AABB()
{
}
public AABB(Vector3D a,Vector3D b)
{
_min = a;
_max = b;
}
public Vector3D Min {
get => _min; set => _min = value; }
public Vector3D Max {
get => _max; set => _max = value; }
public bool hit(Ray r, double tmin,double tmax)
{
for (var a = 0; a < 3; a++)
{
var t0 = Math.Min((Min[a] - r.Origin[a]) / r.Direction[a],
(Max[a] - r.Origin[a]) / r.Direction[a]);
var t1 = Math.Max((Min[a] - r.Origin[a]) / r.Direction[a],
(Max[a] - r.Origin[a]) / r.Direction[a]);
tmin = Math.Max(t0, tmin);
tmax = Math.Min(t1, tmax);
if (tmax <= tmin)
return false;
}
return true;
}
}
3.在GeometryObject基类中添加抽象函数:
//生成物体的包围盒
public abstract bool Bounding_box(double t0,double t1,out AABB box);
4.修改子类Sphere和Moving_sphere
Sphere
public override bool Bounding_box(double t0, double t1,out AABB box)
{
box = new AABB(Center - new Point3D(Radius, Radius, Radius),
Center + new Point3D(Radius, Radius, Radius));
return true;
}
Moving_sphere
public override bool Bounding_box(double t0, double t1,out AABB box)
{
AABB aabb1 = new AABB();
//得到t0和t1两个时刻的包围盒
AABB box0=new AABB(Center0 - new Point3D(radius, radius, radius),
Center0 + new Point3D(radius, radius, radius));
AABB box1 = new AABB(Center1 - new Point3D(radius, radius, radius),
Center1 + new Point3D(radius, radius, radius));
//将t0时的盒子和t1时的盒子放进一个大盒子里
box = Surrounding_box(box0, box1);
return true;
}
5.在GeometryObject中添加Surrounding_box函数
public AABB Surrounding_box(AABB box0, AABB box1)
{
Vector3D small = new Vector3D(Math.Min(box0.Min.X, box1.Min.X),
Math.Min(box0.Min.Y, box1.Min.Y),
Math.Min(box0.Min.Z, box1.Min.Z));
Vector3D big = new Vector3D(Math.Max(box0.Max.X, box1.Max.X),
Math.Max(box0.Max.Y, box1.Max.Y),
Math.Max(box0.Max.Z, box1.Max.Z));
return new AABB(small, big);
}
6.新建BVHnode类
class BVHnode:GeometryObject
{
GeometryObject left;
GeometryObject right;
AABB box;
public BVHnode(){
}
private static double random()
{
var seed = Guid.NewGuid().GetHashCode();
Random r = new Random(seed);
int i = r.Next(0, 100000);
return (double)i / 100000;
}
//用来排序的嵌套函数
int Compare(GeometryObject a, GeometryObject b, int i)
{
AABB l = new AABB(), r = new AABB();
//if (!a.BoundingBox(0, 0, ref l) || !b.BoundingBox(0, 0, ref r)) throw new Exception("NULL");
return l.Min[i] - r.Min[i] < 0 ? -1 : 1;
}
//用来排序的分割数组的嵌套函数。
GeometryObject[] SplitArray(GeometryObject[] Source, int StartIndex, int EndIndex)
{
var result = new GeometryObject[EndIndex - StartIndex + 1];
for (var i = 0; i <= EndIndex - StartIndex; i++) result[i] = Source[i + StartIndex];
return result;
}
public BVHnode(GeometryObject[] p, int n, double time0, double time1)
{
//随机一个轴。 x轴:0 y轴:1 z轴:3
int method = (int)(3 * random());
//转换为List然后使用排序,最后再转换为Array
//排序规则使用lambda表达式转向比较函数,并加入轴向参数
var temp_list = p.ToList();
temp_list.Sort((a, b) => Compare(a, b, method));
p = temp_list.ToArray();
//检测当前子节点数量,如果大于2则继续分割。
switch (n)
{
case 1:
left = right = p[0];
break;
case 2:
left = p[0];
right = p[1];
break;
default: //拆分
left = new BVHnode(SplitArray(p, 0, n / 2 - 1), n / 2, time0, time1);
right = new BVHnode(SplitArray(p, n / 2, n - 1), n - n / 2, time0, time1);
break;
}
//根据子节点生成当前节点的包围盒
AABB box_left = new AABB(), box_right = new AABB();
//if (!left.BoundingBox(time0, time1, ref box_left) || !right.BoundingBox(time0, time1, ref box_right))
// throw new Exception("no bounding box in bvh_node constructor");
box = Surrounding_box(box_left, box_right);
}
public override bool Bounding_box(double t0, double t1,out AABB b)
{
b=box;
return true;
}
//检查光线是否和盒子相交
public override bool Hit(Ray ray, out ShadeRec sr)
{
double t_min=0, t_max= 1e-5;
if (box.hit(ray, t_min, t_max))
{
ShadeRec left_rec, right_rec;
bool hit_left = left.Hit(ray,out left_rec);
bool hit_right = right.Hit(ray,out right_rec);
if (hit_left && hit_right)
{
if (left_rec.HitT < right_rec.HitT)
sr = left_rec;
else
sr = right_rec;
return true;
}
else if (hit_left)
{
sr = left_rec;
return true;
}
else if (hit_right)
{
sr = right_rec;
return true;
}
else
{
sr = null;
return false;
}
}
else
{
sr = null;
return false;
}
}
public override bool ShadowHit(Ray ray)
{
throw new NotImplementedException();
}
}
参考专栏:https://zhuanlan.zhihu.com/c_189375388
码云地址:https://gitee.com/xmr123/Ray-Tracing/tree/BVH