【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实现

  • 效果

    image

  • 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;
      }
      

代码说明

代码结构

  • 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

    点击查看代码

    “`cpp
    #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;
    

    };

    </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>