Rendering an obj mesh with custom component presents visual artifacts

Hello, 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 FMeshBatch::bWireframe = true :

As you can see, the model on the right renders with noticeable visual artifacts.

I started by creating a custom class that extends UPrimitiveComponent and a custom proxy for it that extends FPrimitiveComponent:

// UTestPrimitiveComponent.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/PrimitiveComponent.h"
#include "UTestPrimitiveComponent.generated.h"

class FPrimitiveSceneProxy;
class FObjectInitializer;
class UStaticMesh;
class FStaticMeshDescription;
struct FRawMesh;

USTRUCT()
struct FOBJMesh {
	GENERATED_BODY()
TArray<FVector> vertices;
TArray<FVector> normals;
TArray<FVector2D> uvs;

TArray<int32> tex_indices; // Unused
TArray<uint32> vertex_indices;
TArray<int32> normals_indices; // Unused
};

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class RENDERINGPIPELINE_API UTestPrimitiveComponent : 
public UPrimitiveComponent
{
GENERATED_BODY()
private:
FOBJMesh* TheMesh;
void ParseLine(FString& line, FRawMesh& Desc);
void ReadNormalFromLine(FString& line, FVector& normal);
void ReadUVFromLine(FString& line, FVector2D& uv);
void ReadVertexFromLine(FString& line, FVector& vertex);
void ReadFaceInfoFromLine(FString& element, FRawMesh& Desc);
public:
UTestPrimitiveComponent(const FObjectInitializer&);
void BeginPlay() override;
__forceinline FOBJMesh* GetMesh() { return TheMesh; }

FPrimitiveSceneProxy* CreateSceneProxy() override;
};

WIthout getting into much detail, this is my Proxy’s code (i already ruled out the OBJ parsing step by loading a cube and checking with VS’s debugger if the vertices and indices were loaded correctly):

#include "UTestPrimitiveComponent.h"
#include "Components/MeshComponent.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "Engine/StaticMesh.h"
#include "PrimitiveSceneProxy.h"
#include "StaticMeshDescription.h"
#include "Rendering/StaticMeshVertexBuffer.h"
#include "RawMesh.h"

#include "RHICommandList.h"
#include "PrimitiveSceneProxy.h"

#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/DefaultValueHelper.h"
#include "Containers/Array.h"

#include "EngineMinimal.h"

class UTestPrimitiveComponent;

template <typename T>
static FVertexBufferRHIRef CreateVertexBufferWithData(TArray<T>& data) {
    FRHIResourceCreateInfo info;
    auto size = sizeof (T)* data.Num();
    FVertexBufferRHIRef buf = RHICreateVertexBuffer(size, BUF_Static | BUF_ShaderResource, info);
    void* ptr = static_cast<void*>(RHILockVertexBuffer(buf, 0, size, RLM_WriteOnly));
    FMemory::Memcpy(ptr, static_cast<void*>(data.GetData()), size);
    RHIUnlockVertexBuffer(buf);
    return buf;
}

struct FOBJIndexBuffer : public FIndexBuffer {
    TArray<uint32> 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<void*>(RHILockIndexBuffer(IndexBufferRHI, 0, ArraySize, RLM_WriteOnly));
	    FMemory::Memcpy(ptr, static_cast<void*>(Indices.GetData()), ArraySize);
	    RHIUnlockIndexBuffer(IndexBufferRHI);
    }
};

struct FOBJPositionBuffer : public FVertexBuffer {
    TArray<FVector> 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<FVector2D> uvs;
    TArray<FTangentData> 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(&TexCoordsBuffer);
	    BeginInitResource(&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("OBJ Component")),
	    VertexFactory(GetScene().GetFeatureLevel(), "FObjSceneProxy"),
	    TheComponent(Component) {
	
	    IndexBuffer.Indices = TheComponent->GetMesh()->vertex_indices;
	    PositionBuffer.Vertices = TheComponent->GetMesh()->vertices;
	    MeshBuffers.uvs = Component->GetMesh()->uvs;

	    for (int i = 1; i < 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(&IndexBuffer);
	    BeginInitResource(&PositionBuffer);
	    BeginInitResource(&MeshBuffers);
	    InitFactory();
	    BeginInitResource(&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 = &VertexFactory;
	    Params.PositionBuffer = &PositionBuffer;
	    Params.MeshBuffers = &MeshBuffers;

	    ENQUEUE_RENDER_COMMAND(OBJInitVertexFactory)([Params] (FRHICommandListImmediate& RHICmdList) {
		    FLocalVertexFactory::FDataType Data;
		    // Position stuff
		    Data.PositionComponent = FVertexStreamComponent(
			    Params.PositionBuffer,
			    0,
			    sizeof(FVector),
			    VET_Float3
		    );
		    Data.PositionComponentSRV = Params.PositionBuffer->PositionComponentSRV;

		    // Tex Coords stuff
		    Data.TextureCoordinatesSRV = Params.MeshBuffers->TexCoordsBufferSRV;
		    Data.TextureCoordinates.Add(FVertexStreamComponent(
			    &Params.MeshBuffers->TexCoordsBuffer,
			    0,
			    sizeof(FVector2D),
			    VET_Float4, // Doppio della dimensione (VET_Float2): Why?
			    EVertexStreamUsage::ManualFetch
		    ));

		    // Tangents stuff
		    Data.TangentsSRV = Params.MeshBuffers->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(
			    &Params.MeshBuffers->TangentsBuffer,
			    TangentXOffset,
			    TangentSizeInBytes,
			    TangentElemType,
			    EVertexStreamUsage::ManualFetch
		    );

		   Data.TangentBasisComponents[1] = FVertexStreamComponent(
			    &Params.MeshBuffers->TangentsBuffer,
			    TangentZOffset,
			    TangentSizeInBytes,
			    TangentElemType,
			    EVertexStreamUsage::ManualFetch
		    );

		    Data.LightMapCoordinateComponent = FVertexStreamComponent(
			    &Params.MeshBuffers->TexCoordsBuffer,
			    0,
			    sizeof(FVector2D),
			    VET_Float2,
			    EVertexStreamUsage::ManualFetch
		    );

		    FColorVertexBuffer::BindDefaultColorVertexBuffer(Params.Factory, Data, 
FColorVertexBuffer::NullBindStride::FColorSizeForComponentOverride);
		    Params.Factory->SetData(Data);
		    Params.Factory->InitResource();
		    });
    }

    // (StartIndex + IndexCount) * IndexBuffer->GetStride() 
    void DrawStaticElements(FStaticPrimitiveDrawInterface* DrawInterface)
    {
	
	    check(IsInRenderingThread());

	    FMaterialRenderProxy* Proxy = Material->GetRenderProxy();
	    FMeshBatch batch;
	    batch.bWireframe = true;
	    batch.VertexFactory = &VertexFactory;
	    batch.Type = PT_TriangleList;
	    batch.MaterialRenderProxy = Proxy;
	    batch.DepthPriorityGroup = SDPG_World;
	    batch.CastShadow = true;
	    batch.LODIndex = 0;

	    auto& BatchElement = batch.Elements[0];
	    BatchElement.IndexBuffer = &IndexBuffer;

	    // for i = FirstIndex to NumPrimitives ?
	    BatchElement.FirstIndex = 0;
	    BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;

	    DrawInterface->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() && Result.bOpaque && Result.bRenderInMainPass;
	    Result.bStaticRelevance = true;
	    Result.bDynamicRelevance = false;
	    return Result;
    	}
};

Solved, the issue was that Blender starts counting vertices from 1(so yes, the problem was how i was reading the obj file)