Unreal學(xué)習(xí)之簡(jiǎn)單三角形的繪制詳解
1. 概述
之所以寫(xiě)這個(gè)繪制簡(jiǎn)單三角形的實(shí)例其實(shí)是想知道如何在Unreal中通過(guò)代碼繪制自定義Mesh,如果你會(huì)繪制一個(gè)三角形,那么自然就會(huì)繪制復(fù)雜的Mesh了。所以這是很多圖形工作者的第一課。
2. 詳論
2.1 代碼實(shí)現(xiàn)
Actor是Unreal的基本顯示對(duì)象,有點(diǎn)類(lèi)似于Unity中的GameObject或者OSG中的Node。因此,我們首先要實(shí)現(xiàn)一個(gè)繼承自AActor的類(lèi)
頭文件CustomMeshActor.h:
#pragma once // clang-format off #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "CustomMeshActor.generated.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 DeltaTime) override; UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UStaticMeshComponent* staticMeshComponent; };
實(shí)現(xiàn)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(); //設(shè)置包圍盒之后調(diào)用這個(gè)函數(shù)起效,否則會(huì)被視錐體剔除 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("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, 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)); // 設(shè)置頂點(diǎn)數(shù)據(jù) 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); //切線(xiàn) StaticMeshBuildVertices[m].TangentY = FVector(1, 0, 0); //副切線(xiàn) StaticMeshBuildVertices[m].TangentZ = FVector(0, 0, 1); //法向量 } LODResources.bHasColorVertexData = false; //頂點(diǎn)buffer LODResources.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices); //法線(xiàn),切線(xiàn),貼圖坐標(biāo)buffer LODResources.VertexBuffers.StaticMeshVertexBuffer.Init( StaticMeshBuildVertices, 1); //設(shè)置索引數(shù)組 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.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; } double boundArray[7] = {0, 0, 0, 200, 200, 200, 200}; //設(shè)置包圍盒 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); }
然后將這個(gè)類(lèi)對(duì)象ACustomMeshActor拖放到場(chǎng)景中,顯示結(jié)果如下:
2.2 解析:Component
1.Actor只是一個(gè)空殼,具體的功能是通過(guò)各種類(lèi)型的Component實(shí)現(xiàn)的(這一點(diǎn)與Unity不謀而合),這里使用的是UStaticMeshComponent,這也是Unreal場(chǎng)景中用的最多的Mesh組件。
2.這里組件初始化是在BeginPlay()中創(chuàng)建的,如果在構(gòu)造函數(shù)中創(chuàng)建,那么就不能使用NewObject,而應(yīng)該使用如下方法:
// 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()中創(chuàng)建和在構(gòu)造函數(shù)中創(chuàng)建的區(qū)別就在于前者是運(yùn)行時(shí)創(chuàng)建,而后者在程序運(yùn)行之前就創(chuàng)建了,可以在未運(yùn)行的編輯器狀態(tài)下看到靜態(tài)網(wǎng)格體和材質(zhì)。
4.承接2,在構(gòu)造函數(shù)中創(chuàng)建的UStaticMeshComponent移動(dòng)性被設(shè)置成Static了,這時(shí)運(yùn)行會(huì)提示“光照需要重建”,也就是靜態(tài)對(duì)象需要烘焙光照,在工具欄"構(gòu)建"->"僅構(gòu)建光照"烘培一下即可。這種方式運(yùn)行時(shí)渲染效率最高。
5.對(duì)比4,運(yùn)行時(shí)創(chuàng)建的UStaticMeshComponent移動(dòng)性可以設(shè)置成Stationary,表示這個(gè)靜態(tài)物體不移動(dòng),啟用緩存光照法,并且緩存動(dòng)態(tài)陰影。
2.3 解析:材質(zhì)
在UE編輯器分別創(chuàng)建了紅色和綠色簡(jiǎn)單材質(zhì),注意材質(zhì)是單面還是雙面的,C++代碼設(shè)置的要和材質(zhì)藍(lán)圖中設(shè)置的要保持一致。最開(kāi)始我參考的就是參考文獻(xiàn)1中的代碼,代碼中設(shè)置成雙面,但是我自己的材質(zhì)藍(lán)圖中用的單面,程序啟動(dòng)直接崩潰了。
如果場(chǎng)景中材質(zhì)顯示不正確,比如每次瀏覽場(chǎng)景時(shí)的效果都不一樣,說(shuō)明可能法向量沒(méi)有設(shè)置,我最開(kāi)始就沒(méi)有注意這個(gè)問(wèn)題以為是光照的問(wèn)題。
單面材質(zhì)的話(huà),正面是逆時(shí)針序還是順時(shí)針序?從這個(gè)案例來(lái)看應(yīng)該是逆時(shí)針。UE是個(gè)左手坐標(biāo)系,X軸向前,法向量是(0, 0, 1),從法向量的一邊看過(guò)去,頂點(diǎn)順序是(100, 100, 50)->(100, 0, 50)->(0, 0, 50),明顯是逆時(shí)針。
2.4 解析:包圍盒
包圍盒參數(shù)最好要設(shè)置,UE似乎默認(rèn)實(shí)現(xiàn)了視景體裁剪,不在范圍內(nèi)的物體會(huì)不顯示。如果在某些視角場(chǎng)景對(duì)象突然不顯示了,可能包圍盒參數(shù)沒(méi)有設(shè)置正確,導(dǎo)致視景體裁剪錯(cuò)誤地篩選掉了當(dāng)前場(chǎng)景對(duì)象。
FBoxSphereBounds BoundingBoxAndSphere; //... RenderData->Bounds = BoundingBoxAndSphere; //... mesh->CalculateExtendedBounds(); //設(shè)置包圍盒之后調(diào)用這個(gè)函數(shù)起效,否則會(huì)被視錐體剔除
即使是一個(gè)平面,包圍盒的三個(gè)Size參數(shù)之一也不能為0,否則還是可能會(huì)在某些視角場(chǎng)景對(duì)象不顯示。
2.5 解析:Section
Mesh內(nèi)部是可以進(jìn)行劃分的,劃分成多少個(gè)section就使用多少個(gè)材質(zhì),比如這里劃分了兩個(gè)section,最后就使用了兩個(gè)材質(zhì)。如下代碼所示:
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. 其他
除了本文介紹的方法之外,也有其他的實(shí)現(xiàn)辦法,具體可以參考文獻(xiàn)3-5。實(shí)在是沒(méi)有時(shí)間進(jìn)行進(jìn)一步的研究了,因此記錄備份一下。另外,文獻(xiàn)6-7可能對(duì)了解UE關(guān)于Mesh的內(nèi)部實(shí)現(xiàn)有所幫助,筆者反正是看麻了。不得不說(shuō),這么一個(gè)微小的功能涉及到的內(nèi)容還真不少,看來(lái)有的研究了。
以上就是Unreal學(xué)習(xí)之簡(jiǎn)單三角形的繪制詳解的詳細(xì)內(nèi)容,更多關(guān)于Unreal繪制三角形的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言 風(fēng)靡一時(shí)的黃金礦工游戲?qū)崿F(xiàn)流程詳解
《黃金礦工》是一款非常經(jīng)典的游戲。在游戲中,玩家通過(guò)不斷挖礦,獲取金子,最終能夠闖入下一關(guān)。在這個(gè)過(guò)程中,會(huì)不斷有巖石、煙霧、老鼠來(lái)?yè)v亂,甚至還會(huì)出現(xiàn)扛著炸藥包的小老鼠,玩家必須戰(zhàn)勝它們,才能進(jìn)入更深的礦坑2021-11-11關(guān)于C++類(lèi)的成員初始化列表的相關(guān)問(wèn)題
下面小編就為大家?guī)?lái)一篇關(guān)于C++類(lèi)的成員初始化列表的相關(guān)問(wèn)題。小編覺(jué)得挺2016-05-05關(guān)于Qt添加opencv和libtorch庫(kù)的問(wèn)題
這篇文章主要介紹了Qt添加opencv和libtorch庫(kù)的相關(guān)知識(shí),兩種方法一種是通過(guò)手動(dòng)添加,一種是通過(guò)qt creator添加,需要的朋友可以參考下2022-01-01在Visual Studio中用C++語(yǔ)言創(chuàng)建DLL動(dòng)態(tài)鏈接庫(kù)圖文教程
這篇文章主要介紹了在Visual Studio中用C++語(yǔ)言創(chuàng)建DLL動(dòng)態(tài)鏈接庫(kù)圖文教程,本文詳細(xì)講解了DLL庫(kù)的創(chuàng)建過(guò)程,并給出了代碼示例,需要的朋友可以參考下2014-09-09C++實(shí)現(xiàn)LeetCode(52.N皇后問(wèn)題之二)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(52.N皇后問(wèn)題之二),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語(yǔ)言中的setlinebuf()、utmpname()、rewind函數(shù)使用
這篇文章主要介紹了C語(yǔ)言中的setlinebuf()、utmpname()、rewind函數(shù)使用,是C語(yǔ)言中操作文件的一些基本函數(shù),需要的朋友可以參考下2015-08-08C++學(xué)習(xí)筆記之類(lèi)與對(duì)象詳解
這篇文章主要為大家介紹了C++類(lèi)與對(duì)象,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2021-12-12C++中Digraphs、Trigraphs和Tokens的深入講解
這篇文章主要給大家介紹了關(guān)于C++中Digraphs、Trigraphs和Tokens的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C++具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09