title: Rendering an obj mesh in Unreal Engine 4 with custom component presents visual artifacts
body: <p>i'm trying to render an obj file by loading it through a custom component.
  The model on the left is straigth out exported from blender, using a scale of 100, while the model on the right is from the same .obj file, but parsed drectly from code and rendered by building a custom Vertex Factory, Index Buffer and Position Buffer, with <code>FMeshBatch::bWireframe = true</code> :
  <img src="https://i.ibb.co/tcGb5sJ/suzanne.png" alt="alt text" /></p>
  <p>As you can see, the model on the right renders with noticeable visual artifacts.</p>
  <p>I started by creating a custom class that extends <code>UPrimitiveComponent</code> and a custom proxy for it that extends <code>FPrimitiveComponent</code>:</p>
  <pre><code>// UTestPrimitiveComponent.h
  // Fill out your copyright notice in the Description page of Project Settings.
  
  #pragma once
  
  #include &quot;CoreMinimal.h&quot;
  #include &quot;Components/PrimitiveComponent.h&quot;
  #include &quot;UTestPrimitiveComponent.generated.h&quot;
  
  class FPrimitiveSceneProxy;
  class FObjectInitializer;
  class UStaticMesh;
  class FStaticMeshDescription;
  struct FRawMesh;
  
  USTRUCT()
  struct FOBJMesh {
      GENERATED_BODY()
  TArray&lt;FVector&gt; vertices;
  TArray&lt;FVector&gt; normals;
  TArray&lt;FVector2D&gt; uvs;
  
  TArray&lt;int32&gt; tex_indices; // Unused
  TArray&lt;uint32&gt; vertex_indices;
  TArray&lt;int32&gt; normals_indices; // Unused
  };
  
  UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
  class RENDERINGPIPELINE_API UTestPrimitiveComponent : 
  public UPrimitiveComponent
  {
  GENERATED_BODY()
  private:
  FOBJMesh* TheMesh;
  void ParseLine(FString&amp; line, FRawMesh&amp; Desc);
  void ReadNormalFromLine(FString&amp; line, FVector&amp; normal);
  void ReadUVFromLine(FString&amp; line, FVector2D&amp; uv);
  void ReadVertexFromLine(FString&amp; line, FVector&amp; vertex);
  void ReadFaceInfoFromLine(FString&amp; element, FRawMesh&amp; Desc);
  public:
  UTestPrimitiveComponent(const FObjectInitializer&amp;);
  void BeginPlay() override;
  __forceinline FOBJMesh* GetMesh() { return TheMesh; }
  
  FPrimitiveSceneProxy* CreateSceneProxy() override;
  };
  </code></pre>
  <p>While this is the listing for both the proxy and the component:</p>
  <pre><code>#include &quot;UTestPrimitiveComponent.h&quot;
  #include &quot;Components/MeshComponent.h&quot;
  #include &quot;GenericPlatform/GenericPlatformFile.h&quot;
  #include &quot;Engine/StaticMesh.h&quot;
  #include &quot;PrimitiveSceneProxy.h&quot;
  #include &quot;StaticMeshDescription.h&quot;
  #include &quot;Rendering/StaticMeshVertexBuffer.h&quot;
  #include &quot;RawMesh.h&quot;
  
  #include &quot;RHICommandList.h&quot;
  #include &quot;PrimitiveSceneProxy.h&quot;
  
  #include &quot;Misc/FileHelper.h&quot;
  #include &quot;Misc/Paths.h&quot;
  #include &quot;Misc/DefaultValueHelper.h&quot;
  #include &quot;Containers/Array.h&quot;
  
  #include &quot;EngineMinimal.h&quot;
  
  class UTestPrimitiveComponent;
  
  template &lt;typename T&gt;
  static FVertexBufferRHIRef CreateVertexBufferWithData(TArray&lt;T&gt;&amp; data) {
      FRHIResourceCreateInfo info;
      auto size = sizeof (T)* data.Num();
      FVertexBufferRHIRef buf = RHICreateVertexBuffer(size, BUF_Static | BUF_ShaderResource, info);
      void* ptr = static_cast&lt;void*&gt;(RHILockVertexBuffer(buf, 0, size, RLM_WriteOnly));
      FMemory::Memcpy(ptr, static_cast&lt;void*&gt;(data.GetData()), size);
      RHIUnlockVertexBuffer(buf);
      return buf;
  }
  
  struct FOBJIndexBuffer : public FIndexBuffer {
      TArray&lt;uint32&gt; Indices;
  
      virtual void InitRHI() override {
          FRHIResourceCreateInfo info;
          auto ElementSizeInBytes = sizeof(uint32);
          auto ArraySize = ElementSizeInBytes * Indices.Num();
          IndexBufferRHI = RHICreateIndexBuffer(ElementSizeInBytes, ArraySize, BUF_Static, info);
          void* ptr = static_cast&lt;void*&gt;(RHILockIndexBuffer(IndexBufferRHI, 0, ArraySize, RLM_WriteOnly));
          FMemory::Memcpy(ptr, static_cast&lt;void*&gt;(Indices.GetData()), ArraySize);
          RHIUnlockIndexBuffer(IndexBufferRHI);
      }
  };
  
  struct FOBJPositionBuffer : public FVertexBuffer {
      TArray&lt;FVector&gt; Vertices;
      FShaderResourceViewRHIRef PositionComponentSRV;
  
      virtual void InitRHI() override {
          FRHIResourceCreateInfo info;
          VertexBufferRHI = CreateVertexBufferWithData(Vertices);
          if (VertexBufferRHI)
          {
              PositionComponentSRV = 
  RHICreateShaderResourceView(FShaderResourceViewInitializer(VertexBufferRHI, PF_R32_FLOAT));
      }
      }
  };
  
  struct FOBJMeshBuffers : public FRenderResource {
      struct FTangentData {
          FPackedNormal X;
          FPackedNormal Z;
      public:
          FTangentData(FPackedNormal InZ, FPackedNormal InX) 
          : X(InX), Z(InZ) {}
      };
  
      FVertexBuffer TexCoordsBuffer;
      FVertexBuffer TangentsBuffer;
      TArray&lt;FVector2D&gt; uvs;
      TArray&lt;FTangentData&gt; Tangents;
  
  
      FShaderResourceViewRHIRef TexCoordsBufferSRV;
      FShaderResourceViewRHIRef TangentsBufferSRV;
  
      virtual void InitRHI() override {
          TexCoordsBuffer.VertexBufferRHI = CreateVertexBufferWithData(uvs);
          if (TexCoordsBuffer.VertexBufferRHI) {
          // PF_G16R16F if using half precision, todo when implementing full thing
              TexCoordsBufferSRV = RHICreateShaderResourceView(FShaderResourceViewInitializer(TexCoordsBuffer.VertexBufferRHI, PF_G32R32F));
          }
          TangentsBuffer.VertexBufferRHI = CreateVertexBufferWithData(Tangents);
          if (TangentsBuffer.VertexBufferRHI) {
              // PF_R8G8B8A8_SNORM if using half precision, todo when implementing full thing
              TangentsBufferSRV = 
  RHICreateShaderResourceView(FShaderResourceViewInitializer(TangentsBuffer.VertexBufferRHI, PF_R8G8B8A8_SNORM));
          }
          BeginInitResource(&amp;TexCoordsBuffer);
          BeginInitResource(&amp;TangentsBuffer);
      }
  };
  
  struct FOBJRenderData {
  
      FOBJPositionBuffer OBJPositionBuffer;
      FOBJMeshBuffers OBJMeshBuffers;
  
  };
  
  class OBJSceneProxy : public FPrimitiveSceneProxy {
  public:
      UMaterialInterface* Material;
      FLocalVertexFactory VertexFactory;
      FOBJMeshBuffers MeshBuffers;
      FOBJPositionBuffer PositionBuffer;
      FOBJIndexBuffer IndexBuffer;
  
      UTestPrimitiveComponent* TheComponent;
  
      OBJSceneProxy(UTestPrimitiveComponent* Component)
          : FPrimitiveSceneProxy(Component, TEXT(&quot;OBJ Component&quot;)),
          VertexFactory(GetScene().GetFeatureLevel(), &quot;FObjSceneProxy&quot;),
          TheComponent(Component) {
      
          IndexBuffer.Indices = TheComponent-&gt;GetMesh()-&gt;vertex_indices;
          PositionBuffer.Vertices = TheComponent-&gt;GetMesh()-&gt;vertices;
          MeshBuffers.uvs = Component-&gt;GetMesh()-&gt;uvs;
  
          for (int i = 1; i &lt; IndexBuffer.Indices.Num(); i++) {
                          // This block of code here is mostly wrong; Still, it shouldn't impact the rendering as much 
              // Blender y u start counting from 1
              auto CurIndex = IndexBuffer.Indices[i] - 1;
              auto PrevIndex = IndexBuffer.Indices[i - 1] - 1;
  
              auto prevVertex = PositionBuffer.Vertices[PrevIndex];
              auto curVertex = PositionBuffer.Vertices[CurIndex];
              auto TangentRight = (curVertex - prevVertex).GetSafeNormal();
              auto TangentFront = FVector::CrossProduct(TangentRight, curVertex.GetSafeNormal());
              MeshBuffers.Tangents.Add(FOBJMeshBuffers::FTangentData(TangentFront, TangentRight));
         }
  
          BeginInitResource(&amp;IndexBuffer);
          BeginInitResource(&amp;PositionBuffer);
          BeginInitResource(&amp;MeshBuffers);
          InitFactory();
          BeginInitResource(&amp;VertexFactory);
  
          Material = UMaterial::GetDefaultMaterial(MD_Surface);
      
      }
  
  
      virtual ~OBJSceneProxy()
      {
          IndexBuffer.ReleaseResource();
          PositionBuffer.ReleaseResource();
          MeshBuffers.TexCoordsBuffer.ReleaseResource();
          MeshBuffers.TangentsBuffer.ReleaseResource();
          MeshBuffers.ReleaseResource();
          VertexFactory.ReleaseResource();
      }
  
      void InitFactory() {
          struct FactoryParams {
              FLocalVertexFactory* Factory;
              FOBJMeshBuffers* MeshBuffers;
              FOBJPositionBuffer* PositionBuffer;
          } Params;
          Params.Factory = &amp;VertexFactory;
          Params.PositionBuffer = &amp;PositionBuffer;
          Params.MeshBuffers = &amp;MeshBuffers;
  
          ENQUEUE_RENDER_COMMAND(OBJInitVertexFactory)([Params] (FRHICommandListImmediate&amp; RHICmdList) {
              FLocalVertexFactory::FDataType Data;
              // Position stuff
              Data.PositionComponent = FVertexStreamComponent(
                  Params.PositionBuffer,
                  0,
                  sizeof(FVector),
                  VET_Float3
              );
              Data.PositionComponentSRV = Params.PositionBuffer-&gt;PositionComponentSRV;
  
              // Tex Coords stuff
              Data.TextureCoordinatesSRV = Params.MeshBuffers-&gt;TexCoordsBufferSRV;
              Data.TextureCoordinates.Add(FVertexStreamComponent(
                  &amp;Params.MeshBuffers-&gt;TexCoordsBuffer,
                  0,
                  sizeof(FVector2D),
                  VET_Float4, // Doppio della dimensione (VET_Float2): Why?
                  EVertexStreamUsage::ManualFetch
              ));
  
              // Tangents stuff
              Data.TangentsSRV = Params.MeshBuffers-&gt;TangentsBufferSRV;
  
              typedef FPackedNormal TangentType;
              auto TangentElemType = VET_Short2;
              auto TangentXOffset = STRUCT_OFFSET(FOBJMeshBuffers::FTangentData, X);
              auto TangentZOffset = STRUCT_OFFSET(FOBJMeshBuffers::FTangentData, Z);
              auto TangentSizeInBytes = sizeof(TangentType);
          
              Data.TangentBasisComponents[0] = FVertexStreamComponent(
                  &amp;Params.MeshBuffers-&gt;TangentsBuffer,
                  TangentXOffset,
                  TangentSizeInBytes,
                  TangentElemType,
                  EVertexStreamUsage::ManualFetch
              );
  
             Data.TangentBasisComponents[1] = FVertexStreamComponent(
                  &amp;Params.MeshBuffers-&gt;TangentsBuffer,
                  TangentZOffset,
                  TangentSizeInBytes,
                  TangentElemType,
                  EVertexStreamUsage::ManualFetch
              );
  
              Data.LightMapCoordinateComponent = FVertexStreamComponent(
                  &amp;Params.MeshBuffers-&gt;TexCoordsBuffer,
                  0,
                  sizeof(FVector2D),
                  VET_Float2,
                  EVertexStreamUsage::ManualFetch
              );
  
              FColorVertexBuffer::BindDefaultColorVertexBuffer(Params.Factory, Data, 
  FColorVertexBuffer::NullBindStride::FColorSizeForComponentOverride);
              Params.Factory-&gt;SetData(Data);
              Params.Factory-&gt;InitResource();
              });
      }
  
      // (StartIndex + IndexCount) * IndexBuffer-&gt;GetStride() 
      void DrawStaticElements(FStaticPrimitiveDrawInterface* DrawInterface)
      {
      
          check(IsInRenderingThread());
  
          FMaterialRenderProxy* Proxy = Material-&gt;GetRenderProxy();
          FMeshBatch batch;
          batch.bWireframe = true;
          batch.VertexFactory = &amp;VertexFactory;
          batch.Type = PT_TriangleList;
          batch.MaterialRenderProxy = Proxy;
          batch.DepthPriorityGroup = SDPG_World;
          batch.CastShadow = true;
          batch.LODIndex = 0;
  
          auto&amp; BatchElement = batch.Elements[0];
          BatchElement.IndexBuffer = &amp;IndexBuffer;
  
          // for i = FirstIndex to NumPrimitives ?
          BatchElement.FirstIndex = 0;
          BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;
  
          DrawInterface-&gt;DrawMesh(batch, MAX_FLT);
      }
  
      FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const
      {
          // Partially copied from StaticMeshRenderer.cpp
          FPrimitiveViewRelevance Result;
          Result.bDrawRelevance = true;
          Result.bRenderInMainPass = true;
          Result.bVelocityRelevance = IsMovable() &amp;&amp; Result.bOpaque &amp;&amp; Result.bRenderInMainPass;
          Result.bStaticRelevance = true;
          Result.bDynamicRelevance = false;
          return Result;
          }
  };
  
  
     
  UTestPrimitiveComponent::UTestPrimitiveComponent(const FObjectInitializer&amp; ObjectInitializer) :
      UPrimitiveComponent(ObjectInitializer) {
      UActorComponent::PrimaryComponentTick.bCanEverTick = true;
      TheMesh = new FOBJMesh();
      FString result;
      auto gamePath = FPaths::ProjectContentDir();
      auto fullPath = FPaths::Combine(gamePath, TEXT(&quot;RawContent&quot;), TEXT(&quot;cube.obj&quot;));
      FFileHelper::LoadFileToString(result, *fullPath);
  
      FRawMesh meshInfo;
      TArray&lt;FString&gt; lines;
      result.ParseIntoArrayLines(lines);
      for (auto line : lines) {
          ParseLine(line, meshInfo);
      }
  }
  
  void UTestPrimitiveComponent::BeginPlay()
  {
      GEngine-&gt;AddOnScreenDebugMessage(1, 5.0f, FColor::Red,
          FString::Printf(TEXT(&quot;Vertices: %d, UVs: %d, Normals: %d&quot;), TheMesh-&gt;vertices.Num(), TheMesh-&gt;uvs.Num(), TheMesh-&gt;normals.Num()));
      GEngine-&gt;AddOnScreenDebugMessage(2, 5.0f, FColor::Red,
          FString::Printf(TEXT(&quot;VIndices: %d, UVIndices: %d, NIndices: %d&quot;), TheMesh-&gt;vertex_indices.Num(), TheMesh-&gt;tex_indices.Num(), TheMesh-&gt;normals_indices.Num()));
  }
  
  FPrimitiveSceneProxy* UTestPrimitiveComponent::CreateSceneProxy()
  {
      return (FPrimitiveSceneProxy*)(new OBJSceneProxy(this));
  }
  
  void UTestPrimitiveComponent::ParseLine(FString&amp; line, FRawMesh&amp; Desc)
  {
      if (line[0] == 'v') {
          if (line[1] == 'n') {
              // It is a normal
              line.RemoveFromStart(TEXT(&quot;vn &quot;));
              FVector normal;
              ReadNormalFromLine(line, normal);
              TheMesh-&gt;normals.Add(normal);
          }
          else if (line[1] == 't') {
              // It is an uv vertex
              line.RemoveFromStart(TEXT(&quot;vt &quot;));
              FVector2D uv;
              ReadUVFromLine(line, uv);
              TheMesh-&gt;uvs.Add(uv);
          }
          else {
              // It is a vertex
              line.RemoveFromStart(TEXT(&quot;v &quot;));
              FVector vertex;
              ReadVertexFromLine(line, vertex);
              TheMesh-&gt;vertices.Add(vertex);
          }
      }
      else if (line[0] == 'f') {
          line.RemoveFromStart(&quot;f &quot;);
          TArray&lt;FString&gt; elements;
          line.ParseIntoArray(elements, TEXT(&quot; &quot;));
          for (int i = 0; i &lt; elements.Num(); i++) {
              ReadFaceInfoFromLine(elements[i], Desc);
          }
      }
      // Ignore the line if it's a comment, a usrlib/matlib/other stuff directive
      else return;
  }
  
  void UTestPrimitiveComponent::ReadNormalFromLine(FString&amp; line, FVector&amp; normal)
  {
      TArray&lt;FString&gt; elements;
      line.ParseIntoArray(elements, TEXT(&quot; &quot;));
      FDefaultValueHelper::ParseFloat(elements[0], normal.X);
      FDefaultValueHelper::ParseFloat(elements[1], normal.Y);
      FDefaultValueHelper::ParseFloat(elements[2], normal.Z);
  }
  
  void UTestPrimitiveComponent::ReadUVFromLine(FString&amp; line, FVector2D&amp; uv)
  {
      TArray&lt;FString&gt; elements;
      line.ParseIntoArray(elements, TEXT(&quot; &quot;));
      FDefaultValueHelper::ParseFloat(elements[0], uv.X);
      FDefaultValueHelper::ParseFloat(elements[1], uv.Y);
      if (elements.Num() == 3) {
          // We have a w! Ignore, UE4 does not support it
          // FDefaultValueHelper::ParseFloat(elements[2], uv.Z);
      }
  }
  
  void UTestPrimitiveComponent::ReadVertexFromLine(FString&amp; line, FVector&amp; vertex)
  {
      TArray&lt;FString&gt; elements;
      line.ParseIntoArray(elements, TEXT(&quot; &quot;));
      FDefaultValueHelper::ParseFloat(elements[0], vertex.X);
      FDefaultValueHelper::ParseFloat(elements[1], vertex.Y);
      FDefaultValueHelper::ParseFloat(elements[2], vertex.Z);
      if (elements.Num() == 4) {
          // We have a w! UE4 does not support it
          // FDefaultValueHelper::ParseFloat(elements[3], vertex.W);
      }
  }
  
  void UTestPrimitiveComponent::ReadFaceInfoFromLine(FString&amp; element, FRawMesh&amp; Desc)
  {
      int vertex_index;
      // Splitting the vi/ui/ni triplet
      TArray&lt;FString&gt; tripletString;
      element.ParseIntoArray(tripletString, TEXT(&quot;/&quot;));
      FDefaultValueHelper::ParseInt(tripletString[0], vertex_index);
      TheMesh-&gt;vertex_indices.Add(vertex_index);
      /* UE4 does not use this stuff
      if (tripletString.Num() &gt; 1) {
          FDefaultValueHelper::ParseInt(tripletString[1], uv_index);
          TheMesh-&gt;tex_indices.Add(uv_index);
      }
      if (tripletString.Num() &gt; 2) {
          FDefaultValueHelper::ParseInt(tripletString[2], normal_index);
          TheMesh-&gt;normals_indices.Add(normal_index);
      }
      */
  }
  </code></pre>
  <p>I have absolutely no idea why the rendering is broken, could anyone help me?</p>