模型
NIBIRU引擎为开发者提供了以下两种创建模型的方式:
(1)开发者从控件窗口或控件中拖拽或选择几何体列表的基本几何模型,引擎会自动创建一个节点,添加网格渲染组件(NMeshRenderer),同时根据几何模型类型填充上对应网格。
开发者也可以通过NResources来实现代码动态创建几何网格,再通过NActorManager创建对应模型节点,自动添加网格渲染组件(NMeshRenderer)并填充创建的几何网格。
(2)开发者导入到资源窗口中的模型文件在拖放至场景中时会为自动解析该模型文件生成模型资产,并根据模型资产自动构建节点树,带网格的节点会根据模型资产是否包含骨骼决定添加网格渲染组件(NMeshRenderer)还是蒙皮网格渲染组件(NSkinnedMeshRenderer),并填充网格。
开发者也可以通过NResources构建NModelResource来实现代码动态创建并加载模型文件,再通过NActorManager构建模型节点树,自动给包含网格节点添加对应网格组件并填充网格数据。
同时开发者还可以获取网格组件(NMeshComponent),修改使用材质以及各种状态,实现不同的渲染效果。
模型资产(NModelResource)
模型资产提供加载模型接口,保存加载好的模型数据(节点层级结构、顶点数据、索引数据、材质数据等),支持模型数据的复用。
模型资产管理
模型资产的创建、获取、删除接口都由NResources提供,常用接口如下表所示:
| 接口名称 | 返回值 | 接口含义 |
|---|---|---|
| LoadModelFromFile(const std::string& path, NS_MODELRESOURCE_CALLBACK callback = nullptr, bool needReload = false) | NModelResourcePtr | 加载并解析文件路径下模型。fileName表示文件路径;callback表示加载完成回调,空则为同步,不为空则为异步;needReload表示是否强制重新加载, true: 重新加载模型, false: 已经加载完成不再加载; |
| LoadModelFromFile(const std::string& path, EXTRA_VERTEX_FUNCTION extraVertexFunc, NS_MODELRESOURCE_CALLBACK callback = nullptr, bool needReload = false) | NModelResourcePtr | 加载并解析文件路径下模型。fileName表示文件路径;extraVertexFunc表示对顶点进行额外计算方法;callback表示加载完成回调,空则为同步,不为空则为异步;needReload表示是否强制重新加载, true: 重新加载模型, false: 已经加载完成不再加载; |
| LoadModelFromFile(const std::string& path, const NModelResourceLoadConfig& config) | NModelResourcePtr | 根据config加载并解析文件路径下模型并返回模型资产对象 |
| LoadModelFromMemory(const std::string& path, NResourceMemoryDataPtr memoryData, NS_MODELRESOURCE_CALLBACK callback = nullptr, bool needReload = false) | NModelResourcePtr | 加载模型资产通过传入文件数据。fileName表示文件路径;memoryData表示文件数据;callback表示加载完成回调,空则为同步,不为空则为异步;needReload表示是否强制重新加载, true: 重新加载模型, false: 已经加载完成不再加载; |
| LoadModelFromMemory(const std::string& path, NResourceMemoryDataPtr memoryData, const NModelResourceLoadConfig& config) | NModelResourcePtr | 根据config加载模型资产通过传入文件数据。 |
| GetModelResource(const std::string& name) | NModelResourcePtr | 获取名称为name的模型资产 |
| DestroyModelResource(const std::string& name) | Void | 删除名称为name的模型资产 |
| DestroyModelResource(NModelResourcePtr ms) | Void | 删除模型资产对象 |
模型资产接口
模型资产提供加载模型、是否存在动画、加载状态接口,常用接口如下表所示:
| 接口名称 | 返回值 | 接口含义 |
|---|---|---|
| HasAnimations() | bool | 获取模型资产是否存在动画数据 |
| GetLoadState() | EResourceLoadState | 获取模型资产加载状态。Unloaded: 初始态(未加载) ;Loading: 正在加载中; Loaded: 加载完成;LoadError: 加载失败;LoadingWaitGPU: 等待GL线程 |
| SetDefaultMaterial(const NMaterialPtr& mat) | void | 设置模型默认材质, 在调用CreateModelActor前设置, 设置为nullptr则使用解析模型得到材质 |
| CreateAnimationClip(const std::string& takeClipName, const std::string& clipName, float startFrame, float endFrame) | NAnimationClipPtr | 创建一个名为clipName动画切片, 数据来源名为takeClipName动画切片, 起始帧为startFrame, 结束帧为endFrame |
| GetAnimationClip(const std::string& clipName) | NAnimationClipPtr | 获取名为clipName动画切片 |
动态构建模型示例
同步加载模型文件
{
//同步加载模型
//加载对应路径模型文件,只写路径就是同步
NModelResourcePtr mr = NResources::LoadModelFromFile("Assets/Models/Teapot/Teapot.obj");
//创建actor,需要放在加载模型文件后
NActorPtr actor = NActorManager::CreateModelActor("Teapot", mr);
//设置显示位置
actor->SetLocalPosition(Vector3(-1, 0, 5));
}异步加载模型文件
{
//异步加载模型
//加载对应路径模型资源,加上回调就是异步
NModelResourcePtr mr = NResources::LoadModelFromFile("Assets/Models/Teapot/Teapot.obj", [=](NModelResourcePtr modelResource)
{
NActorPtr actor = NActorManager::CreateModelActor("Teapot", modelResource);
actor->SetLocalPosition(Vector3(0, 0, 5));
}, false);
}创建几何模型
{
//几何模型
//创建Cube几何体
auto cubeActor = NActorManager::CreateModelActor("testCube", NPrimitiveType::PT_CUBE);
//设置位置
cubeActor->SetLocalPosition(Vector3(0, 0, 5));
}网格组件(NMeshComponent)
NMeshComponent是网格组件的基类,提供网格渲染的通用接口,常用接口如下表所示:
| 接口名称 | 返回值 | 接口含义 |
|---|---|---|
| SetMaterial(NMaterialPtr mat) | Void | 设置第一个材质实例为mat. |
| SetMaterial(uint32_t id, NMaterialPtr mat) | Void | 设置第id个材质实例为mat. |
| GetMaterial(uint32_t id = 0) | NMaterialPtr | 获取第id个材质实例对象. |
| SetSharedMaterial(uint32_t id, NMaterialPtr mat) | Void | 设置第id个共享材质为mat. |
| GetSharedMaterial(uint32_t id = 0) | NMaterialPtr | 获取第id个共享材质对象. |
| GetMaterialCount() | uint32_t | 获取材质实例数量. |
| SetEnableCulling(bool enableCulling) | void | 设置是否能剔除 |
| IsEnableCulling() | bool | 获取是否能参与剔除 |
| SetReceiveShadows(bool b) | void | 设置是否接收阴影 |
| IsReceiveShadows() | bool | 获取是否接收阴影 |
| SetCastShadows(bool b) | void | 设置是否投射阴影 |
| SetMesh(NMeshPtr nmesh) | void | 设置网格数据 |
| GetSharedMesh() | NMeshPtr | 获取共享网格数据 |
| GetMesh() | NMeshPtr | 获取网格数据实例 |
网格(NMesh)
NMesh是对外提供的网格类型,可以获取顶点、法线、索引等数据,用户可以对网格进行编辑等操作。常用接口如下表所示:
| 接口名称 | 返回值 | 接口含义 |
|---|---|---|
| SetVertices(const std::vector<Vector3>& vertices) | void | 设置顶点数据 |
| GetVertices() | const std::vector<Vector3>& | 获取顶点数据 |
| SetNormals(const std::vector<Vector3>& normals) | void | 设置法线数据 |
| GetNormals() | const std::vector<Vector3>& | 获取法线数据 |
| SetUV(const std::vector<Vector2>& uv) | void | 设置UV数据 |
| GetUV() | const std::vector<Vector2>& | 获取UV数据 |
| SetVertexColors(const std::vector<Color32>& colors) | void | 设置顶点颜色 |
| GetVertexColors() | const std::vector<Color32>& | 获取顶点颜色 |
| SetTangents(const std::vector<Vector3>& tangents) | void | 设置切线数据 |
| GetTangents() | const std::vector<Vector3>& | 获取切线数据 |
| SetBitangents(const std::vector<Vector3>& bitangents) | void | 设置副切线数据 |
| GetBitangents() | const std::vector<Vector3>& | 获取副切线数据 |
| SetTriangles(const std::vector<uint32>& indices) | void | 设置第0个索引列表为indices,类型为PT_Triangles(会清除所有已存在SubMeshDescriptor) |
| SetTriangles(int subMesh, const std::vector<uint32>& indices) | void | 设置第submesh个索引列表为indices,类型为PT_Triangles |
| SetIndices(const std::vector<uint32>& indices, EPrimitiveType type = EPrimitiveType::PT_Triangles) | void | 设置第0个索引列表为indices,类型为type(会清除所有已存在SubMeshDescriptor) |
| SetIndices(int subMesh, EPrimitiveType type, const std::vector<uint32>& indices) | void | 设置第submesh个索引列表为indices,类型为type |
| SetIndices(int subMesh, EPrimitiveType type, const std::vector<uint32>& indices, int indicesStart, int indicesLength) | void | 设置第submesh个索引列表为indices,类型为type,起始索引为indicesStart,索引数量为indicesLength |
| GetIndices(int subMesh) | std::vector<uint32> | 获取第subMesh个索引列表 |
| SetSubMeshCount(uint32 count) | void | 设置网格submesh数量 |
| GetSubMeshCount() | uint32 | 获取网格submesh数量 |
| SetSubMesh(uint32 index, const SubMeshDescriptor& descriptor) | void | 设置第index个submesh的描述为descriptor |
| GetSubMesh(uint32 index) | const SubMeshDescriptor& | 获取第index个submesh的描述 |
网格渲染组件(NMeshRenderer)
网格渲染组件用于渲染填充网格,继承自网格组件(NMeshComponent)。
蒙皮网格渲染组件(NSkinnedMeshRenderer)
蒙皮网格渲染组件渲染可变形网格,继承自网格组件(NMeshComponent)。当将可变形网格或具有可变形网格的模型添加到场景中时,引擎会将蒙皮网格渲染组件添加到生成的NActor中。
使用示例
自定义网格
{
//自定义Mesh
//创建Actor
auto customActor = NActorManager::CreateActor("CustomActor");
customActor->SetLocalPosition(Vector3(0, 0, 5));
//添加组件
auto meshCom = customActor->AddComponent<NMeshComponent>();
//创建NMesh
NMeshPtr mesh = NResources::CreateMesh();
//组装顶点数据
std::vector<Vector3> vertices;
vertices.push_back(Vector3(-1, -1, 0));
vertices.push_back(Vector3(-1, 1, 0));
vertices.push_back(Vector3(1, 1, 0));
vertices.push_back(Vector3(1, -1, 0));
//组装法线
std::vector<Vector3> normals;
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
//组装索引
std::vector<uint32> indices;
indices.push_back(0);
indices.push_back(1);
indices.push_back(2);
indices.push_back(0);
indices.push_back(2);
indices.push_back(3);
//填充数据
mesh->SetVertices(vertices);
mesh->SetNormals(normals);
//设置索引数据,绘制类型为三角面
mesh->SetTriangles(indices);
//在NMesh所有数据组装好再调用SetMesh,若meshCom未设置材质,引擎会自动使用PBR材质
meshCom->SetMesh(mesh);
//用户可以根据Submesh数量(默认数量为1)修改材质
NMaterialPtr mat = NResources::CreateMaterial("TestMaterial", "PBR");
mat->SetMainColor(Color::Red);
meshCom->SetMaterial(0, mat);
}模型弯曲
{
//计算曲率半径,根据模型缩放计算相对地球的比例
//模型缩放系数
double scaleFactor = 1;
auto meshCom = GetNActor()->GetComponent<NMeshComponent>();
if (meshCom)
{
//获取NMesh
NMeshPtr mesh = meshCom->GetMesh();
std::vector<Vector3> vertices = mesh->GetVertices();
for (int i = 0; i < vertices.size(); i++)
{
//地球半径
double RADIUS = 50;
//离地高度
double height = 1;
double CurvatureRadius = (RADIUS + height) / scaleFactor;
Vector3 pos = vertices[i];
Vector3 pp = pos;
//模型向上类型
bool isYup = false;
if (isYup)
{
pp += Vector3(0, CurvatureRadius, 0);
Vector3 dir = pp.NormalizedCopy();
pp = dir * (CurvatureRadius + pos.y);
pp -= Vector3(0, CurvatureRadius, 0);
}
else
{
pp -= Vector3(0, 0, CurvatureRadius);
Vector3 dir = pp.NormalizedCopy();
pp = dir * (CurvatureRadius + pos.z);
pp += Vector3(0, 0, CurvatureRadius);
}
vertices[i] = pp;
}
//修改顶点
mesh->SetVertices(vertices);
//设置NMesh
meshCom->SetMesh(mesh);
}
}自定义多SubMesh网格
{
//自定义Mesh
//创建Actor
auto customActor = NActorManager::CreateActor("customActor");
customActor->SetLocalPosition(Vector3(1, 0, 5));
//添加组件
auto meshCom = customActor->AddComponent<NMeshRenderer>();
//创建NMesh
NMeshPtr mesh = NResources::CreateMesh();
//组装顶点数据
std::vector<Vector3> vertices;
vertices.push_back(Vector3(-1, -1, 0));
vertices.push_back(Vector3(-1, 1, 0));
vertices.push_back(Vector3(1, 1, 0));
vertices.push_back(Vector3(1, -1, 0));
vertices.push_back(Vector3(-2, -2, 0));
vertices.push_back(Vector3(-2, 2, 0));
vertices.push_back(Vector3(2, 2, 0));
vertices.push_back(Vector3(2, -2, 0));
//组装法线
std::vector<Vector3> normals;
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
normals.push_back(Vector3(0, 0, -1));
//组装索引
std::vector<unsigned int> indices;
//三角面索引
indices.push_back(0);
indices.push_back(1);
indices.push_back(2);
indices.push_back(0);
indices.push_back(2);
indices.push_back(3);
//画线
indices.push_back(4);
indices.push_back(5);
indices.push_back(5);
indices.push_back(6);
indices.push_back(6);
indices.push_back(7);
indices.push_back(7);
indices.push_back(4);
//填充数据
mesh->SetVertices(vertices);
mesh->SetNormals(normals);
mesh->SetIndices(indices);
//先设置submesh数量
mesh->SetSubMeshCount(2);
//再设置submesh的具体描述
SubMeshDescriptor subMeshDes;
//总索引的起始位置
subMeshDes.indexStart = 0;
//索引数量
subMeshDes.indexCount = 6;
mesh->SetSubMesh(0, subMeshDes);
//设置第二个submesh具体描述
SubMeshDescriptor subMeshDes1;
subMeshDes1.indexStart = 6;
subMeshDes1.indexCount = 8;
subMeshDes1.type = EPrimitiveType::PT_Lines;
mesh->SetSubMesh(1, subMeshDes1);
//在NMesh所有数据组装好再调用SetMesh
meshCom->SetMesh(mesh);
//根据Submesh数量设置材质
NMaterialPtr mat = NResources::CreateMaterial("TestMat1", "PBR");
NMaterialPtr mat1 = NResources::CreateMaterial("TestMat2", "Unlit");
mat1->SetMainColor(Color::Red);
meshCom->SetMaterial(0, mat);
meshCom->SetMaterial(1, mat1);
}替换材质
{
//示例创建引擎提供内置着色器类型材质,要创建自定义着色器类型材质如:NResources::CreateMaterial("TestMat", "PBRNormalMap");
NMaterialPtr mat = NResources::CreateMaterial("TestMat", EMaterialTemplate::MT_Blinphong);
//假设当前脚本绑定在拥有NMeshComponent的NActor上
NMeshComponentPtr meshCom = GetNActor()->GetComponent<NMeshComponent>();
//演示将所有材质实例替换为新创建出来的材质,也可以仅替换对应材质
if (meshCom)
{
for (int i = 0; i < meshCom->GetMaterialCount(); i++)
{
meshCom->SetMaterial(i, mat);
}
}
}动画控制组件(NAnimator)
开发者在导入包含动画帧数据的模型时,引擎会自动为根节点添加NAnimator组件。NAnimator组件主要用于控制动画播放、停止、切片等操作。NAnimator的常用接口如下表所示:
| 接口名称 | 返回值 | 接口含义 |
|---|---|---|
| MakeState(NAnimationClipPtr clip, const std::string& stateName) | NAnimationStatePtr | 使用动画切片clip制作名为stateName状态 |
| CrossFade(const std::string& stateName, float normalizedTransitionDuration) | bool | 从当前帧开始过渡到名为stateName动画状态,过渡时间为当前状态总时间的normalizedTransitionDuration(0~1) |
| CrossFadeInFixedTime(const std::string& stateName, float fixedTime) | bool | 从当前帧开始过渡到名为stateName动画状态,过渡时间为fixedTime |
| Play(const std::string& stateName, float normalizedTime = 0.f) | bool | 播放名为stateName状态,时间偏移为stateName状态总时间的normalizedTime(0~1) |
| PlayInFixedTime(const std::string& stateName, float fixedTime = 0.f) | bool | 播放名为stateName状态,,时间偏移为fixedTime |
| GetCurrentState() | NAnimationStatePtr | 获取当前状态 |
| GetNextState() | NAnimationStatePtr | 获取下一个状态 |
| GetAvatar() | NAvatarPtr | 获取替身系统 |
| Play() | bool | 播放动画 |
| Stop() | void | 停止动画并将模型显示为动画第一帧 |
| Reset() | void | 重置动画到当前动画状态第一帧。 |
| IsPlaying() | bool | 获取动画组件是否处于播放动画状态。 |
| GetAnimationState(const std::string& stateName) | NAnimationStatePtr | 获取名为stateName的动画状态。 |
| SetNeedFresh() | void | 设置无动画状态机需要根据骨骼节点计算一帧动画 |
| FindBone(const std::string& name) | int | 查找名为name的骨骼索引 |
动画状态(NAnimationState)
动画状态NAnimationState控制在播放任何动画的速度和循环状态。NAnimationState的常用接口如下表所示:
| 接口名称 | 返回值 | 接口含义 |
|---|---|---|
| GetAnimationClip() | NAnimationClipPtr | 获取状态中存储的动画切片 |
| GetLength() | float | 获取动画切片的长度,单位秒 |
| SetSpeed(float speed) | void | 设置动画播放速度,正数为正向播放,负数为倒放 |
| GetSpeed() | float | 获取动画播放速度 |
| SetLoop(bool isLoop) | void | 设置当前状态是否循环播放 |
| IsLoop() | bool | 获取当前状态是否循环播放 |
动画切片(NAnimationClip)
动画切片NAnimationClip保存动画数据。
动画使用示例
//脚本绑在骨骼模型根节点上,从根节点获取NAnimator脚本
if (NInput::GetKeyDown(KeyboardKeys::F))
{
auto animator = GetNActor()->GetComponent<NAnimator>();
if (animator)
{
//播放动画
animator->Play();
}
}
else if (NInput::GetKeyDown(KeyboardKeys::G))
{
auto animator = GetNActor()->GetComponent<NAnimator>();
if (animator)
{
//停止动画
animator->Stop();
}
}
else if (NInput::GetKeyDown(KeyboardKeys::H))
{
auto animator = GetNActor()->GetComponent<NAnimator>();
if (animator)
{
//获取模型资源
NModelResourcePtr mr = NResources::GetModelResource("Assets/Models/xiaohuangyu/xiaohuangyu.FBX");
//创建游泳动画切片
NAnimationClipPtr swimClip = mr->CreateAnimationClip("Take 001", "swim", 0, 150);
//制作游泳动画状态
NAnimationStatePtr swimState = animator->MakeState(swimClip, "swim");
//设置游泳动画状态属性
swimState->SetLoop(true);
//创建死亡动画切片
NAnimationClipPtr dieClip = mr->CreateAnimationClip("Take 001", "die", 200, 243);
//制作死亡动画状态
NAnimationStatePtr dieState = animator->MakeState(dieClip, "die");
}
}
else if (NInput::GetKeyDown(KeyboardKeys::J))
{
auto animator = GetNActor()->GetComponent<NAnimator>();
if (animator)
{
//从正中间开始播放制作好的动画状态
animator->Play("swim", 0.5);
}
}
else if (NInput::GetKeyDown(KeyboardKeys::K))
{
auto animator = GetNActor()->GetComponent<NAnimator>();
if (animator)
{
//从当前状态过渡播放到die状态
animator->CrossFade("die", 0.1);
}
}
