【UE4】GAMES101 图形学作业6:BVH划分算法加速求交(含二叉树应用)
总览
- 在之前的编程练习中,我们实现了基础的光线追踪算法,具体而言是光线传输、光线与三角形求交。
- 我们采用了这样的方法寻找光线与场景的交点:遍历场景中的所有物体,判断光线是否与它相交。
- 在场景中的物体数量不大时,该做法可以取得良好的结果,但当物体数量增多、模型变得更加复杂,该做法将会变得非常低效。
- 因此,我们需要加速结构来加速求交过程。在本次练习中,我们重点关注物体划分算法
Bounding Volume Hierarchy (BVH)
。本练习要求你实现Ray-BoundingVolume 求交与BVH 查找。- 首先,你需要从上一次编程练习中引用以下函数:
Render()
in Renderer.cpp
将你的光线生成过程粘贴到此处,并且按照新框架更新相应调用的格式。Triangle::getIntersection
in Triangle.hpp
将你的光线-三角形相交函数粘贴到此处,并且按照新框架更新相应相交信息的格式。
- 在本次编程练习中,你需要实现以下函数:
IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg)
in the Bounds3.hpp
这个函数的作用是判断包围盒BoundingBox 与光线是否相交,你需要按照课程介绍的算法实现求交过程。getIntersection(BVHBuildNode* node, const Ray ray)
in BVH.cpp
建立BVH 之后,我们可以用它加速求交过程。该过程递归进行,你将在其中调用你实现的Bounds3::IntersectP.
- 首先,你需要从上一次编程练习中引用以下函数:
评分
- [5 points] 提交格式正确,包含所有需要的文件;代码可以在虚拟机下正确编译运行。
- [20 points] 包围盒求交:正确实现光线与包围盒求交函数。
- [15 points] BVH 查找:正确实现BVH 加速的光线与场景求交。
- [加分项20 points] SAH 查找:自学SAH(Surface Area Heuristic) , 正确实现SAH 加速,并且提交结果图片,并在README.md 中说明SVH 的实现方法,并对比BVH、SVH 的时间开销。
(可参考http://15462.courses.cs.cmu.edu/fall2015/lecture/acceleration/slide_024,也可以查找其他资料)
UE4实现
-
版本 4.26.2
-
AHw6_Main::Render()
修改作业5光线生成的代码void AHw6_Main::Render() { FTexture2DMipMap& Mip = T_Result->PlatformData->Mips[0]; FColor* Data = (FColor*)Mip.BulkData.Lock(LOCK_READ_ONLY); float scale = UKismetMathLibrary::DegTan(fov * 0.5f); float imageAspectRatio = width / (float)height; // Use this variable as the eye position to start your rays. FVector eye_pos = cameraComp->GetComponentLocation(); int m = rowNumber * width; for (int i = 0; i < width; ++i) { float x = (2 * (i + 0.5) / (float)width - 1) * imageAspectRatio * scale; float y = (1 - 2 * (rowNumber + 0.5) / (float)height) * scale; FVector dir = FVector(1, x, y); // Don't forget to normalize this direction! dir.Normalize(); FVector finalColor = castRay(Ray(eye_pos, dir), 0) * 255; framebuffer[m] = FColor(finalColor.X, finalColor.Y, finalColor.Z, 255); Data[m] = framebuffer[m]; //texture Data m++; } updateCounter++; Mip.BulkData.Unlock(); if (updateCounter >= updateTextureIntval) { updateCounter = 0; T_Result->UpdateResource(); } rowNumber++; if (m == width * height) { bStartScan = false; //TextureFromImgArr(framebuffer); } }
-
UHw6_Triangle::getIntersection(Ray ray)
打到三角面,即为结果Intersection UHw6_Triangle::getIntersection(Ray ray) { Intersection inter; if (FVector::DotProduct(ray.direction, normal) > 0) return inter; double u, v, t_tmp = 0; FVector pvec = FVector::CrossProduct(ray.direction, e2); double det = FVector::DotProduct(e1, pvec); if (fabs(det) < EPSILON) return inter; double det_inv = 1. / det; FVector tvec = ray.origin - v0; u = FVector::DotProduct(tvec, pvec) * det_inv; if (u < 0 || u > 1) return inter; FVector qvec = FVector::CrossProduct(tvec, e1); v = FVector::DotProduct(ray.direction, qvec) * det_inv; if (v < 0 || u + v > 1) return inter; t_tmp = FVector::DotProduct(e2, qvec) * det_inv; // TODO find ray triangle intersection if (t_tmp < 0) return inter; inter.distance = t_tmp; inter.coords = ray(t_tmp); inter.happened = true; inter.m = m; inter.normal = normal; inter.obj = Cast<IObjectInterface>(this); return inter; }
包围盒相交计算
-
图示 AABB
-
bool Bounds3::IntersectP(const Ray& ray, const FVector& invDir, const FVector& dirisNeg)
bool IntersectP(const Ray& ray, const FVector& invDir, const FVector& dirisNeg) const { // to-do float tEnter = -kInfinity; float tExit = kInfinity; for (int i = 0; i < 3; i++) { float tMin = (pMin[i] - ray.origin[i]) * invDir[i]; float tMax = (pMax[i] - ray.origin[i]) * invDir[i]; if (dirisNeg[i] < 0) std::swap(tMin, tMax); tEnter = std::max(tEnter, tMin); tExit = std::min(tExit, tMax); } return tEnter < tExit && tExit >= 0; }
BVH 加速查找
-
图示
-
Intersection UBVHAccel::getIntersection(UBVHBuildNode node, const Ray& ray)*
Intersection UBVHAccel::getIntersection(UBVHBuildNode* node, const Ray& ray) const { // TODO Traverse the BVH to find intersection Intersection isect; if (node ==nullptr) return isect; FVector dirisNeg = FVector::ZeroVector; //轴正负 dirisNeg.X = ray.direction.X > 0 ? 1 : -1; dirisNeg.Y = ray.direction.Y > 0 ? 1 : -1; dirisNeg.Z = ray.direction.Z > 0 ? 1 : -1; if (false == node->bounds.IntersectP(ray, ray.direction_inv, dirisNeg)) //判断是否在包围盒 return isect; if (node->left == nullptr && node->right == nullptr) { //直到叶子节点 isect = node->object->getIntersection(ray); if(isect.happened) node->bounds.Draw(GetOuter()->GetWorld(), FLinearColor::Blue, 0.05f); return isect; } //node->bounds.Draw(worldContex, FLinearColor::Yellow, 0.1f); Intersection leftInter = getIntersection(node->left, ray); // 不到叶子,就一直递归下去 Intersection rightInter = getIntersection(node->right, ray); return leftInter.distance < rightInter.distance ? leftInter : rightInter; //选比较近的 }
效果
-
jpg
-
gif
小结
-
移植时,原生C++和UE4 C++混用容易出问题,特别是普通结构体存储UObject* 对象
-
存储接口用
TScriptInterface<IObjectInterface>
、TArray<TScriptInterface<IObjectInterface>>
并用UPROPERTY()
修饰,不要图省事而用 IObjectInterface* -
UE4 中的最值
- TNumericLimits
::Max(); - TNumericLimits
::Min();
- TNumericLimits
-
FVector
- 求最值
- pMin = pMin.ComponentMin(b2.pMin);
- pMax = pMax.ComponentMax(b2.pMax);
- UKismetMathLibrary::VLerp
- 求最值
-
UObject 中的getWorld() 可以打印日志,但貌似没法再场景中绘制点、线、框
-
MoveTemp() 使用
-
UPROPERTY()不能描述TSharedPtr Unrecognized type 'TSharedPtr' - type must be a UCLASS, USTRUCT or UENUM
-
获取顶点数据
FStaticMeshLODResources& LodResources = SM->RenderData->LODResources[LODIndex]; FPositionVertexBuffer& VertexPosBuffer = LodResources.VertexBuffers.PositionVertexBuffer;
-
TsharedPtr 的类型判定:使用
std::is_same<std::remove_reference<decltype( class ins )::type, class >::value
TSharedPtr<AreaLight> area_ptr = StaticCastSharedPtr<AreaLight>(light); if (std::is_same<std::remove_reference<decltype(*light)>::type, AreaLight>::value) { UKismetSystemLibrary::PrintString(GetWorld(), "is AreaLight"); }
-
轴方面的问题,注意交换、正负
-
SVH 没有实现
附录
代码框架
源码就不放了,有点乱。本文地址
-
文件目录
/GAMES101/Source/GAMES101/Assignment6 ├─ Hw6_Main.cpp ├─ Hw6_Main.h ├─ BVHAccel.cpp - ├─ BVHAccel.h ├─ Hw6_MeshTriangle.cpp ├─ Hw6_MeshTriangle.h ├─ Bounds3.h ├─ Hw6_Global.h └─ ObjectInterface.h
-
class AHw6_Main : public AActor
入口和渲染void InitialScene(); //初始化场景组件 void buildBVHTree(); //构建 BVH 树 Intersection intersect(const Ray& ray) const; //求交 void Render(); //渲染入口,计算光线属性,更新贴图 FVector castRay(const Ray &ray, int depth); //光照计算 FVector reflect(const FVector& I, const FVector& N); //反射 FVector refract(const FVector& I, const FVector& N, const float& ior); //折射 float fresnel(const FVector& I, const FVector& N, const float& ior); //菲涅尔 void CreateTexture(); //贴图相关
-
class UBVHBuildNode : public UObject
BVH 节点Bounds3 bounds; //区域范围 UBVHBuildNode* left; //左节点 UBVHBuildNode* right; //右节点 TScriptInterface<IObjectInterface> object; //区域内的物体
-
class UBVHAccel : public UObject
BVH 加速树//初始化 void Init(TArray<TScriptInterface<IObjectInterface>> p, int maxPrimsInNode = 1, SplitMethod splitMethod = SplitMethod::NAIVE, bool _bDraw = false); Intersection Intersect(const Ray& ray) const; //求交,本质还是调用 getIntersection Intersection getIntersection(UBVHBuildNode* node, const Ray& ray)const; //二叉树搜索求交 UBVHBuildNode* recursiveBuild(TArray<TScriptInterface<IObjectInterface>> objects); 构建节点,可递归 UPROPERTY() UBVHBuildNode* root; UPROPERTY() TArray<TScriptInterface<IObjectInterface>> primitives; bool IntersectP(const Ray& ray) const; //无用
-
class Bounds3
表示包围盒空间范围的类FVector pMin, pMax; // two points to specify the bounding box Bounds3() //三种构造方法 Bounds3(const FVector p) : pMin(p), pMax(p) {} Bounds3(const FVector p1, const FVector p2) FVector Diagonal() const { return pMax - pMin; } //对角线向量 int maxExtent() const // 最大轴分量,0、1、2分别代表 X、Y、Z 轴,便于按最长划分空间 double SurfaceArea() const // 表面积,暂时无用 FVector Centroid() { return 0.5 * pMin + 0.5 * pMax; } // 中心点位置 bool Overlaps(const Bounds3& b1, const Bounds3& b2) // 两空间是否重叠,暂时无用 bool Inside(const FVector& p, const Bounds3& b) // 点是否在空间内 inline const FVector& operator[](int i) const // 取pMin pMax void Union(const Bounds3& b2) //求空间并集 void Union(const FVector& p) bool IntersectP(const Ray& ray, const FVector& invDir, const FVector& dirisNeg) const //判断光线是否通过空间包围盒 void Draw(const UObject* worldContex, FLinearColor color, float time = 0.02f, float thickness = 1.5f)
-
IObjectInterface
接口UINTERFACE(MinimalAPI) class UObjectInterface : public UInterface class GAMES101_API IObjectInterface { GENERATED_BODY() public: virtual bool intersect(const Ray& ray) = 0; virtual bool intersect(const Ray& ray, float&, uint32_t&) const = 0; virtual Intersection getIntersection(Ray _ray) = 0; virtual void getSurfaceProperties(const FVector&, const FVector&, const uint32_t&, const FVector2D&, FVector&, FVector2D&) const = 0; virtual FVector evalDiffuseColor(const FVector2D&) const = 0; virtual Bounds3 getBounds() const = 0; };
-
class UHw6_Triangle : public UObject, public IObjectInterface
三角面FVector v0, v1, v2; // vertices A, B ,C , counter-clockwise order FVector e1, e2; // 2 edges v1-v0, v2-v0; FVector t0, t1, t2; // texture coords FVector normal; Material* m; Bounds3 bounding_box; UHw6_Triangle() {} void Init(FVector _v0, FVector _v1, FVector _v2, Material* _m = nullptr) //初始化顶点位置,计算法线(ue4坐标轴需要换下位置)、包围盒 Intersection getIntersection(Ray ray) override; // 求交,重点 void getSurfaceProperties( const FVector& P, const FVector& I, const uint32_t& index, const FVector2D& uv, FVector& N, FVector2D& st) const override { N = normal; }; FVector evalDiffuseColor(const FVector2D&) const override {return FVector(0.5, 0.5, 0.5);} Bounds3 getBounds() const override { return bounding_box; } bool intersect(const Ray& ray) override{ return true; } bool intersect(const Ray& ray, float& tnear, uint32_t& index) const override { return true; }
-
class AHw6_MeshTriangle : public AActor, public IObjectInterface
网格物体Bounds3 bounding_box; Material* m; UPROPERTY() TArray<FVector> vertices; UPROPERTY() TArray<uint32> vertexIndex; UPROPERTY() TArray<FVector2D> stCoordinates; UPROPERTY() TArray<TScriptInterface<IObjectInterface>> triangles; UPROPERTY() UBVHAccel* bvhTree; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* meshComp; void getStaticMeshData(); //将 mesh 拆分成三角面构造 BVH Tree void buildBVHTree(); // 判断光线是否和三角面相交 bool rayTriangleIntersect(const FVector& v0, const FVector& v1, const FVector& v2, const FVector& orig, const FVector& dir, float& tnear, float& u, float& v) const; // 判断是否相交 bool intersect(const Ray& ray, float& tnear, uint32_t& index) const override; bool intersect(const Ray& ray) override { return true; } // 获取空间区域 Bounds3 getBounds() const override { return bounding_box; } // 获取表面信息 void getSurfaceProperties( const FVector& P, const FVector& I, const uint32_t& index, const FVector2D& uv, FVector& N, FVector2D& st) const override; // 获取点对应的颜色 FVector evalDiffuseColor(const FVector2D& st) const override; // 获取相交信息 Intersection getIntersection(Ray ray) override;
-
Hw6_Global.h
全局设置-
enum EMaterialType
材质类型,光照依此进行不同的计算DIFFUSE_AND_GLOSSY REFLECTION_AND_REFRACTION REFLECTION
-
class Material
材质,本作也中尚是单一材质EMaterialType m_type; FVector m_color; FVector m_emission; float ior; float Kd, Ks; float specularExponent;
-
class Light
普通光源FVector position; FVector intensity=FVector::OneVector;
-
class AreaLight : public Light
区域光源,本作也中该部分暂时不需要使用,只在转换时作用float length; FVector normal; FVector u; FVector v;
-
struct Ray
光线结构体FVector origin; //起点 FVector direction, direction_inv; //方向 FVector operator()(double _t) const //返回相交点的位置
-
struct Intersection
相交信息结构体bool happened; //相交标志位 FVector coords; //相交点位置 FVector normal; //交点法线 double distance; //起点到交点距离 IObjectInterface* obj; //交点的物体 Material* m; //材质
-