【UE4】GAMES101 图形学作业2:光栅化和深度缓存
阅读原文时间:2023年07月11日阅读:1
  • 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣。所以这一次我们继续推进一步——在屏幕上画出一个实心三角形,换言之,栅格化一个三角形。上一次作业中,在视口变化之后,我们调用了函数rasterize_wireframe(const Triangle& t)。

  • 但这一次,你需要自己填写并调用函数 rasterize_triangle(const Triangle& t)。该函数的内部工作流程如下:

  • 注意

    • 请注意我们是如何初始化depth buffer 和注意z values 的符号。为了方便同学们写代码,我们将z 进行了反转,保证都是正数,并且越大表示离视点越远。
    • 在你自己的计算机或虚拟机上下载并使用我们更新的框架代码。你会注意到,在main.cpp 下的get_projection_matrix() 函数是空的。请复制粘贴你在第一次作业中的实现来填充该函数。
  • [20 分] 正确实现三角形栅格化算法。

  • [10 分] 正确测试点是否在三角形内。

  • [10 分] 正确实现z-buffer 算法, 将三角形按顺序画在屏幕上。

  • [提高项5 分] 用super-sampling 处理Anti-aliasing

    你可能会注意到,当我们放大图像时,图像边缘会有锯齿感。我们可以用super-sampling来解决这个问题,即对每个像素进行2 * 2 采样,并比较前后的结果(这里并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个samplelist。最后,如果你实现正确的话,你得到的三角形不应该有不正常的黑边。

效果

  • 静态展示

  • 由于绘制看不出锯齿优化效果,因此在像素点取点间隔略作处理,每五个点取一个像素点,如果在三角形内,则自动将未扫描的五个点补全。

代码结构说明

  • class AActor_Assignment2 作为测试,相当于main入口
  • class URasterizer2Widget 作为显示
  • class Rasterizer2 作为光栅器 (从原代码框架移植移植)
    • rasterize_triangle(): 执行三角形栅格化算法
    • static bool insideTriangle(): 测试点是否在三角形内。
  • class Triangle2 作为三角形 (从原代码框架移植移植)

代码

  • class AActor_Assignment2

    • Actor_Assignment2 .h

      #include "CoreMinimal.h"
      #include "GameFramework/Actor.h"
      #include "Rasterizer2.h"
      #include "Rasterizer2Widget.h"
      #include "Actor_Assignment2.generated.h"
      
      UCLASS()
      class GAMES101_API AActor_Assignment2 : public AActor
      {
          GENERATED_BODY()
      
      public:
          // Sets default values for this actor's properties
          AActor_Assignment2();
      
      protected:
          // Called when the game starts or when spawned
          virtual void BeginPlay() override;
      
      public:
          // Called every frame
          virtual void Tick(float DeltaTime) override;
      FMatrix get_view_matrix(FVector eye_pos);
      FMatrix get_model_matrix(float rotation_angle);
      FMatrix get_model_matrix_anyAxis(FVector axis, float angle);
      FMatrix get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar);
      
      void TextureFromImage_Internal(const TArray<FColor>& SrcData, const bool UseAlpha = false);
      
      void SwitchMASS() { m_rasterizer->SwitchMASS();UKismetSystemLibrary::PrintString(GetWorld(), TEXT("SwitchMASS")); }
      public: UPROPERTY(BlueprintReadWrite) TArray<FVector> pos;
      UPROPERTY(BlueprintReadWrite)
          TArray&lt;FIntVector&gt; ind;
      
      UPROPERTY(BlueprintReadWrite)
          TArray&lt;FVector&gt; cols;
      
      UPROPERTY(EditAnywhere)
          FVector2D CanvasSize;
      
      UPROPERTY(EditAnywhere)
          TSubclassOf&lt;URasterizer2Widget&gt; RasterizerWidgetClass;
      
      UPROPERTY()
          URasterizer2Widget* screenUi;
      
      UPROPERTY(VisibleAnywhere)
          UTexture2D* T_Result;
      private: rst::pos_buf_id pos_id; rst::ind_buf_id ind_id; rst::col_buf_id col_id; float angle; FVector eye_pos; private: TSharedPtr<rst::Rasterizer2> m_rasterizer; };
    • Actor_Assignment2 .cpp

      #include "Actor_Assignment2.h"
      #include "Kismet/KismetMathLibrary.h"
      #include "Kismet/GameplayStatics.h"
      
      // Sets default values
      AActor_Assignment2::AActor_Assignment2()
      {
           // 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;
          CanvasSize = FVector2D(700, 700);
      }
      
      // Called when the game starts or when spawned
      void AActor_Assignment2::BeginPlay()
      {
          Super::BeginPlay();
      angle = 0;
      eye_pos = { 0,0,5 };
      
      pos = {
          {2, 0, -2},
          {0, 2, -2},
          {-2, 0, -2},
          {3.5, -1, -5},
          {2.5, 1.5, -5},
          {-1, 0.5, -5}
      };
      
      ind = {
              {0, 1, 2},
              {3, 4, 5}
      };
      
      cols = {
              {217.0, 238.0, 185.0},
              {217.0, 238.0, 185.0},
              {217.0, 238.0, 185.0},
              {185.0, 217.0, 238.0},
              {185.0, 217.0, 238.0},
              {185.0, 217.0, 238.0}
      };
      
      m_rasterizer = MakeShareable(new rst::Rasterizer2(CanvasSize.X, CanvasSize.Y));
      pos_id = m_rasterizer-&gt;load_positions(pos);
      ind_id = m_rasterizer-&gt;load_indices(ind);
      col_id = m_rasterizer-&gt;load_colors(cols);
      
      if (RasterizerWidgetClass != nullptr) {
          APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
          PC-&gt;SetShowMouseCursor(true);
          screenUi = CreateWidget&lt;URasterizer2Widget&gt;(PC, RasterizerWidgetClass);
          T_Result = UTexture2D::CreateTransient(CanvasSize.X, CanvasSize.Y, PF_B8G8R8A8);
          screenUi-&gt;TCanvas-&gt;SetBrushFromTexture(T_Result);
          screenUi-&gt;AddToViewport();
      }
      else {
          UKismetSystemLibrary::PrintString(GetWorld(), TEXT("RasterizerWidgetClass is null"));
      }
      } // Called every frame void AActor_Assignment2::Tick(float DeltaTime) { Super::Tick(DeltaTime);
      m_rasterizer-&gt;clear(rst::Buffers::Color | rst::Buffers::Depth);
      
      //angle += 0.2f;
      m_rasterizer-&gt;set_model(get_model_matrix_anyAxis(FVector(0, 0.2, 1), angle));
      m_rasterizer-&gt;set_view(get_view_matrix(eye_pos));
      m_rasterizer-&gt;set_projection(get_projection_matrix(45, 1, 0.1, 50)); //此处 Znear 和 Zfar 取负号
      
      m_rasterizer-&gt;draw(pos_id, ind_id, col_id, rst::Primitive::Triangle);
      TextureFromImage_Internal(m_rasterizer-&gt;frame_buffer());
      } FMatrix AActor_Assignment2::get_view_matrix(FVector eye_loc) { FMatrix view = FMatrix::Identity;
      FMatrix translate = FMatrix(
          FPlane(1, 0, 0, -eye_loc.X),
          FPlane(0, 1, 0, -eye_loc.Y),
          FPlane(0, 0, 1, -eye_loc.Z),
          FPlane(0, 0, 0, 1));
      
      view = translate * view;
      return view;
      } // 绕 Z 轴旋转 FMatrix AActor_Assignment2::get_model_matrix(float rotation_angle) { FMatrix model = FMatrix::Identity;
      // TODO: Implement this function
      // Create the model matrix for rotating the triangle around the Z axis.
      // Then return it.
      float fcos = UKismetMathLibrary::DegCos(rotation_angle);
      float fsin = UKismetMathLibrary::DegSin(rotation_angle);
      FMatrix rotate = FMatrix(
          FPlane(fcos, -fsin, 0, 0),
          FPlane(fsin, fcos, 0, 0),
          FPlane(0, 0, 1, 0),
          FPlane(0, 0, 0, 1));
      
      model = rotate * model;
      return model;
      } // 任意轴旋转 FMatrix AActor_Assignment2::get_model_matrix_anyAxis(FVector axis, float rotation_angle) { FMatrix model = FMatrix::Identity;
      axis.Normalize(0.0001);
      FMatrix N = FMatrix(
          FPlane(0, -axis.Z, axis.Y, 0),
          FPlane(axis.Z, 0, -axis.X, 0),
          FPlane(-axis.Y, axis.X, 0, 0),
          FPlane(0, 0, 0, 0));
      
      FMatrix rotate4f = FMatrix::Identity * UKismetMathLibrary::DegCos(rotation_angle);
      
      // nnt = axis x axis的转置
      FMatrix nnT = FMatrix(
          FPlane(axis.X * axis.X, axis.X * axis.Y, axis.X * axis.Z, 0),
          FPlane(axis.Y * axis.X, axis.Y * axis.Y, axis.Y * axis.Z, 0),
          FPlane(axis.Z * axis.X, axis.Z * axis.Y, axis.Z * axis.Z, 0),
          FPlane(0, 0, 0, 0));
      
      rotate4f += nnT * (1 - UKismetMathLibrary::DegCos(rotation_angle));
      rotate4f += N * UKismetMathLibrary::DegSin(rotation_angle);
      
      rotate4f.M[3][3] = 1;
      model = rotate4f * model;
      return model;
      } FMatrix AActor_Assignment2::get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) { // Students will implement this function FMatrix projection = FMatrix::Identity;
      float t = zNear * UKismetMathLibrary::DegTan(eye_fov / 2);
      float b = -t;
      float r = t * aspect_ratio;
      float l = -r;
      
      FMatrix translate = FMatrix(
          FPlane(2 * zNear / (r - l), 0, -(r + l) / (r - l), 0),
          FPlane(0, 2 * zNear / (t - b), -(t + b) / (t - b), 0),
          FPlane(0, 0, -(zNear + zFar) / (zNear - zFar), 2 * zNear * zFar / (zNear - zFar)),
          FPlane(0, 0, -1, 0));
      projection = translate * projection;
      return projection;
      } // 修改Texture里的内容 void AActor_Assignment2::TextureFromImage_Internal( const TArray<FColor>& SrcData, const bool UseAlpha) { const int32 SrcWidth = CanvasSize.X; const int32 SrcHeight = CanvasSize.Y; // Create the texture
      //T_Result-&gt;ReleaseResource();
      // Lock the texture so it can be modified
      uint8* MipData = static_cast&lt;uint8*&gt;(T_Result-&gt;PlatformData-&gt;Mips[0].BulkData.Lock(LOCK_READ_WRITE));
      
      // Create base mip.
      uint8* DestPtr = NULL;
      const FColor* SrcPtr = NULL;
      for (int32 y = 0; y &lt; SrcHeight; y++)
      {
          DestPtr = &amp;MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)];
          SrcPtr = const_cast&lt;FColor*&gt;(&amp;SrcData[(SrcHeight - 1 - y) * SrcWidth]);
          for (int32 x = 0; x &lt; SrcWidth; x++)
          {
              *DestPtr++ = SrcPtr-&gt;B;
              *DestPtr++ = SrcPtr-&gt;G;
              *DestPtr++ = SrcPtr-&gt;R;
              if (UseAlpha)
              {
                  *DestPtr++ = SrcPtr-&gt;A;
              }
              else
              {
                  *DestPtr++ = 0xFF;
              }
              SrcPtr++;
          }
      }
      
      // Unlock the texture
      T_Result-&gt;PlatformData-&gt;Mips[0].BulkData.Unlock();
      T_Result-&gt;UpdateResource();
      }
  • class Rasterizer2

    • Rasterizer2.h

      #pragma once
      #include "CoreMinimal.h"
      #include "Triangle2.h"
      #include "Kismet/KismetSystemLibrary.h"
      
      namespace rst
      {
          enum class Buffers
          {
              Color = 1,
              Depth = 2
          };
      inline Buffers operator|(Buffers a, Buffers b)
      {
          return Buffers((int)a | (int)b);
      }
      
      inline Buffers operator&amp;(Buffers a, Buffers b)
      {
          return Buffers((int)a &amp; (int)b);
      }
      
      enum class Primitive
      {
          Line,
          Triangle
      };
      
      /*
       * For the curious : The draw function takes two buffer id's as its arguments. These two structs
       * make sure that if you mix up with their orders, the compiler won't compile it.
       * Aka : Type safety
       * */
      struct pos_buf_id
      {
          int pos_id = 0;
      };
      
      struct ind_buf_id
      {
          int ind_id = 0;
      };
      
      struct col_buf_id
      {
          int col_id = 0;
      };
      
      class GAMES101_API Rasterizer2
      {
      public: 
      Rasterizer2(int w, int h);
      pos_buf_id load_positions(const TArray&amp;lt;FVector&amp;gt;&amp;amp; positions);
      ind_buf_id load_indices(const TArray&amp;lt;FIntVector&amp;gt;&amp;amp; indices);
      col_buf_id load_colors(const TArray&amp;lt;FVector&amp;gt;&amp;amp; colors);
      
      void set_model(const FMatrix&amp;amp; m);
      void set_view(const FMatrix&amp;amp; v);
      void set_projection(const FMatrix&amp;amp; p);
      
      void set_pixel(const FVector&amp;amp; point, const FVector&amp;amp; color);
      void clear(Buffers buff);
      void draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type);
      
      TArray&amp;lt;FColor&amp;gt;&amp;amp; frame_buffer() { return frame_buf; }
      void SwitchMASS() { bMASS = !bMASS;} private: //void draw_line(Eigen::Vector3f begin, Eigen::Vector3f end); void rasterize_triangle(const Triangle2&amp; t); // VERTEX SHADER -&gt; MVP -&gt; Clipping -&gt; /.W -&gt; VIEWPORT -&gt; DRAWLINE/DRAWTRI -&gt; FRAGSHADER private: FMatrix model; FMatrix view; FMatrix projection;
      TMap&amp;lt;int,TArray&amp;lt;FVector&amp;gt;&amp;gt; pos_buf;
      TMap&amp;lt;int,TArray&amp;lt;FIntVector&amp;gt;&amp;gt; ind_buf;
      TMap&amp;lt;int,TArray&amp;lt;FVector&amp;gt;&amp;gt; col_buf;
      
      TArray&amp;lt;FColor&amp;gt; frame_buf;
      TArray&amp;lt;float&amp;gt; depth_buf;
      
      int get_index(int x, int y);
      
      int width, height;
      
      int next_id = 0;
      int get_next_id() { return next_id++; }
      bool bMASS = false; };
      }
    • Rasterizer2.cpp

      #include "Rasterizer2.h"
      #include <tuple>
      #include <cmath>
      #include "Kismet/KismetMathLibrary.h"
      // Sets default values
      
      rst::pos_buf_id rst::Rasterizer2::load_positions(const TArray<FVector>& positions)
      {
          auto id = get_next_id();
          pos_buf.Emplace(id, positions);
          return { id };
      }
      
      rst::ind_buf_id rst::Rasterizer2::load_indices(const TArray<FIntVector>& indices)
      {
          auto id = get_next_id();
          ind_buf.Emplace(id, indices);
          return { id };
      }
      
      rst::col_buf_id rst::Rasterizer2::load_colors(const TArray<FVector>& cols)
      {
          auto id = get_next_id();
          col_buf.Emplace(id, cols);
          return { id };
      }
      
      auto to_vec4(const FVector& v3, float w = 1.0f)
      {
          return FVector4(v3.X, v3.Y, v3.Z, w);
      }
      
      static bool insideTriangle(float x, float y, const TArray<FVector>& _v)
      {
          // TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
          FVector2D point=FVector2D(x, y);
      FVector2D v0 = FVector2D(_v[0].X, _v[0].Y);
      FVector2D v1 = FVector2D(_v[1].X, _v[1].Y);
      FVector2D v2 = FVector2D(_v[2].X, _v[2].Y);
      
      FVector2D AB = v1 - v0;
      FVector2D BC = v2 - v1;
      FVector2D CA = v0 - v2;
      
      FVector2D AP = point - v0;
      FVector2D BP = point - v1;
      FVector2D CP = point - v2;
      
      return    UKismetMathLibrary::CrossProduct2D(AB, AP) &gt; 0
          &amp;&amp; UKismetMathLibrary::CrossProduct2D(BC, BP) &gt; 0
          &amp;&amp; UKismetMathLibrary::CrossProduct2D(CA, CP) &gt; 0;
      } static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const TArray<FVector>& v) { float c1 = (x * (v[1].Y - v[2].Y) + (v[2].X - v[1].X) * y + v[1].X * v[2].Y - v[2].X * v[1].Y) / (v[0].X * (v[1].Y - v[2].Y) + (v[2].X - v[1].X) * v[0].Y + v[1].X * v[2].Y - v[2].X * v[1].Y); float c2 = (x * (v[2].Y - v[0].Y) + (v[0].X - v[2].X) * y + v[2].X * v[0].Y - v[0].X * v[2].Y) / (v[1].X * (v[2].Y - v[0].Y) + (v[0].X - v[2].X) * v[1].Y + v[2].X * v[0].Y - v[0].X * v[2].Y); float c3 = (x * (v[0].Y - v[1].Y) + (v[1].X - v[0].X) * y + v[0].X * v[1].Y - v[1].X * v[0].Y) / (v[2].X * (v[0].Y - v[1].Y) + (v[1].X - v[0].X) * v[2].Y + v[0].X * v[1].Y - v[1].X * v[0].Y); return { c1,c2,c3 }; } void rst::Rasterizer2::draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type) { auto& buf = pos_buf[pos_buffer.pos_id]; auto& ind = ind_buf[ind_buffer.ind_id]; auto& col = col_buf[col_buffer.col_id];
      float f1 = (50 - 0.1) / 2.0;
      float f2 = (50 + 0.1) / 2.0;
      
      FMatrix mvp = projection * view * model;
      for (auto&amp; i : ind)
      {
          Triangle2 t;
          TArray&lt;FVector4&gt; v;
          v.Add(mvp.GetTransposed().TransformFVector4(to_vec4(buf[i[0]], 1.0f)));
          v.Add(mvp.GetTransposed().TransformFVector4(to_vec4(buf[i[1]], 1.0f)));
          v.Add(mvp.GetTransposed().TransformFVector4(to_vec4(buf[i[2]], 1.0f)));
      //Homogeneous division
      for (auto&amp;amp; vec : v) {
          vec *= 1 / vec.W;
      }
      //Viewport transformation
      for (auto&amp;amp; vert : v)
      {
          vert.X = 0.5 * width * (vert.X + 1.0);
          vert.Y = 0.5 * height * (vert.Y + 1.0);
          vert.Z = vert.Z * f1 + f2;
      }
      
      for (int j = 0; j &amp;lt; 3; ++j)
      {
          t.setVertex(j, UKismetMathLibrary::Conv_Vector4ToVector(v[j]));
          //t.setVertex(j, UKismetMathLibrary::Conv_Vector4ToVector(v[j]));
          //t.setVertex(j, UKismetMathLibrary::Conv_Vector4ToVector(v[j]));
      }
      
      auto col_x = col[i[0]];
      auto col_y = col[i[1]];
      auto col_z = col[i[2]];
      
      t.setColor(0, col_x[0], col_x[1], col_x[2]);
      t.setColor(1, col_y[0], col_y[1], col_y[2]);
      t.setColor(2, col_z[0], col_z[1], col_z[2]);
      
      rasterize_triangle(t);
      }
      } //Screen space rasterization void rst::Rasterizer2::rasterize_triangle(const Triangle2& t) { auto v = t.toVector4();
      // 画出三角形所在边界
      // x_l = x_min ; x_r = x_max ; y_b = y_min ; y_t = y_max
      int x_l = std::floor(std::min(v[0].X, std::min(v[1].X, v[2].X)));
      int x_r = std::ceil(std::max(v[0].X, std::max(v[1].X, v[2].X)));
      int y_b = std::floor(std::min(v[0].Y , std::min(v[1].Y, v[2].Y)));
      int y_t = std::ceil(std::max(v[0].Y, std::max(v[1].Y, v[2].Y)));
      
      if (bMASS) {
          // 四等分后中心点,如果像素间隔扩大,中心也相对变化
          TArray&lt;FVector2D&gt; posOffset = { {0.25,0.25},{0.25,0.75},{0.75,0.25},{0.75,0.75} };
      //由于过于清晰,可在此处将递增改为 5 或 10,相应四等分中心点也会变化
      for (int x = x_l; x &amp;lt;= x_r; x++)
          for (int y = y_b; y &amp;lt;= y_t; y++) {
      
              float minDepth = TNumericLimits&amp;lt;float&amp;gt;::Max();
              float percentage = 0;
      
              for (int i = 0; i &amp;lt; 4; i++) {
                  if (insideTriangle((float)x + posOffset[i].X, (float)y + posOffset[i].Y, t.v)) {
                      //重心坐标插值
                      auto BarycentricParam = computeBarycentric2D((float)x + posOffset[i].X, (float)y + posOffset[i].Y, t.v);
                      float alpha = std::get&amp;lt;0&amp;gt;(BarycentricParam);
                      float beta = std::get&amp;lt;1&amp;gt;(BarycentricParam);
                      float gamma = std::get&amp;lt;2&amp;gt;(BarycentricParam);
      
                      float w_reciprocal = 1.0f / (alpha / v[0].W + beta / v[1].W + gamma / v[2].W);
                      float z_interpolated = alpha * v[0].Z / v[0].W + beta * v[1].Z / v[1].W + gamma * v[2].Z / v[2].W;
                      z_interpolated *= w_reciprocal;
                      minDepth = std::min(minDepth, z_interpolated);
                      percentage += 0.25f;
                  }
              }
              if (percentage &amp;gt; 0 &amp;amp;&amp;amp;  depth_buf[get_index(x, y)] &amp;gt; minDepth) {
                  // 递增改变的话,应补充未被扫面的部分
                  FVector color = t.getColor() * percentage;
                  FVector point = FVector((float)x, (float)y, minDepth);
                  depth_buf[get_index(x, y)] = minDepth;
                  set_pixel(point, color);
              }
          }
      } else { for (int x = x_l; x &lt;= x_r; x++) for (int y = y_b; y &lt;= y_t; y++) {
              if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v)) {
                  //重心坐标插值
                  auto BarycentricParam = computeBarycentric2D((float)x + 0.5f, (float)y + 0.5f, t.v);
                  float alpha = std::get&amp;lt;0&amp;gt;(BarycentricParam);
                  float beta = std::get&amp;lt;1&amp;gt;(BarycentricParam);
                  float gamma = std::get&amp;lt;2&amp;gt;(BarycentricParam);
      
                  float w_reciprocal = 1.0f / (alpha / v[0].W + beta / v[1].W + gamma / v[2].W);
                  float z_interpolated = alpha * v[0].Z / v[0].W + beta * v[1].Z / v[1].W + gamma * v[2].Z / v[2].W;
                  z_interpolated *= w_reciprocal;
      
                  if (depth_buf[get_index(x, y)] &amp;gt; z_interpolated) {
                      FVector color = t.getColor();
                      FVector point = FVector((float)x, (float)y, z_interpolated);
                      depth_buf[get_index(x, y)] = z_interpolated;
                      set_pixel(point, color);
                  }
              }
          }
      }
      } void rst::Rasterizer2::set_model(const FMatrix& m) { model = m; } void rst::Rasterizer2::set_view(const FMatrix& v) { view = v; } void rst::Rasterizer2::set_projection(const FMatrix& p) { projection = p; } void rst::Rasterizer2::clear(rst::Buffers buff) { if ((buff & rst::Buffers::Color) == rst::Buffers::Color) { for (FColor& item : frame_buf) { item = FColor(0, 0, 0, 0); } } if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth) { for (float& item : depth_buf) { item = TNumericLimits<float>::Max(); } } } rst::Rasterizer2::Rasterizer2(int w, int h) : width(w), height(h) { frame_buf.SetNum(w * h); depth_buf.SetNum(w * h); } int rst::Rasterizer2::get_index(int x, int y) { return (height - 1 - y) * width + x; } void rst::Rasterizer2::set_pixel(const FVector& point, const FVector& color) { //old index: auto ind = point.y() + point.x() * width; auto ind = (height - 1 - point.Y) * width + point.X; frame_buf[ind] = FColor(color.X, color.Y, color.Z, 255); }
  • class Triangle2

    • Triangle2.h

      #pragma once
      #include "CoreMinimal.h"
      
      class GAMES101_API Triangle2
      {
      public:
          Triangle2();
          ~Triangle2();
      
      public:
          TArray<FVector> v; /*the original coordinates of the triangle, v0, v1, v2 in counter clockwise order*/
          /*Per vertex values*/
          FVector color[3]; //color at each vertex;
          FVector2D tex_coords[3]; //texture u,v
          FVector normal[3]; //normal vector for each vertex
      void setVertex(int ind, FVector ver); /*set i-th vertex coordinates */
      void setNormal(int ind, FVector n); /*set i-th vertex normal vector*/
      void setColor(int ind, float r, float g, float b); /*set i-th vertex color*/
      FVector getColor() const { return color[0] * 255; } // Only one color per triangle.
      void setTexCoord(int ind, float s, float t); /*set i-th vertex texture coordinate*/
      TArray&lt;FVector4&gt; toVector4() const;
      };
    • Triangle2.cpp

      #include "Triangle2.h"
      
      Triangle2::Triangle2()
      {
          v.Add(FVector::ZeroVector);
          v.Add(FVector::ZeroVector);
          v.Add(FVector::ZeroVector);
      color[0] = color[1] = color[2] = FVector::ZeroVector;
      tex_coords[0] = tex_coords[1] = tex_coords[2] = FVector2D::ZeroVector;
      } Triangle2::~Triangle2(){} void Triangle2::setVertex(int ind, FVector ver) { v[ind] = ver; } void Triangle2::setNormal(int ind, FVector n) { normal[ind] = n; } void Triangle2::setColor(int ind, float r, float g, float b) { if ((r < 0.0) || (r > 255.) || (g < 0.0) || (g > 255.) || (b < 0.0) || (b > 255.)) { fprintf(stderr, "ERROR! Invalid color values"); fflush(stderr); exit(-1); }
      color[ind] = FVector((float)r / 255., (float)g / 255., (float)b / 255.);
      return;
      } void Triangle2::setTexCoord(int ind, float s, float t) { tex_coords[ind] = FVector2D(s, t); } TArray<FVector4> Triangle2::toVector4() const { TArray<FVector4> res; for(int i = 0; i < 3; i++) { res.Add(FVector4(v[i].X, v[i].Y, v[i].Z, 1.0f)); } return res; }
  • class URasterizer2Widget

    • Rasterizer2Widget.h

      #pragma once
      #include "CoreMinimal.h"
      #include "Blueprint/UserWidget.h"
      #include "Slate/SlateBrushAsset.h"
      #include "Components/Image.h"
      #include "Components/Button.h"
      #include "Rasterizer2Widget.generated.h"
      
      UCLASS()
      class GAMES101_API URasterizer2Widget : public UUserWidget
      {
          GENERATED_BODY()
      protected:
          virtual void NativePreConstruct();
          //virtual int32 NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
      public:
          //UFUNCTION(BlueprintCallable,Category="GAMES101 | 2")
              //void DrawBackground(FPaintContext& Context) const;
      UFUNCTION()
      void SwitchMASS();
      public: UPROPERTY() USlateBrushAsset* SlateBrushAsset;
      UPROPERTY(EditAnywhere, meta = (BindWidget))
          UImage* TCanvas;
      
      UPROPERTY(EditAnywhere, BlueprintReadWrite , meta = (BindWidget))
          UButton* Btn_Mass;
      private: FVector2D CanvasSize; bool bDrawSwitch = false; int32 drawInterval = 10; };
    • Rasterizer2Widget.cpp

      #include "Rasterizer2Widget.h"
      #include "Blueprint/WidgetBlueprintLibrary.h"
      #include "Actor_Assignment2.h"
      #include "Kismet/GameplayStatics.h"
      
      void URasterizer2Widget::NativePreConstruct()
      {
          Super::NativePreConstruct();
          PreConstruct(IsDesignTime());
          if (Btn_Mass!=nullptr)
          {
              Btn_Mass->OnClicked.AddDynamic(this, &URasterizer2Widget::SwitchMASS);
              UKismetSystemLibrary::PrintString(GetWorld(), TEXT("NativeConstruct"));
          }
      }
      void URasterizer2Widget::SwitchMASS()
      {
          TArray<AActor*> OutActors;
          UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor_Assignment2::StaticClass(), OutActors);
          if (OutActors.IsValidIndex(0)) {
              Cast<AActor_Assignment2>(OutActors[0])->SwitchMASS();
          }
      }
      /*
      int32 URasterizer2Widget::NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
      {
          if (bHasScriptImplementedPaint)
          {
              FPaintContext Context(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
              //OnPaint(Context);
              //DrawBackground(Context);
              return FMath::Max(LayerId, Context.MaxLayer);
          }
          return LayerId;
      }
      
      void URasterizer2Widget::DrawBackground(FPaintContext& Context) const
      {
          if (CanvasSize == FVector2D::ZeroVector) return;
      UWidgetBlueprintLibrary::DrawBox(Context, FVector2D::ZeroVector, CanvasSize, SlateBrushAsset, FColor(0, 0, 0, 0.6 * 255));
      for (int i = 0; i &lt;= CanvasSize.X / 20; i++) {
          int start = i * 20;
          UWidgetBlueprintLibrary::DrawLine(Context, FVector2D(start, 0), FVector2D(start, CanvasSize.Y), FColor(120, 120, 120, 255));
          UWidgetBlueprintLibrary::DrawLine(Context, FVector2D(0, start), FVector2D(CanvasSize.X, start), FColor(120, 120, 120, 255));
      }
      } */