【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实现
效果
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
点击查看代码
```cpp
#pragma onceinclude "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;
};
</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<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& 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<uint32_t>& vertsIndex, const uint32_t& numTris, const TArray 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;
};
</details>
【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)的更多相关文章
- 【UE4】GAMES101 图形学作业2:光栅化和深度缓存
总览 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣.所以这一次我们继续推进一步--在屏幕上画出一个实心三角形,换言之,栅格化一个三角形.上一次作业中,在视口变化之后,我 ...
- 【UE4】GAMES101 图形学作业4:贝塞尔曲线
总览 Bézier 曲线是一种用于计算机图形学的参数曲线. 在本次作业中,你需要实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更 ...
- 【UE4】GAMES101 图形学作业3:Blinn-Phong 模型与着色
总览 在这次编程任务中,我们会进一步模拟现代图形技术.我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与Fragment Shader,并且支持了纹理映 ...
- 【UE4】GAMES101 图形学作业1:mvp 模型、视图、投影变换
总览 到目前为止,我们已经学习了如何使用矩阵变换来排列二维或三维空间中的对象.所以现在是时候通过实现一些简单的变换矩阵来获得一些实际经验了.在接下来的三次作业中,我们将要求你去模拟一个基于CPU 的光 ...
- 【UE4】GAMES101 图形学作业0:矩阵初识
作业描述 给定一个点P=(2,1), 将该点绕原点先逆时针旋转45◦,再平移(1,2), 计算出变换后点的坐标(要求用齐次坐标进行计算). UE4 知识点 主要矩阵 FMatrix FBasisVec ...
- UE4物理模块(二)---建立物体碰撞
在前文中介绍了什么是物理以及如何在UE4和PhysX中进行可视化调试: Jerry:UE4物理模块(一)---概述与可视化调试zhuanlan.zhihu.com 这里调试只谈到了碰撞盒(后续还会有 ...
- 回顾Games101图形学(一)几何变换中一些公式的推导
回顾Games101 chatper1 - 6 前言 本文只写回顾后重新加深认识的知识 透视除法的意义 经过MVP矩阵之后,将模型空间下某点的坐标,转换成了裁剪空间下的坐标,此时因为裁剪空间的范围是x ...
- 《Shader入门精要》中MVP变换的Projection矩阵与《GAMES101图形学入门》中的区别
game101的透视投影的投影矩阵是这样的 正交投影是这样的 而shader入门精要的透视投影矩阵是这样子 正交投影矩阵是这样子 game101的透视投影是这样得到的 而正交投影的时候并没有假设中心点 ...
- 探究光线追踪技术及UE4的实现
目录 一.光线追踪概述 1.1 光线追踪是什么 1.2 光线追踪的特点 1.3 光线追踪的历史 1.4 光线追踪的应用 二.光线追踪的原理 2.1 光线追踪的物理原理 2.2 光线追踪算法 2.3 R ...
随机推荐
- C#多线程开发-线程池03
你好,我是阿辉. 前面2篇文章介绍了线程的基础知识和线程同步,下面我们来一起认识学习下,线程池的使用. 线程池 创建线程是昂贵的操作,所以为每个短暂的异步操作创建线程会产生显著的开销.一般情况下,都会 ...
- SQLServer数据库查询语法
SQLServer数据库查询语法 前言: SQLServer数据库介绍: SQLServer数据库是微软公司推出的一款关系型数据库系统,SQL Server是一个可扩展的.高性能的.为分布式客户机/服 ...
- 聊聊 Jmeter 如何并发执行 Python 脚本
1. 前言 大家好,我是安果! 最近有小伙伴后台给我留言,说自己用 Django 写了一个大文件上传的 Api 接口,现在想本地检验一下接口并发的稳定性,问我有没有好的方案 本篇文章以文件上传为例,聊 ...
- Linux(二)——常用命令
一.一般指令 立刻关机:shutdown -h now 1分钟后关机:shutdown -h 1 立刻重启:shutdown -r now 立刻关机:halt 立刻重启:reboot 把内存的数据同步 ...
- Python - 面向对象编程 - __del__() 析构方法
del 语句 Python 提供了 del 语句用于删除不再使用的变量 语法 del 表达式 删除变量的栗子 var = "hello" del var print(var) # ...
- 史上最详细的信号使用说明(已被收藏和N次)
Unix环境高级编程(第三版) 第10章 信号 文章目录 1. 引言 2. 信号的概念 2.1 信号操作之忽略信号 2.2 信号操作之捕捉信号 2.3 信号操作之执行系统默认操作 2.4 常见的信号 ...
- Spring Boot中使用@Async的时候,千万别忘了线程池的配置!
上一篇我们介绍了如何使用@Async注解来创建异步任务,我可以用这种方法来实现一些并发操作,以加速任务的执行效率.但是,如果只是如前文那样直接简单的创建来使用,可能还是会碰到一些问题.存在有什么问题呢 ...
- SQL语句分组获取记录的第一条数据的方法
使用Northwind 数据库 首先查询Employees表 查询结果: city列里面只有5个城市 使用ROW_NUMBER() OVER(PARTITION BY COL1 ORDER BY CO ...
- JavaScrip中 Array.reduce()
数组的方法 reduce() reduce方法在数组的每一项元素上都会执行回调函数. 语法:array.reduce( callBack [ , init] ) // 语法arrary.reduce ...
- swiper轮播高度不正常
第一次进入页面可能是网速原因,图片加载问题等吧,导致轮播图高度很大,下面出现空白, 需要加入参数 autoHeight: true, observer: true, observeParents: t ...