【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)
总览
- 在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就可以执行着色并返回像素颜色。
- 在这次作业中,我们要实现两个部分:
- 光线的生成
- 光线与三角的相交。
- 本次代码框架的工作流程为:
- 从main 函数开始。我们定义场景的参数,添加物体(球体或三角形)到场景中,并设置其材质,然后将光源添加到场景中。
- 调用Render(scene) 函数。在遍历所有像素的循环里,生成对应的光线并将返回的颜色保存在帧缓冲区(framebuffer)中。在渲染过程结束后,帧缓冲区中的信息将被保存为图像。
- 在生成像素对应的光线后,我们调用CastRay 函数,该函数调用trace 来查询光线与场景中最近的对象的交点。
- 然后,我们在此交点执行着色。我们设置了三种不同的着色情况,并且已经为你提供了代码。你需要修改的函数是:
- Renderer.cpp 中的
Render()
:这里你需要为每个像素生成一条对应的光线,然后调用函数castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相应像素中。 - Triangle.hpp 中的
rayTriangleIntersect()
: v0, v1, v2 是三角形的三个顶点,orig 是光线的起点,dir 是光线单位化的方向向量。tnear, u, v 是你需要使用我们课上推导的Moller-Trumbore 算法来更新的参数。
- Renderer.cpp 中的
UE4实现
-
版本 4.26.2
-
Render()
-
主要在于将屏幕上的坐标,映射成从视源位置(相机位置)发出的射线
-
像素位置映射到 [-1, 1],注意上下颠倒问题
-
主要代码
void AActor_Assignment5::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) { // generate primary ray direction float pixelCenterX = (float)(i + 0.5f) / width; float pixelCenterY = (float)(rowNumber + 0.5f) / height; float x = (2 * pixelCenterX - 1) * imageAspectRatio * scale; float y = (1- 2 * pixelCenterY) * scale; //轴颠倒 FVector dir = FVector(1, x, y); // Don't forget to normalize this direction! dir.Normalize(); FVector finalColor = castRay(eye_pos, dir, 0)*255; framebuffer[m] = FColor(finalColor.X, finalColor.Y, finalColor.Z, 255); //Data[(height - 1 - rowNumber) * width + i] = framebuffer[m]; Data[m] = framebuffer[m]; //texture Data m++; } Mip.BulkData.Unlock(); T_Result->UpdateResource(); rowNumber++; if (m==width*height) { bFinishedScan = true; TextureFromImgArr(framebuffer); } }
-
-
rayTriangleIntersect()
-
公式
-
代码
bool rayTriangleIntersect(const FVector& v0, const FVector& v1, const FVector& v2, const FVector& orig, const FVector& dir, float& tnear, float& u, float& v) const { // TODO: Implement this function that tests whether the triangle // that's specified bt v0, v1 and v2 intersects with the ray (whose // origin is *orig* and direction is *dir*) // Also don't forget to update tnear, u and v. FVector E1 = v1 - v0; FVector E2 = v2 - v0; FVector S = orig - v0; FVector S1 = FVector::CrossProduct(dir, E2); FVector S2 = FVector::CrossProduct(S, E1); float factor = FVector::DotProduct(S1, E1); float t = 1.0f / factor * FVector::DotProduct(S2, E2); float b1 = 1.0f / factor * FVector::DotProduct(S1, S); float b2 = 1.0f / factor * FVector::DotProduct(S2, dir); if (t > 0 && b1 > 0 && b2 > 0 && (1 - b1 - b2) > 0) { tnear = t; u = b1; v = b2; return true; } return false; }
-
-
效果
代码说明
代码结构
- AActor_Assignment5 测试入口及光线追踪主要函数,继承自AActor
BeginPlay()
初始化入口,调用InitialScene()InitialScene()
初始化各种数据Tick()
调用 Render()Render()
按行进行光线扫描,更新 TexturecastRay()
计算光照和反射后的颜色trace()
判断光线与物体是否相交reflect()
计算反射方向refract()
计算折射方向fresnel()
计算菲涅尔系数CreateTexture()
创建 Texture
- AHw5_Shape 物体形状类,继承自AActor
intersect()
光线相交检测getSurfaceProperties()
物体表面属性evalDiffuseColor()
根据uv计算当前点的颜色SetCenterAndRadius()
设置球心和半径
- AHw5_Sphere 球形,继承自AHw5_Shape
intersect()
重写光线相交检测getSurfaceProperties()
重写物体表面属性
- AHw5_MeshTriangle 三角面构成的物体
intersect()
重写光线相交检测getSurfaceProperties()
重写物体表面属性evalDiffuseColor()
重写根据uv计算当前点的颜色SetProperty()
设置三角面顶点、uv坐标数据等DarwWireframe()
绘制线框
源码
-
Actor_Assignment5.h
点击查看代码
#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Hw5_Shape.h" #include "Camera/CameraComponent.h" #include "Components/BillboardComponent.h" #include "Actor_Assignment5.generated.h" USTRUCT() struct FHit_payload { GENERATED_BODY() float tNear; uint32 index; FVector2D uv; AHw5_Shape* hit_obj; }; UCLASS() class GAMES101_API AActor_Assignment5 : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AActor_Assignment5(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; void InitialScene(); void Render(); FVector castRay(const FVector& orig, const FVector& dir, int depth); TOptional<FHit_payload> trace(const FVector& orig, const FVector& dir); 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(); void TextureFromImgArr(const TArray<FColor>& SrcData); public: UPROPERTY(EditAnywhere) int32 width = 128; UPROPERTY(EditAnywhere) int32 height = 96; float fov = 90; FVector backgroundColor = FVector(0.235294, 0.67451, 0.843137); UPROPERTY(EditAnywhere) int maxDepth = 5; float epsilon = 0.001; //本处偏移需要放大十倍,否则有噪点 TArray<FVector> lights_pos; float lights_Intensity; UPROPERTY() TArray<AHw5_Shape*> objects; UPROPERTY(VisibleAnywhere) USceneComponent* rootComp; UPROPERTY(VisibleAnywhere) UCameraComponent* cameraComp; UPROPERTY(VisibleAnywhere) UBillboardComponent* light1; UPROPERTY(VisibleAnywhere) UBillboardComponent* light2; UPROPERTY(VisibleAnywhere,BlueprintReadWrite) UTexture2D* T_Result; bool bFinishedScan = false; int32 rowNumber = 0; UPROPERTY() TArray<FColor> framebuffer; };
-
Actor_Assignment5.cpp
点击查看代码
#include "Actor_Assignment5.h" #include "Hw5_Sphere.h" #include "Hw5_MeshTriangle.h" #include "Kismet/GameplayStatics.h" float kInfinity = TNumericLimits<float>::Max(); // Sets default values AActor_Assignment5::AActor_Assignment5() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; rootComp = CreateDefaultSubobject<USceneComponent>(TEXT("rootComp")); SetRootComponent(rootComp); cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("cameraComp")); cameraComp->SetupAttachment(rootComp); cameraComp->SetWorldLocation(FVector(-600, 0, 0)); cameraComp->bCameraMeshHiddenInGame=false; light1 = CreateDefaultSubobject<UBillboardComponent>(TEXT("l1")); light2 = CreateDefaultSubobject<UBillboardComponent>(TEXT("l2")); light1->SetupAttachment(rootComp); light2->SetupAttachment(rootComp); light1->SetHiddenInGame(false); light2->SetHiddenInGame(false); static ConstructorHelpers::FObjectFinder<UTexture2D> texAsset(TEXT("Texture2D'/Engine/EditorResources/LightIcons/S_LightDirectional.S_LightDirectional'")); if (texAsset.Succeeded()) { light1->SetSprite(texAsset.Object); light2->SetSprite(texAsset.Object); } } // Called when the game starts or when spawned void AActor_Assignment5::BeginPlay() { Super::BeginPlay(); InitialScene(); framebuffer.Init(FColor(0,0, 0, 255), width * height); CreateTexture(); } // Called every frame void AActor_Assignment5::Tick(float DeltaTime) { Super::Tick(DeltaTime); if(!bFinishedScan) Render(); } void AActor_Assignment5::InitialScene() { FTransform Spawntransform = FTransform(FRotator::ZeroRotator, FVector::ZeroVector); AHw5_Sphere* Sphere1 = GetWorld()->SpawnActorDeferred<AHw5_Sphere>(AHw5_Sphere::StaticClass(), Spawntransform); if (Sphere1) { Sphere1->materialType = EMaterialType::DIFFUSE_AND_GLOSSY; Sphere1->diffuseColor = FVector(0.6, 0.7, 0.8); Sphere1->SetCenterAndRadius(FVector(4, -1, 0) * 50, 2 * 50); UGameplayStatics::FinishSpawningActor(Sphere1, Spawntransform); objects.Add(Sphere1); } AHw5_Sphere* Sphere2 = GetWorld()->SpawnActorDeferred<AHw5_Sphere>(AHw5_Sphere::StaticClass(), Spawntransform); if (Sphere2) { Sphere2->materialType = EMaterialType::REFLECTION_AND_REFRACTION;//REFLECTION_AND_REFRACTION; Sphere2->ior = 1.5; Sphere2->SetCenterAndRadius(FVector(0, 0.5, 0) * 50, 1.5 * 50); UGameplayStatics::FinishSpawningActor(Sphere2, Spawntransform); objects.Add(Sphere2); } Spawntransform = FTransform(FRotator::ZeroRotator, FVector::ZeroVector); AHw5_MeshTriangle* mesh = GetWorld()->SpawnActorDeferred<AHw5_MeshTriangle>(AHw5_MeshTriangle::StaticClass(), Spawntransform); if (mesh) { mesh->materialType = EMaterialType::DIFFUSE_AND_GLOSSY; TArray<FVector> verts = { {-3, 6,-3}, {9, 6,-3}, {9, -6, -3}, {-3, -6, -3} }; for (auto& v : verts) v *= 50; TArray<uint32> vertIndex = { 0, 1, 3, 1, 2, 3 }; TArray<FVector2D> st = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; mesh->SetProperty(verts, vertIndex, 2, st); UGameplayStatics::FinishSpawningActor(mesh, Spawntransform); objects.Add(mesh); } lights_Intensity = 0.5 ; lights_pos.Add(FVector(-28, -20, 70) * 10); lights_pos.Add(FVector(4, 30, 50) * 10); light1->SetWorldLocation(lights_pos[0]); light2->SetWorldLocation(lights_pos[1]); } void AActor_Assignment5::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) { // generate primary ray direction float pixelCenterX = (float)(i + 0.5f) / width; float pixelCenterY = (float)(rowNumber + 0.5f) / height; float x = (2 * pixelCenterX - 1) * imageAspectRatio * scale; float y = (1- 2 * pixelCenterY) * scale; //轴颠倒 FVector dir = FVector(1, x, y); // Don't forget to normalize this direction! dir.Normalize(); FVector finalColor = castRay(eye_pos, dir, 0)*255; framebuffer[m] = FColor(finalColor.X, finalColor.Y, finalColor.Z, 255); //Data[(height - 1 - rowNumber) * width + i] = framebuffer[m]; Data[m] = framebuffer[m]; //texture Data m++; } Mip.BulkData.Unlock(); T_Result->UpdateResource(); rowNumber++; if (m==width*height) { bFinishedScan = true; TextureFromImgArr(framebuffer); } } FVector AActor_Assignment5::castRay(const FVector& orig, const FVector& dir, int depth) { if (depth > maxDepth) { return FVector(0.0, 0.0, 0.0); } FVector hitColor = backgroundColor; auto payload = trace(orig, dir); if (payload.IsSet()) { FVector hitPoint = orig + dir * payload->tNear; //UKismetSystemLibrary::DrawDebugPoint(GetWorld(), hitPoint, 1, FColor::Red, 20); UKismetSystemLibrary::DrawDebugLine(GetWorld(), orig, hitPoint, FColor(255,0,0,48), .02f, 1.0f); FVector N; // normal FVector2D st; // st coordinates payload->hit_obj->getSurfaceProperties(hitPoint, dir, payload->index, payload->uv, N, st); switch (payload->hit_obj->materialType) { case EMaterialType::REFLECTION_AND_REFRACTION: { FVector reflectionDirection = reflect(dir, N); FVector refractionDirection = refract(dir, N, payload->hit_obj->ior); FVector reflectionRayOrig = (FVector::DotProduct(reflectionDirection, N) < 0) ? hitPoint - N * epsilon : hitPoint + N * epsilon; FVector refractionRayOrig = (FVector::DotProduct(refractionDirection, N) < 0) ? hitPoint - N * epsilon : hitPoint + N * epsilon; FVector reflectionColor = castRay(reflectionRayOrig, reflectionDirection, depth + 1); FVector refractionColor=FVector::ZeroVector; if (reflectionDirection.Size() != 0) refractionColor = castRay(refractionRayOrig, refractionDirection, depth + 1); float kr = fresnel(dir, N, payload->hit_obj->ior); hitColor = reflectionColor * kr + refractionColor * (1 - kr); break } case EMaterialType::REFLECTION: { float kr = fresnel(dir, N, payload->hit_obj->ior); FVector reflectionDirection = reflect(dir, N); FVector reflectionRayOrig = (FVector::DotProduct(reflectionDirection, N) < 0) ? hitPoint - N * epsilon : hitPoint + N * epsilon; hitColor = castRay(reflectionRayOrig, reflectionDirection, depth + 1) * kr; break; } default: { // [comment] // We use the Phong illumation model int the default case. The phong model // is composed of a diffuse and a specular reflection component. // [/comment] FVector lightAmt = FVector::ZeroVector, specularColor = FVector::ZeroVector; FVector shadowPointOrig = (FVector::DotProduct(dir, N) < 0) ? hitPoint + N * epsilon : hitPoint - N * epsilon; // [comment] // Loop over all lights in the scene and sum their contribution up // We also apply the lambert cosine law // [/comment] for (auto& light_position : lights_pos) { FVector lightDir = light_position - hitPoint; // square of the distance between hitPoint and the light float lightDistance2 = FVector::DotProduct(lightDir, lightDir); lightDir.Normalize(); float LdotN = std::max(0.f, FVector::DotProduct(lightDir, N)); // is the point in shadow, and is the nearest occluding object closer to the object than the light itself? auto shadow_res = trace(shadowPointOrig, lightDir); bool inShadow = shadow_res.IsSet() && (shadow_res->tNear * shadow_res->tNear < lightDistance2); if(!inShadow) UKismetSystemLibrary::DrawDebugLine(GetWorld(), hitPoint, light_position, FColor(255,0,0,48), .02f, 1.0f); lightAmt += inShadow ? FVector::ZeroVector : lights_Intensity * LdotN* FVector::OneVector; FVector reflectionDirection = reflect(-lightDir, N); specularColor += powf(std::max(0.f, -FVector::DotProduct(reflectionDirection, dir)), payload->hit_obj->specularExponent) * lights_Intensity* FVector::OneVector; } hitColor = lightAmt * payload->hit_obj->evalDiffuseColor(st) * payload->hit_obj->Kd +specularColor * payload->hit_obj->Ks; break; } } } return hitColor; } TOptional<FHit_payload> AActor_Assignment5::trace(const FVector& orig, const FVector& dir) { float tNear = kInfinity; TOptional<FHit_payload> payload; for (const auto& object : objects) { float tNearK = kInfinity; uint32_t indexK; FVector2D uvK; if (object->intersect(orig, dir, tNearK, indexK, uvK) && tNearK <= tNear) { payload.Emplace(); payload->hit_obj = object; payload->tNear = tNearK; payload->index = indexK; payload->uv = uvK; tNear = tNearK; } } return payload; } FVector AActor_Assignment5::reflect(const FVector& I, const FVector& N) { FVector res = I - 2 * FVector::DotProduct(I, N) * N; res.Normalize(); return res; } FVector AActor_Assignment5::refract(const FVector& I, const FVector& N, const float& ior) { float cosi = UKismetMathLibrary::FClamp(FVector::DotProduct(I, N), -1, 1); //注意使用 FClamp float etai = 1, etat = ior; FVector n = N; if (cosi < 0) { cosi = -cosi; } else { std::swap(etai, etat); n = -N; } float eta = etai / etat; float k = 1 - eta * eta * (1 - cosi * cosi); FVector res = k < 0 ? FVector::ZeroVector : eta * I + (eta * cosi - sqrtf(k)) * n; res.Normalize(); return res; } float AActor_Assignment5::fresnel(const FVector& I, const FVector& N, const float& ior) { float cosi = UKismetMathLibrary::FClamp(FVector::DotProduct(I, N), -1, 1); float etai = 1, etat = ior; if (cosi > 0) { std::swap(etai, etat); } // Compute sini using Snell's law float sint = etai / etat * sqrtf(std::max(0.f, 1 - cosi * cosi)); // Total internal reflection if (sint >= 1) { return 1; } else { float cost = sqrtf(std::max(0.f, 1 - sint * sint)); cosi = fabsf(cosi); float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost)); float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost)); return (Rs * Rs + Rp * Rp) / 2.0; } } void AActor_Assignment5::CreateTexture() { T_Result = UTexture2D::CreateTransient(width, height, PF_B8G8R8A8); T_Result->UpdateResource(); FUpdateTextureRegion2D* RegionColor = new FUpdateTextureRegion2D(0, 0, 0, 0, width, height); T_Result->UpdateTextureRegions( (int32)0, (uint32)1, RegionColor, (uint32)(4 * 640), (uint32)4, (uint8*)framebuffer.GetData() ); } void AActor_Assignment5::TextureFromImgArr(const TArray<FColor>& SrcData) { const int32 SrcWidth = width; const int32 SrcHeight = height; // Create the texture //T_Result->ReleaseResource(); // Lock the texture so it can be modified uint8* MipData = static_cast<uint8*>(T_Result->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE)); // Create base mip. uint8* DestPtr = NULL; const FColor* SrcPtr = NULL; for (int32 y = 0; y < SrcHeight; y++) { DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; SrcPtr = const_cast<FColor*>(&SrcData[(SrcHeight - 1 - y) * SrcWidth]); for (int32 x = 0; x < SrcWidth; x++) { *DestPtr++ = SrcData[SrcWidth * y + x].B; *DestPtr++ = SrcData[SrcWidth * y + x].G; *DestPtr++ = SrcData[SrcWidth * y + x].R; *DestPtr++ = 0xFF; SrcPtr++; } } // Unlock the texture T_Result->PlatformData->Mips[0].BulkData.Unlock(); if (T_Result == nullptr) UKismetSystemLibrary::PrintString(GetWorld(), TEXT("T_Result is null")); }
</details>
-
AHw5_Shape .h
点击查看代码
#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Hw5_Shape.generated.h" UENUM(BlueprintType) enum class EMaterialType:uint8 { DIFFUSE_AND_GLOSSY, REFLECTION_AND_REFRACTION, REFLECTION }; UCLASS(Abstract) class GAMES101_API AHw5_Shape : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AHw5_Shape() : materialType(EMaterialType::DIFFUSE_AND_GLOSSY) , ior(1.3) , Kd(0.8) , Ks(0.2) , diffuseColor(0.2) , specularExponent(25) {}; public: virtual bool intersect(const FVector&, const FVector&, float&, uint32_t&, FVector2D&) const { return false; }; virtual void getSurfaceProperties(const FVector&, const FVector&, const uint32_t&, const FVector2D&, FVector&, FVector2D&) const {}; virtual FVector evalDiffuseColor(const FVector2D&) const { return diffuseColor; } // material properties EMaterialType materialType; float ior; float Kd, Ks; FVector diffuseColor; float specularExponent; };
</details>
-
Hw5_Sphere.h
点击查看代码
#include "CoreMinimal.h" #include <algorithm> #include "Hw5_Shape.h" #include "Kismet/KismetSystemLibrary.h" #include "Hw5_Sphere.generated.h" inline bool solveQuadratic(const float& a, const float& b, const float& c, float& x0, float& x1) { float discr = b * b - 4 * a * c; if (discr < 0) return false; else if (discr == 0) x0 = x1 = -0.5 * b / a; else { float q = (b > 0) ? -0.5 * (b + sqrt(discr)) : -0.5 * (b - sqrt(discr)); x0 = q / a; x1 = c / q; } if (x0 > x1) std::swap(x0, x1); return true; } UCLASS() class GAMES101_API AHw5_Sphere : public AHw5_Shape { GENERATED_BODY() public: void SetCenterAndRadius(const FVector& c, const float& r) { center = c; radius = r; radius2 = r * r; UKismetSystemLibrary::DrawDebugSphere(GetWorld(), center, radius, 40.0f, FLinearColor::White, 3600, 1.0f); } bool intersect(const FVector& orig, const FVector& dir, float& tnear, uint32_t&, FVector2D&) const override { // analytic solution FVector L = orig - center; float a = FVector::DotProduct(dir, dir); float b = 2 * FVector::DotProduct(dir, L); float c = FVector::DotProduct(L, L) - radius2; float t0, t1; if (!solveQuadratic(a, b, c, t0, t1)) return false; if (t0 < 0) t0 = t1; if (t0 < 0) return false; tnear = t0; return true; } void getSurfaceProperties(const FVector& P, const FVector&, const uint32_t&, const FVector2D&, FVector& N, FVector2D&) const override { N = P - center; N.Normalize(); } private: FVector center; float radius, radius2; };
</details>
-
AHw5_MeshTriangle.h
点击查看代码
#pragma once #include "CoreMinimal.h" #include "Hw5_Shape.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet/KismetSystemLibrary.h" #include "Hw5_MeshTriangle.generated.h" UCLASS() class GAMES101_API AHw5_MeshTriangle : public AHw5_Shape { GENERATED_BODY() public: void DarwWireframe() { for (uint32_t k = 0; k < numTriangles; ++k) { const FVector& v0 = vertices[vertexIndex[k * 3]]; const FVector& v1 = vertices[vertexIndex[k * 3 + 1]]; const FVector& v2 = vertices[vertexIndex[k * 3 + 2]]; UKismetSystemLibrary::DrawDebugLine(GetWorld(), v0, v1, FLinearColor::White, 3600, 2.0f); UKismetSystemLibrary::DrawDebugLine(GetWorld(), v1, v2, FLinearColor::White, 3600, 2.0f); UKismetSystemLibrary::DrawDebugLine(GetWorld(), v2, v0, FLinearColor::White, 3600, 2.0f); } } void SetProperty(const TArray<FVector>& verts, const TArray<uint32_t>& vertsIndex, const uint32_t& numTris, const TArray<FVector2D> st) { vertices = verts; vertexIndex = vertsIndex; numTriangles = numTris; stCoordinates = st; DarwWireframe(); } bool rayTriangleIntersect(const FVector& v0, const FVector& v1, const FVector& v2, const FVector& orig, const FVector& dir, float& tnear, float& u, float& v) const { // TODO: Implement this function that tests whether the triangle // that's specified bt v0, v1 and v2 intersects with the ray (whose // origin is *orig* and direction is *dir*) // Also don't forget to update tnear, u and v. FVector E1 = v1 - v0; FVector E2 = v2 - v0; FVector S = orig - v0; FVector S1 = FVector::CrossProduct(dir, E2); FVector S2 = FVector::CrossProduct(S, E1); float factor = FVector::DotProduct(S1, E1); float t = 1.0f / factor * FVector::DotProduct(S2, E2); float b1 = 1.0f / factor * FVector::DotProduct(S1, S); float b2 = 1.0f / factor * FVector::DotProduct(S2, dir); if (t > 0 && b1 > 0 && b2 > 0 && (1 - b1 - b2) > 0) { tnear = t; u = b1; v = b2; return true; } return false; } bool intersect(const FVector& orig, const FVector& dir, float& tnear, uint32_t& index, FVector2D& uv) const override { bool intersect = false; for (uint32_t k = 0; k < numTriangles; ++k) { const FVector& v0 = vertices[vertexIndex[k * 3]]; const FVector& v1 = vertices[vertexIndex[k * 3 + 1]]; const FVector& v2 = vertices[vertexIndex[k * 3 + 2]]; float t, u, v; if (rayTriangleIntersect(v0, v1, v2, orig, dir, t, u, v) && t < tnear) { tnear = t; uv.X = u; uv.Y = v; index = k; intersect |= true; } } return intersect; } void getSurfaceProperties(const FVector&, const FVector&, const uint32_t& index, const FVector2D& uv, FVector& N, FVector2D& st) const override { const FVector& v0 = vertices[vertexIndex[index * 3]]; const FVector& v1 = vertices[vertexIndex[index * 3 + 1]]; const FVector& v2 = vertices[vertexIndex[index * 3 + 2]]; FVector e0 = v1 - v0; FVector e1 = v1 - v2; // 注意向量方向,v2 - v1 会造成法线方向反向 e0.Normalize(); e1.Normalize(); N = FVector::CrossProduct(e0, e1); N.Normalize(); const FVector2D& st0 = stCoordinates[vertexIndex[index * 3]]; const FVector2D& st1 = stCoordinates[vertexIndex[index * 3 + 1]]; const FVector2D& st2 = stCoordinates[vertexIndex[index * 3 + 2]]; st = st0 * (1 - uv.X - uv.Y) + st1 * uv.X + st2 * uv.Y; } FVector evalDiffuseColor(const FVector2D& st) const override { float scale = 5; float pattern = (fmodf(st.X * scale, 1) > 0.5) ^ (fmodf(st.Y * scale, 1) > 0.5); return UKismetMathLibrary::VLerp(FVector(0.815, 0.235, 0.031), FVector(0.937, 0.937, 0.231), pattern); } UPROPERTY() TArray<FVector> vertices; uint32_t numTriangles; UPROPERTY() TArray<uint32> vertexIndex; UPROPERTY() TArray<FVector2D> stCoordinates; };
作者:砥才人
出处:https://www.cnblogs.com/shiroe
本系列文章为笔者整理原创,只发表在博客园上,欢迎分享本文链接,如需转载,请注明出处!