【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)
阅读原文时间:2021年10月25日阅读:5
  • 在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就可以执行着色并返回像素颜色。

  • 在这次作业中,我们要实现两个部分:

    • 光线的生成
    • 光线与三角的相交。
  • 本次代码框架的工作流程为:

  • 效果

  • 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() 按行进行光线扫描,更新 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::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-&gt;hit_obj-&gt;getSurfaceProperties(hitPoint, dir, payload-&gt;index, payload-&gt;uv, N, st);
      switch (payload-&gt;hit_obj-&gt;materialType) {
      case EMaterialType::REFLECTION_AND_REFRACTION:
      {
          FVector reflectionDirection = reflect(dir, N);
          FVector refractionDirection = refract(dir, N, payload-&gt;hit_obj-&gt;ior);
          FVector reflectionRayOrig = (FVector::DotProduct(reflectionDirection, N) &lt; 0) ?
              hitPoint - N * epsilon :
              hitPoint + N * epsilon;
          FVector refractionRayOrig = (FVector::DotProduct(refractionDirection, N) &lt; 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-&gt;hit_obj-&gt;ior);
          hitColor = reflectionColor * kr + refractionColor * (1 - kr);
          break
      }
      case EMaterialType::REFLECTION:
      {
          float kr = fresnel(dir, N, payload-&gt;hit_obj-&gt;ior);
          FVector reflectionDirection = reflect(dir, N);
          FVector reflectionRayOrig = (FVector::DotProduct(reflectionDirection, N) &lt; 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) &lt; 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&amp; 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() &amp;&amp; (shadow_res-&gt;tNear * shadow_res-&gt;tNear &lt; 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-&gt;hit_obj-&gt;specularExponent) * lights_Intensity* FVector::OneVector;
          }
    
          hitColor = lightAmt * payload-&gt;hit_obj-&gt;evalDiffuseColor(st) * payload-&gt;hit_obj-&gt;Kd +specularColor * payload-&gt;hit_obj-&gt;Ks;
          break;
      }
      }
    } return hitColor;

    }

    TOptional AActor_Assignment5::trace(const FVector& orig, const FVector& dir)

    {

    float tNear = kInfinity;

    TOptional 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& 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 &gt; 0 &amp;&amp; b1 &gt; 0 &amp;&amp; b2 &gt; 0 &amp;&amp; (1 - b1 - b2) &gt; 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>

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器