【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; //材質
      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM