網(wǎng)站首頁 編程語言 正文
1. 概述
之所以寫這個繪制簡單三角形的實(shí)例其實(shí)是想知道如何在Unreal中通過代碼繪制自定義Mesh,如果你會繪制一個三角形,那么自然就會繪制復(fù)雜的Mesh了。所以這是很多圖形工作者的第一課。
2. 詳論
2.1 代碼實(shí)現(xiàn)
Actor是Unreal的基本顯示對象,有點(diǎn)類似于Unity中的GameObject或者OSG中的Node。因此,我們首先要實(shí)現(xiàn)一個繼承自AActor的類
頭文件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)用這個函數(shù)起效,否則會被視錐體剔除
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); //切線
StaticMeshBuildVertices[m].TangentY = FVector(1, 0, 0); //副切線
StaticMeshBuildVertices[m].TangentZ = FVector(0, 0, 1); //法向量
}
LODResources.bHasColorVertexData = false;
//頂點(diǎn)buffer
LODResources.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices);
//法線,切線,貼圖坐標(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); }
然后將這個類對象ACustomMeshActor拖放到場景中,顯示結(jié)果如下:
2.2 解析:Component
1.Actor只是一個空殼,具體的功能是通過各種類型的Component實(shí)現(xiàn)的(這一點(diǎn)與Unity不謀而合),這里使用的是UStaticMeshComponent,這也是Unreal場景中用的最多的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)行時創(chuàng)建,而后者在程序運(yùn)行之前就創(chuàng)建了,可以在未運(yùn)行的編輯器狀態(tài)下看到靜態(tài)網(wǎng)格體和材質(zhì)。
4.承接2,在構(gòu)造函數(shù)中創(chuàng)建的UStaticMeshComponent移動性被設(shè)置成Static了,這時運(yùn)行會提示“光照需要重建”,也就是靜態(tài)對象需要烘焙光照,在工具欄"構(gòu)建"->"僅構(gòu)建光照"烘培一下即可。這種方式運(yùn)行時渲染效率最高。
5.對比4,運(yùn)行時創(chuàng)建的UStaticMeshComponent移動性可以設(shè)置成Stationary,表示這個靜態(tài)物體不移動,啟用緩存光照法,并且緩存動態(tài)陰影。
2.3 解析:材質(zhì)
在UE編輯器分別創(chuàng)建了紅色和綠色簡單材質(zhì),注意材質(zhì)是單面還是雙面的,C++代碼設(shè)置的要和材質(zhì)藍(lán)圖中設(shè)置的要保持一致。最開始我參考的就是參考文獻(xiàn)1中的代碼,代碼中設(shè)置成雙面,但是我自己的材質(zhì)藍(lán)圖中用的單面,程序啟動直接崩潰了。
如果場景中材質(zhì)顯示不正確,比如每次瀏覽場景時的效果都不一樣,說明可能法向量沒有設(shè)置,我最開始就沒有注意這個問題以為是光照的問題。
單面材質(zhì)的話,正面是逆時針序還是順時針序?從這個案例來看應(yīng)該是逆時針。UE是個左手坐標(biāo)系,X軸向前,法向量是(0, 0, 1),從法向量的一邊看過去,頂點(diǎn)順序是(100, 100, 50)->(100, 0, 50)->(0, 0, 50),明顯是逆時針。
2.4 解析:包圍盒
包圍盒參數(shù)最好要設(shè)置,UE似乎默認(rèn)實(shí)現(xiàn)了視景體裁剪,不在范圍內(nèi)的物體會不顯示。如果在某些視角場景對象突然不顯示了,可能包圍盒參數(shù)沒有設(shè)置正確,導(dǎo)致視景體裁剪錯誤地篩選掉了當(dāng)前場景對象。
FBoxSphereBounds BoundingBoxAndSphere;
//...
RenderData->Bounds = BoundingBoxAndSphere;
//...
mesh->CalculateExtendedBounds(); //設(shè)置包圍盒之后調(diào)用這個函數(shù)起效,否則會被視錐體剔除
即使是一個平面,包圍盒的三個Size參數(shù)之一也不能為0,否則還是可能會在某些視角場景對象不顯示。
2.5 解析:Section
Mesh內(nèi)部是可以進(jìn)行劃分的,劃分成多少個section就使用多少個材質(zhì),比如這里劃分了兩個section,最后就使用了兩個材質(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í)在是沒有時間進(jìn)行進(jìn)一步的研究了,因此記錄備份一下。另外,文獻(xiàn)6-7可能對了解UE關(guān)于Mesh的內(nèi)部實(shí)現(xiàn)有所幫助,筆者反正是看麻了。不得不說,這么一個微小的功能涉及到的內(nèi)容還真不少,看來有的研究了。
原文鏈接:https://www.cnblogs.com/charlee44/p/17084110.html
相關(guān)推薦
- 2023-05-31 pandas.DataFrame的for循環(huán)迭代的實(shí)現(xiàn)_python
- 2022-04-28 shell中的curl網(wǎng)絡(luò)請求的實(shí)現(xiàn)_linux shell
- 2022-01-03 CSS字體屬性之復(fù)合屬性
- 2023-07-08 qt打開項(xiàng)目缺少ui_文件,使用手動生成
- 2023-06-03 C++11學(xué)習(xí)之右值引用和移動語義詳解_C 語言
- 2022-08-25 Python??中的pass語句語法詳析_python
- 2022-07-22 linux centos 7 vim配置詳解
- 2022-10-16 Android面向切面基于AOP實(shí)現(xiàn)登錄攔截的場景示例_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支