【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

    image

  • 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 加速查找

  • 图示

    image

  • 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

    image

  • gif
    image

小结

  • 移植时,原生C++和UE4 C++混用容易出问题,特别是普通结构体存储UObject* 对象

  • 存储接口用 TScriptInterface<IObjectInterface>TArray<TScriptInterface<IObjectInterface>> 并用 UPROPERTY() 修饰,不要图省事而用 IObjectInterface*

  • UE4 中的最值

    • TNumericLimits::Max();
    • TNumericLimits::Min();
  • 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");
    }
    
  • 轴方面的问题,注意交换、正负

    image

  • 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 UObjectBVH 节点

    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; //材质
      
posted @ 2021-10-28 20:27  砥才人  阅读(1178)  评论(0编辑  收藏  举报