使用自定义组件在虚幻引擎4中渲染obj网格会呈现视觉瑕疵

问题描述

我正在尝试通过自定义组件加载obj文件来呈现它。 左侧的模型使用100的比例从搅拌器中导出,而右侧的模型来自相同的.obj文件,但直接从代码中进行解析并通过构建自定义的顶点工厂,索引缓冲区和位置进行渲染用FMeshBatch::bWireframe = true缓冲:

alt text

如您所见,右侧的模型呈现出明显的视觉伪像。

我首先创建了一个扩展了UPrimitiveComponent自定义类和一个扩展了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;
};

这是代理和组件的清单:

#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,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,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,sizeof(FVector),VET_Float3
            );
            Data.PositionComponentSRV = Params.PositionBuffer->PositionComponentSRV;

            // Tex Coords stuff
            Data.TextureCoordinatesSRV = Params.MeshBuffers->TexCoordsBufferSRV;
            Data.TextureCoordinates.Add(FVertexStreamComponent(
                &Params.MeshBuffers->TexCoordsBuffer,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,EVertexStreamUsage::ManualFetch
            );

            Data.LightMapCoordinateComponent = FVertexStreamComponent(
                &Params.MeshBuffers->TexCoordsBuffer,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;
        }
};


   
UTestPrimitiveComponent::UTestPrimitiveComponent(const FObjectinitializer& Objectinitializer) :
    UPrimitiveComponent(Objectinitializer) {
    UActorComponent::PrimaryComponentTick.bCanEverTick = true;
    TheMesh = new FOBJMesh();
    FString result;
    auto gamePath = FPaths::ProjectContentDir();
    auto fullPath = FPaths::Combine(gamePath,TEXT("RawContent"),TEXT("cube.obj"));
    FFileHelper::LoadFiletoString(result,*fullPath);

    FRawMesh meshInfo;
    TArray<FString> lines;
    result.ParseIntoArrayLines(lines);
    for (auto line : lines) {
        ParseLine(line,meshInfo);
    }
}

void UTestPrimitiveComponent::BeginPlay()
{
    GEngine->AddOnScreenDebugMessage(1,5.0f,FColor::Red,FString::Printf(TEXT("Vertices: %d,UVs: %d,normals: %d"),TheMesh->vertices.Num(),TheMesh->uvs.Num(),TheMesh->normals.Num()));
    GEngine->AddOnScreenDebugMessage(2,FString::Printf(TEXT("VIndices: %d,UVIndices: %d,NIndices: %d"),TheMesh->vertex_indices.Num(),TheMesh->tex_indices.Num(),TheMesh->normals_indices.Num()));
}

FPrimitiveSceneProxy* UTestPrimitiveComponent::CreateSceneProxy()
{
    return (FPrimitiveSceneProxy*)(new OBJSceneProxy(this));
}

void UTestPrimitiveComponent::ParseLine(FString& line,FRawMesh& Desc)
{
    if (line[0] == 'v') {
        if (line[1] == 'n') {
            // It is a normal
            line.RemoveFromStart(TEXT("vn "));
            FVector normal;
            ReadnormalFromLine(line,normal);
            TheMesh->normals.Add(normal);
        }
        else if (line[1] == 't') {
            // It is an uv vertex
            line.RemoveFromStart(TEXT("vt "));
            FVector2D uv;
            ReadUVFromLine(line,uv);
            TheMesh->uvs.Add(uv);
        }
        else {
            // It is a vertex
            line.RemoveFromStart(TEXT("v "));
            FVector vertex;
            ReadVertexFromLine(line,vertex);
            TheMesh->vertices.Add(vertex);
        }
    }
    else if (line[0] == 'f') {
        line.RemoveFromStart("f ");
        TArray<FString> elements;
        line.ParseIntoArray(elements,TEXT(" "));
        for (int i = 0; i < 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& line,FVector& normal)
{
    TArray<FString> elements;
    line.ParseIntoArray(elements,TEXT(" "));
    FDefaultValueHelper::ParseFloat(elements[0],normal.X);
    FDefaultValueHelper::ParseFloat(elements[1],normal.Y);
    FDefaultValueHelper::ParseFloat(elements[2],normal.Z);
}

void UTestPrimitiveComponent::ReadUVFromLine(FString& line,FVector2D& uv)
{
    TArray<FString> elements;
    line.ParseIntoArray(elements,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& line,FVector& vertex)
{
    TArray<FString> elements;
    line.ParseIntoArray(elements,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& element,FRawMesh& Desc)
{
    int vertex_index;
    // Splitting the vi/ui/ni triplet
    TArray<FString> tripletString;
    element.ParseIntoArray(tripletString,TEXT("/"));
    FDefaultValueHelper::ParseInt(tripletString[0],vertex_index);
    TheMesh->vertex_indices.Add(vertex_index);
    /* UE4 does not use this stuff
    if (tripletString.Num() > 1) {
        FDefaultValueHelper::ParseInt(tripletString[1],uv_index);
        TheMesh->tex_indices.Add(uv_index);
    }
    if (tripletString.Num() > 2) {
        FDefaultValueHelper::ParseInt(tripletString[2],normal_index);
        TheMesh->normals_indices.Add(normal_index);
    }
    */
}

我完全不知道为什么渲染损坏了,有人可以帮我吗?

解决方法

已解决,问题是Blender开始从1开始计算顶点(所以,问题是我如何读取obj文件)