Unreal学习笔记2-绘制简单三角形
1. 概述
之所以写这个绘制简单三角形的实例其实是想知道如何在Unreal中通过代码绘制自定义Mesh,如果你会绘制一个三角形,那么自然就会绘制复杂的Mesh了。所以这是很多图形工作者的第一课。
2. 详论
2.1. 代码实现
Actor是Unreal的基本显示对象,有点类似于Unity中的GameObject或者OSG中的Node。因此,我们首先要实现一个继承自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;
};
实现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("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));
// 设置顶点数据
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.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};
//设置包围盒
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拖放到场景中,显示结果如下:

2.2. 解析:Component
Actor只是一个空壳,具体的功能是通过各种类型的Component实现的(这一点与Unity不谋而合),这里使用的是UStaticMeshComponent,这也是Unreal场景中用的最多的Mesh组件。
这里组件初始化是在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);
}
}
承接2,在BeginPlay()中创建和在构造函数中创建的区别就在于前者是运行时创建,而后者在程序运行之前就创建了,可以在未运行的编辑器状态下看到静态网格体和材质。
承接2,在构造函数中创建的UStaticMeshComponent移动性被设置成Static了,这时运行会提示“光照需要重建”,也就是静态对象需要烘焙光照,在工具栏"构建"->"仅构建光照"烘培一下即可。这种方式运行时渲染效率最高。
对比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的内部实现有所帮助,笔者反正是看麻了。不得不说,这么一个微小的功能涉及到的内容还真不少,看来有的研究了。
4. 参考
- UE4绘制简单三角形(二)
- UE4之坐标系
- [UE4 C++]三种方式绘制三角形
- Building a StaticMesh in C++ during runtime
- Build static mesh from description
- 虚幻 – StaticMesh 分析
- Creating a Custom Mesh Component in UE4
Unreal学习笔记2-绘制简单三角形的更多相关文章
- Unity3D学习笔记1——绘制一个三角形
目录 1. 绪论 2. 概述 3. 详论 3.1. 准备 3.2. 实现 3.3. 解析 3.3.1. 场景树对象 3.3.2. 绘制方法 4. 结果 1. 绪论 最近想学习一下Unity3d,无奈发 ...
- Unity3D学习笔记2——绘制一个带纹理的面
目录 1. 概述 2. 详论 2.1. 网格(Mesh) 2.1.1. 顶点 2.1.2. 顶点索引 2.2. 材质(Material) 2.2.1. 创建材质 2.2.2. 使用材质 2.3. 光照 ...
- Spring MVC 学习笔记10 —— 实现简单的用户管理(4.3)用户登录显示全局异常信息
</pre>Spring MVC 学习笔记10 -- 实现简单的用户管理(4.3)用户登录--显示全局异常信息<p></p><p></p>& ...
- Spring MVC 学习笔记9 —— 实现简单的用户管理(4)用户登录显示局部异常信息
Spring MVC 学习笔记9 -- 实现简单的用户管理(4.2)用户登录--显示局部异常信息 第二部分:显示局部异常信息,而不是500错误页 1. 写一个方法,把UserException传进来. ...
- Spring MVC 学习笔记8 —— 实现简单的用户管理(4)用户登录
Spring MVC 学习笔记8 -- 实现简单的用户管理(4)用户登录 增删改查,login 1. login.jsp,写在外面,及跟WEB-INF同一级目录,如:ls Webcontent; &g ...
- WebGL学习笔记二——绘制基本图元
webGL的基本图元点.线.三角形 gl.drawArrays(mode, first,count) first,代表从第几个点开始绘制即顶点的起始位置 count,代表绘制的点的数量. mode,代 ...
- blfs(systemv版本)学习笔记-制作一个简单的桌面系统
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 大概思路: lfs(系统)+xorg(驱动)+i3-wm(窗口+桌面)+lightdm(显示管理器+登录管理器) 链接: lfs ...
- [原创]java WEB学习笔记41:简单标签之带属性的自定义标签(输出指定文件,计算并输出两个数的最大值 demo)
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- [原创]java WEB学习笔记40:简单标签概述(背景,使用一个标签,标签库的API,SimpleTag接口,创建一个自定义的标签的步骤 和简单实践)
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- python学习笔记(3)--turtle简单绘制
参考:大学生mooc 北京理工大学的python程序与设计课程 蟒蛇绘制代码如下: #pythonDraw.py import turtle turtle.setup(650,350,200,200) ...
随机推荐
- Python标准库之 xml.etree.ElementTree
Element类型是一种灵活的容器对象,用于在内存中存储结构化数据. 每个element对象都具有以下属性: 1. tag:string对象,表示数据代表的种类. 2. attrib:dictiona ...
- 【转】Linux文件权限
转载一篇写得非常详细的linux文件权限,方便自己查阅! 转载来源:https://www.cnblogs.com/keyi/p/8124841.html ---------------------- ...
- Mybatis:解决调用带有集合类型形参的mapper方法时,集合参数为空或null的问题
此文章有问题,待修改! 使用Mybatis时,有时需要批量增删改查,这时就要向mapper方法中传入集合类型(List或Set)参数,下面是一个示例. // 该文件不完整,只展现关键部分 @Mappe ...
- ironic组件硬件自检服务——ironic-inspector
介绍 ironic-inspector是一个用于硬件自检的辅助型服务,它可以对被ironic组件管理的裸金属节点进行硬件自检,通过在裸金属节点上运行内存系统,发现裸金属节点的硬件信息,例如CPU数量和 ...
- 【OpenStack云平台】网络控制节点 HA 集群配置
个人名片: 因为云计算成为了监控工程师 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying 网络控制节点运行在管理网络和数据网络中,如果虚拟机实例要连接到互联网,网络控制节点也需要具备 ...
- Redis集群研究和实践(基于redis 3.2.5)(一)
前言 Redis 是我们目前大规模使用的缓存中间件,由于它强大高效而又便捷的功能,得到广泛的使 用. Redis在2015年发布了3.0.0,官方支持了redis cluster.至此结束了redis ...
- Git基操记录
小结 说多了都是泪,最近在整理Java的笔记,记笔记我比较喜欢使用markdown(当时使用Hexo的原因之一),毕竟作为刚入门槛一点点的程序小白,还没用上Idea(很多大佬都推荐这个),目前还在使用 ...
- oracle 内置函数(一)数值函数
oracle内置函数主要分为四类: 数值函数 字符函数 日期函数 转换函数 本分析数值函数: 一.四舍五入round round(double,m) double:我们要处理的小数. m: defau ...
- 深入浅出Seata的AT模式
目录 一.业务背景 二.Seata架构 1.核心组件 2.AT模式 三.案例分析 1.流程分析 2.写隔离 3.读隔离 四.对比XA模式 五.参考源码 单个掉队,导致集体被动摆烂: 一.业务背景 在分 ...
- 笔试面试--Java基础知识
一.基本概念 1.Java的优点 纯面向对象 平台无关性,"一次编译,到处运行(JVM上)",跨平台,可移植性 丰富类库:多线程.网络通信.垃圾回收 安全性(数组边界检测.byte ...