YivanLee:虚幻4渲染编程专题概述及目录
UnrealEngine4经过很多年的迭代,内部发生了很多变化,当然很多东西也是保留的,方法和思路还是有沿袭下来,最近做了很多资源操作相关的工作,发现在项目开发过程中对工具开发的要求始终存在,即使UE4工具链强大,但是遇到很多实际项目的问题的时候仍然有很强的工具开发需求,不过UE4的工具开发难度至少是Unity的20倍,所以Unity用多了的开发人员可能会非常不习惯UE4的工具开发流程。
但是翻看各种资料,发现网上几乎没有相关资料,或者比较零散。再加之我之前的工具篇写得我自己也不是很满意并且我想写一些实用的进阶内容,所以我打算重写一下工具篇V2,下面我使用的引擎版本是4.26。
【1】Create your first UE4 tool
创建一个UE4插件,选择EditorStandard alone window,创建以后重新编译项目启动编辑器
启动以后就可以看到我们的插件工具的图标了,这里我把图标重新改了一下,找到插件目录,把里面的图片替换一下即可
我这里就提一下,具体介绍可以看我之前的文章:
YivanLee:虚幻4渲染编程(工具篇)【第一卷:开发我们的第一个引擎工具】
【2】GetSelectedAssets
在引擎工具开发过程中,我们常常需要各种资源数据,比如要做一个资源批处理的工具,我们需要拿到我们需要的资源。工具获取资源数据的方法无非就那么几种:
-
从资源管理器获取资源
-
从场景里获取资源
-
把资源指认给工具面板,从工具面板获取资源
这里先介绍
第一种
比较常用的,从资源管理器获取资源。这一步其实比较简单调用资源管理器的接口即可。
#include "Editor/UnrealEd/Public/AssetSelection.h"
//...
TArray<FAssetData> SelectedAssets;
AssetSelectionUtils::GetSelectedAssets(SelectedAssets);
//...
如上图所示,我选中了一些贴图资源,我点下按钮以后他们被我的工具获取到了。这里的
FAssetData
类型里包含了很多资源的信息,如路径,名字等。AssetSelection.h里还有很多其它有用的轮子。
第二种
从场景里获取工具需要操作的数据。这个也非常简单
#include "Engine/Selection.h"
//...
TArray<AActor*>SelectedActors;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
AActor* Actor = Cast<AActor>(*It);
if (Actor)
{
SelectedActors.Add(MoveTemp(Actor));
}
}
//...
还是两行代码就可以拿到场景中我们选中的物体,但是这些接口不太方便记忆,想要的时候还是要搜一会儿的。
第三种
方式可以参考我以前的文章,我这里就步赘述了
YivanLee:虚幻4渲染编程(工具篇)【第四卷:CustomizeDetailPanelForPlugin】
【3】Assets Management(
Texture2D Asset
)
我们常常需要用代码创建资源,下面以创建Texture资源为例:
1 Create UTexture Assets in C++
最简单的直接创建方法如下:
代码如下:
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
#include "Editor/UnrealEd/Public/PackageTools.h"
#include "AssetTools/Public/AssetToolsModule.h"
//...
FString AssetName = TEXT("TestTexture");
FString PackageName = TEXT("/Game/TestDir/") + AssetName;
UPackage* NewtexturePackage = CreatePackage(nullptr, *PackageName);
UTexture2D* NewTexture = NewObject<UTexture2D>(NewtexturePackage, FName(*AssetName), RF_Public | RF_Standalone);
FAssetRegistryModule::AssetCreated(NewTexture);
NewtexturePackage->MarkPackageDirty();
//...
虚幻4中各种名字,各种路径的概念十分复杂。这里创建Package需要使用虚拟相对路径。即上述代码的:
FString PackageName = TEXT("/Game/TestDir/") + AssetName;
其中以/Game/开头。Package和Asset之间的关系最好是去看UE的文件组织相关的资料,这两者的概念搞清楚以后理解起来还是很简单的。毕竟一个package里可以放多个object
2 Create Assets with auto unique name
我上面的例子是写死的,我们还可以用UE提供的轮子,来自动创建名字。
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
#include "Editor/UnrealEd/Public/PackageTools.h"
#include "AssetTools/Public/AssetToolsModule.h"
//...
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
FString AssetName = TEXT("DefualtTexture");
FString PackageName = TEXT("DefualtPackage");
AssetToolsModule.Get().CreateUniqueAssetName(TEXT("/Game/TestDir/") + PackageName, TEXT(""), PackageName, AssetName);
UPackage* NewtexturePackage = CreatePackage(nullptr, *PackageName);
UTexture2D* NewTexture = NewObject<UTexture2D>(NewtexturePackage, FName(*AssetName), RF_Public | RF_Standalone);
FAssetRegistryModule::AssetCreated(NewTexture);
NewtexturePackage->MarkPackageDirty();
//...
当需要大量程序化处理资源的时候自动生成名字还是很有必要的。
3 Interactive Create UTexture2D Assets
当然有时候还需要工具使用者决定资源的名字和路径,这时我们也可以使用UE的这个轮子
FAssetToolsModule
来帮忙,三行代码就能完成。
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
#include "Editor/UnrealEd/Public/PackageTools.h"
#include "AssetTools/Public/AssetToolsModule.h"
#include "Editor/UnrealEd/Classes/Factories/Texture2dFactoryNew.h"
//...
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
UTexture2DFactoryNew* FactoryInstance = DuplicateObject<UTexture2DFactoryNew>(GetDefault<UTexture2DFactoryNew>(), GetTransientPackage());
AssetToolsModule.Get().CreateAssetWithDialog(UTexture2D::StaticClass(), FactoryInstance);
//...
但是上述资源创建好了以后是空的
下面我们就开始向里面添加数据。
4
Fill data into UTexture2D Asset
UTexture的原始数据存在Source里,UE每种不同资源数据组织方式不太一样,但是如果之前你有引擎开发的经验的话,其实这些数据也很好找出来。
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
#include "Editor/UnrealEd/Public/PackageTools.h"
#include "AssetTools/Public/AssetToolsModule.h"
#include "Editor/UnrealEd/Classes/Factories/Texture2dFactoryNew.h"
//...
FString AssetName = TEXT("TestTexture");
FString PackageName = TEXT("/Game/TestDir/") + AssetName;
UPackage* NewtexturePackage = CreatePackage(nullptr, *PackageName);
UTexture2D* NewTexture = NewObject<UTexture2D>(NewtexturePackage, FName(*AssetName), RF_Public | RF_Standalone);
uint32 textureWidth = 256;
uint32 textureHeight = 256;
NewTexture->Source.Init(textureWidth, textureHeight, 1, 1, TSF_BGRA8);
uint8* MipData = NewTexture->Source.LockMip(0);
for (uint32 y = 0; y < textureHeight; y++)
{
uint8* PixlePtr = &MipData[(textureHeight - 1 - y) * textureWidth * sizeof(uint8) * 4];
for (uint32 x = 0; x < textureWidth; x++)
{
//B
*PixlePtr++ = 255;
//G
*PixlePtr++ = 0;
//R
*PixlePtr++ = 0;
//A
*PixlePtr++ = 255;
}
}
NewTexture->Source.UnlockMip(0);
NewTexture->SRGB = false;
NewTexture->CompressionSettings = TC_Default;
NewTexture->MipGenSettings = TMGS_FromTextureGroup;
NewTexture->CompressionNoAlpha = false;
NewTexture->PostEditChange();
FAssetRegistryModule::AssetCreated(NewTexture);
NewtexturePackage->MarkPackageDirty();
//...
除此以外引擎也为我们提供了一些现成的轮子
FImageUtils::CreateTexture2D
FImageUtils
里也有大量其它很有用的轮子。
上述都是EditorTime的时候对资源进行修改的,在游戏打包后上述的代码全部是不可以使用的。上面我仅仅是随便填了点数据到新的贴图里,下面我从一张已经存在的贴图里读取数据,然后填充到新的贴图里
代码如下:
#include "Runtime/Projects/Public/Interfaces/IPluginManager.h"
#include "Editor/UnrealEd/Public/AssetSelection.h"
#include "Engine/Selection.h"
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
#include "Editor/UnrealEd/Public/PackageTools.h"
#include "AssetTools/Public/AssetToolsModule.h"
#include "Editor/UnrealEd/Classes/Factories/Texture2dFactoryNew.h"
//...
FReply SArtToolsMain::TestClick3()
{
TArray<FAssetData> SelectedAssets;
AssetSelectionUtils::GetSelectedAssets(SelectedAssets);
const FAssetData& asset = SelectedAssets[0];
if(asset == nullptr) return FReply::Handled();
UObject* obj = asset.GetAsset();
UTexture2D* SelectedTexture = Cast<UTexture2D>(obj);
if(SelectedTexture == nullptr) return FReply::Handled();
FString AssetName = TEXT("TestTexture");
FString PackageName = TEXT("/Game/TestDir/") + AssetName;
UPackage* NewtexturePackage = CreatePackage(nullptr, *PackageName);
UTexture2D* NewTexture = NewObject<UTexture2D>(NewtexturePackage, FName(*AssetName), RF_Public | RF_Standalone);
uint32 textureWidth = SelectedTexture->GetSizeX();
uint32 textureHeight = SelectedTexture->GetSizeY();
NewTexture->Source.Init(textureWidth, textureHeight, 1, 1, SelectedTexture->Source.GetFormat());
uint8* MipData = NewTexture->Source.LockMip(0);
uint8* SelectedMipData = SelectedTexture->Source.LockMip(0);
FMemory::Memcpy(MipData, SelectedMipData, textureWidth * textureHeight * sizeof(uint8) * 4);
NewTexture->Source.UnlockMip(0);
SelectedTexture->Source.UnlockMip(0);
NewTexture->SRGB = SelectedTexture->SRGB;
NewTexture->CompressionSettings = SelectedTexture->CompressionSettings;
NewTexture->MipGenSettings = SelectedTexture->MipGenSettings;
NewTexture->CompressionNoAlpha = SelectedTexture->CompressionNoAlpha;
NewTexture->PostEditChange();
FAssetRegistryModule::AssetCreated(NewTexture);
NewtexturePackage->MarkPackageDirty();
return FReply::Handled();
}
//...
用同样的方法也可以从Render Target里抽数据创建静态资源,官方有这样的接口,我这里就不赘述了
UTexture2D* UTextureRenderTarget2D::ConstructTexture2D
5 Modify Utexture2D resource settings
综合之前的方法,下面来对已经存在的texture资源的设置进行批量修改,现在我对如下图所示的几张贴图进行修改
#include "Runtime/Projects/Public/Interfaces/IPluginManager.h"
#include "Editor/UnrealEd/Public/AssetSelection.h"
#include "Engine/Selection.h"
#include "Runtime/AssetRegistry/Public/AssetRegistryModule.h"
#include "Editor/UnrealEd/Public/PackageTools.h"
#include "AssetTools/Public/AssetToolsModule.h"
#include "Editor/UnrealEd/Classes/Factories/Texture2dFactoryNew.h"
//...
TArray<FAssetData> SelectedAssets;
AssetSelectionUtils::GetSelectedAssets(SelectedAssets);
for(const FAssetData& asset : SelectedAssets)
{
UObject* obj = asset.GetAsset();
UTexture2D* texture = Cast<UTexture2D>(obj);
if (texture)
{
texture->SRGB = false;
texture->CompressionSettings = TC_Default;
texture->MipGenSettings = TMGS_FromTextureGroup;
texture->CompressionNoAlpha = true;
texture->PostEditChange();
asset.GetPackage()->MarkPackageDirty();
}
}
//...
用之前的思路,先选中内容浏览器里我需要修改的贴图,然后触发我的修改逻辑
当然上面都是些非常基础的Texture2D相关的资源处理,还有很多高级处理方法我会日后再作介绍。
需要特别注意的一点是,UE的纹理数据存在source里,如果在我们修改的过程中把Source数据改坏了就只有重新导入资源了!所以在批量修改前务必做好备份。