【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)

总览

  • 在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就可以执行着色并返回像素颜色。
  • 在这次作业中,我们要实现两个部分:
    • 光线的生成
    • 光线与三角的相交。
  • 本次代码框架的工作流程为:
    1. 从main 函数开始。我们定义场景的参数,添加物体(球体或三角形)到场景中,并设置其材质,然后将光源添加到场景中。
    2. 调用Render(scene) 函数。在遍历所有像素的循环里,生成对应的光线并将返回的颜色保存在帧缓冲区(framebuffer)中。在渲染过程结束后,帧缓冲区中的信息将被保存为图像。
    3. 在生成像素对应的光线后,我们调用CastRay 函数,该函数调用trace 来查询光线与场景中最近的对象的交点。
    4. 然后,我们在此交点执行着色。我们设置了三种不同的着色情况,并且已经为你提供了代码。你需要修改的函数是:
      • Renderer.cpp 中的Render():这里你需要为每个像素生成一条对应的光线,然后调用函数castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相应像素中。
      • Triangle.hpp 中的rayTriangleIntersect(): v0, v1, v2 是三角形的三个顶点,orig 是光线的起点,dir 是光线单位化的方向向量。tnear, u, v 是你需要使用我们课上推导的Moller-Trumbore 算法来更新的参数。

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()

    • 公式

      image

    • 代码

      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;
      }
      
  • 效果
    image

    image

代码说明

代码结构

  • AActor_Assignment5 测试入口及光线追踪主要函数,继承自AActor
    • BeginPlay() 初始化入口,调用InitialScene()
    • InitialScene() 初始化各种数据
    • Tick() 调用 Render()
    • Render() 按行进行光线扫描,更新 Texture
    • castRay() 计算光照和反射后的颜色
    • 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;
    };
    
posted @ 2021-10-25 11:23  砥才人  阅读(913)  评论(2编辑  收藏  举报