在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就可以执行着色并返回像素颜色。
在这次作业中,我们要实现两个部分:
本次代码框架的工作流程为:
效果
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()
计算折射方向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
点击查看代码
```cpp
#pragma once
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;
};
</details>
Actor_Assignment5.cpp
点击查看代码
```cpp
#include "Actor_Assignment5.h"
#include "Hw5_Sphere.h"
#include "Hw5_MeshTriangle.h"
#include "Kismet/GameplayStatics.h"
float kInfinity = TNumericLimits::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
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
{
float tNear = kInfinity;
TOptional
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& 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
点击查看代码
```cpp
#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
点击查看代码
```cpp
#include "CoreMinimal.h"
#include
#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
点击查看代码
```cpp
#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& verts, const TArray
{
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;
};
</details>
手机扫一扫
移动阅读更方便
你可能感兴趣的文章