總覽
- 在之前的編程練習中,我們實現了基礎的光線追蹤算法,具體而言是光線傳輸、光線與三角形求交。
- 我們采用了這樣的方法尋找光線與場景的交點:遍歷場景中的所有物體,判斷光線是否與它相交。
- 在場景中的物體數量不大時,該做法可以取得良好的結果,但當物體數量增多、模型變得更加復雜,該做法將會變得非常低效。
- 因此,我們需要加速結構來加速求交過程。在本次練習中,我們重點關注物體划分算法
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; //材質
-