开发者

Unreal学习之简单三角形的绘制详解

目录
  • 1. 概述
  • 2. 详论
    • 2.1 代码实现
    • 2.2 解析:Component
    • 2.3 解析:材质
    • 2.4 解析:包围盒
    • 2.5 解析:Section
  • 3. 其他

    1. 概述

    之所以写这个绘制简单三角形的实例其实是想知道如何在Unreal中通过代码绘制自定义Mesh,如果你会绘制一个三角形,那么自然就会绘制复杂的Mesh了。所以这是很多图形工作者的第一课。

    2. 详论

    2.1 代码实现

    Actor是Unreal的基本显示对象,有点类似于Unity中的GameObject或者OSG中的Node。因此,我们首先要实现一个继承自AActor的类

    头文件CustomMeshActor.h:

    #pragma once
    
    // clang-format off
    #include "CoreMipythonnimal.h"
    #include "GameFramework/Actor.h"
    #include "CustomMeshActor.generatersGpnd.h"
    // clang-format on
    
    UCLASS()
    class UESTUDY_API ACustomMeshActor : public AActor {
      GENERATED_BODY()
    
     public:
      // Sets default values for this actor's properties
      ACustomMeshActor();
    
     protected:
      // Called when the game starts or when spawned
      virtual void BeginPlay() override;
    
      UStaticMesh* CreateMesh();
      void CreateGeometry(FStaticMeshRenderData* RenderData);
      void CreateMaterial(UStaticMesh* mesh);
    
     public:
      // Called every frame
      virtual void Tick(float Deltwww.devze.comaTime) override;
    
      UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
      UStaticMeshComponent* staticMeshComponent;
    };
    

    实现CustomMeshActor.cpp:

    #include "CustomMeshActor.h"
    
    #include "Output.h"
    
    // Sets default values
    ACustomMeshActor::ACustomMeshActor() {
      // 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;
    }
    
    // Called when the game starts or when spawned
    void ACustomMeshActor::BeginPlay() {
      Super::BeginPlay();
    
      staticMeshComponent = NewObject<UStaticMeshComponent>(this);
    
      staticMeshComponent->SetMobility(EComponentMobility::Stationary);
      SetRootComponent(staticMeshComponent);
      staticMeshComponent->RegisterComponent();
    
      UStaticMesh* mesh = CreateMesh();
      if (mesh) {
        staticMeshComponent->SetStaticMesh(mesh);
      }
    }
    
    UStaticMesh* ACustomMeshActor::CreateMesh() {
      UStaticMesh* mesh = NewObject<UStaticMesh>(staticMeshComponent);
      mesh->NeverStream = true;
      mesh->SetIsBuiltAtRuntime(true);
    
      TUniquePtr<FStaticMeshRenderData> RenderData =
          MakeUnique<FStaticMeshRenderData>();
    
      CreateGeometry(RenderData.Get());
    
      CreateMaterial(mesh);
    
      mesh->SetRenderData(MoveTemp(RenderData));
      mesh->InitResources();
      mesh->CalculateExtendedBounds();  //设置包围盒之后调用这个函数起效,否则会被视锥体剔除
      return mesh;
    }
    
    void ACustomMeshActor::CreateMaterial(UStaticMesh* mesh) {
      UMaterial* material1 = (UMaterial*)StaticLoadObject(
          UMaterial::StaticClass(), nullptr,
          TEXT("Material'/Game/Materials/RedColor.RedColor'"));
    
      mesh->AddMaterial(material1);
    
      UMaterial* material2 = (UMaterial*)StaticLoadObject(
          UMaterial::StaticClass(), nullptr,
          TEXT(www.devze.com"Material'/Game/Materials/GreenColor.GreenColor'"));
    
      mesh->AddMaterial(material2);
    }
    
    void ACustomMeshActor::CreateGeometry(FStaticMeshRenderData* RenderData) {
      RenderData->AllocateLODResources(1);
      FStaticMeshLODResources& LODResources = RenderData->LODResources[0];
    
      int vertexNum = 4;
    
      TArray<FVector> xyzList;
      xyzList.Add(FVector(0, 0, 50));
      xyzList.Add(FVector(100, 0, 50));
      xyzList.Add(FVector(100, 100,android 50));
      xyzList.Add(FVector(0, 100, 50));
    
      TArray<FVector2D> uvList;
      uvList.Add(FVector2D(0, 1));
      uvList.Add(FVector2D(0, 0));
      uvList.Add(FVector2D(1, 0));
      uvList.Add(FVector2D(1, 1));
    
      // 设置顶点数据
      TArray<FStaticMeshBuildVertex> StaticMeshBuildVertices;
      StaticMeshBuildVertices.SetNum(vertexNum);
      for (int m = 0; m < vertexNum; m++) {
        StaticMeshBuildVertices[m].Position = xyzList[m];
        StaticMeshBuildVertices[m].Color = FColor(255, 0, 0);
        StaticMeshBuildVertices[m].UVs[0] = uvList[m];
        StaticMeshBuildVertices[m].TangentX = FVector(0, 1, 0);  //切线
        StaticMeshBuildVertices[m].TangentY = FVector(1, 0, 0);  //副切线
        StaticMeshBuildVertices[m].TangentZ = FVector(0, 0, 1);  //法向量
      }
    
      LODResources.bHasColorVertexData = false;
    
      //顶点buffer
      LODResources.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices);
    
      //法线,切线,贴图坐标buffer
      LODResources.VertexBuffers.StaticMeshVertexBuffer.Init(
          StaticMeshBuildVertices, 1);
    
      //设置索引数组
      TArray<uint32> indices;
      int numTriangles = 2;
      int indiceNum = numTriangles * 3;
      indices.SetNum(indiceNum);
      indices[0] = 2;
      indices[1] = 1;
      indices[2] = 0;
      indices[3] = 3;
      indices[4] = 2;
      indices[5] = 0;
    
      LODResources.IndexBuffer.SetIndices(indices,
                                          EIndexBufferStride::Type::AutoDetect);
    
      LODResources.bHasDepthOnlyIndices = false;
      LODResources.bHasReversedIndices = false;
      LODResources.bHasReversedDepthOnlyIndices = false;
      // LODResources.bHasAdjacencyInfo = false;
    
      FStaticMeshLODResources::FStaticMeshSectionArray& Sections =
          LODResources.Sections;
      {
        FStaticMeshSection& section = Sections.AddDefaulted_GetRef();
    
        section.bEnableCollision = false;
        section.MaterialIndex = 0;
        section.NumTriangles = 1;
        section.FirstIndex = 0;
        section.MinVertexIndex = 0;
        section.M开发者_Python培训axVertexIndex = 2;
      }
      {
        FStaticMeshSection& section = Sections.AddDefaulted_GetRef();
    
        section.bEnableCollision = false;
        section.MaterialIndex = 0;
        section.NumTriangles = 1;
        section.FirstIndex = 3;
        section.MinVertexIndex = 3;
        section.MaxVertexIndex = 5;
      }
    
      double boundArray[7] = {0, 0, 0, 200, 200, 200, 200};
    
      //设置包围盒
      FBoxSphereBounds BoundingBoxAndSphere;
      BoundingBoxAndSphere.Origin =
          FVector(boundArray[0], boundArray[1], boundArray[2]);
      BoundingBoxAndSphere.BoxExtent =
          FVector(boundArray[3], boundArray[4], boundArray[5]);
      BoundingBoxAndSphere.SphereRadius = boundArray[6];
      RenderData->Bounds = BoundingBoxAndSphere;
    }
    
    // Called every frame
    void ACustomMeshActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); }
    

    然后将这个类对象ACustomMeshActor拖放到场景中,显示结果如下:

    Unreal学习之简单三角形的绘制详解

    2.2 解析:Component

    1.Actor只是一个空壳,具体的功能是通过各种类型的Component实现的(这一点与Unity不谋而合),这里使用的是UStaticMeshComponent,这也是Unreal场景中用的最多的Mesh组件。

    2.这里组件初始化是在BeginPlay()中创建的,如果在构造函数中创建,那么就不能使用NewObject,而应该使用如下方法:

    // Sets default values
    ACustomMeshActor::ACustomMeshActor() {
        // 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;
    
        staticMeshComponent =
            CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SceneRoot"));
        staticMeshComponent->SetMobility(EComponentMobility::Static);
        SetRootComponent(staticMeshComponent);
    
        UStaticMesh* mesh = CreateMesh();
        if (mesh) {
            staticMeshComponent->SetStaticMesh(mesh);
        }
    }
    

    3.承接2,在BeginPlay()中创建和在构造函数中创建的区别就在于前者是运行时创建,而后者在程序运行之前就创建了,可以在未运行的编辑器状态下看到静态网格体和材质。

    4.承接2,在构造函数中创建的UStaticMeshComponent移动性被设置成Static了,这时运行会提示“光照需要重建”,也就是静态对象需要烘焙光照,在工具栏"构建"->"仅构建光照"烘培一下即可。这种方式运行时渲染效率最高。

    5.对比4,运行时创建的UStaticMeshComponent移动性可以设置成Stationary,表示这个静态物体不移动,启用缓存光照法,并且缓存动态阴影。

    2.3 解析:材质

    在UE编辑器分别创建了红色和绿色简单材质,注意材质是单面还是双面的,C++代码设置的要和材质蓝图中设置的要保持一致。最开始我参考的就是参考文献1中的代码,代码中设置成双面,但是我自己的材质蓝图中用的单面,程序启动直接崩溃了。

    如果场景中材质显示不正确,比如每次浏览场景时的效果都不一样,说明可能法向量没有设置,我最开始就没有注意这个问题以为是光照的问题。

    单面材质的话,正面是逆时针序还是顺时针序?从这个案例来看应该是逆时针。UE是个左手坐标系,X轴向前,法向量是(0, 0, 1),从法向量的一边看过去,顶点顺序是(100, 100, 50)->(100, 0, 50)->(0, 0, 50),明显是逆时针。

    2.4 解析:包围盒

    包围盒参数最好要设置,UE似乎默认实现了视景体裁剪,不在范围内的物体会不显示。如果在某些视角场景对象突然不显示了,可能包围盒参数没有设置正确,导致视景体裁剪错误地筛选掉了当前场景对象。

    FBoxSphereBounds BoundingBoxAndSphere;
    //...
    RenderData->Bounds = BoundingBoxAndSphere;
    //...
    mesh->CalculateExtendedBounds();  //设置包围盒之后调用这个函数起效,否则会被视锥体剔除
    

    即使是一个平面,包围盒的三个Size参数之一也不能为0,否则还是可能会在某些视角场景对象不显示。

    2.5 解析:Section

    Mesh内部是可以进行划分的,划分成多少个section就使用多少个材质,比如这里划分了两个section,最后就使用了两个材质。如下代码所示:

    FStaticMeshLODResources::FStaticMeshSectionArray& Sections =
        LODResources.Sections;
    {
      FStaticMeshSection& section = Sections.AddDefaulted_GetRef();
    
      section.bEnableCollision = false;
      section.MaterialIndex = 0;
      section.NumTriangles = 1;
      section.FirstIndex = 0;
      section.MinVertexIndex = 0;
      section.MaxVertexIndex = 2;
    }
    {
      FStaticMeshSection& section = Sections.AddDefaulted_GetRef();
    
      section.bEnableCollision = false;
      section.MaterialIndex = 0;
      section.NumTriangles = 1;
      section.FirstIndex = 3;
      section.MinVertexIndex = 3;
      section.MaxVertexIndex = 5;
    }
    

    3. 其他

    除了本文介绍的方法之外,也有其他的实现办法,具体可以参考文献3-5。实在是没有时间进行进一步的研究了,因此记录备份一下。另外,文献6-7可能对了解UE关于Mesh的内部实现有所帮助,笔者反正是看麻了。不得不说,这么一个微小的功能涉及到的内容还真不少,看来有的研究了。

    以上就是Unreal学习之简单三角形的绘制详解的详细内容,更多关于Unreal绘制三角形的资料请关注我们其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜