This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

1
02-Note/.keep Normal file
View File

@@ -0,0 +1 @@
this file is created for keeping the folder after git.

11
02-Note/02-Note.md Normal file
View File

@@ -0,0 +1,11 @@
# 02-Note Overview
```ccard
type: folder_brief_live
noteOnly: true
style: card
```
### 网址收藏
- [[模型资源]]
- [[资源笔记]]
- [[UE知识库]]

View File

@@ -0,0 +1,75 @@
# Wifi密码
Nswl67730588
# 打印机
172.168.6.3
admin
98614258
# NAS
FS6712x
172.168.5.17
admin / root
NiceFuture0521
## SVN
- svn://172.168.5.17/ASoul_UE5/ASoul_UE5
- admin admin 123456
- svn://172.168.5.17/EOE_UE4
- dazhi 123456
- yuege 123456
- jiajie 123456
## 共享盘账号密码
`//172.168.5.17/NSWL_TECH`
NSWL_TECH设置为Z盘。
登录用户dev
密码NiceFuture0521
## Perforce
```c++
docker run -d --restart unless-stopped \
-v /volume1/Docker/perforce/p4:/p4 \
-p 1666:1666 \
blueroses/perforce-helix-p4d:2024.5
```
大小棚用P4
```c++
docker run -d --restart unless-stopped \
-v /volume1/Docker/perforce/p4_studio:/p4 \
-p 1660:1666 \
blueroses/perforce-helix-p4d:2024.5
```
```c++
docker run -d --restart unless-stopped \
-v /volume1/Docker/perforce/p4_studio3:/p4 \
-p 1661:1666 \
blueroses/perforce-helix-p4d:2024.5
```
```c++
docker run -d --restart unless-stopped \
-v /volume1/Docker/perforce/p4_test:/p4 \
-p 1662:1666 \
blueroses/perforce-helix-p4d:2024.5
```
## chishin/wol-go-web
```bash
docker run --name=wol -d \
-e "WOLHTTPPORT=7000" \
-p 7000:7000 \
-v /volume1/Docker/wol/externall-file-on-host.csv:/app/computer.csv \
--restart unless-stopped \
dabondi/go-rest-wol
```
```
docker run -d --net=host \
--env PORT=7000 \
chishin/wol-go-web
```
## VPN
- https://115.236.67.234:8443
- loujiajie
- ios支付账号
- https://secloud1.ruijie.com.cn/SSLVPNClient

92
02-Note/ASoul/ASoul.md Normal file
View File

@@ -0,0 +1,92 @@
---
title: EOE相关
date: 2024-04-08 14:49:40
excerpt:
tags:
rating: ⭐
status: inprogress
destination:
share: false
obsidianUIMode: source
---
# ASoul
前面几个阶段的软件都是打包过的UE客户端里面带对应的几个功能。
## 流程架构
![[导播台架构图.canvas|Untitled]]
## 外部数据输入
- 青瞳动捕(网络)
- FaceMask面捕数据网络
- 动捕手套(蓝牙)
### FaceMask
内部定制的面捕App FaceMask考虑到成本目前还是使用Iphone11。
**面部头盔定制**
头盔针对每个中之人的头型分别进行调整(大小、动捕捕捉点)
除此之外头盔进行了轻量化的定制,减少中之人的头部负担,提升最长演出时间。**主要使用延迟摄像头数据线的方式(第三方定制)** 将摄像头安装到头盔前面的支架上。
## 导播台程序
程序采用C/S架构实现Server端负责数据接收以及同步客户端数据客户端负责发送导播人员的控制命令。每个部分操作都分别在一台电脑上操作理论上也可以放在一台电脑上但UI界面屏幕放不下
优点:
- 容灾:
- 性能扩展只需要扩展渲染机与服务器担当服务器的电脑即可。主要是显卡渲染机、CPU主频高、高速内存、高速固态硬盘。
- 相对好上手无需熟悉UE
- 资产管理:该导播台软件
- 制作其他派生产品方便
缺点:
- 流程规范&严格:
- 需要程序来拓展功能:如果有拓展功能或者改善功能,需要有一定工作经验的程序编写代码来实现,大概率无法通过蓝图进行添加。
服务器运行程序:
- 导播台程序服务端:同步各个导播台客户端的数据。
- Perforce Helix Core项目、引擎、外部资产版本管理。
个人并不推荐使用SVN建议现阶段使用Perforce Helix Core的免费版5个用户和20个工作区
***部分功能的实现方式因为没有看过代码所以只能靠猜***
### 数据接收&动作数据重定向
负责接收上述数据并且根据预设进行重定向与IK计算。骨骼数据同步略微费局域网带宽
### 舞台角色控制
1. 舞台角色添加、移除、显示(过渡特效)
2. 角色装备修改。比如手上应援棒、锤子;身上的翅膀;头上的帽子。
3. 角色特效。
同时可以看到头发、衣服的物理模拟效果。
### 各机位画面预览
预制若干镜头与机位视角大致为20+个。可以通过**StreamDock**进行切换。需要由导播员进行切换。支持虚拟摄像头(平板、手机)。
也可以预制镜头使用Sequence资产导入。
### 渲染机
用于渲染画面之后将信号输入到OBS推流机中。
硬件:
- 显卡Nvidia 4090
### 监视器
用于查看视频推流最终结果。如果有NG情况可以一键切掉画面黑屏或者vMix Pro的NG等待画面
## OBS推流机&vMix Pro
推流机运行OBS接收各种信号并且混合(有接混音台)。
### vMix Pro
主要用于推送PPT、图片、视频到OBS推流机中。一键切掉画面也是通过它实现的。
## 云服务
RTC服务一般用于线下Live降低延迟。可以找阿里云、腾讯云等各种服务商。
# 结果
1. P4V工程交付一个单独下载的版本能够提供版本管理规范以及分支设计与使用。ASoul组的程序以及美术仅使用P4V GUI各种操作。
2. 华枢说会提供修改过的引擎、项目与插件。
3. 可以拿到内部定制的面捕软件FaceMask源码工程。
4. 代码复杂度5年工作经验以上 6个程序 做了4个月因为有经过预演8个月
5. 线下Live 降低延迟的服务,RTC.
6. 需要对接字节IT进行服务器移交、宽带。
7. ASoul服务器运行的服务 导播台UE Server端、以及P4V。
# 交接
## 交接预判
1. 各种非交付软件的数据移交方式。P4V许可以及数据。
2. 判断是否修改引擎代码。在Asoul的UE工程上右键Switch UnrealEngine Version查看里面是否是Binary Build或者Source Build。确认是否可以使用官方的工程目录。
3. 询问是否可以提供VS生成的类图判断项目的代码量以及技术难度。
4. 查看资产规范文档。
5. 人员组织架构。需要哪些技术栈。方便后续复现。

View File

@@ -0,0 +1,6 @@
- [ ] Config
-
- [ ] Content
- [ ] Source
- [ ] Plugins
- [ ] EditorTool

View File

@@ -0,0 +1,16 @@
# 共性问题
- [ ] 巨蛋VJ材质有鬼影
# BP04
- RayMarching 云
# BP16
- Tick
- 乃琳
- BP Nai Lin Birthday 2023_上海 29~30 => 34~35
- 伴舞乃琳与非伴舞乃琳的Outline渲染停止。
- 地形草停止。
- grass.Enable 0
- DMXComponent
- DMX数据层 K掉。
- 场景变换卡顿。

View File

@@ -0,0 +1,90 @@
# 才艺位置重定向
- Override Instance Data
- Transform Origin Actor勾选LiveArea Actor即可。
#
1. **BP_1_kaichang**:问题比较大。
2. BP_2_beginner基本没问题会有2处掉帧。
3. BP_3_OnlyMyRailgun没问题。
# TODO
- PVW、PGM部分大屏失效
- 平面反射
- DMX优化
- CharactereMovmentCom优化
- 其他
## 已做记录
1. [x] MediaPlayer硬件加速与TSR。
2. [x] BP_1开头平面反射优化以及场景BP_PannelReflction K没
3. [x] CPU粒子优化
4. [x] 伴舞角色MovemenComponent以及Tick优化。
5. [x] DMX Beam优化。
6. [ ] Ultra_Dynamic_Sky的方向光级联阴影贴图设置。
#
- [ ] 节目Sequence播放完之后会卡住。
- [ ] 感觉是DMX的问题
- [ ] M_Beam_Master2
1. BP_1_kaichang
1. [ ] 开场特效光粒子掉帧。
2. [ ] 开场反射过于清晰。
2. [ ] BP_2_beginner 灯光开启时,黄光一闪卡顿。
3. [ ] BP_3_OnlyMyRailgun结束时掉帧。
4. [ ] BP_4_28Reasons结束卡柱。
5. [ ] BP_6_Mago靠右灯光闪一下卡住。
6. [ ] 节目8最后卡住。
7. [ ] 节目9QQ炫舞 卡。
1. [ ] BP_PlaneFaceCam_Wgt 相关Actor的制作有问题。
8. [ ] 节目11中后会有掉帧。
# BP_1_kaichang
## 性能问题
1. 粒子问题性能问题
1. NS_StarFlow/Cinematics/FX/star/NS_StarFlow是CPU粒子。
2. NS_StarCenter/Cinematics/FX/star/NS_StarCenter是CPU粒子。
2. 场景的Sequence还有一个Spawnable的Planar Reflection需要去掉。
3. BP_PlanarReflection在镜头不需要平面反射的时候应该设置为禁用。
4. 2905077~开始性能暴降:
1. DMX光柱性能影响
1. M_Beam_Master1 SM_Beam_RM 左右两边的侧灯各6盏
1. DMX Beam Visibility Ratio 1 => 0.12
2. 场景中的其他Beam材质也建议修改这个参数。
2. BP_PannelReflction会渲染体积云与阴影。
3. Ultra_Dynamic_Sky的方向光级联阴影贴图设置。
5. 2906750
1. M_Beam_Master1 SM_Beam_RM
6. **2913380**
1. 群贝拉场景。
1. 将CharacterMovement的tick与Update相关东西去掉。
7. 2916200
1. 透明渲染问题。 后面就几个红色的射灯调整一下啊DMX Beam Visibility Ratio 1 => 0.06,并且调整材质参数集的开合大小。
1. M_Beam_Master15 SM_Beam_RM
2. M_Beam_Master16 SM_Beam_RM
3. M_Beam_Master17 SM_Beam_RM
4. M_Beam_Master18 SM_Beam_RM
5. M_Beam_Master19 SM_Beam_RM
8. 2916349
1. M_Beam_Master21
2. M_Beam_Master14
9. 2921820
1. NS_Pyrotechnics_01/Cinematics/FX/FestivalFX/FX/NS_Pyrotechnics_01
10. **2926862**
1. 多个贝拉上台,有性能问题。
## 非性能建议
1. Cam_yaobi_02 2892625 这里建议暂时把屏幕空间反射关了,并且控制平面反射范围。
1. 屏幕空间反射亮度设置为0。
2. 发光贝拉建议把投影关了。
3. 2899860~2900140的星光反射会消失。
# BP_16_YYDYG
- 可以考虑把BP_PannelReflction K掉因为角色脚下的台子没有反射。
- Sequencer 去掉体积云。
- DMX
- M_Beam_Master5
- M_Beam_Master14

View File

@@ -0,0 +1,45 @@
# 测试流程
1. 需要手机与电脑处于同一个网段。
2. 设置MotionServer Ip设置、ARKitNetConfig.ini、MotionNetConfig.ini、MotionNetConfig2.ini
3. 打开FaceMask设置角色名称、电脑IP并且点连接。
4. 打开MotionProcess设置角色名称并且点连接。
**可以直接打开Map_MotionProcess进行开发与测试。**
## Editor测试方式
GM_TsLiveDirectorGameMode => PC_TsDirectorController=> BP_MotionSender0 BP_MotionReceiver0
# IdolAnimInstance
UpdateAnimation每帧执行PrepareMocapParameters()会获取TsMotionRetargetComponent的引用正常情况会获取IdolActor的Controller中的TsMotionRetargetComponent。
TsMotionRetargetComponent包含TsChingmuMocapReceiverActor => ChingmuMocapReceiverActor
# 相关动画节点
## AnimNode_FullBody
青瞳的动捕数据通过**AnimNode_FullBody**节点进行接收。具体是通过AMotionReceiverActor接收逻辑。
## AnimNode_FacialExpression
FaceMask面捕节点。
但具体的数据接收是在TsMediaPipeMocapReceiverActor与TsMotionRetargetComponent。
### FacialExpressionConfigAsset
用于设置表情各种数据。所有角色的表情资产位于`Content/LiveDirector/FaceExpressionConfig`
比较关键的曲线映射也就是将Arkit面捕数据从一个BlendShape0~1映射成5个对应的blendShape这样做到更加细腻的表情效果。比如tongueOut =>
tongueOut_1
tongueOut_2
tongueOut_3
tongueOut_4
tongueOut_5
BlendShape Maya源文件位于
## HandPoseAnimNode调整手部Pose
FName HandPoseDataTablePath = TEXT("DataTable'/Game/ResArt/HandPose/DT_HandPoseConfig.DT_HandPoseConfig'");
# 相关Actor
- AMotionReceiverActor动捕数据接收。
- AMediaPipeMocapReceiverActor面捕数据接收。
## AMediaPipeMocapReceiverActor
1. AMediaPipeMocapReceiverActorTick => OnGetMediaPipeData() => **(TsMediaPipeSkeleton)Skeleton.OnGetMediaPipeData(Data)** 这个函数逻辑在TsMediaPipeMocapReceiverActor。
2. TsMediaPipeMocapReceiverActorReceiveTick() => UpdateAnimation() 对数据进行过滤调整之后,将**面捕数据塞入AnimNode_FacialExpression**。

View File

@@ -0,0 +1,513 @@
# 相关类
- TsArkitDataReceiver(ArkitDataReceiver)
- TsChingmuMocapReceiverActor(ChingmuMocapReceiverActor)
- TsMotionReceiverActor(MotionReceiverActor) => BP_MotionReceiver定义了MotionNetConfig.ini。
- TsMotionSenderActor(MotionSenderActor)
# TsChingmuMocapReceiverActor
***地图里只会有一个生成的TsChingmuMocapReceiverActor来管理动捕数据接收***
1. Init()在Server才会Spawn TsChingmuMocapReceiverActor。
2. ConnectChingMu()**ChingmuComp.StartConnectServer()**
3. Multicast_AligmMotionTime()寻找场景中的BP_MotionReceiver并且调用Receiver.AlignTimeStamp()。
## ChingmuMocapReceiverActor
核心逻辑:
- ***FChingmuThread::Run()***
- ***AChingmuMocapReceiverActor::Tick()***
- AChingmuMocapReceiverActor::DoSample()
```c++
void AChingmuMocapReceiverActor::BeginPlay()
{
Super::BeginPlay();
MaxHumanCount = 10;
MaxRigidBodyCount = 10;
CacheLimit = 240;
SampledHumanData = NewObject<UMocapFrameData>();
ThreadInterval = 0.002;
BackIndexCount = int64(UMotionUtils::BackSampleTime / (1000.0 / CHINGMU_SERVER_FPS));//BackSampleTime = 100ms CHINGMU_SERVER_FPS =120ms
ChingmuComp = Cast<UChingMUComponent>(GetComponentByClass(UChingMUComponent::StaticClass()));
if (ChingmuComp == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("Chingmu Component is missing!!"));
}
Thread = new FChingmuThread("Chingmu Data Thread", this);
Sender = GetMotionSender();
}
```
FChingmuThread::Run()中处理完[[#ST_MocapFrameData]]之后将几个演员动补数据存入FrameQueue之后。在Tick()出队之后数据存入AllHumanFrames/AllRigidBodyFrames。
- AllHumanFrames
- ID
- std::vector<ST_MocapFrameData*> Frames
- ID
- TimeStamp
- FrameIndex
- BonesWorldPos
- BonesLocalRot
```c++
void AChingmuMocapReceiverActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if(!Sender)
{
Sender = GetMotionSender();
}
const auto CurTime = ULiveDirectorStatics::GetUnixTime();//获取当前系统时间
if(UseThread)
{
// 线程方式
// 在数据队列中获取青瞳数据
while (!FrameQueue.IsEmpty())//处理完所有
{
ST_MocapFrameData* Frame;
if (FrameQueue.Dequeue(Frame))//出队
{
PutMocapDataIntoFrameList(Frame);//将帧数数据塞入对应HuamnID/RigidBodyID的AllHumanFrames/AllRigidBodyFrames中。
}
}
}
DoSample(AllHumanFrames);
DoSample(AllRigidBodyFrames);
// 每隔1s计算一次平均包间隔
if (CurTime - LastCheckIntervalTime > 1000)
{
if (AllHumanFrames.Num() > 0)
{
AllHumanFrames[0]->CalculatePackageAverageInterval(this->PackageAverageInterval);
LastCheckIntervalTime = CurTime;
}
}
}
```
### 采样相关逻辑
- ***SampleByTimeStamp***()
```c++
void AChingmuMocapReceiverActor::DoSample(TArray<MocapFrames*>& Frames)
{
for (auto i = 0; i < Frames.Num(); i++)
{
Frames[i]->CheckSize(CacheLimit);//判断当前帧数据是否超过指定长度240帧2~4秒数据移除超出长度的数据。
if (SampleByTimeStamp(Frames[i]->Frames))//对数据进行插值当前插值数据存在SampledHumanData。
{
SendFrameToCharacter();//执行对应的TsChingmuMocapReceiverActor.ts中的逻辑主要是触发一个事件讲数据传递给TsMotionRetargetComponent.ts 或者 TsSceneLiveLinkPropActor.ts动捕道具
}
}
}
class MocapFrames
{
public:
int ID;
std::vector<ST_MocapFrameData*> Frames = {};
public:
MocapFrames(): ID(0)
{
}
bool CheckSize(const int Limit)
{
if (Frames.size() > Limit)
{
const int DeletedCount = Frames.size() / 2;
for (auto i = 0; i < DeletedCount; i++)
{
auto Data = Frames[i];
if (Data)
{
delete Data;
}
Data = nullptr;
}
Frames.erase(Frames.cbegin(), Frames.cbegin() + DeletedCount);
return true;
}
return false;
}
};
```
对数据进行插值,当前插值数据存在**SampledHumanData**。
```c++
bool AChingmuMocapReceiverActor::SampleByTimeStamp(std::vector<ST_MocapFrameData*>& DataList)
{
const int64 SampleTime = ULiveDirectorStatics::GetUnixTime() - UMotionUtils::BackSampleTime;//UMotionUtils::BackSampleTime = 100ms,采样100ms的数据。
int Previous = -1;
int Next = -1;
for (int Index = DataList.size() - 1; Index > 0; Index--)//从Last => First遍历所有数据确定插值的2个数据Index。
{
const ST_MocapFrameData* Data = DataList[Index];
if (Data == nullptr)
{
continue;
}
if (Data->TimeStamp - SampleTime > 0)
{
Next = Index;
}
else
{
Previous = Index;
break;
}
}
if (bShowSampleLog)
{
UE_LOG(LogTemp, Warning, TEXT("prev: %d, next: %d, total: %llu"), Previous, Next, DataList.size());
}
if (Previous != -1 && Next != -1)
{
const auto p = DataList[Previous];
const auto n = DataList[Next];
const float Factor = (n->TimeStamp - p->TimeStamp) > 0
? (1.0 * (SampleTime - p->TimeStamp) / (n->TimeStamp - p->TimeStamp))
: 1.0;
// Bone world pos cannot lerp like this
// It will cause bone length changes all the time
SampledHumanData->ID = p->ID;
SampledHumanData->TimeStamp = SampleTime;
SampledHumanData->FrameIndex = p->FrameIndex;
for (auto Index = 0; Index < 23; Index++)//对23个骨骼进行差值。
{
SampledHumanData->BonesWorldPos[Index] = UKismetMathLibrary::VLerp(
p->BonesWorldPos[Index], n->BonesWorldPos[Index], Factor);
SampledHumanData->BonesLocalRot[Index] = UKismetMathLibrary::RLerp(p->BonesLocalRot[Index].Rotator(),
n->BonesLocalRot[Index].Rotator(),
Factor, true).Quaternion();
}
return true;
}
if (Previous != -1)//容错处理全都是Previous数据太旧直接清空。
{
SampledHumanData->CopyFrom(DataList[Previous]);
if(SampleTime - DataList[Previous]->TimeStamp > UMotionUtils::MotionTimeout)
{
// data is too old, clear the data list.
DataList.clear();
}
return true;
}
if (Next != -1)//没有Previous直接复制Next的数据。
{
SampledHumanData->CopyFrom(DataList[Next]);
return true;
}
return false;
}
```
### FChingmuThread
用途为:
- 获取当前系统时间。
- 使用异步Task的方式通过调用**UChingMUComponent::FullBodyMotionCapBaseBonesLocalSpaceRotation()** 来更新每个演员的动捕数据。动捕数据存储在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***中。
- 管理HumanToLastReceiveTime以此管理每个动捕演员的动画数据时长。
- OwnerActor->OnGetHumanData_NotInGameThread()
- 根据当前时间与当前Frames从UChingMUComponent中将数据复制到[[#ST_MocapFrameData]]中。
- 将[[#ST_MocapFrameData]]转换成JSON后使用AMotionSenderActor::OnGetRawMocapData_NotInGameThread()发送。
- 将当前帧数据加入FrameQueue队列。
- 线程睡眠0.001s。以此保证AChingmuMocapReceiverActor::Tick()中可以把数据都处理完。
```c++
uint32 FChingmuThread::Run()
{
FTransform Tmp;
while (bRun)
{
if (OwnerActor && OwnerActor->UseThread && OwnerActor->ChingmuComp && OwnerActor->ChingmuComp->IsConnected())
{
CurTime = ULiveDirectorStatics::GetUnixTime();
// Human
for (auto HumanIndex = 0; HumanIndex < OwnerActor->MaxHumanCount; HumanIndex++)
{
const auto bRes = OwnerActor->ChingmuComp->FullBodyMotionCapBaseBonesLocalSpaceRotation(
OwnerActor->ChingmuFullAddress, HumanIndex, TmpTimeCode);
if (bRes)
{
if (!HumanToLastReceiveTime.Contains(HumanIndex))//空数据处理。
{
HumanToLastReceiveTime.Add(HumanIndex, 0);
}
if (HumanToLastReceiveTime[HumanIndex] != TmpTimeCode.Frames)//判断是否收到新的Frame数据
{
HumanToLastReceiveTime[HumanIndex] = TmpTimeCode.Frames;
OwnerActor->OnGetHumanData_NotInGameThread(HumanIndex, CurTime, TmpTimeCode.Frames);
}
else
{
// get same frame, skip
break;
}
}
}
// Rigidbody
for (auto RigidBodyIndex = OwnerActor->RigidBodyStartIndex; RigidBodyIndex < OwnerActor->RigidBodyStartIndex
+ OwnerActor->MaxRigidBodyCount; RigidBodyIndex++)
{
OwnerActor->ChingmuComp->GetTrackerPoseTC(OwnerActor->ChingmuFullAddress, RigidBodyIndex, Tmp,
TmpTimeCode);
if (!RigidBodyToLastReceiveTransform.Contains(RigidBodyIndex))
{
RigidBodyToLastReceiveTransform.Add(RigidBodyIndex, FTransform::Identity);
}
// 道具的TmpTimeCode.Frames永远为0所以无法用帧数判断
// 改为transform判断
if (!RigidBodyToLastReceiveTransform[RigidBodyIndex].Equals(Tmp))
{
RigidBodyToLastReceiveTransform[RigidBodyIndex] = Tmp;
OwnerActor->OnGetRigidBodyData_NotInGameThread(RigidBodyIndex, Tmp, CurTime, TmpTimeCode.Frames);
}
}
}
if (bRun)
{
FPlatformProcess::Sleep(OwnerActor ? OwnerActor->ThreadInterval : 0.004);
}
else
{
break;
}
}
UE_LOG(LogTemp, Warning, TEXT("%s finish work."), *ThreadName)
return 0;
}
```
## ST_MocapFrameData
- ST_MocapFrameData为动捕数据的原始帧数据。
```c++
#define MOCAP_BONE_COUNT 23
enum E_MotionType
{
Human,
RigidBody
};
enum E_SourceType
{
Mocap,
CMR,
Replay
};
struct ST_MocapFrameData
{
int ID;
int64 TimeStamp;
int FrameIndex;
E_MotionType MotionType;
E_SourceType SourceType;
FVector BonesWorldPos[MOCAP_BONE_COUNT];
FQuat BonesLocalRot[MOCAP_BONE_COUNT];
};
class LIVEDIRECTOR_API UMocapFrameData : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int ID;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray<FVector> BonesWorldPos;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray<FQuat> BonesLocalRot;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int64 TimeStamp;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int FrameIndex;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int MotionType; // 0 human; 1 rigidbody
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int SourceType; // 0 mocap, 1 cmr
public:
void CopyFrom(const ST_MocapFrameData* Other)
{
ID = Other->ID;
TimeStamp = Other->TimeStamp;
FrameIndex = Other->FrameIndex;
MotionType = Other->MotionType;
SourceType = Other->SourceType;
for (auto Index = 0; Index < 23; Index++)
{
BonesWorldPos[Index] = Other->BonesWorldPos[Index];
BonesLocalRot[Index] = Other->BonesLocalRot[Index];
}
}
};
class MocapFrames
{
public:
int ID;
std::vector<ST_MocapFrameData*> Frames = {};
void CalculatePackageAverageInterval(float& Res)
{
if(Frames.size() > 0)
{
auto First = Frames[0];
auto Last = Frames[Frames.size() - 1];
if(Last->FrameIndex > First->FrameIndex)
{
Res = 1.0 * (Last->TimeStamp - First->TimeStamp) / (Last->FrameIndex - First->FrameIndex);
}
}
}
};
```
# MotionCapture(青瞳插件)
实现1个组件与3个动画节点
- [[#ChingMUComponent]]
- [[#AnimNode_ChingMUPose]]:接受骨骼动捕数据。
- [[#AnimNode_ChingMURetargetPose]]:接受重定向后的骨骼动捕数据。
- AnimNode_ChingMURetargetPoseForBuild:
## ***ChingMUComponent***
1. Init
1. BeginPlay()取得ini文件中的配置信息取得当前角色的SkeletonMesh => CharacterSkinMesh;取得BoneName=>BoneIndex Map、TPose状态下骨骼的旋转值、TposeParentBonesRotation。
2. Connect
1. StartConnectServer()motionCapturePlugin->ConnectCommand = "ConnectServer"。具体逻辑会在FMotionCapture::Tick()处理。
2. DisConnectServer()motionCapturePlugin->ConnectCommand = "DisConnect"。
3. [[#CalculateBoneCSRotation()]]
4. [[#FullBodyMotionCapBaseBonesLocalSpaceRotation]]
### CalculateBoneCSRotation
> Get Human Fullbody Tracker data ,including of 23joints localRotation and root joint world Position
1. m_motioncap->CMHuman()调用DLL的CMHumanExtern()获取一个Double数组前3个是RootLocation后面全是Rotation。
2. 计算最终的四元数旋转值。
3. 返回的形参 FQuat* BonesComponentSpaceRotation数组指针。
### FullBodyMotionCapBaseBonesLocalSpaceRotation
相比CalculateBoneCSRotation增加了时间码以及GlobalLocation的动捕数据获取。
1. m_motioncap->CMHuman()调用DLL的CMHumanExtern()获取一个Double数组前3个是RootLocation后面全是Rotation。
2. motionCapturePlugin->CMHumanGlobalRTTC()调用DLL的CMHumanGlobalRTTC()1-24 New Features。计算**VrpnTimeCode**以及**GlobalLocationList**。
数据存在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***。
## FAnimNode_ChingMUPose
1. Initialize_AnyThread():取得**ChingMUComponent**。
2. Update_AnyThread():调用**ChingMUComponent->CalculateBoneCSRotation()**
3. Evaluate_AnyThread()对23根骨骼进行遍历取得RefPose后将从Update_AnyThread()获得动捕数据(**Rotation**覆盖到上面ComponentSpace**根骨骼需要额外添加Location数据**。最后将数据从ComponentSpace => LocalSpace。
## AnimNode_ChingMURetargetPose
1. Initialize_AnyThread()创建曲线逻辑TCHour、TCMinute、TCSecond、TCFrame
2. Update_AnyThread()
3. Evaluate_AnyThread():相关逻辑都实现在这里。
### AnimNode_ChingMURetargetPose::Evaluate_AnyThread()
# TsMotionReceiverActor
只在BeginPlay()中调用了this.MarkAsClientSeamlessTravel(); 具体逻辑在`AMotionReceiverActor`
## MotionReceiverActor
![[动捕逻辑思维导图.canvas]]
# Config与BoneName相关逻辑
1. Config/FullBodyConfig.json储存了对应的骨骼名称、Morph以及RootMotion骨骼名称。
1. 通过 UMotionUtils::GetModelBones()、UMotionUtils::GetMoveableBones()、UMotionUtils::GetMorphTargets()获取名称数组。
2. GetModelBones()
1. 主要在FAnimNode_FullBody::Initialize_AnyThread()被调用。
2. 填充`TArray<FBoneReference> BoneRefList;`顺带初始化SampledFullBodyData。
3. InitBoneRefIndex()初始化BoneRefList中每个FBoneReference的BoneIndex通过骨骼名称找到如果没有找到会提示对应的Log。
4. FAnimNode_FullBody::Evaluate_AnyThread(),作用在[[#ApplyDataToPose()]]。
3. GetMorphTargets()
1. 主要在FAnimNode_FullBody::Initialize_AnyThread()被调用。
## ApplyDataToPose()
### BoneTransform
1. 遍历BoneRefList从UMotionUtils::GetModelBones()获得)
2. 对BoneIndex有效的骨骼进行一些操作。
1. 取得当前动画蓝图输出Pose的**骨骼Index**以及**采样后动捕数据的旋转值**。
2. 如果骨骼名是Hips就将当前Index设置给HipsIndex。
3. 将旋转值应用到OutputPose中。
4. 判断当前骨骼名是否为MoveableBones中的名称将这些骨骼的Location设置到OutputPose中。
### MorphValues
将对应MorphTarget数据应用到对应的CurveChannel上。
### RootMotion
根据bUseHipsTranslation变量执行不同的逻辑
#### MapTranslationToHips
调用函数形参如下:
```c++
MapTranslationToHips(Output, EvaluatedFullBodyData, 0, HipsIndex);
```
1. 获取Joints骨骼的Locaiton作为RootMotion数据
2. 判断Joints骨骼是不是根骨骼如果**是**则调整RootMotion数据轴向。
3. 将Joints骨骼的Location归零。
4. 如果Hips骨骼有效则将RootMotion数据加到其Location上。
#### ExtractRootMotionInfo
1. 获取Joints骨骼的Locaiton作为RootMotion数据。
2. 判断Joints骨骼是不是根骨骼如果**不是**则调整RootMotion数据轴向。**轴向与MapTranslationToHips()不同**
3. 将Joints骨骼的Location归零。
4. 将RootMotion设置给AnimInstance的RootMotionLocation。
5. 如果Hips骨骼有效进行一堆计算最终将Rotation设置AnimInstance的RootMotionRotation。
# 采样接收数据
- [x] 确定ChingMu重定向后Transform数组长度。
- 数据长度150个但中间有很多事空值。
- [ ]
TArray<MocapFrames*>
使用cmr二郎拖。
# UE4移植Mocap问题确定
ASoul的Control有一步摆初始Pose的操作让下列骨骼的初始化Pose发生了改变。
Spine UE4角度<33.144 0.356 0.649> => UE5角度<33.418,-1.266,0.351>
Spine~Spine3
Chest
ChestMid
Neck1
Head
LeftArm
LeftForeArm
LeftHand
RightArm
RightForeArm
RightHand
# UE4 ChingMu重定向
- 移植FullBody的以下语句。
```c++
bGetMotionData = Recv->SampleFullBodyData_AnimationThread(ValidIdentity,
ULiveDirectorStatics::GetUnixTime() -
UMotionUtils::BackSampleTime * 2,
SampledFullBodyData);
```
##
- AChingmuMocapReceiverActor创建线程=>
- FChingmuThread负责接收数据并且塞入AChingmuMocapReceiverActor的FrameQueue=>
- [x] 添加时间轴判断,避免加入重复的帧。
- AChingmuMocapReceiverActorTick=>
- 从FrameQueue提取动捕帧数据并且塞入`TArray<MocapRetargetFrames*> AllHumanFrames`PutMocapDataIntoFrameList())。
- DoSample()
1. SampleByTimeStamp():对所有帧进行采样。
2. SendFrameToCharacter()逻辑在Puerts中发送给MotionProcess动捕数据。
- CalculatePackageAverageInterval():相关逻辑感觉没用。
- FAnimNode_FullBody =>
- Update_AnyThread()bGetMotionData = Recv->SampleFullBodyData_AnimationThread()取得对应HumanID的动捕数据。
- Evaluate_AnyThread()取得SampledFullBodyData => ApplyDataToPose()。

View File

@@ -0,0 +1,17 @@
{
"nodes":[
{"id":"2666bc7c541cb485","type":"text","text":"FChingmuThread::Run()\n\n发送数据\nOnGetHumanData_NotInGameThread() => PutMocapDataIntoQueue => Sender->OnGetRawMocapData_NotInGameThread(jsonStr);\n\n```c++\nwhile (bRun)\n{\n\tif (OwnerActor && OwnerActor->UseThread && OwnerActor->ChingmuComp && OwnerActor->ChingmuComp->IsConnected())\n\t{\n\t\tCurTime = ULiveDirectorStatics::GetUnixTime();\n\t\t// Human\n\t\tfor (auto HumanIndex = 0; HumanIndex < OwnerActor->MaxHumanCount; HumanIndex++)\n\t\t{\n\t\t\tconst auto bRes = OwnerActor->ChingmuComp->FullBodyMotionCapBaseBonesLocalSpaceRotation(\n\t\t\t\tOwnerActor->ChingmuFullAddress, HumanIndex, TmpTimeCode);\n\t\t\tif (bRes)\n\t\t\t{\n\t\t\t\tif (!HumanToLastReceiveTime.Contains(HumanIndex))\n\t\t\t\t{\n\t\t\t\t\tHumanToLastReceiveTime.Add(HumanIndex, 0);\n\t\t\t\t}\n\t\t\t\tif (HumanToLastReceiveTime[HumanIndex] != TmpTimeCode.Frames)\n\t\t\t\t{\n\t\t\t\t\tHumanToLastReceiveTime[HumanIndex] = TmpTimeCode.Frames;\n\t\t\t\t\tOwnerActor->OnGetHumanData_NotInGameThread(HumanIndex, CurTime, TmpTimeCode.Frames);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// get same frame, skip\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\tif (bRun)\n\t{\n\t\tFPlatformProcess::Sleep(OwnerActor ? OwnerActor->ThreadInterval : 0.004);\n\t}\n\telse\n\t{\n\t\tbreak;\n\t}\n}\n\n```","x":-600,"y":-420,"width":980,"height":1180},
{"id":"c5705d4ff792be0b","type":"text","text":"**ChingmuComp.StartConnectServer()** 在UI界面控制链接服务器。\nAChingmuMocapReceiverActor::BeginPlay()创建FChingmuThread。","x":-360,"y":-640,"width":500,"height":140},
{"id":"668c865498842d96","type":"text","text":"AChingmuMocapReceiverActor::Tick()\n\n```c++\nconst auto CurTime = ULiveDirectorStatics::GetUnixTime();\nif(UseThread)\n{\n\t// 线程方式\n\t// 在数据队列中获取青瞳数据\n\twhile (!FrameQueue.IsEmpty())\n\t{\n\t\tST_MocapFrameData* Frame;\n\t\tif (FrameQueue.Dequeue(Frame))\n\t\t{\n\t\t\tPutMocapDataIntoFrameList(Frame);\n\t\t}\n\t}\n}\n\nDoSample(AllHumanFrames);\nDoSample(AllRigidBodyFrames);\n\n// 每隔1s计算一次平均包间隔\nif (CurTime - LastCheckIntervalTime > 1000)\n{\n\tif (AllHumanFrames.Num() > 0)\n\t{\n\t\tAllHumanFrames[0]->CalculatePackageAverageInterval(this->PackageAverageInterval);\n\t\tLastCheckIntervalTime = CurTime;\n\t}\n}\n```","x":-600,"y":820,"width":980,"height":800},
{"id":"04df15f334d740f3","type":"text","text":"IdolAnimInstance & Anim_FullBody\n\nIdolAnimInstance主要是取得场景中的**AMotionReceiverActor**以及设置身份。\nAnim_FullBody\n\n```c++\nvoid FAnimNode_FullBody::Update_AnyThread(const FAnimationUpdateContext& Context)\n{\n\tSourcePose.Update(Context);\n\tEMotionSourceType MotionSourceType = EMotionSourceType::MST_MotionServer;\n\tconst UIdolAnimInstance* IdolAnimInstance = Cast<UIdolAnimInstance>(\n\t\tContext.AnimInstanceProxy->GetAnimInstanceObject());\n\tif (IdolAnimInstance)\n\t{\n\t\tMotionSourceType = IdolAnimInstance->GetMotionSourceType();\n\t}\n\tif (MotionSourceType == EMotionSourceType::MST_MotionServer)\n\t{\n\t\tconst FString ValidIdentity = GetFullBodyIdentity(Context);\n\t\tconst auto Recv = GetMotionReceiver(Context);\n\t\tif (!ValidIdentity.IsEmpty() && Recv.IsValid())\n\t\t{\n\t\t\tbGetMotionData = Recv->SampleFullBodyData_AnimationThread(ValidIdentity,\n\t\t\t ULiveDirectorStatics::GetUnixTime() -\n\t\t\t UMotionUtils::BackSampleTime * 2,\n\t\t\t SampledFullBodyData);\n\t\t}\n\t}\n}\n\nvoid FAnimNode_FullBody::Evaluate_AnyThread(FPoseContext& Output)\n{\n\tSourcePose.Evaluate(Output);\n\tif (!InitializedBoneRefIndex)\n\t{\n\t\tInitBoneRefIndex(Output);\n\t\tInitializedBoneRefIndex = true;\n\t}\n\tEMotionSourceType MotionSourceType = EMotionSourceType::MST_MotionServer;\n\tconst UIdolAnimInstance* IdolAnimInstance = Cast<UIdolAnimInstance>(\n\t\tOutput.AnimInstanceProxy->GetAnimInstanceObject());\n\tif (IdolAnimInstance)\n\t{\n\t\tMotionSourceType = IdolAnimInstance->GetMotionSourceType();\n\t}\n\n\tFMotionFrameFullBodyData& EvaluatedFullBodyData = SampledFullBodyData;\n\n\tswitch (MotionSourceType)\n\t{\n\tcase EMotionSourceType::MST_MotionServer:\n\t\tif (!bGetMotionData)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\tEvaluatedFullBodyData = SampledFullBodyData;\n\t\tbreak;\n\tcase EMotionSourceType::MST_SequoiaReplay:\n\t\t{\n\t\t\t// Evaluate from sequoia source.\n\t\t\tconst FSequoiaMotionSource& MotionSource = FSequoiaMotionSource::Get();\n\t\t\tconst FString ValidIdentity = GetFullBodyIdentity(Output);\n\t\t\tif (const FMotionFrameFullBodyData* FrameSnapshot = MotionSource.EvaluateFrame_AnyThread(ValidIdentity))\n\t\t\t{\n\t\t\t\tEvaluatedFullBodyData = *FrameSnapshot;\n\t\t\t\tbGetMotionData = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tUE_LOG(LogTemp, Warning, TEXT(\"%s No Sequoia Frame Data found.AvatarName=%s\"),\n\t\t\t\t ANSI_TO_TCHAR(__FUNCTION__), *ValidIdentity)\n\t\t\t\tbGetMotionData = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\tApplyDataToPose(Output, EvaluatedFullBodyData);\n}\n```","x":-960,"y":1720,"width":1700,"height":2080},
{"id":"778e83e66edd5118","x":-903,"y":3980,"width":1586,"height":197,"type":"text","text":"bool AMotionReceiverActor::SampleFullBodyData_AnimationThread()\n1. 对CharacterToFrameList里的角色数据进行采样并将采样数据存储到SampledFullBodyData中。\n2. CharacterToFrameList的数据会在接收到网络传递的逻辑后填充ASimpleUDPReceiverActor::OnReceiveData_NetworkThread() => ProcessReceivedData_NetworkThread => PutFrameIntoQueue_NetworkThread() "},
{"id":"521dba38cdd6c593","x":-460,"y":4300,"width":700,"height":120,"type":"text","text":"FMotionFrameFullBodyData& EvaluatedFullBodyData = SampledFullBodyData;\nApplyDataToPose(Output, EvaluatedFullBodyData);"}
],
"edges":[
{"id":"b6e4d43c4c38cf16","fromNode":"2666bc7c541cb485","fromSide":"bottom","toNode":"668c865498842d96","toSide":"top"},
{"id":"34998812ac1bd8a8","fromNode":"c5705d4ff792be0b","fromSide":"bottom","toNode":"2666bc7c541cb485","toSide":"top"},
{"id":"2e063b7710fd9a81","fromNode":"668c865498842d96","fromSide":"bottom","toNode":"04df15f334d740f3","toSide":"top"},
{"id":"ddef3dd868ca08bf","fromNode":"04df15f334d740f3","fromSide":"bottom","toNode":"778e83e66edd5118","toSide":"top","label":"Update_AnyThread"},
{"id":"037baa41a3eb9866","fromNode":"778e83e66edd5118","fromSide":"bottom","toNode":"521dba38cdd6c593","toSide":"top","label":"Evaluate_AnyThread"}
]
}

View File

@@ -0,0 +1,29 @@
# 手部IK逻辑
主要用于设置**一些道具配套的手部姿势并且限制演员做出一些NG手势**。具体逻辑位于ControlRig XXX中。里面需要传入一些HandIKTarget Transform这里以吉他为例首先相关计算从载入道具开始到RefreshInstrumentIK为止
- LoadPropByConfig =>
- CheckPropPose=>
- TriggerInstrumentPose=>
- TriggerInstrumentIK
- RefreshInstrumentIK
# 重定向相关
逻辑主要分为TsRetargetManagerComponent以及动画蓝图蓝图中ControlRig。
- MotionProcess端会走重定向逻辑。
- 其他客户端会接受MotionProcess => MotionServer广播的Motion数据。
## TsRetargetManagerComponent
该组件会计算当前角色骨骼与标准的Human骨骼的比例以此计算出一些用于重定向的数据并且开启重定向中的PostProcess
- ModelScale
- LegScale
- HipDiff
## ControlRig
ControlRig中有一个Mocap骨骼与角色骨骼所有控制器都在Mocap骨骼上。
1. 接收动捕数据并且将数据设置到Mocap骨骼骨骼上。
2. PostProcess。
3. 除Hip外的骨骼设置Rotation到角色骨骼上Hips只设置Transform。
4. 后处理。
5. 将Hips骨骼数据传递到Joints上。
# IK问题记录
处理平跟与高跟NaiLin_ControlRig_Heel /Game/ResArt/CharacterArt/NaiLin/ControlRig/NaiLin_ControlRig_Heel

View File

@@ -0,0 +1,163 @@
"PropName": "梦境楼梯",
"AssetPath": "/Game/Props/SceneProps/Items/BP_Steps.BP_Steps",
定义场景A与场景BA=>B。
1. 创建一个和A或者B相同的场景道具。使用LevelInstance。
2. 制作渐变效果在A或者B溶解完成后进行一次切换区域。
1. 或者使用一个其他模型做过度。
3. 物理问题可以使用 Ignore Component Transform 来解决。
manager.LayerManager.EnterLevelArea(this.preset.LevelAreaPreset.UUID, manager.LevelSwitchType);
# 角色会隐藏问题解决
1. TsIdolControllerActor.ts中绑定了若干事件
```typescript
RegisterEventListener(): void
{
this.ListenerWrapper_SwitchLiveArea = (liveAreaUUID: UE.Guid) => { this.SwitchToLiveArea(liveAreaUUID) }
this.ListenerWrapper_OnFinishCreateTmpArea = (liveAreaUUID: UE.Guid) => { this.RequireSwitchToLiveArea(liveAreaUUID) }
this.ListenerWrapper_SceneChanged = (levelName:string)=>{this.OnSceneChanged() };
this.ListenerWrapper_BeforeSceneChanged = (levelName:string)=>{this.BeforeSceneChanged() };
DirectorEventSystem.RegisterEventListener(this, DirectorEvent.OnFinishSwitchLiveAreaLocal, this.ListenerWrapper_SwitchLiveArea)
DirectorEventSystem.RegisterEventListener(this, DirectorEvent.OnFinishSwitchSubLevelLocal,this.ListenerWrapper_SceneChanged)
DirectorEventSystem.RegisterEventListener(this, DirectorEvent.BeforeSwitchLevel,this.ListenerWrapper_BeforeSceneChanged)
DirectorEventSystem.RegisterEventListener(this, DirectorEvent.OnFinishCreateTmpLiveAreaLocal,this.ListenerWrapper_OnFinishCreateTmpArea)
}
```
- ListenerWrapper_SwitchLiveArea角色移动到其他LiveArea的核心逻辑。
- ListenerWrapper_OnFinishCreateTmpArea无逻辑。
- ListenerWrapper_SceneChanged卸载所有道具this.PropComp.OnSceneChanged()
- ListenerWrapper_BeforeSceneChanged将角色与衣服从LiveAreaDetach? this.DressModel.K2_DetachFromActor()
## ListenerWrapper_SwitchLiveArea
```c++
SwitchToLiveArea(TargetLiveAreaGUID: UE.Guid): void {
console.warn(this.Identity.RootTag.TagName.toString() + ' switch to live area ' + TargetLiveAreaGUID.ToString())
this.LiveAreaGIUD = TargetLiveAreaGUID
this.SetTransformToLiveArea()
if (this.PropComp.DressModel && this.PropComp.DressModel.MovementComp&&this.PropComp.DressModel.MovementComp.ManulMovement) {
console.warn(this.PropComp.DressModel.GetName() + ' is in free move mode, will not teleport to new area!')
return
}
var liveAreaMgr = LiveAreaUtils.GetLievAreaManagerInstance(this)
if (liveAreaMgr && liveAreaMgr.IsTmpLiveArea(TargetLiveAreaGUID)) {
// teleport to the target live area without fx
this.PropComp.Teleport(TargetLiveAreaGUID)
} else {
this.PropComp.DressModelTeleport(TargetLiveAreaGUID)
}
}
```
## 相关事件
- BeforeSwitchArea_Multicast
## 笔记
- SwitchToLiveArea()
- 设置Idol的位置。
- this.OnSwitchLiveArea.Broadcast(oriUUID, uuid);
        - DirectorEventSystem.Emit(this, DirectorEvent.OnFinishSwitchLiveAreaLocal, this.CurrentLiveAreaUUID) TsDirectorCamManagerActor.ts
        - console.log('切换直播区域,area=[' + (liveArea as UE.LiveAreaActor).Title + ']')
        - DirectorCamUtil.EnsureWorkShopReady(this, placement.UUID, () => { this.SwitchWorkShopInAreaServer(0) }) ***TsDirectorCamManagerActor.ts***
        - SwitchWorkShopInAreaServer
        - this.HandlePreviewTaskDataMulticast(newPreviewTaskData);
        - this.RequestPVWTaskServer(newPVWTaskData);
```ts
    //推流
    E_StartCut(progress: number): void {
        this.camManagerCache = DirectorCamUtil.PushStreamOnServerAsync(this, UE.EPushStreamMethod.Cut, false, this.camManagerCache)
    }
function PushStreamOnServerAsync(context:UE.Object, method:UE.EPushStreamMethod,bForce:boolean, camManagerCache? : TsDirectorCamManagerActor):TsDirectorCamManagerActor{
let camManager = GetCamManager(context, camManagerCache)
if(camManager){
camManager.PushStreamOnServerAsync(method, bForce)
}
return camManager
}
// Server端判断是否可以推流。存在网络延迟<E5BBB6>?
@ufunction.ufunction(ufunction.Reliable, ufunction.ServerAPI)
PushStreamOnServerAsync(method:UE.EPushStreamMethod, bForce : boolean):void{
let newPGMTaskData = DirectorCamUtil.CopyTaskData(this.prestreamTaskData)
this.RequestPGMTaskServer(newPGMTaskData, method, bForce)
}
```
## TsMovableLiveAreaComponent
- TsMovableLiveAreaComponent
##
```ts
/** 使用cmd进入区域跳过加载等待 其他方法不可调用 */
@ufunction.ufunction(ufunction.ServerAPI, ufunction.Reliable)
EnterAreaByCMD(areaUUIDStr: string): void {
let areaManager = this.GetLevelAreaManager();
if (!areaManager) {
return;
}
let area: UE.LiveAreaActor = null;
if (areaUUIDStr != null || areaUUIDStr != "") {
let uuid = new UE.Guid();
if (UE.Guid.Parse(areaUUIDStr, $ref(uuid))) {
area = areaManager.GetLiveArea(uuid);
}
}
if (area == null) {
area = areaManager.GetAllLiveAreas().GetRef(0)
}
if (area == null) {
console.error("no area")
return
}
if (area.UUID.op_Equality(areaManager.CurrentLiveAreaUUID)) {
return
}
let bHasData = false;
let presetId:UE.Guid;
let manager = this.GetManager()
for (let index = 0; index < manager.Config.AreaPresets.Num(); index++) {
const element = manager.Config.AreaPresets.GetRef(index);
if (element.AreaGuid.op_Equality(area.UUID)) {
presetId = element.UUID;
bHasData = true;
break;
}
}
let levelName = UE.GameplayStatics.GetCurrentLevelName(this, true);
if (!bHasData) {
manager.AddAreaPreset( levelName, area.UUID, area.Title)
for (let index = 0; index < manager.Config.AreaPresets.Num(); index++) {
const element = manager.Config.AreaPresets.GetRef(index);
if (element.AreaGuid.op_Equality(area.UUID)) {
presetId = element.UUID;
break;
}
}
}
let viewTarget = areaManager.GetViewTarget(area.UUID)
if (!viewTarget) {
this.AddViewTarget(area.UUID, area.Title);
}
manager.AddConfigLevelSetting(levelName);
this.BeforeSwitchArea_Multicast(manager.CurPresetId);
manager.CurPresetId = presetId;
areaManager.SwitchToLiveArea(area.UUID);
}
}
```

View File

@@ -0,0 +1,2 @@
# 光捕相机类型
BP_HandHeldCam => HandHeldCamera同过BP_HandHeldCamera_Proxy Actor实时发送LiveLink数据。

View File

@@ -0,0 +1,40 @@
{
"nodes":[
{"id":"300a2e3e614685a2","type":"group","x":-500,"y":-20,"width":1000,"height":560,"label":"导播台程序"},
{"id":"035350cfe6c5a215","type":"group","x":-500,"y":-400,"width":660,"height":275,"label":"外部数据输入"},
{"id":"2eec2fb1d3a37d06","type":"group","x":540,"y":-20,"width":360,"height":193,"label":"云服务"},
{"id":"63e99817023a9452","type":"group","x":-140,"y":580,"width":360,"height":120,"label":"其他工具"},
{"id":"9c4c9310461193d8","type":"text","text":"舞台角色控制","x":-125,"y":113,"width":250,"height":60},
{"id":"39bafcd9161d7e0a","type":"text","text":"导播台程序","x":-480,"y":20,"width":250,"height":60},
{"id":"5b68848d0ae9aef3","type":"text","text":"数据接收&动作数据重定向","x":-125,"y":20,"width":250,"height":60},
{"id":"64c78f2c7f900857","type":"text","text":"青瞳动捕输入","x":-460,"y":-360,"width":250,"height":60},
{"id":"3100f1c53b772812","type":"text","text":"[[ASoul#FaceMask|FaceMask]]","x":-460,"y":-240,"width":250,"height":60},
{"id":"6024a903f9025bbf","type":"text","text":"动捕手套","x":-140,"y":-360,"width":250,"height":60},
{"id":"2573e7521a0b567d","type":"text","text":"虚拟摄像头","x":-140,"y":-240,"width":250,"height":60},
{"id":"15d2538302d51394","type":"text","text":"导播台专用服务器\n","x":-480,"y":206,"width":250,"height":60},
{"id":"b6635d1e5df0f9c5","type":"text","text":"[[ASoul#vMix Pro|vMix Pro]]","x":-120,"y":620,"width":250,"height":60},
{"id":"ddccb7a9337eac2c","type":"text","text":"RTC服务","x":560,"y":0,"width":250,"height":60},
{"id":"42bf522a819d47f0","type":"text","text":"弹幕服务","x":560,"y":83,"width":250,"height":60},
{"id":"6aa20a6c6e56213d","type":"text","text":"[[ASoul#各机位画面预览|PVW预览View]]","x":-480,"y":460,"width":250,"height":60},
{"id":"2f70c72b00fdbb6f","type":"text","text":"PGM输出结果View","x":-125,"y":460,"width":250,"height":60},
{"id":"f72bf493de6576c9","type":"text","text":"MultiView24个镜头预览View","x":220,"y":460,"width":250,"height":60},
{"id":"fd18d36587eee2af","type":"text","text":"Rendering多台","x":-125,"y":320,"width":250,"height":60},
{"id":"4b3e00a7f02c7d52","type":"text","text":"场景舞台控制","x":-125,"y":206,"width":250,"height":60},
{"id":"d4895a6dd8e8f492","type":"text","text":"OBS 推流机器","x":-120,"y":740,"width":250,"height":60}
],
"edges":[
{"id":"8cac5632ec79bc5f","fromNode":"035350cfe6c5a215","fromSide":"bottom","toNode":"5b68848d0ae9aef3","toSide":"top"},
{"id":"5868048a50b5a58f","fromNode":"5b68848d0ae9aef3","fromSide":"bottom","toNode":"9c4c9310461193d8","toSide":"top"},
{"id":"7803dd9c4b820e03","fromNode":"b6635d1e5df0f9c5","fromSide":"bottom","toNode":"d4895a6dd8e8f492","toSide":"top"},
{"id":"f2b08ba7d08b3340","fromNode":"fd18d36587eee2af","fromSide":"bottom","toNode":"2f70c72b00fdbb6f","toSide":"top"},
{"id":"9abe6e198d80985b","fromNode":"39bafcd9161d7e0a","fromSide":"bottom","toNode":"15d2538302d51394","toSide":"top"},
{"id":"267b808f74c75bf5","fromNode":"15d2538302d51394","fromSide":"right","toNode":"9c4c9310461193d8","toSide":"left"},
{"id":"93536d243befa03f","fromNode":"9c4c9310461193d8","fromSide":"left","toNode":"15d2538302d51394","toSide":"right","fromEnd":"arrow"},
{"id":"1604450697d995c7","fromNode":"5b68848d0ae9aef3","fromSide":"left","toNode":"15d2538302d51394","toSide":"top"},
{"id":"369c9efd6310be10","fromNode":"fd18d36587eee2af","fromSide":"bottom","toNode":"6aa20a6c6e56213d","toSide":"top"},
{"id":"ac7f688b0b8c7faa","fromNode":"2f70c72b00fdbb6f","fromSide":"bottom","toNode":"b6635d1e5df0f9c5","toSide":"top"},
{"id":"19c630f3b1a8d958","fromNode":"fd18d36587eee2af","fromSide":"bottom","toNode":"f72bf493de6576c9","toSide":"top"},
{"id":"93756fbfe027d071","fromNode":"4b3e00a7f02c7d52","fromSide":"left","toNode":"15d2538302d51394","toSide":"right","fromEnd":"arrow"},
{"id":"828363663f0ad9fa","fromNode":"15d2538302d51394","fromSide":"bottom","toNode":"fd18d36587eee2af","toSide":"left"}
]
}

View File

@@ -0,0 +1,114 @@
# 前言
输入`run`显示所有命令说明。
- PVW: 切换PVW
- PGM: 切换PGM
- 0: 切换Operator
- 3: 切换三级
- HandCam: 切换手持相机
- 11: 切换FreeMove
- ReMidi: 刷新midi板子
- DebugFrame: debug frame
- EnterArea: 进入Area (可填uuid)
- xs: 进入异世界雪山1 Area (测试用)
- DMXAlign: 强行对齐DMX-Area
- ReDeck: 强行加载当前区域的镜头数据并刷新StreamDeck
- IdolStatus: 角色动作状态
- AllIdolStatus: 所有角色动作状态
- IdolRelativeTr: 所有角色位置信息
- MotionLog: 角色动作Log
- IdolCache: 角色缓存情况(ServerOnly)
- GetMotionOffset: 获取动作时间偏移
- MotionReceiveStatus: 角色动作数据接收情况
- ResetOffsetTime: 重置所有角色动作包时间偏移
- SetRes: 设置目标分辨率,如run SetRes 1920 1080
- HipsTranslation: 使用Hips位移
- IdolCostume: 加4个团服角色
- ShowUI: UE4中的ShowUI指令迁移
- BindPGM2: 重新绑定PGM2的固定机位
- LipSync: 设置LipSync音频-静音阈值
- UdexGlove: 使用宇叠科技新手套(部分角色适用)
- GenerateMeshConfig: 生成 mesh config(技术用)
涉及到:
- TsLiveDirectorGameInstance.ts
- TsDirectorConsoleCommandHandler.ts
# 命令执行逻辑
## Run
TsDirectorConsoleCommandHandler.ts
```ts
static HandleConsoleCommand(gameInstance: TsLiveDirectorGameInstance, consoleCommand: string): void {
if(consoleCommand == '' || consoleCommand.toLocaleLowerCase() == 'help'){
TsDirectorConsoleCommandHandler.Help()
return
}
var parts = consoleCommand.split(' ')
var funcName = parts[0]
var func = TsDirectorConsoleCommandHandler.GetFunctionByName(funcName)
if (func == null) {
console.error('Not exist cmd ' + consoleCommand)
return
}
switch (parts.length) {
case 1: func(gameInstance); break;
case 2: func(gameInstance, parts[1]); break;
case 3: func(gameInstance, parts[1], parts[2]); break;
case 4: func(gameInstance, parts[1], parts[2], parts[3], parts[4]); break;
case 5: func(gameInstance, parts[1], parts[2], parts[3], parts[4], parts[5]); break;
default: console.error('Cmd paramenter is wrong!')
}
}
```
主要的几个切换导播台的命令基本是调用`gameInstance.SetDirectorModeStr("XXX")`
进入Area函数为调用`TsDirectorConsoleCommandHandler._EnterArea(gameInstance, "0C1E0DD349EDD9860ED8BDBB55A736F3")``_EnterArea`的代码为:
```ts
static _EnterArea(gameInstance: TsLiveDirectorGameInstance, areaUUID: string): void {
let mapEnvironment = Utils.GetMapEnvironmentManager(gameInstance.GetWorld());
if (mapEnvironment && mapEnvironment.LayerManager) {
mapEnvironment.LayerManager.EnterAreaByCMD(areaUUID);
}
}
```
TsLiveDirectorGameInstance.ts
```typescript
Run(CMDStr: $Ref<string>) : void{
let consoleCommand = $unref(CMDStr)
TsDirectorConsoleCommandHandler.HandleConsoleCommand(this, consoleCommand)
}
```
## SetDirectorModeStr
位于`ULiveDirectorGameInstance`
TsLiveDirectorGameInstance.ts
## 其他有用函数
```ts
static _HipsTranslation(gameInstance: TsLiveDirectorGameInstance, value:number): void {
var actors = UE.NewArray(UE.Actor)
UE.GameplayStatics.GetAllActorsOfClass(gameInstance, TsIdolActor.StaticClass(), $ref(actors))
for (var i = 0; i < actors.Num(); i++) {
var model = actors.GetRef(i) as TsIdolActor
if (model) {
var anim = model.Mesh.GetAnimInstance() as UE.IdolAnimInstance
let fullbodyNode = Reflect.get(anim, 'AnimGraphNode_Fullbody') as UE.AnimNode_FullBody
if (fullbodyNode) {
//fullbodyNode.bUseHipsTranslation = value > 0
}
anim.SetRootMotionMode(value > 0 ? UE.ERootMotionMode.NoRootMotionExtraction : UE.ERootMotionMode.RootMotionFromEverything)
model.RootComponent.K2_SetRelativeLocationAndRotation(new UE.Vector(0, 0, model.CapsuleComponent.CapsuleHalfHeight), new UE.Rotator(0, 0, 0), false, null, false)
console.warn("use hips translation " + (value > 0))
}
}
}
```
# RuntimeEditor插件
# 三级导播台
run 3
# MotionProcess
资产位于UIAssets/Character/WBP_CharacterItem
UI逻辑位于TsCharacterItem.ts的TsCharacterItem

View File

@@ -0,0 +1,219 @@
# 前言
默认存储数据路径C:\LiveDirectorSaved\Sequoia
操作方式:
1. 4级中使用`Ctrl + Shift + D`勾选Sequoia编辑器后显示。
2. Ctrl + 剪切轨道。
PS. 编辑器状态下可以打开Sequoia编辑器界面再进4级前Ctrl+Shift+D点击后就可以打开。
# 相关类
- TS`LiveDirector\Script\Sequoia`
- TsSequoiaManagerActor
- OnPlayButtonClicked()Sequoia播放函数。主要逻辑是打开Sequoia的序列化数据之后创建或取得播放器斌进行播放/停止。
- TsSequoiaData => USequoiaData => USequoiaObject
- TsSequoiaBinding => USequoiaBinding => UNameableSequoiaObject => USequoiaObject
- TsSequoiaTake => USequoiaTake => UNameableSequoiaObject => USequoiaObject
- *SequoiaDirectorCamTake*
- TsSequoiaTrack => USequoiaTrack => UNameableSequoiaObject => USequoiaObject
- CharacterLiveLinkAnimTrack
- SequoiaMotionTrack
- *SequoiaCamShotTrack*(SequoiaCamShotEvalTemplate)***相机Take的CamShot轨道。***
- *SequoiaCamTargetTrack*(SequoiaCamTargetEvalTemplate)***相机Take的Target轨道。***
- SequoiaAudioTrack
- TsSequoiaSection => USequoiaSection
- *SequoiaCamSection*(TS)
- *SequoiaCamTargetSection*(TS)
- TsSequoiaSectionWithFileRef
- CharacterLiveLinkAnimSection
- SequoiaMotionSection
- SequoiaAudioSection
- ISequoiaEvalTemplate
- *SequoiaCamShotEvalTemplate*
- *SequoiaCamTargetEvalTemplate*
- CharacterLiveLinkAnimEvalTemplate
- SequoiaMotionEvalTemplate
- SequoiaAudioEvalTemplate
- ICamShotEvalHandle
- *SingleCamShotEvalHandle*
- *DoubleCamShotEvalhandle*
- c++`LiveDirector\Source\Modules\Sequoia`
- SequoiaPlayer
- PlayInternal():播放逻辑,主要调用`SequoiaData->Evaluate();`
- USequoiaObject => UObject
# 播放逻辑
```c++
TsSequoiaManagerActor@OnPlayButtonClicked: start play : 大聲鑽石
[2024.11.26-04.21.03:648][613]Puerts: (0x00000BD7686682F0) SequoiaManager@ Composer: On start playing...
[2024.11.26-04.21.03:649][613]Puerts: (0x00000BD7686682F0) DirectorCamSequoiaHandle : Enter CamTarget Section: Idol.JiaRan
[2024.11.26-04.21.03:649][613]Puerts: (0x00000BD7686682F0) DirectorCamSequoiaHandle : play Cam Section: ZhuJiwei_Zheng16-24mm group:CC8F4D734664869EC8FE788E7550AC31 index:0 scrub:false
[2024.11.26-04.21.03:665][614]Puerts: (0x00000BD7686682F0) request PGM: WorkShop
```
1. Sequoia界面点击播放后调用TsSequoiaManagerActor::OnPlayButtonClicked()
2. SequoiaPlayer::PlayInternal(),设置时间范围后。
3. USequoiaData::Evaluate()。
1. 调用所有USequoiaBinding::Evaluate()。
1. 调用所有USequoiaTrack::Evaluate()。
2. 调用所有USequoiaTake::Evaluate()。
1. 调用所有USequoiaTrack::Evaluate()。
PS. 实际上Sequoia的镜头录制数据会创建SequoiaCamShotTrack、SequoiaCamTargetTrack轨道。
## USequoiaTrack::Evaluate()
```c++
void USequoiaTrack::Evaluate(TRange<FFrameTime> EvaluationRange, ESequoiaEvaluateType EvalType)
{
Super::Evaluate(EvaluationRange, EvalType);
TArray<FSequoiaEvalSection> EvalSections;
USequoiaUtil::GetEvalSections(Sections, EvaluationRange, EvalSections);//根据播放范围取得Section
OnEvaluate(EvalSections, EvaluationRange.GetLowerBoundValue(), EvaluationRange.GetUpperBoundValue(), EvalType);//调用蓝图类的BlueprintImplementableEvent事件。
}
```
在TsSequoiaTrack中Overrider了OnEvaluate():
```ts
OnEvaluate(EvalSections: $Ref<UE.TArray<UE.SequoiaEvalSection>>, EvalStartTime: UE.FrameTime, EvalEndTime: UE.FrameTime, EvalType: UE.ESequoiaEvaluateType) : void{
if(!this.CanEvaluate() || !EvalSections){
return
}
if(!this.evalTemplate){
this.evalTemplate = this.CreateTemplate()
if(!this.evalTemplate){
return
}
this.evalTemplate.InitTemplate(this)
}
let newEvalSections = new Array<TsSequoiaSection>()
let evalSectionsRef = $unref(EvalSections)
for(let index = 0; index < evalSectionsRef.Num(); index ++){
let sectionRef = evalSectionsRef.GetRef(index)
let tsSection = sectionRef.Section as TsSequoiaSection
if(!sectionRef || !tsSection){
continue
}
if(sectionRef.EvalMode == UE.ESequoiaEvaluateMode.EEM_Inside || sectionRef.EvalMode == UE.ESequoiaEvaluateMode.EEM_JumpIn){
newEvalSections.push(tsSection)
if(newEvalSections.length >= MAX_EVAL_COUNT){
break
}
}
}
let bTemplateSourceChanged = this.IsTemplateSourceChanged(newEvalSections)
if(bTemplateSourceChanged){
this.evalTemplate.SetTemplateSource(newEvalSections, EvalType)
}
this.evalTemplate.Evaluate(EvalStartTime, EvalEndTime, EvalType)
this.lastEvalType = EvalType
}
```
看得出主要是主要逻辑是:
1. 创建指定类型的evalTemplate之后调用`evalTemplate.InitTemplate()`。
2. 取得ESequoiaEvaluateMode为EEM_Inside与EEM_JumpIn的所有EvalSections。
3. 判断Template是否发生改变如果改变则调用`evalTemplate.SetTemplateSource()`。
4. 调用`evalTemplate::Evaluate()`。
## SequoiaCamSection =>
SequoiaCamSection => TsSequoiaSection。
- 数据Model类使用SequoiaCamSectionModel。
- SequoiaCamShotEvalTemplate
## ISequoiaEvalTemplateSequoiaCamShotEvalTemplate
- InitTemplate
- SetTemplateSource用来设置对应的*ICamShotEvalHandle*
- Evaluate
ISequoiaEvalTemplate => ICamShotEvalHandle.Eval()
在计算Section、以及FrameOffset参数之后调用***DirectorCamSequoiaHandle.PlayCamShotSection()***在创建newTask***CamTaskDataRPC***之后最终会调用directorCamManager.RequestPGMTaskServerUnreliable()/RequestPVWTaskServer()进入导播系统循环。
```ts
let newTask = new UE.CamTaskDataRPC()
newTask.WorkShopId = DirectorCamUtil.CopyGuid(workShopId)
newTask.CamGroupId = DirectorCamUtil.CopyGuid(camSection.camGroupId)
newTask.CamIndex = camSection.camIndex
newTask.StartFrame = camSection.GetStartFrameOffset().Value + frameOffset
newTask.bPreviewOneFrame = bPreviewOneFrame
```
## DirectorCamManager
1. this.RequestPVWTask(newPVWTaskData)
2. this.HandlePreStreamTaskDataMulticast(newPVWTaskData)
1. DirectorCamUtil.EnsureWorkShopReady()确保WorkShop有效并且已经初始化之后就调用对应函数。
1. DirectorEventSystem.Emit(this, DirectorEvent.OnPVWTaskRequested)
2. this.HandlePreStreamTaskByNetTag()
1. PVW => this.HandlePVWTask()
1. DirectorCamUtil.SubmitNewCommandIfDataNotChanged()
3.  this.RecordLastPVWTime()
### HandlePVWTask()
```ts
HandlePVWTask(): void {
if(!DirectorCamUtil.SubmitNewCommandIfDataNotChanged(this.preStreamTask, this.prestreamTaskData)){
if (this.preStreamTask) {
this.preStreamTask.Stop()
}
this.preStreamTask = DirectorCamUtil.CreateCamTask(this, this.prestreamTaskData, CamTaskType.FullStream, this.droneCamera,
this.PVWWindow, this.handHeldCamera)
if (this.preStreamTask) {
this.preStreamTask.Start()
if (this.PVWWindow) {
this.PVWWindow.SetViewBorderColor(0, new UE.LinearColor(0, 1, 0, 1))
}
console.log('PVW Task: ' + this.preStreamTask.workShop.BindPlacement.Title + " " + this.preStreamTask.groupName + " " +
this.preStreamTask.camName)
}
}
}
```
通过DirectorCamUtil.SubmitNewCommandIfDataNotChanged()判断CamTaskDataRPC是否相同如果Camera机位相同则直接提交。
如果不同,比如换机位了,就调用
`DirectorCamUtil.CreateCamTask() => this.preStreamTask.Start()`
来发送新的任务。
```ts
export function SubmitNewCommandIfDataNotChanged(task : DirectorCamTask, taskData : UE.CamTaskDataRPC):boolean{
let bSubmitSuccess = false
if(task && taskData){
let bWorkShopEqual = IsGuidEqual(task.workShopId, taskData.WorkShopId)
let bCamGroupEqual = IsGuidEqual(task.groupId, taskData.CamGroupId)
let bCamIndexEqual = task.GetMainCamIndex() == taskData.CamIndex
let bNotSpecialCam = taskData.CamIndex != HANDHELD_CAM_INDEX && taskData.CamIndex != DRONE_CAM_INDEX
if(bWorkShopEqual && bCamGroupEqual && bCamIndexEqual && bNotSpecialCam){
// submit new cmd
task.SubmitNewCommand(taskData)
bSubmitSuccess = true
}
}
return bSubmitSuccess
}
```
## 裁剪相关逻辑
DirectorCamSequoiaHandle.PlayCamShotSection()
# 其他
## 添加自定义轨道
往Sequoia添加一个自定义轨道可以按照以下大体步骤进行开发
1. 大部分的拓展逻辑都写在SequoiaCustomBuilderTool.ts
2. 在SequoiaCustomBuilderTools.ts 的BindingType,TrackType,SectionType中添加组定义类型.在关系Map(BindingToTrackMap)中添加从属关系
3. 在Sequoia代码文件夹下创建拓展文件夹创建对应的TsBinding,TsTrack,TsSection等对应的UObject以及Model类可以参考DirectorCam.
4. Model文件用于数据序列化和存储通常不要使用UE类型UObject文件是真正的逻辑类
5. 创建Binding和BindingModel类分别定义AssignModel和构造函数用来承接数据
1. 在SequoiaCustomBuildertool.CreateBindingModel 和 CreateEmptyBindingModelByBindingType中新增新类型的Model创建。
2. 在SequoiaCustomBuildertool.CreateBinding中添加新Binding类型的创建
3. Track,Take,Section也是类似于Binding的方式在CustomBuilderTool中添加创建代码。
4. 至此就完成了数据部分的定义和代码。
1. 录制逻辑需要首先创建对应的录制逻辑继承自ISequoiaTakeRecorder.
2. 在SequoiaHelper.BuildTakeRecorders 中根据参数创建对应的recorder.

View File

@@ -0,0 +1,9 @@
# 启动逻辑
1. ULiveDirectorGameInstance::ParseCommandLine()解析DirectorMode、PGMMode字符串并设置。
2. SetDirectorModeStr()
3. SetDirectorMode()
4. 调用蓝图事件OnDirectorModeChanged()
TsLiveDirectorGameInstance extends UE.LiveDirectorGameInstance
TsDirectorCamManagerActor.ts

View File

@@ -0,0 +1,20 @@
# 相关类
- WBP_LevelFunctionView
- TsMapEnvironmentFunctionManager
- TsCommonVisibleLevelFunction
- TsCommonGravityLevelFunction
- TsCommonTimeLevelFunction
- TsCommonVariantComponent
- TsCommonWindLevelFunction
- TsBaseLevelFunctionActor
# 糖果工厂的移动LiveArea实现
- UITsCandyFactoryDetails.tsx
- 逻辑实现:/ResArt/Scene/Map_Stylized_Vilage/BP/BP_CandyFactoryLift
- 基类TsBaseLevelFunctionActor
2个圈转到相对原点。停下。
# Area动画
/Props/AreaSequence/天空之城_降落

View File

@@ -0,0 +1,6 @@
ALiveDirectorGameMode::GetSeamlessTravelActorList
https://www.uejoy.com/archives/1130
使用了UE5的RelicationGraphUDirectorReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConnection()

View File

@@ -0,0 +1,76 @@
Content/UIAssets/MapEnvironments/Prop/WBP_MapEnvironmentSelectPropPopupView
搜索逻辑:
```c++
private ChangeSource(source: number/**@cpp:int*/): void {
this.curSelect = null;
MapEnvironmentSetting.SelectPropType = source;
this.widget.List.ClearListItems();
this.widget.List.BP_ClearSelection();
this.widget.Effect.SetVisibility((source == UE.EScenePropType.Item || source == UE.EScenePropType.LiveLink)? UE.ESlateVisibility.Visible: UE.ESlateVisibility.Collapsed);
let colorNormal = new UE.LinearColor(0, 0, 0, 0);
let colorSelect = new UE.LinearColor(73/255, 73/255, 73/255, 1);
for (let index = 0; index < this.btnBgs.Num(); index++) {
const element = this.btnBgs.GetRef(index);
element.SetBrushColor(index == source? colorSelect: colorNormal);
}
if (this.manager == null) {
return
}
let search =this.widget.SearchBox.GetText();
let assets = this.manager.AssetManager.AllScenePropAssetList
for (let index = 0; index < assets.Num(); index++) {
const element = assets.GetRef(index);
if (element.PropType === source) {
if (search === "" || element.DisplayName.includes(search)) {
this.widget.List.AddItem(element);
}
}
}
}
```
- WBP_AreaSelectPopupView
- WBP_MapEnvironmentSingleSelectItemView
```c++
private SetSelectLevel(levelData: UE.MapEnvironmentLevelItem): void {
this.curSelectLevelName = levelData.LevelData.LevelName;
this.curSelectAreaName = "";
this.curSelectAreaGuid = new UE.Guid();
this.bAreaIsDynamic = false;
this.widget.AreaList.ClearListItems();
this.widget.AreaList.BP_ClearSelection();
let areas = levelData.LevelData.Areas;
// let selectIdx = -1;
for (let i = 0; i < areas.Num(); i++) {
const area = areas.GetRef(i);
let data = new UE.MapEnvironmentAreaItem();
data.AreaData = area;
this.widget.AreaList.AddItem(data);
// if (DirectorCamUtil.IsGuidEqual(area.UUID, this.curSelectAreaGuid)) {
// selectIdx = i;
// }
}
// if (selectIdx >= 0) {
// this.widget.AreaList.SetSelectedIndex(selectIdx);
// }
this.RefreshDynamicBtn();
}
```

View File

@@ -0,0 +1,4 @@
身份判断逻辑:
```c++
  if (Utils.IsCurDirectorModeEqual(this, DirectorMode.IdolControllerMaster))
```

View File

@@ -0,0 +1,532 @@
# TODO
1. [x] 项目目录中的EditorTools\\Python\\MaterialSetCharacter\\SetCharacter.py 是否是不需要使用的?
1. 以前使用的工具脚本,可以不用。
2. [ ] 思诺生日会
1. [ ] 道具衣服导入流程。
2. [ ] 直播流程跑通
4. [ ] Sequence播放逻辑跑通。
1. [ ] 使用c++实现一个新轨道以此来捕捉当前的对应角色Actor的引用。
2. [ ] 在播放最后放置Cut/Take逻辑
3. [ ] 将这个播放功能放到4级里。
5. [ ] DCC插件
- [ ] 角色、衣服重新绑定。以及重定向。
- 角色裸模,骨骼权重传递给衣服。之后修改轴向脚本。
- [ ] 动画蓝图中的ASoul自研修形插件匹配。也就是在Maya中输出一个骨骼以及BlendShape信息JSON之后导入UE。
6. [ ] ChaosBone
1. 参考资产KawaiiPhysics与ChaosBone混用`Content\ResArt\CharacterArt\BeiLa\BeiLa_SwimSuit\Animations\ABP_SK_BeiLa_Swimsuit_PostProcess.uasset`
7. [ ] 考虑将Sequence TakeRecord功能移植到导播台项目中先确定可能性
# Console
1. run0
2. run3运行三级导播。
3.
# 直播流程
PS.
1. 码率12000、1080p 一个画面 35Mbps上下行。外部直播需要准备主备路2条ASoul主线路200M上下行对等。
2.
## 导播台Server/Client启动流程
1. 渲染机机房
1. Server先开桌面找到`ue5 - 直播Server`或者`ue4`启动bat。
2. 其他几台渲染机再开,`ue5直播用`。StartClient_win启动bat。
2. 导播台Client顺序随意
1. 桌面找到`ue5直播用`文件夹启动bat。
2. StartClient_MotionProcessor.bat、MotionServer.bat右上一。
3. StartClient_MapEnvironment开启4级导播台右上二
4. StartCline_win开启3级导播台输入命令Run3。右下二。
3. 启动青瞳客户端。右下一。
1. 开启动捕数据测试工具Qingtong_Record选择一个数据进行连通性测试。确定右上有输入动捕数据输入。
2. 关闭Qingtong_Record启动CMTracker桌面有快捷方式。
3. 在MotionProcessor中确认青瞳机器的IP正确。
4. 动捕棚设备确定。
4. 动捕设备确认确认是否正常输入数据到MotionProcessor。
5. 推流机开启打开OBS填写推流地址ping一下内网以及网页在线测速确认网络环境正常。左上二左下一。左下上一二都可以
6. vMix开启连接OBS。左下上一二都可以
7. 使用B站、抖音直播的私密测试账号推流私密画面确定推流以及音频输出没有问题。
8. 确认设备
1. 确认使用的平板、iphone等设备的摄像头是胶布贴住的。
9. 动捕演员就位动捕设备就位后重新开启MotionProcessor。
10. 检查
1. 检查虚拟偶像模型是否贴地(贴地、浮空)
2. 换套衣服检查肩膀是否有问题。如果有问题使用MotionProcessor修正肩膀效果。
3. PVW PGM 时间同步点击StartCline_win中的时间同步按钮。
## 导播台使用
1. 播放VJ视频
1. 远程放置一个远程VJ播放用Actor。
2. 远程连麦
1. 手机连接。
2. 走声卡,传递到调音台。
## StreamDock
- 第一行第一个按3下切换成下一页。
- 镜头分专用镜头LiveArea中设定的镜头)、通用镜头(所有场景都可以使用)
- 添加镜头的方法也可以用于播放特效以及其他Sequence中可以制作的效果 月哥有记录
1. 新建一个Sequence并且K好镜头。
2. 创建CameraGroup DataAsset资产。
3. 将该资产加入到场景中的LevelArea下面的XX数组就。
4. 点击导播工具按钮。
## 添加道具、物品、场景、特效
1. 添加物品道具。
1. 目录Content-Props-CharacterProps-Items找到对应类型的道具复制后修改模型资产即可。
2. 添加道具类TsIdelPropActor
3. 具有描边、骨骼模型、静态模型、Niagara组件。
4. 属性
1. 骨骼模型Socket设置类属性的MountPointToTransform来设置。
2. Prop
1. DisplayName导播那里的显示的名称。
2. 添加场景
3. 添加角色
4. 添加衣服
## 相关Bat作用
1. StartListenServer服务器。
2. 导播台
1. StartClient_WinPVW预览屏、PGW推流机、Preview小窗口* 2
2. StartClient_Win_VideoProcess视频处理将视频推到另外2个View上。
3. StartClient_MapEnvironment导播台控制地图场景。4级、1级视图-Layout处切换
4. StartClient_IdolController_Master导播台角色控制相关。3级
5. StartClient_HandHeldCam导播台手持相机。
6. StartClient_MotionProcessor导播台动捕
7. 线下的
1. PGM2
2. PGMCameraRenderer
8. Pico
1. StartClient_PicoClient_0
2. StartClient_PicoClient_1
3. StartClient_PicoClient_2
4. StartClient_PicoClient_3
3. 启动编辑器项目目录中的StartEditor_Win.bat
4. MotionServer动捕相关。
## 修改配置
1. MotionServer通过修改源码来指定IP。
2. 上述StartClient_MapEnvironment.Bat修改Server IP。
3. 切身份、调试StartClient_IdolController_Master、PVW预览屏、PGW推流机、Preview小窗口ChangeNetTag Operator.IdolController.Master
# Project
1. DesignerStreamDock插件相关包括SreamDock插件工程直接连接UE打包工程读取相关配置之后自动生成图标预设并且加载、若干预设文件。
2. **Engine**编译版引擎。添加过ShaderModel、修改过Shader、添加过若干Editor代码。
3. **LiveDirector**:导播台项目工程。
4. StartLiveDirector启动Bat文件。
5. StartLiveDirectorCluster分布式启动方案带一个库。
6. Tools一些第三方库
1. JS加密加密代码方便给第三方。
2. MotionReplayer动捕数据回放工具。
3. **MotionServer**动捕数据处理Server。主要用于转发青瞳的动捕数据给其他Client。 MotionServer - Server - DirectorLive Client使用动画蓝图中的IsMotionProcess变量判断身份。
1. 还包括自研的面部App FaceMask的数据转发。
4. obs-studioOBS源码添加了若干插件但用不了因为技术服务属于字节。
5. PixelStream移植自UE官方UE4版本有若干小改动。UE5版本不用。
6. Protobuf动捕数据传输google的那个协议。
7. VCluster未完成用于拉起所有机器的导播台程序。
## 重定向逻辑
1. MotionProcessor关卡中每个角色的动画蓝图负责重定向。
1. 使用一个Control Rig节点进行重定向。接收MotionScale、SpineScale、ArmScale、HipsDiff以及MocapData、ShoulderDiff、ZOffset。之后进行自定义计算。优势就是非常灵活。
2. TsMotionRetargetComponent.ts
3. 挂载在BP_IdolController中。
肩校准左边的滑块,用于调整动捕角色与游戏模型的比例。
- 默认默认比例是计算出来对于角色Motion走路效果最佳的比例。
- 对齐:团舞,所有连接角色的比例统一设置成一个数值(互相匹配,针对腿)。
- 对齐(道具模式,不建议使用,半废弃):团舞,所有连接角色的比例统一设置成一个数值(互相匹配,针对手)。
## 渲染管线
1. 添加ShaderModel。
1. ToonLit通过CustomDatas传入阴影给Diffuse与Specular
2. ToonCustomBxDF只有Diffuse 阴影过渡Specular靠Matcap。
2. 改了ShadingModels.ush.
3. Encode/Decode GBuffer.
4. UE5 Encode/Decode GBuffer.
# Plugins
1. AssetProcess自研资源规范性检测、安全性检测。
2. AVAudioUE4商城公用库音频播放库。
3. ChaosBone自研骨骼物理模拟插件。
4. ChingReciver青瞳的插件。
5. DataTableEditorUntilit数据表插件。商城。
6. DirectAssistanter辅助工具。
7. DTWebBrower移植自UE、网页浏览器内嵌。
8. ControlRig移植自UE。
9. FacialExpression自研面捕驱动插件。
10. FFMepg移植自FFMEPG。
11. GFurPro毛发插件。
12. GloveProcess自研手套数据后处理插件。手套数据后处理插件。机器学习算法姿势会进行匹配选择一个最好的姿势输出。
13. JNAAniamtion自研动画编辑相关。
14. KantanChert商城图表插件。
15. KawaiiPhysics
16. LDAssist商城美术编辑工具。
17. MotionCapture青瞳的插件。
18. NDIO:NDIO
19. PixelCapture官方
20. PixelStream
21. Protobuf
22. puerts
23. ReactUMG
24. RuntimeImportAudio商城
25. RuntimeEditorASoul导播台的实时编辑器模块。
26. SerialComPLugin串口插件。一些灯现在不用了。
27. SimpleTCPServer移植官方加修改有在用。
28. SimpleUDP移植官方加修改有在用。
29. SPCR 布料插件
30. StreamDockLink
31. TextureShare自研Pico相关插件现在没用。
32. VRCapture自研Pico相关
33. VRPlaybackUE自研Pico相关。
34. VRTrack 自研VR手套相关。
## JNAAniamtion
字节自研的蒙皮角色蒙皮修形插件实现了LinearPsdSlover、PsdSlover、TwistSlover、ComposeDriver、SkinWeightDriver。
需要使用中台研发的Maya插件导出角色的基础结构JSON文件并将后缀名改成**UJSON**后导入。
1. [x] JNAnimation空。
2. [x] JNAnimationEd定义动画节点**AnimGraphNode_JNPoseDriver**。
3. [x] JNCustomAssetEd定义角色蒙皮修形资产。定义数据格式位于**UJNPoseDriverAsset**
4. [x] JNCustomAsset定义JSON数据载体UJNPoseDriverAsset(UBaseJsonAsset -> UObject)。
5. [ ] [[#JNAnimationTools]]
1. FAnimNode_JNPoseDriver
2. FComposeDriver
3. FLinearSolver
4. FPoseDriverUtils
5. FPSDSlover
6. FSkinWeightDriver
7. FSolverDriver
8. FTwistSolver
### JNCustomAsset
```c++
USTRUCT(BlueprintType)
struct FDrivenInfos
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FString> BlendShape;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FString> Joint;
};
USTRUCT(BlueprintType)
struct FAniCruveInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> Input;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FDrivenInfos DrivenInfos;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> Tangent;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> OutTangent;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> InTangent;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> Value;
};
USTRUCT(BlueprintType)
struct FPSDAniCurveInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> B;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> U;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> D;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> F;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> DF;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> UF;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> DB;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> UB;
};
USTRUCT(BlueprintType)
struct FPSDSloverInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FPSDAniCurveInfo aniCurveInfos;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FString driver;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> matrix;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FString parent;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> aimAxis;
};
USTRUCT(BlueprintType)
struct FLinearSolverInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
float coefficient;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FString attribute;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FAniCruveInfo> aniCurveInfos;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FString driver;
};
USTRUCT(BlueprintType)
struct FComposeDriverInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FString> curveName;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FString> blendshape;
};
USTRUCT(BlueprintType)
struct FSkinWeightDriverInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
int index;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FString joint;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FString> influenceObjects;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> weights;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<float> initPoint;
};
USTRUCT(BlueprintType)
struct FTwistSloverInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FString inputJoint;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
int twistAxis;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FString> twistJoints;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
bool isReverse;
};
USTRUCT(BlueprintType)
struct FPoseDriverSolversInfo
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FPSDSloverInfo> psdSolvers;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FLinearSolverInfo> linearSolvers;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FComposeDriverInfo> composeDrivers;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FSkinWeightDriverInfo> skinWeightDrivers;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
TArray<FTwistSloverInfo> twistSolvers;
};
UCLASS(BlueprintType)
class JNCUSTOMASSET_API UJNPoseDriverAsset : public UBaseJsonAsset
{
GENERATED_BODY()
public:
virtual bool ParseFromJsonObject(const TSharedRef<FJsonObject>& JsonObjectRef) override;
public:
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
FPoseDriverSolversInfo SloversInfo;
};
```
### JNAnimationTools
# Script
1. DirectorCam与24个镜头相关。
2. EditorRuntimeEditor相关。
3. LiveDirector
1. [ ] Character/TsIdolActor.ts[[角色流程#TsIdolActor.ts]]
4. Camera
5. Characrer:
6. Danma:弹幕相关View以及控制。
7. DeckLinkViewProcess视频处理叠加UI之类的操作。
8. DecorationUI UMG类定义。
9. DeviceINputActorMedia以及串口控制器Actor
10. DirectorFrameWorkGameMode、Contorl、 UIManage之类的通用框架。
11. DirectorToolMenu编辑器UI相关。
12. Level场景切换控制器。
13. LiveArea直播区域。
14. MapEnvironmentLevel里的效果以及相关逻辑。天气控制。
15. Pico相关。
16. Prop道具相关道具。
17. QuickControl简单UI控制器。
18. ScreenPlayerTextureRenderer将视频渲染成贴图之后再场景中渲染。
19. SeiSenderOBS Sei信息。
20. VideoStreamTransition转场功能闪白、转视频啥的。
21. Python小工具
22. Sequoia运镜录制剪辑工具。自研类似Sequence的 runtime editor控制镜头。控制灯光。
23. SimpleLiveDirector提供给外部供应商的简单版程序。
## Source
1. AppShells做了一半还没用。
2. Editor/HotKeyManager快捷键相关可以通过配置实现。
3. LiveDirector
4. LiveDirectorEditor
5. Module:
1. BlackMagicInputUE官方移植视频采集卡。
2. BlackMagicOutputUE官方移植视频采集卡。
3. DeckLinkOuputUE官方移植视频采集卡。
4. GameCluster未完成
6. MultiViewRenderer20个View的UI相关。
7. UIModuleUI样式定义功能。
### HotKeyManager
实现UI控件SHotKeyEditor、SHotkeyInput。主要的功能是注册名为HotKeyManager的Tab里面塞了前面2个UI控件。
TODO找到在哪使用
### LiveDirector
#### AIACao后续大概率不会再用
AI NLP相关需要自己接入服务。
#### AIgo一欧元滤波器
TODO找到在哪使用
#### Animation
- FAnimNode_FullBody动画蓝图中**非MotionProcessor** LiveDirector Client使用该节点。
- FSequenceMotionSource一个单例类没有继承猜测与Sequoia系统记录数据有关。
#### Camera
- DirectorCamera
- CameraCopySettings
- CamTarget
- CamTargetManagerActor
- DroneCamera
## Material
ResArt-CommonMaterial-MaterialM_ToonHair_v01。v02为新版。
- CommonMaterial-FunctionsShadingModel的骚操作。
## Character
- BP_Idlo_Base
- 继承=>
## LightChannel
1. 角色为LightChannel2。
## 资源
- Characters角色蓝图类。
- 命名规则`BP_角色名_XXX`
- 由技术同学进行配置,
- 主要配置Outline材质。
- DressName导播软件会
- 动画蓝图:服装布料效果。
- ResArt
- CharacterArt
- Material
- Mesh
- Animations
- Dissolve溶解材质个数与名称需要与角色材质一一对应。
- MakeDissolveMaterial.py生成对应的溶解材质。
-
场景:
- BP_ASoulSky天空盒控制。
- 场景变体使用UE官方的SceneVariantManager。
- LiveDirector - Editor- SceneVariant SceneVariantTool
- 使用一个工具蓝图来创建场景变体以此来实现场景溶解切换效果。
### 添加资产流程
#### 角色流程
如果只是修改若干资产数值需要点击全量生成。或者直接去修改JSON文件IdolPropAesstConfig
#### 道具流程
Tag可以控制
1. 谁可以拿
2. 拿的手势
3. 道具类型
4. 左右手的MountPoint数据
#### 场景流程
1. 角色需要`LiveArea`Actor
2. Sequence与Camera需要`CameraRoot`Actor需要与LiveArea完全重叠
1. ~~FollowMovementComponent编写各种相机跟踪物体IdolName、Socket、Bone~~
2. 摄像机挂载FollowingComponment。
3.
# 实现Sequence控制角色与镜头
1. Sequence镜头控制屏蔽ASoul系统的镜头控制屏蔽DirectorCam相关功能。
1. DirectorCam C++目录着重看Subsystem以及CameraManager。
2. 只要的耦合在Puerts脚本中 DirectorCam目录中TsDirectorCamManagerActor、以及目录下面的StreamDock。
3. 1个LiveArea - 1个WorkShop - 多个CameraGroup - 24个镜头
2. 录制动画/动捕动画切换:修改动画蓝图。将录制的动画塞入。
3. 新添加功能实现24个镜头与Sequence镜头切换位于DirectorCam - StreamDcok。
# 修型
在动画蓝图节点中既控制BlendShape也有骨骼。
# StreamDock
1. 有了相关操作后刷新StreamDcok。
2. 配置蓝图。EUWBP_
1. 与LiveArea有关。
1. Dock实时读取BP_DefaultCamPlacem的目标Serup。
2. 生成LiveArea模板需要使用工具DirectorTool-生成LiveArea。
# FaceMask位置
P4V - Trunk - tools - ARFaceCap
- 版本为Unity2020.1.16
# MotionServer
青瞳 -》 UE版MotionServer 重定向、过滤 -》.net MotionServer转发。
# UE5版本改动
1. 程序方面多了大世界系统逻辑,世界分区(WorldComposition)。其他逻辑是一样的。
2. 材质、渲染使用一些trick技巧强行在材质中塞入一个宏强行打开CustomData来塞ShadowColor。
## 版本升级
1. 程序升级代码后QA负责测试。
2. TA效果美术、QA负责观察。
# Content
## Maps
### 开发相关
- Maps
- Scenes
- Map_LookDev
- Map_Hide
- Map_Lightt
- Map_LookDev角色LookDev
- Map_LookDev_WZY
- Map_Props
### 大世界
- Maps
- Map_SeasideCity
- Map_SeasideCity
- Map_CHNature
- Map_CHNature
- Map_Stylized_Vilage
- Map_Stylized_Vilage
- Map_WorlddEnd
- Map_WorldEnd
其他用了大世界技术的小型地图:
- Maps
- Map_GreenScreen绿幕效果。
- Map_LiveLive场景。

View File

@@ -0,0 +1,63 @@
# 编译
1. 需要安装CUDA Toolkit 11.8。
2. 添加系统变量CUDA_PATH。数值为 "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.8";
3. 生成解决方案后,
# 移植到官方引擎
1. 替换修改的引擎usf文件。
2. 移除PostProcessTonemap.usf中的修改部分。
1. `tonemapping那里多加了一张图从c++加的`
```hlsl
void MainPS(
in noperspective float2 UV : TEXCOORD0,
in noperspective float2 InVignette : TEXCOORD1,
in noperspective float4 GrainUV : TEXCOORD2,
in noperspective float2 ScreenPos : TEXCOORD3,
in noperspective float2 FullViewUV : TEXCOORD4,
float4 SvPosition : SV_POSITION, // after all interpolators
out float4 OutColor : SV_Target0
#if OUTPUT_LUMINANCE
, out float OutLuminance: SV_Target1
#endif
)
{
float Luminance;
FGBufferData SamplerBuffer = GetGBufferData(UV * View.ResolutionFractionAndInv.x, false);
if (SamplerBuffer.CustomStencil > 1.0f && abs(SamplerBuffer.CustomDepth - SamplerBuffer.Depth) < 1)
{
OutColor = SampleSceneColor(UV);
}
else
{
OutColor = TonemapCommonPS(UV, InVignette, GrainUV, ScreenPos, FullViewUV, SvPosition, Luminance);
}
#if OUTPUT_LUMINANCE
OutLuminance = Luminance;
#endif
}
```
还原成原本样式:
```
void MainPS(
in noperspective float2 UV : TEXCOORD0,
in noperspective float2 InVignette : TEXCOORD1,
in noperspective float4 GrainUV : TEXCOORD2,
in noperspective float2 ScreenPos : TEXCOORD3,
in noperspective float2 FullViewUV : TEXCOORD4,
float4 SvPosition : SV_POSITION, // after all interpolators
out float4 OutColor : SV_Target0
#if OUTPUT_LUMINANCE
, out float OutLuminance: SV_Target1
#endif
)
{
float Luminance;
FGBufferData SamplerBuffer = GetGBufferData(UV * View.ResolutionFractionAndInv.x, false);
OutColor = TonemapCommonPS(UV, InVignette, GrainUV, ScreenPos, FullViewUV, SvPosition, Luminance);
#if OUTPUT_LUMINANCE
OutLuminance = Luminance;
#endif
}
```

View File

@@ -0,0 +1,158 @@
TsScreenPlayerTextureRenderer => AMultiViewActor
# 渲染逻辑
- UMultiViewRendererComponent::DrawMultiViewCameras()
渲染函数:GetRendererModule().BeginRenderingViewFamily(&SceneCanvas, &ViewFamily);
摄像机相关函数:FSceneView* UMultiViewRendererComponent::CalcSceneView(FSceneViewFamily* InViewFamily, UCineCameraComponent* InCamera,
const uint32 InViewIndex)
# 多屏与采集卡
以Preview为例
TsDirectorCamManagerActor.ts
```c++
this.PreviewWindow = UE.MultiViewActor.Open(this.GetWorld(), UE.EMultiViewCameraLayout.Display_1920x1080_Layout_4x4, UE.EMultiViewMultiGPUMode.HalfSplit)
this.PreviewWindow.SetRenderFeaturePlanarReflection(false);
this.PreviewWindow.SetRenderFeatureNiagara(false);
// video output
let videoOutputParam = new UE.VideOutputParam()
videoOutputParam.bBlackMagicCard = false
videoOutputParam.bLazyStart = false
this.PreviewWindow.StartVideoOutput(videoOutputParam)
```
PVW&PGM使用 **BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL**  也就是/Game/ResArt/BlackmagicMedia/MO_BlackmagicVideoOutput。
```ts
export function StartVideoOutput(camManager : TsDirectorCamManagerActor, targetWindow : UE.MultiViewActor):void{
let videoOutpuParam = new UE.VideOutputParam()
videoOutpuParam.FilmbackMode = camManager.FilmbackMode
videoOutpuParam.OutputConfigPath = BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL
videoOutpuParam.bBlackMagicCard = true
videoOutpuParam.bLazyStart = false
if(camManager.FilmbackMode == UE.EFilmbackMode.EFM_1080x1920){
videoOutpuParam.MatV2H = GetV2HMaterialInstace(camManager)
}
targetWindow.StartVideoOutput(videoOutpuParam)
}
```
- DirectorMode.PreviewbBlackMagicCard = false
- PVW&PGMbBlackMagicCard = true
## c++
核心函数在于**AMultiViewActor::StartVideoOutput**
# TS传入设置位置
- TsDirectorCamManagerActor.ts SwitchToDirectMode(newTag: UE.GameplayTag)
- FilmbackHelper.ts StartVideoOutput()
SwitchToDirectMode()
```ts
case DirectorMode.Preview:
console.log('启动Splite4x4预览窗口')
if (shouldCreateWindow) {
this.PreviewWindow = UE.MultiViewActor.Open(this.GetWorld(), UE.EMultiViewCameraLayout.Display_1920x1080_Layout_4x4, UE.EMultiViewMultiGPUMode.HalfSplit)
this.PreviewWindow.SetRenderFeaturePlanarReflection(false);
this.PreviewWindow.SetRenderFeatureNiagara(false);
// video output
let videoOutputParam = new UE.VideOutputParam()
videoOutputParam.bBlackMagicCard = false
videoOutputParam.bLazyStart = false
this.PreviewWindow.StartVideoOutput(videoOutputParam)
}
```
```ts
function StartVideoOutput(camManager : TsDirectorCamManagerActor, targetWindow : UE.MultiViewActor):void{
if(!BE_USE_DECKLINK){
let videoOutpuParam = new UE.VideOutputParam()
videoOutpuParam.FilmbackMode = camManager.FilmbackMode
videoOutpuParam.OutputConfigPath = BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL
videoOutpuParam.bBlackMagicCard = true
videoOutpuParam.bLazyStart = false
if(camManager.FilmbackMode == UE.EFilmbackMode.EFM_1080x1920){
videoOutpuParam.MatV2H = GetV2HMaterialInstace(camManager)
}
targetWindow.StartVideoOutput(videoOutpuParam)
}
}
```
# UDeckLinkMediaCapture
m_DeckLinkOutputDevice = DeckLinkDiscovery->GetDeviceByName(m_DeviceName);
```c++
FDeckLinkDeviceDiscovery::DeckLinkDeviceArrived(IDeckLink *device)
{
/*TComPtr<IDeckLink> device_;
device_ = device;*/
TComPtr<FDeckLinkOutputDevice> newDeviceComPtr = new FDeckLinkOutputDevice(device);
if (!newDeviceComPtr->Init())
return S_OK;
std::lock_guard<std::recursive_mutex> lock(m_DeviceMutex);
FString deviceName = newDeviceComPtr->GetDeviceName();//看这个Com对象的设备名称是否对应
if (!m_Devices.Contains(deviceName) )
{
m_Devices.Add(deviceName,newDeviceComPtr);
}
return S_OK;
}
```
DeckLinkDeviceArrived调用逻辑位于**DeckLinkAPI_h.h**。
## ADeckLinkOutputActor
ADeckLinkOutputActor的DeviceName 默认值为"DeckLink Mini Monitor 4K";
判断错误函数
UMediaCapture::CaptureTextureRenderTarget2D()=>UMediaCapture::StartSourceCapture() => ValidateMediaOutput()
```c++
bool UMediaCapture::ValidateMediaOutput() const
{
if (MediaOutput == nullptr)
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. The Media Output is invalid."));
return false;
}
FString FailureReason;
if (!MediaOutput->Validate(FailureReason))
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. %s."), *FailureReason);
return false;
}
if(DesiredCaptureOptions.bAutostopOnCapture && DesiredCaptureOptions.NumberOfFramesToCapture < 1)
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. Please set the Number Of Frames To Capture when using Autostop On Capture in the Media Capture Options"));
return false;
}
return true;
}
```
```c++
bool UDeckLinkMediaCapture::InitBlackmagic(int _Width, int _Height)
{
if (DeckLinkDiscovery == nullptr)
{
return false;
}
Width = _Width;
Height = _Height;
check(Height > 0 && Width > 0)
BMDDisplayMode displayMode = GetDisplayMode(Width, Height);
m_DeckLinkOutputDevice = DeckLinkDiscovery->GetDeviceByName(m_DeviceName);
if (m_DeckLinkOutputDevice.Get() == nullptr)
{
return false;
}
if (!m_DeckLinkOutputDevice->EnableOutput(displayMode, bmdFormat8BitYUV))
{
m_DeckLinkOutputDevice.Reset();
return false;
}
return true;
}
```
***DeckLinkDiscovery->GetDeviceByName(m_DeviceName);***

View File

@@ -0,0 +1,266 @@
# 相关蓝图类
BP_Live里面可以指定MediaPlayer以及MediaTexture并且替换蓝图子StaticMesh材质中的EmissiveMap为MediaTexture。
# 导播台
之后就可以将视频放到指定的Saved文件夹里就可以在导播台播放了。
# NDI 播放逻辑
通过道具来添加NDI 设置。
## 道具
- BP_ProjectorD0
- BP_Screen011
## 相关注释掉的代码
- TsMapEnvironmentAssets.ts
- TsMapEnvironmentSingleSelectItemView.ts
- SetMediaData()
- TsScreenPlayerItemView.ts
- SetData()
- TsScreenPlayerSelectItemPopupView.ts
- ChangeMediaType()
# NDI播放模糊问题解决
- bool UNDIMediaReceiver::CaptureConnectedVideo()
```c++
bool UNDIMediaReceiver::Initialize(const FNDIConnectionInformation& InConnectionInformation, UNDIMediaReceiver::EUsage InUsage)
{
if (this->p_receive_instance == nullptr)
{
if (IsValid(this->InternalVideoTexture))
this->InternalVideoTexture->UpdateResource();
// create a non-connected receiver instance
NDIlib_recv_create_v3_t settings;
settings.allow_video_fields = false;
settings.bandwidth = NDIlib_recv_bandwidth_highest;
settings.color_format = NDIlib_recv_color_format_fastest;
p_receive_instance = NDIlib_recv_create_v3(&settings);
// check if it was successful
if (p_receive_instance != nullptr)
{
// If the incoming connection information is valid
if (InConnectionInformation.IsValid())
{
//// Alright we created a non-connected receiver. Lets actually connect
ChangeConnection(InConnectionInformation);
}
if (InUsage == UNDIMediaReceiver::EUsage::Standalone)
{
this->OnNDIReceiverVideoCaptureEvent.Remove(VideoCaptureEventHandle);
VideoCaptureEventHandle = this->OnNDIReceiverVideoCaptureEvent.AddLambda([this](UNDIMediaReceiver* receiver, const NDIlib_video_frame_v2_t& video_frame)
{
FTextureRHIRef ConversionTexture = this->DisplayFrame(video_frame);
if (ConversionTexture != nullptr)
{
if ((GetVideoTextureResource() != nullptr) && (GetVideoTextureResource()->TextureRHI != ConversionTexture))
{
GetVideoTextureResource()->TextureRHI = ConversionTexture;
RHIUpdateTextureReference(this->VideoTexture->TextureReference.TextureReferenceRHI, ConversionTexture);
}
if ((GetInternalVideoTextureResource() != nullptr) && (GetInternalVideoTextureResource()->TextureRHI != ConversionTexture))
{
GetInternalVideoTextureResource()->TextureRHI = ConversionTexture;
RHIUpdateTextureReference(this->InternalVideoTexture->TextureReference.TextureReferenceRHI, ConversionTexture);
}
}
});
// We don't want to limit the engine rendering speed to the sync rate of the connection hook
// into the core delegates render thread 'EndFrame'
FCoreDelegates::OnEndFrameRT.Remove(FrameEndRTHandle);
FrameEndRTHandle.Reset();
FrameEndRTHandle = FCoreDelegates::OnEndFrameRT.AddLambda([this]()
{
while(this->CaptureConnectedMetadata())
; // Potential improvement: limit how much metadata is processed, to avoid appearing to lock up due to a metadata flood
this->CaptureConnectedVideo();
});
#if UE_EDITOR
// We don't want to provide perceived issues with the plugin not working so
// when we get a Pre-exit message, forcefully shutdown the receiver
FCoreDelegates::OnPreExit.AddWeakLambda(this, [&]() {
this->Shutdown();
FCoreDelegates::OnPreExit.RemoveAll(this);
});
// We handle this in the 'Play In Editor' versions as well.
FEditorDelegates::PrePIEEnded.AddWeakLambda(this, [&](const bool) {
this->Shutdown();
FEditorDelegates::PrePIEEnded.RemoveAll(this);
});
#endif
}
return true;
}
}
return false;
}
```
绘制函数
```c++
/**
Attempts to immediately update the 'VideoTexture' object with the last capture video frame
from the connected source
*/
FTextureRHIRef UNDIMediaReceiver::DisplayFrame(const NDIlib_video_frame_v2_t& video_frame)
{
// we need a command list to work with
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
// Actually draw the video frame from cpu to gpu
switch(video_frame.frame_format_type)
{
case NDIlib_frame_format_type_progressive:
if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVY)
return DrawProgressiveVideoFrame(RHICmdList, video_frame);
else if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVA)
return DrawProgressiveVideoFrameAlpha(RHICmdList, video_frame);
break;
case NDIlib_frame_format_type_field_0:
case NDIlib_frame_format_type_field_1:
if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVY)
return DrawInterlacedVideoFrame(RHICmdList, video_frame);
else if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVA)
return DrawInterlacedVideoFrameAlpha(RHICmdList, video_frame);
break;
}
return nullptr;
}
```
DrawProgressiveVideoFrame
UNDIMediaReceiver::CaptureConnectedVideo
=>
DisplayFrame NDIlib_frame_format_type_progressive NDIlib_FourCC_video_type_UYVY
=>
DrawProgressiveVideoFrame
## Shader Binding RT
设置RT
```c++
FTextureRHIRef TargetableTexture;
// check for our frame sync object and that we are actually connected to the end point
if (p_framesync_instance != nullptr)
{
// Initialize the frame size parameter
FIntPoint FrameSize = FIntPoint(Result.xres, Result.yres);
if (!RenderTarget.IsValid() || !RenderTargetDescriptor.IsValid() ||
RenderTargetDescriptor.GetSize() != FIntVector(FrameSize.X, FrameSize.Y, 0) ||
DrawMode != EDrawMode::Progressive)
{
// Create the RenderTarget descriptor
RenderTargetDescriptor = FPooledRenderTargetDesc::Create2DDesc(
FrameSize, PF_B8G8R8A8, FClearValueBinding::None, TexCreate_None, TexCreate_RenderTargetable | TexCreate_SRGB, false);
// Update the shader resource for the 'SourceTexture'
// The source texture will be given UYVY data, so make it half-width
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 1))
const FRHITextureCreateDesc CreateDesc = FRHITextureCreateDesc::Create2D(TEXT("NDIMediaReceiverProgressiveSourceTexture"))
.SetExtent(FrameSize.X / 2, FrameSize.Y)
.SetFormat(PF_B8G8R8A8)
.SetNumMips(1)
.SetFlags(ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::Dynamic);
SourceTexture = RHICreateTexture(CreateDesc);
#elif (ENGINE_MAJOR_VERSION == 4) || (ENGINE_MAJOR_VERSION == 5)
FRHIResourceCreateInfo CreateInfo(TEXT("NDIMediaReceiverProgressiveSourceTexture"));
TRefCountPtr<FRHITexture2D> DummyTexture2DRHI;
RHICreateTargetableShaderResource2D(FrameSize.X / 2, FrameSize.Y, PF_B8G8R8A8, 1, TexCreate_Dynamic,
TexCreate_RenderTargetable, false, CreateInfo, SourceTexture,
DummyTexture2DRHI);
#else
#error "Unsupported engine major version"
#endif
// Find a free target-able texture from the render pool
GRenderTargetPool.FindFreeElement(RHICmdList, RenderTargetDescriptor, RenderTarget, TEXT("NDIIO"));
DrawMode = EDrawMode::Progressive;
}
#if ENGINE_MAJOR_VERSION >= 5
TargetableTexture = RenderTarget->GetRHI();
#elif ENGINE_MAJOR_VERSION == 4
TargetableTexture = RenderTarget->GetRenderTargetItem().TargetableTexture;
...
...
// Initialize the Render pass with the conversion texture
FRHITexture* ConversionTexture = TargetableTexture.GetReference();
FRHIRenderPassInfo RPInfo(ConversionTexture, ERenderTargetActions::DontLoad_Store);
// Needs to be called *before* ApplyCachedRenderTargets, since BeginRenderPass is caching the render targets.
RHICmdList.BeginRenderPass(RPInfo, TEXT("NDI Recv Color Conversion"));
```
设置NDI传入的UYVY
```c++
// set the texture parameter of the conversion shader
FNDIIOShaderUYVYtoBGRAPS::Params Params(SourceTexture, SourceTexture, FrameSize,
FVector2D(0, 0), FVector2D(1, 1),
bPerformsRGBtoLinear ? FNDIIOShaderPS::EColorCorrection::sRGBToLinear : FNDIIOShaderPS::EColorCorrection::None,
FVector2D(0.f, 1.f));
ConvertShader->SetParameters(RHICmdList, Params);
// Create the update region structure
FUpdateTextureRegion2D Region(0, 0, 0, 0, FrameSize.X/2, FrameSize.Y);
// Set the Pixel data of the NDI Frame to the SourceTexture
RHIUpdateTexture2D(SourceTexture, 0, Region, Result.line_stride_in_bytes, (uint8*&)Result.p_data);
```
## 解决方案
[NDI plugin质量问题](https://forums.unrealengine.com/t/ndi-plugin-quality-trouble/1970097)
I changed only shader “NDIIO/Shaders/Private/NDIIOShaders.usf”.
For example function **void NDIIOUYVYtoBGRAPS (// Shader from 8 bits UYVY to 8 bits RGBA (alpha set to 1)):**
_WAS:_
```c++
float4 UYVYB = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerB, InUV);
float4 UYVYT = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV);
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
float4 YUVA;
float FracX = PosX % 2.0f;
YUVA.x = (1 - FracX) * UYVYT.y + FracX * UYVYT.w;
YUVA.yz = UYVYB.zx;
YUVA.w = 1;
```
_I DID:_
```c++
float4 UYVYB = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerB, InUV);
float4 UYVYT0 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV + float2(-0.25f / NDIIOShaderUB.InputWidth, 0));
float4 UYVYT1 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV + float2(0.25f / NDIIOShaderUB.InputWidth, 0));
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
float4 YUVA;
float FracX = (PosX % 2.0f) * 0.5f;
YUVA.x = (1 - FracX) * UYVYT1.y + FracX * UYVYT0.w;
YUVA.yz = UYVYB.zx;
YUVA.w = 1;
```
Small changes but result is seems much more better.
Of course, I added a bit of sharpness to the material after I changed the shader, but even without that, the result looks better than in the original version.
滤波资料:https://zhuanlan.zhihu.com/p/633122224
## UYVYYUV422
- https://zhuanlan.zhihu.com/p/695302926
- https://blog.csdn.net/gsp1004/article/details/103037312
![](https://i-blog.csdnimg.cn/blog_migrate/24b41fd36ff7902670e11a8005afb370.jpeg)

View File

@@ -0,0 +1,50 @@
# 前言
1. 角色需要`BP_LiveArea`LiveAreaActor
2. Sequence与Camera需要`CameraRoot`Actor需要与LiveArea完全重叠
1. ~~FollowMovementComponent编写各种相机跟踪物体IdolName、Socket、Bone~~
2. 摄像机挂载FollowingComponment。
# LiveDirector
## BP_LiveArea
基类为**ALiveAreaActor**位于Source/LiveDirector/DirectorFramework/LiveAreaActor.h。直播区域占位用Actor可以用于定义
- WeatherOverride进入该区域后天气系统重载。
- WeatherBlendDuration天气系统重载过渡时间。
- CareLayers该区域加载时会自动加载关联的 DataLayer 层级。
# DirectorCam
## Core
### BP_CamPlacement_LiveArea
一般挂载在**BP_LiveArea**下面。使用LiveDirector - DirectorCam - Template下的模板生成。继承关系为**BP_CamWorkShopPlacement -> ACamWorkShopPlacementActor**位于Modules/DirectorCam/Core/CamWorkShopPlacementActor.h
## Data
### UDirectorCamGroupData
镜头组数据资产,里面存储***各个镜头LevelSequence***。
### UDirectorCamSetupData
里面存储各种***UDirectorCamGroupData***。
# LevelSequences
LevelSequence的帧数为30fps。
- DirectorCamSetupData
- Example5400x4800(大动捕室)/Game/LevelSequences/Example5400x4800/CamSetup_5400x4800
- DirectorCamGroupData/Game/LevelSequences/Example5400x4800/General
- Dance
## 创建一个新镜头的步骤
***推荐直接复制之前制作的镜头,并在此基础上进行编辑。***
1. 在LevelSequences目录下新建一个LevelSequence资产。
2. 拖入角色蓝图资产到到LevelSequence中Spawnable并且Attach指定的**LiveArea**Actor下对齐直播场景的原点。拖入SoundBase资产到LevelSequence中可选
3. 角色蓝图添加CharacterMesh轨道并且给CharacterMesh轨道指定对应的AnimationSequence资产。
4. 添加摄像机
1. CinemaCamera
1. 如果需要追踪则给CinemaCamera添加FollowingMovement组件以及轨道并且指定组件的FollowingActor为对应Actor并且***填写FollowingIdolName***例如Idol.BeiLa。以及FollowingSocketName
2. DirectorCamera为场景相关的相机静态镜头
1. Sequence中添加DirectorCamera之后Attach到CameraRoot下。
2. 如果需要追踪则给DirectorCamera添加FollowingMovement组件以及轨道并且指定组件的FollowingActor为对应Actor并且***填写FollowingIdolName***例如Idol.BeiLa。以及FollowingSocketName
5. 将LevelSequence添加到对应的`UDirectorCamGroupData`资产中。
6.`UDirectorCamGroupData`放入到对应`UDirectorCamGroupData`资产中。
7. 点击ASoul工具-配置ShotGroupBroad通用按钮。配置StreamDock面部按钮以及图标。

View File

@@ -0,0 +1,11 @@
# 添加步骤
1. 在Maps文件中构建**Map_xxx**文件夹以及同名称的大世界Map。
2. 在地图中间添加LiveArea并且设置标题。
3. 添加Config/LiveDirectorAsset/LevelAreaConfig.json配置。
## 相关资产存放位置
- 在Maps文件中构建**Map_xxx**文件夹以及同名称的大世界Map。
- **Maps/Scenes/Map_xxx**存放对应大世界的资产。
- Maps/Epic存放官方商城下载的资源。
- UIAssets/MapEnvironments/ICON_/Area存放LiveArea位置的预览图标
- UIAssets/MapEnvironments/ICON_/Level存放对应Map或者大世界的预览图标

View File

@@ -0,0 +1,18 @@
# BP
BP_ProjectV_EnvironmentBlendable
## UI
- WBP_MapEnvironmentWeatherView
- this.worldEnvironment = this.GetWorldEnvironment();
```ts
private GetWorldEnvironment(): UE.WorldEnvironment {
if (!this.worldEnvironment) {
this.worldEnvironment = UE.GameplayStatics.GetActorOfClass(this, UE.WorldEnvironment.StaticClass()) as UE.WorldEnvironment;
}
return this.worldEnvironment;
}
```

View File

@@ -0,0 +1,149 @@
# 前言
继承关系BP_XXX_Base -> BP_Idol_Base -> TsIdolActor -> AVCharacter -> ACharacter 。
主要逻辑位于TsIdolActor中文件路径为`Script/LiveDirector/Character/TsIdolActor.ts`
# 添加新衣服流程
1. 在Content/Character/XXX中创建继承自BP_XXX_Base的蓝图。
2. 设置SkeletalMesh。并且确保勾选了**Dynamic InsetShadow**。
3. 添加PhysicalAsset并且保证胶囊体大致包裹模型否则阴影会出现被裁剪的问题。
4. 添加对应的OutlineMaterial。
5. 在DressName中输入新衣服的显示名称。
6. 添加Prop标签。
1. Idol.XXX
2. Prop.Dress(第一件衣服需要设置成Prop.Dress.Default)
3. Prop.MountPoint.Body
7. ***脚本扫描***:点击编辑器的嘉然头像->角色道具配置全量生成 or 角色道具配置增量生成。会往IdolPropAssetConfig.json添加对应衣服或者道具配置数据。
# 添加新角色流程笔记
1. 添加一个Idol.xxx标签。
2. 修改下面相关文件[[#含有角色标签的文件]]。
3. 添加对应的蓝图与文件结构。
1. Content/Character
1. 在Idol_Base中添加[[#BP_XXX_Base]]。
2. 设置衣服、头发的SkeletalMesh。并且确保勾选了**Dynamic InsetShadow**。
3. 添加角色、衣服、头发的PhysicalAsset并且保证胶囊体大致包裹模型否则阴影会出现被裁剪的问题。
2. Content/ResArt/CharacterArt放置角色与服装按照
1. 动画蓝图中的***FullBody节点需要设置角色标签***。
1. 指定用于修型的PostProcess动画蓝图。
2. 添加Prop标签。
1. Idol.XXX
2. Prop.Dress
3. Prop.MountPoint.Body
3. ***脚本扫描***:点击编辑器的嘉然头像->角色道具配置全量生成 or 角色道具配置增量生成。会往IdolPropAssetConfig.json添加对应衣服或者道具配置数据。
4. 设置道具挂载信息数据表ResArt/MountPointConfig/DT_MountPointConfig用于设置道具挂载时的相对偏移。
5. ***材质相关操作***
1. 在ResArt/CommonMaterial/Functions/CameraLightCollection中添加对应角色的属性。
2. 在ResArt/CommonMaterial/Functions/MF_CharacterMainLightIntensity中添加对应RoleID。
3. 在ResArt/CommonMaterial/Functions/MF_CharacterRimLightIntensity中添加对应RoleID。
4. 在对应角色的基础材质中设置RoleID数值。
5. 调用Python脚本制作Dissolve材质。LiveDirector/Editor/MaterialMigration/MakeDissolveMaterials.py
## 含有角色标签的文件
1. [x] TsCharacterItem.ts `Script/LiveDirector/Character/View/TsCharacterItem.ts`
1. 3级角色控制界面相关的UI操作。
2. [x] TsCharacterMocapViewTmp.ts :这个是MotionProcess的UI继承控件`/Content/UIAssets/Character/Mocap/WBP_CharacterMocapViewTmp`
1. 在MotionProcess专用地图中创建对应的Idol。
3. [x] TsPropMocapItemTmp.ts
1. 在MotionProcess专用地图中控制道具Attach到对应IdolUI逻辑
4. [x] TsDirectorConsoleCommandHandler.ts
1. 快捷命令 Motion同步相关 GetMotionOffset
2. 快捷命令 快速创建4个角色 IdolCostume
5. [x] TsSpawnPointSettingItem.ts
1. IdolItemUI继承控件`/Content/UIAssets/Character/WBP_SpawnPointSettingItem`
6. [x] TsIdolPropManagerComponent.ts
1. 没有思诺与心怡
2. 需要搞清楚。
7. [x] ~~TsSimpleLevelManager.ts~~
1. SwitchLiveArea()中调用只调用了Idol.BeiLa属于容错语句。
8. ~~CameraDebug.cpp ~~(这个不需求)
## BP_XXX_Base
1. 指定动画蓝图。
2. 指定LiveLinkName。
3. 指定OutlineMaterial。
# AVCharacter
主要实现了`virtual void OnRep_AttachmentReplication() override;`声明了若干BlueprintNativeEvent
- bool CanSyncRelativeTransform();
- void BeforeAttachToNewParent();
- void AfterAttachToNewParent();
## OnRep_AttachmentReplication()
注释:
>// 动捕模式下CanSync=false. 各端自行计算Actor Location, client无需使用Server计算结果
// 自由行走模式下, CanSync=trueclient需要同步server的transform信息。
同步Attachment行为。在AActor::OnRep_AttachmentReplication()的基础上添加:
- 判断CanSync标记以此来决定是否同步Transform
- 未Attach组件=>Attch组件前后添加BeforeAttachToNewParent()、AfterAttachToNewParent()
```c++
auto CanSync = CanSyncRelativeTransform(); //获取Sync标记具体的逻辑位于TsIdolActor.ts中
if (attachmentReplication.AttachParent)
{
if (RootComponent)
{
USceneComponent* AttachParentComponent = (attachmentReplication.AttachComponent ? attachmentReplication.AttachComponent : attachmentReplication.AttachParent->GetRootComponent());
if (AttachParentComponent)
{
if(CanSync)//增加判断Sync判断只有在自由行走模式下才会同步Transform。
{
RootComponent->SetRelativeLocation_Direct(attachmentReplication.LocationOffset);
RootComponent->SetRelativeRotation_Direct(attachmentReplication.RotationOffset);
RootComponent->SetRelativeScale3D_Direct(attachmentReplication.RelativeScale3D);
}
// If we're already attached to the correct Parent and Socket, then the update must be position only.
// AttachToComponent would early out in this case.
// Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated
// if the body is simulated (see AActor::GatherMovement).
const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && attachmentReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent));
if (bAlreadyAttached)
{
// Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above).
if(CanSync)
{
RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None);
}
}
else
{
BeforeAttachToNewParent();//增加BlueprintNativeEvent
RootComponent->AttachToComponent(AttachParentComponent, FAttachmentTransformRules::KeepRelativeTransform, attachmentReplication.AttachSocket);
AfterAttachToNewParent();//增加BlueprintNativeEvent
}
}
}
}
```
# TsIdolActor.ts
## VirtualOverrider
CanSyncRelativeTransform()
- 不需要同步Transform的情况
- AI控制的ACao角色不需要同步。
- 使用TsIdolMovementComponent并且勾选了ManulMovement的情况不需要同步。
- 动画蓝图中使用了**AnimGraphNode_Fullbody**节点并且bGetMotionData为true的情况不需要同步。
具体代码如下:
```typescript
CanSyncRelativeTransform(): boolean {
if (Utils.HasTag(this.PropTags, new UE.GameplayTag("Idol.AIACao"))) {
return false;
}
if(this.MovementComp && this.MovementComp.ManulMovement){
return false
}
var animInstance = this.Mesh.GetAnimInstance() as UE.IdolAnimInstance
let fullbodyNode = Reflect.get(animInstance, 'AnimGraphNode_Fullbody') as UE.AnimNode_FullBody
return !(fullbodyNode && fullbodyNode.bGetMotionData)
}
```
# Prop.Dress.Default
1. TsIdolPropManagerComponent.ts ServerLoadProp()
2. DoLoadProp()
3. ServerDoLoadPropPreset()
4. GetPropPreset()
5. GetDefaultDress()取得DefaultDress标签字符串。
6. GetPropAssetConfigsByTags(tags)根据标签取得对应的资产配置UPropAssetConfig
扫描所有资产TsPropAssetManager.ts CollectAllAssets()

View File

@@ -0,0 +1,18 @@
# 原始流程
1. StartListenServer_(异世界).bat
2. MotionServer.exe
3. StartClient_MotionProcessor.bat
4. Edior
5. MotionProcessor 设置IP 动作传输
6. Editor Open IP
7. 4级添加地图
8. 3级添加角色
9. 在StartListenServer中运行run pvw
# 改进流程
1. 启动Editor并且以专用服务模式启动。Play As Listen Server
2. MotionServer.exe
3. StartClient_MotionProcessor.bat
4. MotionProcessor 设置IP 动作传输
5. 4级添加地图
6. 3级添加角色

View File

@@ -0,0 +1,106 @@
# 相关类
- TsPropActor
- TsIdolPropActor
- TsScenePropActor
- TsPropEffectActor
# 相关资产
ResArt/MountPointConfig/DT_MountPointConfig用于设置道具挂载时的相对偏移。
# MountPoint
GameplayTag中有定义相关Prop的挂载位置标签
- Prop
- MountPoint
- Back
- Body
- Feet
- Head
- HeadBottom
- HeadUp
- Hips
- LeftFoot
- LeftHand
- RightFoot
- RightHand
- Root
对应逻辑TsPropAssetManager.ts中的枚举查找函数为GetMountPointIndexByTagName():
```ts
export const enum MountPointEnum {
    HeadUp,
    Head,
    HeadDown,
    LeftHand,
    RightHand,
    Back,
    Feet,
    Hips
}
```
TsIdolPropManagerComponent.ts
AttachAllProp() => AttachProp()
TsPropAssetManager.ts
```ts
static GetMountPointName(Tag: UE.GameplayTag): string {
if (Tag.TagName.startsWith('Prop.MountPoint')) {
let res = Tag.TagName.split('.')
return res[res.length - 1]
}
return ''
}
```
# 换装代码调用步骤
PrepareLoadNewModel_Multicast
ServerDoLoadPropPreset() => ServerStartSwitchPreset() => ServerLoadProp() => DoLoadProp() => LoadDressByConfig()
- TsDirectorController.ts CreateIdol()
- TsIdolControllerActor.ServerCreateIdolControllerAtLiveArea
- controller.PropComp.ServerLoadPropPreset(0)
- TsIdolPropManagerComponent
- ServerLoadPropPreset()
- ServerLoadProp()
- DoLoadProp()
- LoadDressByConfig
LocalLoadDressByConfig() 本地加载。
LoadDressByConfig() 服务器加载。
# 角色衣服套装预设切换逻辑
在WBP_CharacterItem中5个按钮BtnSuit_1、BtnSuit_2、BtnSuit_3、BtnSuit_4、BtnSuit_5会调用EventOnPresetClicked。
```ts
EventOnPresetClicked(PresetIndex: number, IsDoubleClick: boolean):void {
let curTime = UE.KismetSystemLibrary.GetGameTimeInSeconds(this)
if (curTime - this.LastLoadPresetTime < 0.5) {
console.warn('Click too fast , please try again later')
return
}
this.LastLoadPresetTime = curTime;
if (IsDoubleClick) {
this.LoadPreset(PresetIndex)
} else {
this.PreviewPreset(PresetIndex)
}
}
public LoadPreset(PresetIndex: number): void {
if (this.Idol == null) {
console.error(`TsCharacterItem@LoadPreset error: idol is null`);
return;
}
this.Idol.PropComp.ServerLoadPropPreset(PresetIndex);
}
public PreviewPreset(PresetIndex: number): void {
if (this.Idol == null) {
console.error(`TsCharacterItem@PreviewPreset error: idol is null`);
return;
}
this.Idol.PropComp.ClientLoadPropPreset(PresetIndex);
this.RefreshPresetUIStatus()
}
```

View File

@@ -0,0 +1,264 @@
# TsScreenPlayerTextureRenderer
   - MultiViewActor: UE.MultiViewActor;// 用于渲染pvw/pgm画面
- ReceiveBeginPlay() =>
- this.RegisterLocalEventListener(); =>
- this.ChangeCameraTask(TaskData);
## RegisterLocalEventListener
```ts
private RegisterLocalEventListener(): void {
this.PVWCameraChangeFunc = (TaskData: UE.CamTaskDataRPC) => {
if (this.CurTag == DirectorMode.PVWCameraRenderer) {
this.ChangeCameraTask(TaskData);
}
}
this.PGMCameraChangeFunc = (TaskData: UE.CamTaskDataRPC) => {
if (this.CurTag == DirectorMode.PGMCameraRenderer) {
this.ChangeCameraTask(TaskData);
}
}
this.SwitchDirectorNetTagFunc = (oldTag: UE.GameplayTag, newTag: UE.GameplayTag) => {
this.SwitchDirectorNetTagCallback(oldTag, newTag);
}
DirectorEventSystem.RegisterEventListener(this, DirectorEvent.OnPVWTaskRequested, this.PVWCameraChangeFunc);
DirectorEventSystem.RegisterEventListener(this, DirectorEvent.OnPGMTaskRequested, this.PGMCameraChangeFunc);
DirectorEventSystem.RegisterEventListener(this, DirectorEvent.SwitchDirectorMode, this.SwitchDirectorNetTagFunc)
}
```
## ChangeCameraTask
```ts
ChangeCameraTask(TaskData: UE.CamTaskDataRPC) {
if(!this.bStarted){
return;
}
if(this.DirectorCamManagerActor == null){
this.DirectorCamManagerActor = this.GetCameraManagerActor();
}
// double check
if(this.DirectorCamManagerActor == null){
return;
}
if (this.Task) {
this.Task.Stop();
}
this.Task = DirectorCamUtil.CreateCamTask(this, TaskData, CamTaskType.FullStream, this.DirectorCamManagerActor.droneCamera, null, this.DirectorCamManagerActor.handHeldCamera)
if (this.Task) {
this.Task.Start()
this.BindCamera(this.Task.TryGetBindedMainCamera());
}
}
```
# DirectorCamGroup.cpp
```c++
UDirectorSequencePlayer* UDirectorCamGroup::GetDirectorSequencePlayerFromPool(int CamIndex, bool IsPreview)
{
TargetPlayer = NewObject<UDirectorSequencePlayer>(this);
TargetPlayer->SetCamSequence(CamIndex, SequenceActor);
if(IsPreview)
{
CachedPreviewingPlayer.AddUnique(TargetPlayer);
}
else
{
CachedStreamingPlayer.AddUnique(TargetPlayer);
}
}
```
# PVW & PGM红框效果代码 TsDirectorCamManagerActor.ts
```
//客户机加入流程:
//连接Server->同步当前场景->场景加载完成后收到本地事<E59CB0>?->初始化workShop
//->确认机器身份,执行本机任务(异步任务处理流程)
//异步处理流程:根据机器的性能,加入时间不同,状态差异自适应处理任务逻辑
// 服务端收到任务请<E58AA1>?->确保服务端的workShop初始化完<E58C96>?->处理数据及同<E58F8A>??
// ->客户机收到同步数据后->等待自己的workshop初始化完<E58C96>?->处理自己的逻辑
// 同一种任务只处理最近一<E8BF91>?
```
## HandlePreStreamTaskByNetTag()
相关切区域与切镜代码会调用RequestPVWTaskServer()
RequestPVWTaskServer() =>
RequestPVWTask() => HandlePreStreamTaskDataMulticast() => HandlePreStreamTaskByNetTag()
```ts
HandlePreStreamTaskByNetTag(): void {
if (this.prestreamTaskData) {
switch (Utils.GetDirectorMode(this).TagName) {
case DirectorMode.PVWAndPGM:
if(!DirectorCamUtil.SubmitNewCommandIfDataNotChanged(this.preStreamTask, this.prestreamTaskData)){
if (this.preStreamTask) {
this.preStreamTask.Stop()
}
this.preStreamTask = DirectorCamUtil.CreateCamTask(this, this.prestreamTaskData, CamTaskType.FullStream, this.droneCamera,
this.PVWWindow, this.handHeldCamera)
if (this.preStreamTask) {
this.preStreamTask.Start()
if (this.PVWWindow) {
this.PVWWindow.SetViewBorderColor(0, new UE.LinearColor(0, 1, 0, 1))
}
console.log('PVW Task:' + this.preStreamTask.workShop.BindPlacement.Title + " " + this.preStreamTask.groupName + " " +
this.preStreamTask.camName)
}
}
break
case DirectorMode.Preview:
this.RefreshWindowViewBorderColor()
break
case DirectorMode.PVW:
this.HandlePVWTask()
break;
default:
break
}
}
}
```
# 130mm Bug日志
```c++
[2024.08.06-05.08.09:625][373]Puerts: (0x00000703D1DF42D0) request PVW: WorkShop:3F855CF84C91BBD9207C0FAF5273586C, CamGroup:9223AA8A478BA88F1217CD86911EDAE1, Index=0, StartFrame:0
[2024.08.06-05.08.09:626][373]LogLevelSequence: Starting new camera cut: 'Cam_TalkShowZuo_ZhuJiWei24mm_1'
[2024.08.06-05.08.09:626][373]Puerts: (0x00000703D1DF42D0) 设置相机目标:
[2024.08.06-05.08.09:626][373]Puerts: (0x00000703D1DF42D0) PVW Task: 线下 站\n4 ZhuJiWei24mm
[2024.08.06-05.08.11:550][487]Puerts: Warning: (0x00000703D1DF42D0) PauseOrResumeAllFxProp ,true
[2024.08.06-05.08.11:551][487]Puerts: Warning: (0x00000703D1DF42D0) PauseOrResumeAllFxProp ,false
[2024.08.06-05.08.11:551][487]LogChaosBone: Display: ResetDynamics: 1, ChaosBoneAssets Name: CBA_SK_JiaRan_Costume_BHair
[2024.08.06-05.08.11:551][487]LogChaosBone: Display: ResetDynamics: 1, ChaosBoneAssets Name: CBA_SK_JiaRan_Costume_dress
[2024.08.06-05.08.11:602][490]Puerts: Warning: (0x00000703D1DF42D0) BP_JiaRan_Costume_C_0 set visible false
[2024.08.06-05.08.11:602][490]Puerts: Warning: (0x00000703D1DF42D0) teleport to livearea 031D1C1742D7EB4C76F6B397FF404FD8
[2024.08.06-05.08.11:602][490]Puerts: Warning: (0x00000703D1DF42D0) PauseOrResumeAllFxProp ,true
[2024.08.06-05.08.11:602][490]Puerts: (0x00000703D1DF42D0) X=0.000 Y=0.000 Z=0.000
[2024.08.06-05.08.11:602][490]Puerts: Warning: (0x00000703D1DF42D0) PauseOrResumeAllFxProp ,false
[2024.08.06-05.08.11:602][490]Puerts: (0x00000703D1DF42D0) BP_JiaRan_Costume_C_0,attach prop ,玩具水枪A
[2024.08.06-05.08.11:603][490]Puerts: Warning: (0x00000703D1DF42D0) get socket of JiaRan: RightHandSocket
[2024.08.06-05.08.11:604][490]Puerts: (0x00000703D1DF42D0) Attach prop 玩具水枪A
[2024.08.06-05.08.11:604][490]Puerts: (0x00000703D1DF42D0) Idol.JiaRan successfully switch to 031D1C1742D7EB4C76F6B397FF404FD8
[2024.08.06-05.08.12:085][519]Puerts: Error: (0x00000703D1DF42D0) OnFinishDisappearFx(), but the idol visibility is true!!BP_JiaRan_Costume_C_0
[2024.08.06-05.08.12:107][520]Puerts: Warning: (0x00000703D1DF42D0) BP_JiaRan_Costume_C_0 set visible true
[2024.08.06-05.08.12:108][520]LogBlueprintUserMessages: [BP_JiaRan_Costume_C_0] Apply Material. Oringinal Material: false
[2024.08.06-05.08.12:108][520]Puerts: (0x00000703D1DF42D0) RefreshSceneCaptureShowList
[2024.08.06-05.08.12:109][520]Puerts: (0x00000703D1DF42D0) CheckItemFxPool, size 0
[2024.08.06-05.08.12:109][520]Puerts: (0x00000703D1DF42D0) TriggerHandPose
[2024.08.06-05.08.12:109][520]Puerts: (0x00000703D1DF42D0) TriggerHandPose
[2024.08.06-05.08.12:109][520]Puerts: Warning: (0x00000703D1DF42D0) Disable instrument pose
[2024.08.06-05.08.12:109][520]Puerts: (0x00000703D1DF42D0) Detect hand pose ToyGun
[2024.08.06-05.08.12:109][520]Puerts: (0x00000703D1DF42D0) TriggerHandPose ToyGun
[2024.08.06-05.08.12:455][541]Puerts: (0x00000703D1DF42D0) request PGM: WorkShop:3F855CF84C91BBD9207C0FAF5273586C, CamGroup:9223AA8A478BA88F1217CD86911EDAE1, Index=0, StartFrame:0 , PushMethod =0
[2024.08.06-05.08.12:805][562]Puerts: (0x00000703D1DF42D0) request PGM: WorkShop:3F855CF84C91BBD9207C0FAF5273586C, CamGroup:9223AA8A478BA88F1217CD86911EDAE1, Index=0, StartFrame:0 , PushMethod =0
[2024.08.06-05.08.14:606][670]LogBlueprintUserMessages: [BP_JiaRan_Costume_C_0] Apply Material. Oringinal Material: true
[2024.08.06-05.08.15:822][743]Puerts: (0x00000703D1DF42D0) request PVW: WorkShop:3F855CF84C91BBD9207C0FAF5273586C, CamGroup:DDB1F27A44E7E5B312830A828EB55464, Index=0, StartFrame:0
[2024.08.06-05.08.15:823][743]LogLevelSequence: Starting new camera cut: 'Cine_Camera_Actor_1'
[2024.08.06-05.08.15:823][743]Puerts: (0x00000703D1DF42D0) 设置相机目标:
[2024.08.06-05.08.15:824][743]Puerts: (0x00000703D1DF42D0) PVW Task: 线下 贝拉2024生日 lengthtest
[2024.08.06-05.08.15:824][743]LogBlueprintUserMessages: [BP_Cine_Cam_FOV_1] 35.0
[2024.08.06-05.08.16:256][769]LogBlueprintUserMessages: [BP_Cine_Cam_FOV_1] 35.002922
```
相关Log顺序
- request PVW: WorkShop:3F855CF84C91BBD9207C0FAF5273586C, CamGroup:DDB1F27A44E7E5B312830A828EB55464, Index=0, StartFrame:0
- LogLevelSequence: Starting new camera cut: 'Cine_Camera_Actor_1'
- Puerts: 设置相机目标:
- Puerts: PVW Task: 线下 贝拉2024生日 lengthtest
- PrintString BP_Cine_Cam_FOV_1 35.0
***HandlePVWTask中的prestreamTaskData 不对***
## 日志
```
Puerts: (0x000009E309FB5E30) request PVW: WorkShop:3F855CF84C91BBD9207C0FAF5273586C, CamGroup:B3441B7649E66555CCB558B2C0FD2872, Index=0, StartFrame:0
LogLevelSequence: Starting new camera cut: 'ZhuJiwei_Zheng16-24mm_5'
Puerts: (0x000009E309FB5E30) 130mm bug用log,其他相机1 bindingCam逻辑,ZhuJiwei_Zheng16-24mm_5
Puerts: (0x000009E309FB5E30) 130mm bug用log,其他相机2 bindingCam逻辑,ZhuJiwei_Zheng16-24mm_5
Puerts: (0x000009E309FB5E30) 130mm bug用log,ChangeSequenceCamTarget FullStream Other
Puerts: (0x000009E309FB5E30) 设置相机目标: Idol.BeiLa 0
Puerts: (0x000009E309FB5E30) PVW Task: 线下 单唱站\nFastRhythm ZhuJiwei_Zheng16-24mm
Puerts: (0x000009E309FB5E30) request PVW: WorkShop:3F855CF84C91BBD9207C0FAF5273586C, CamGroup:DDB1F27A44E7E5B312830A828EB55464, Index=0, StartFrame:0
Puerts: (0x000009E309FB5E30) Nice Playing Sequence:LevelSequenceActor_2:BP_lengthtest_1
Puerts: (0x000009E309FB5E30) RebindTarget: BP_BeiLa_Costume_C_0
PIE: Warning: Sequence did not contain any bindings with the tag 'Beila' LevelSequenceActor_2
LogLevelSequence: Starting new camera cut: 'CineCameraActor_Focus130MMTest_1'
Puerts: (0x000009E309FB5E30) 130mm bug用log,其他相机1 bindingCam逻辑,CineCameraActor_Focus130MMTest_1
Puerts: (0x000009E309FB5E30) 130mm bug用log,其他相机2 bindingCam逻辑,CineCameraActor_Focus130MMTest_1
Puerts: (0x000009E309FB5E30) 130mm bug用log,ChangeSequenceCamTarget FullStream Other
Puerts: (0x000009E309FB5E30) 设置相机目标: Idol.BeiLa 0
Puerts: (0x000009E309FB5E30) PVW Task: 线下 贝拉2024生日 BP_lengthtest_1
LogBlueprintUserMessages: [BP_Cine_Cam_FOV_1] 35.0
```
## 可能的相关代码
```c
// 播放指定相机到PVW.
@ufunction.ufunction(ufunction.ServerAPI, ufunction.Reliable)
PlayCamSequenceOnPVWServer(camGroupId:UE.Guid, camIndex:number):void{
let shotExists = this.directorCamSubSystem.ShotExistInGroup(this.prestreamTaskData.WorkShopId, camGroupId, camIndex)
if (shotExists || camIndex == DirectorCamUtil.DRONE_CAM_INDEX || camIndex == DirectorCamUtil.HANDHELD_CAM_INDEX) {
let newPVWTaskData = DirectorCamUtil.CopyTaskData(this.prestreamTaskData)
newPVWTaskData.CamGroupId = camGroupId
newPVWTaskData.CamIndex = camIndex
newPVWTaskData.StartFrame = 0
newPVWTaskData.bPreviewOneFrame = false
this.RequestPVWTaskServer(newPVWTaskData)
}
else {
console.warn('当前镜头不存<E4B88D>?,切换无效!index=' + camIndex)
}
}
class TsDirectorCamSequencePlayBridge extends UE.Actor {
    PlayCamSequence(camGroupId:UE.Guid, camIndex:number): void {
        let camManager = TsDirectorCamManagerActor.Get(this);
        if (camManager) {
            camManager.PlayCamSequenceOnPVWServer(camGroupId, camIndex);
        }
    }
}
```
```c++
// 切换到当前直播区域中的多个workShop
SwitchWorkShopInAreaServer(index: number): void {
// [Server]监听到场景模块切换了直播区域后自动切换<E58887>?0个workshop当前切换事件用来切换当前直播区域中的多个workshop
let levelControl = this.GetLevelAreaManager()
let liveArea = levelControl.GetCurrentLiveArea()
if (!liveArea) {
console.error('DirectorCamManager@ cannot find LiveAreaBy id')
return
}
let placement = DirectorCamUtil.GetPlacementInCurLiveArea(index, liveArea)
if (!placement) {
console.error('DirectorCamManager@SwitchWorkShopEvent:GetPlacementInCurLiveArea failed')
return
}
let GroupData = DirectorCamUtil.GetDefaultGroupData(this, placement.UUID)
if (GroupData) {
// 预览切换workShop后播放第一个分<E4B8AA>?
let newPreviewTaskData = new UE.CamTaskDataRPC()
newPreviewTaskData.WorkShopId = DirectorCamUtil.CopyGuid(placement.UUID)
newPreviewTaskData.CamGroupId = DirectorCamUtil.CopyGuid(GroupData.UUID)
newPreviewTaskData.CamIndex = 0
newPreviewTaskData.OperationId = this.previewTaskData.OperationId + 1
newPreviewTaskData.StartFrame = 0
newPreviewTaskData.bPreviewOneFrame = false
this.HandlePreviewTaskDataMulticast(newPreviewTaskData);
// 预推流切换workShop后播放第一个分组的第一个镜头
let newPVWTaskData = new UE.CamTaskDataRPC()
newPVWTaskData.WorkShopId = DirectorCamUtil.CopyGuid(placement.UUID)
newPVWTaskData.CamGroupId = DirectorCamUtil.CopyGuid(GroupData.UUID)
newPVWTaskData.CamIndex = 0
newPVWTaskData.StartFrame = 0
newPVWTaskData.bPreviewOneFrame = false
this.RequestPVWTaskServer(newPVWTaskData);
}
}
```

View File

@@ -0,0 +1,737 @@
# Common
## Common.ush
添加结构体主要用在材质的CustomNode里。
```c++
// Used by toon shading.
// Define a global custom data structure which can be filled by Custom node in material BP.
struct FToonShadingPerMaterialCustomData
{
// Toon specular
float3 ToonSpecularColor;
float ToonSpecularLocation;
float ToonSpecularSmoothness;
// Toon shadow
float3 ToonShadowColor;
float ToonShadowLocation;
float ToonShadowSmoothness;
float ToonForceShadow;
// Toon secondary shadow
float3 ToonSecondaryShadowColor;
float ToonSecondaryShadowLocation;
float ToonSecondaryShadowSmoothness;
// custom data, usually not used
float4 CustomData0;
float4 CustomData1;
float4 CustomData2;
float4 CustomData3;
};
static FToonShadingPerMaterialCustomData ToonShadingPerMaterialCustomData;
```
## DeferredShadingCommon.ush
1. 实现[[#Encode/Decode函数]]
2. HasCustomGBufferData()函数添加对应的ToonShadingModel宏判断
3. [[#FGBufferData新增变量]]
4. [[#Encode/Decode GBufferData新增逻辑]]
1. Metallic/Specualr/Roughness => ToonShadowLocation/ToonForceShadow/ToonShadowSmoothness
2. AO => ToonSecondaryShadowLocation
3. CustomData => ToonShadowColor/ToonSecondaryShadowSmoothness
4. PrecomputedShadowFactors => ToonSecondaryShadowColor
5. `#define GBUFFER_REFACTOR 0` 以此关闭自动生成Encode/Decode GBufferData代码并使用硬编码调用Encode/Decode GBufferData。
6. `#if WRITES_VELOCITY_TO_GBUFFER` => `#if GBUFFER_HAS_VELOCITY`,以此**关闭写入VELOCITY到GBuffer中**。
### Encode/Decode函数
RGB655 to 8-bit RGB。
将R 256 => 64 ,GB 256 => 32。之后使用2个8bit浮点来存储通道1存储R与G的头两位通道2存储G的后3位与B。
```c++
float2 EncodeColorToRGB655(float3 Color)
{
const uint ChannelR = (1 << 6) - 1;
const uint ChannelG = (1 << 5) - 1;
const uint ChannelB = (1 << 5) - 1;
uint3 RoundedColor = uint3(float3(
round(Color.r * ChannelR),
round(Color.g * ChannelG),
round(Color.b * ChannelB)
));
return float2(
(RoundedColor.r << 2 | RoundedColor.g >> 3) / 255.0,
(RoundedColor.g << 5 | RoundedColor.b ) / 255.0
);
}
float3 DecodeRGB655ToColor(float2 RGB655)
{
const uint ChannelR = (1 << 6) - 1;
const uint ChannelG = (1 << 5) - 1;
const uint ChannelB = (1 << 5) - 1;
uint2 Inputs = uint2(round(RGB655 * 255.0));
uint BitBuffer = (Inputs.x << 8) | Inputs.y;
uint R = (BitBuffer & 0xFC00) >> 10;
uint G = (BitBuffer & 0x03E0) >> 5;
uint B = (BitBuffer & 0x001F);
return float3(R, G, B) * float3(1.0 / ChannelR, 1.0 / ChannelG, 1.0 / ChannelB);
}
```
### FGBufferData新增变量
```c++
struct FGBufferData
{
...
// Toon specular
// 0..1, specular color
half3 ToonSpecularColor;
// 0..1, specular edge position
half ToonSpecularLocation;
// 0..1, specular edge smoothness
half ToonSpecularSmoothness;
// Toon shadow
// 0..1, shadow color
half3 ToonShadowColor;
// 0..1, shadow egde location
half ToonShadowLocation;
// 0..1, shadow edge smoothness
half ToonShadowSmoothness;
// 0..1, force shadow
half ToonForceShadow;
// Toon secondary shadow
// 0..1, secondary shadow color
float3 ToonSecondaryShadowColor;
// 0..1, secondary shadow edge location
float ToonSecondaryShadowLocation;
// 0..1, secondary shadow edge smoothness
float ToonSecondaryShadowSmoothness;
// Toon render
half3 ToonCalcShadowColor;
};
```
### Encode/Decode GBufferData新增逻辑
```c++
void EncodeGBuffer(
FGBufferData GBuffer,
out float4 OutGBufferA,
out float4 OutGBufferB,
out float4 OutGBufferC,
out float4 OutGBufferD,
out float4 OutGBufferE,
out float4 OutGBufferVelocity,
float QuantizationBias = 0 // -0.5 to 0.5 random float. Used to bias quantization.
)
{
...
switch(GBuffer.ShadingModelID)
{
case SHADINGMODELID_TOON_BASE:
OutGBufferB.r = ToonShadingPerMaterialCustomData.ToonShadowLocation;
OutGBufferB.g = ToonShadingPerMaterialCustomData.ToonForceShadow;
OutGBufferB.b = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
OutGBufferC.a = ToonShadingPerMaterialCustomData.ToonSecondaryShadowLocation;
OutGBufferD.a = ToonShadingPerMaterialCustomData.ToonSecondaryShadowSmoothness;
OutGBufferD.rgb = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
OutGBufferE.gba = ToonShadingPerMaterialCustomData.ToonSecondaryShadowColor.rgb;
break;
case SHADINGMODELID_TOON_PBR:
OutGBufferB.g = ToonShadingPerMaterialCustomData.ToonShadowLocation;
OutGBufferD.a = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
OutGBufferD.rgb = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
OutGBufferE.gba = ToonShadingPerMaterialCustomData.ToonSpecularColor.rgb;
break;
case SHADINGMODELID_TOON_SKIN:
OutGBufferB.r = ToonShadingPerMaterialCustomData.ToonShadowLocation;
OutGBufferD.a = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
OutGBufferD.rgb = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
break;
default:
break;
}
...
}
FGBufferData DecodeGBufferData(
float4 InGBufferA,
float4 InGBufferB,
float4 InGBufferC,
float4 InGBufferD,
float4 InGBufferE,
float4 InGBufferF,
float4 InGBufferVelocity,
float CustomNativeDepth,
uint CustomStencil,
float SceneDepth,
bool bGetNormalizedNormal,
bool bChecker)
{
FGBufferData GBuffer = (FGBufferData)0;
...
switch(GBuffer.ShadingModelID)
{
case SHADINGMODELID_TOON_BASE:
GBuffer.ToonShadowColor = InGBufferD.rgb;
GBuffer.ToonShadowLocation = InGBufferB.r;
GBuffer.ToonShadowSmoothness = InGBufferB.b;
GBuffer.ToonForceShadow = InGBufferB.g;
GBuffer.ToonSecondaryShadowColor = InGBufferE.gba;
GBuffer.ToonSecondaryShadowLocation = InGBufferC.a;
GBuffer.ToonSecondaryShadowSmoothness = InGBufferD.a;
GBuffer.Metallic = 0.0;
GBuffer.Specular = 1.0;
GBuffer.Roughness = 1.0;
GBuffer.GBufferAO = 0.0;
GBuffer.IndirectIrradiance = 1.0;
GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? float4(InGBufferE.r, 1.0, 1.0, 1.0) : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 : 1);
GBuffer.StoredMetallic = 0.0;
GBuffer.StoredSpecular = 1.0;
break;
case SHADINGMODELID_TOON_PBR:
GBuffer.ToonSpecularColor = InGBufferE.gba;
GBuffer.ToonShadowColor = InGBufferD.rgb;
GBuffer.ToonShadowLocation = InGBufferB.g;
GBuffer.ToonShadowSmoothness = InGBufferD.a;
GBuffer.ToonSecondaryShadowColor = GBuffer.ToonShadowColor;
GBuffer.ToonForceShadow = 1.0;
GBuffer.ToonSpecularLocation = 1.0;
GBuffer.Specular = 1.0;
GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? float4(InGBufferE.r, 1.0, 1.0, 1.0) : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 : 1);
break;
case SHADINGMODELID_TOON_SKIN:
GBuffer.ToonShadowColor = InGBufferD.rgb;
GBuffer.ToonShadowLocation = InGBufferB.r;
GBuffer.ToonShadowSmoothness = InGBufferD.a;
GBuffer.ToonSecondaryShadowColor = GBuffer.ToonShadowColor;
GBuffer.ToonForceShadow = 1.0;
GBuffer.Metallic = 0.0;
GBuffer.StoredMetallic = 0.0;
GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? float4(InGBufferE.r, 1.0, 1.0, 1.0) : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 : 1);
break;
default:
break;
}
...
};
```
# BasePass
BasePassPixelShader.usf
1. `#if 1` => `#if GBUFFER_REFACTOR && 0`以此关闭自动生成Encode/Decode GBufferData代码并使用硬编码调用Encode/Decode GBufferData。
2. 在FPixelShaderInOut_MainPS()中添加写入FGBufferData逻辑。代码如下
```c++
...
switch(GBuffer.ShadingModelID)
{
case SHADINGMODELID_TOON_BASE:
GBuffer.ToonShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
GBuffer.ToonShadowLocation = ToonShadingPerMaterialCustomData.ToonShadowLocation;
GBuffer.ToonShadowSmoothness = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
GBuffer.ToonForceShadow = ToonShadingPerMaterialCustomData.ToonForceShadow;
GBuffer.ToonSecondaryShadowColor = ToonShadingPerMaterialCustomData.ToonSecondaryShadowColor.rgb;
GBuffer.ToonSecondaryShadowLocation = ToonShadingPerMaterialCustomData.ToonSecondaryShadowLocation;
GBuffer.ToonSecondaryShadowSmoothness = ToonShadingPerMaterialCustomData.ToonSecondaryShadowSmoothness;
GBuffer.Specular = 1.0;
GBuffer.GBufferAO = 0.0;
GBuffer.PrecomputedShadowFactors.gba = 1;
break;
case SHADINGMODELID_TOON_PBR:
GBuffer.ToonSpecularColor = ToonShadingPerMaterialCustomData.ToonSpecularColor.rgb;
GBuffer.ToonShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
GBuffer.ToonShadowLocation = ToonShadingPerMaterialCustomData.ToonShadowLocation;
GBuffer.ToonShadowSmoothness = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
GBuffer.ToonSecondaryShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
GBuffer.ToonForceShadow = 1.0;
GBuffer.Specular = 1.0;
GBuffer.PrecomputedShadowFactors.gba = 1;
break;
case SHADINGMODELID_TOON_SKIN:
GBuffer.ToonShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
GBuffer.ToonShadowLocation = ToonShadingPerMaterialCustomData.ToonShadowLocation;
GBuffer.ToonShadowSmoothness = ToonShadingPerMaterialCustomData.ToonShadowSmoothness;
GBuffer.ToonSecondaryShadowColor = ToonShadingPerMaterialCustomData.ToonShadowColor.rgb;
GBuffer.ToonForceShadow = 1.0;
GBuffer.PrecomputedShadowFactors.g = 1;
break;
default:
break;
}
...
```
# Lighting
## ShadingModels
### ShadingCommon.ush
**添加ShadingModelID宏**
- SHADINGMODELID_TOON_BASE 13
- SHADINGMODELID_TOON_PBR 14
- SHADINGMODELID_TOON_SKIN 15
- SHADINGMODELID_NUM 16
判断是否是IsToonShadingModel:
```c++
bool IsToonShadingModel(uint ShadingModel)
{
uint4 ToonShadingModels = uint4(SHADINGMODELID_TOON_BASE, SHADINGMODELID_TOON_PBR, SHADINGMODELID_TOON_SKIN, 0xFF);
return any(ShadingModel.xxxx == ToonShadingModels);
}
```
## DeferredLightingCommon.ush
修改了AccumulateDynamicLighting()的逻辑。
```c++
FLightAccumulator AccumulateDynamicLighting(
float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion, uint ShadingModelID,
FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos,
inout float SurfaceShadow)
{
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
half3 V = -CameraVector;
half3 N = GBuffer.WorldNormal;
BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
{
const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 4) - (512.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
N = OctahedronToUnitVector(oct1);
}
float3 L = LightData.Direction; // Already normalized
float3 ToLight = L;
float3 MaskedLightColor = LightData.Color;
float LightMask = 1;
if (LightData.bRadialLight)
{
LightMask = GetLocalLightAttenuation( TranslatedWorldPosition, LightData, ToLight, L );
MaskedLightColor *= LightMask;
}
LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost
BRANCH
if( LightMask > 0 )
{
FShadowTerms Shadow;
Shadow.SurfaceShadow = AmbientOcclusion;
Shadow.TransmissionShadow = 1;
Shadow.TransmissionThickness = 1;
Shadow.HairTransmittance.OpaqueVisibility = 1;
const float ContactShadowOpacity = GBuffer.CustomData.a;
GetShadowTerms(GBuffer.Depth, GBuffer.PrecomputedShadowFactors, GBuffer.ShadingModelID, ContactShadowOpacity,
LightData, TranslatedWorldPosition, L, LightAttenuation, Dither, Shadow);
SurfaceShadow = Shadow.SurfaceShadow;
LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms
#if SHADING_PATH_MOBILE
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
FDirectLighting Lighting = (FDirectLighting)0;
half NoL = max(0, dot(GBuffer.WorldNormal, L));
#if TRANSLUCENCY_NON_DIRECTIONAL
NoL = 1.0f;
#endif
Lighting = EvaluateBxDF(GBuffer, N, V, L, NoL, Shadow);
Lighting.Specular *= LightData.SpecularScale;
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
#else // SHADING_PATH_MOBILE
//修改了这里
bool UseToonShadow = IsToonShadingModel(GBuffer.ShadingModelID);
BRANCH
if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 || UseToonShadow)//修改结束
{
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
//修改了这里
BRANCH
if(UseToonShadow)
{
float NoL = dot(N, L);
float ToonNoL = min(NoL, GBuffer.ToonForceShadow);
//合并SurfaceShadow以及Transmision Shadow
Shadow.SurfaceShadow = min(Shadow.SurfaceShadow, Shadow.TransmissionShadow);
//根据ToonShadowSmoothness、ToonShadowLocation、NoL计算阴影亮度最后计算主阴影颜色。
float RangeHalf = GBuffer.ToonShadowSmoothness * 0.5;
float RangeMin = max(0.0, GBuffer.ToonShadowLocation - RangeHalf);
float RangeMax = min(1.0, GBuffer.ToonShadowLocation + RangeHalf);
float ShadowIntensity = Shadow.SurfaceShadow * smoothstep(RangeMin, RangeMax, ToonNoL);
GBuffer.ToonCalcShadowColor = lerp(GBuffer.ToonShadowColor * LightData.SpecularScale, (1.0).xxx, ShadowIntensity);
//计算次级阴影颜色,并最终合成。
RangeHalf = GBuffer.ToonSecondaryShadowSmoothness * 0.5;
RangeMin = max(0.0, GBuffer.ToonSecondaryShadowLocation - RangeHalf);
RangeMax = min(1.0, GBuffer.ToonSecondaryShadowLocation + RangeHalf);
ShadowIntensity = Shadow.SurfaceShadow * smoothstep(RangeMin, RangeMax, ToonNoL);
GBuffer.ToonCalcShadowColor = lerp(GBuffer.ToonSecondaryShadowColor * LightData.SpecularScale, GBuffer.ToonCalcShadowColor, ShadowIntensity);
}
//修改结束
#if NON_DIRECTIONAL_DIRECT_LIGHTING
float Lighting;
if( LightData.bRectLight )
{
FRect Rect = GetRect( ToLight, LightData );
Lighting = IntegrateLight( Rect );
}
else
{
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
}
float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
#else
FDirectLighting Lighting;
if (LightData.bRectLight)
{
FRect Rect = GetRect( ToLight, LightData );
const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
#if REFERENCE_QUALITY
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
#else
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
#endif
}
else
{
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
#if REFERENCE_QUALITY
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
#else
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
#endif
}
//修改了这里
float SurfaceShadow = UseToonShadow ? 1.0 : Shadow.SurfaceShadow;
float TransmissionShadow = UseToonShadow ? 1.0 : Shadow.TransmissionShadow;
Lighting.Specular *= UseToonShadow ? GBuffer.ToonSpecularColor : LightData.SpecularScale;
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
//修改结束
LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light)
#endif
}
#endif // SHADING_PATH_MOBILE
}
return LightAccumulator;
}
```
## ShadingModels.ush
```c++
float3 ToonSpecular(float ToonSpecularLocation, float ToonSpecularSmoothness, float3 ToonSpecularColor, float NoL)
{
float ToonSpecularRangeHalf = ToonSpecularSmoothness * 0.5;
float ToonSpecularRangeMin = ToonSpecularLocation - ToonSpecularRangeHalf;
float ToonSpecularRangeMax = ToonSpecularLocation + ToonSpecularRangeHalf;
return smoothstep(ToonSpecularRangeMin, ToonSpecularRangeMax, NoL) * ToonSpecularColor;
}
```
创建了ToonCustomBxDF**SHADINGMODELID_TOON_BASE**与ToonLitBxDF**SHADINGMODELID_TOON_PBR**、**SHADINGMODELID_TOON_SKIN**2个ShadingModel函数。
### ToonCustomBxDF的修改
Diffuse里面乘以之前在DeferredShadingCommon.ush中计算好的ShadowColor已经计算了NoL
`Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * NoL);`
=>
`Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;`
Speuclar直接归零具体是在BasePass阶段进行计算了。
`Lighting.Specular = 0`
### ToonLitBxDF的修改
Diffuse里面乘以之前在DeferredShadingCommon.ush中计算好的ShadowColor已经计算了NoL
`Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * NoL);`
=>
`Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;`
Speuclar最后乘以了**Shadow.SurfaceShadow**
`Lighting.Specular *= Shadow.SurfaceShadow;`
```c++
FDirectLighting ToonLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
BxDFContext Context;
FDirectLighting Lighting;
#if SUPPORTS_ANISOTROPIC_MATERIALS
bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask);
#else
bool bHasAnisotropy = false;
#endif
float NoV, VoH, NoH;
BRANCH
if (bHasAnisotropy)
{
half3 X = GBuffer.WorldTangent;
half3 Y = normalize(cross(N, X));
Init(Context, N, X, Y, V, L);
NoV = Context.NoV;
VoH = Context.VoH;
NoH = Context.NoH;
}
else
{
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, L, NoL);
#else
Init(Context, N, V, L);
#endif
NoV = Context.NoV;
VoH = Context.VoH;
NoH = Context.NoH;
SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);
}
Context.NoV = saturate(abs( Context.NoV ) + 1e-5);
#if MATERIAL_ROUGHDIFFUSE
// Chan diffuse model with roughness == specular roughness. This is not necessarily a good modelisation of reality because when the mean free path is super small, the diffuse can in fact looks rougher. But this is a start.
// Also we cannot use the morphed context maximising NoH as this is causing visual artefact when interpolating rough/smooth diffuse response.
Lighting.Diffuse = Diffuse_Chan(GBuffer.DiffuseColor, Pow4(GBuffer.Roughness), NoV, NoL, VoH, NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
#else
Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor);
#endif
// Toon Diffuse
Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;
BRANCH
if (bHasAnisotropy)
{
//Lighting.Specular = GBuffer.WorldTangent * .5f + .5f;
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight);
}
else
{
if( IsRectLight(AreaLight) )
{
Lighting.Specular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
}
else
{
// Toon specular
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight);
}
}
Lighting.Specular *= Shadow.SurfaceShadow;
FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, GBuffer.SpecularColor);
// Add energy presevation (i.e. attenuation of the specular layer onto the diffuse component
Lighting.Diffuse *= ComputeEnergyPreservation(EnergyTerms);
// Add specular microfacet multiple scattering term (energy-conservation)
Lighting.Specular *= ComputeEnergyConservation(EnergyTerms);
Lighting.Transmission = 0;
return Lighting;
}
FDirectLighting ToonCustomBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
BxDFContext Context;
FDirectLighting Lighting;
float NoV, VoH, NoH;
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, L, NoL);
#else
Init(Context, N, V, L);
#endif
NoV = Context.NoV;
VoH = Context.VoH;
NoH = Context.NoH;
SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);
Context.NoV = saturate(abs( Context.NoV ) + 1e-5);
#if MATERIAL_ROUGHDIFFUSE
// Chan diffuse model with roughness == specular roughness. This is not necessarily a good modelisation of reality because when the mean free path is super small, the diffuse can in fact looks rougher. But this is a start.
// Also we cannot use the morphed context maximising NoH as this is causing visual artefact when interpolating rough/smooth diffuse response.
Lighting.Diffuse = Diffuse_Chan(GBuffer.DiffuseColor, Pow4(GBuffer.Roughness), NoV, NoL, VoH, NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
#else
Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor);
#endif
// Toon Diffuse
Lighting.Diffuse *= AreaLight.FalloffColor * Falloff * GBuffer.ToonCalcShadowColor;
// Toon specular
// Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * ToonSpecular(GBuffer.ToonSpecularLocation, GBuffer.ToonSpecularSmoothness, GBuffer.ToonSpecularColor, NoL);
// Lighting.Specular *= Shadow.SurfaceShadow;
// FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, GBuffer.SpecularColor);
// Add energy presevation (i.e. attenuation of the specular layer onto the diffuse component
// Lighting.Diffuse *= ComputeEnergyPreservation(EnergyTerms);
Lighting.Specular = 0;
Lighting.Transmission = 0;
return Lighting;
}
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
switch( GBuffer.ShadingModelID )
{
case SHADINGMODELID_DEFAULT_LIT:
case SHADINGMODELID_SINGLELAYERWATER:
case SHADINGMODELID_THIN_TRANSLUCENT:
return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE:
return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_PREINTEGRATED_SKIN:
return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLEAR_COAT:
return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE_PROFILE:
return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_TWOSIDED_FOLIAGE:
return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_HAIR:
return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLOTH:
return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_EYE:
return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_TOON_BASE:
return ToonCustomBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_TOON_PBR:
case SHADINGMODELID_TOON_SKIN:
return ToonLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
default:
return (FDirectLighting)0;
}
}
```
## DeferredLightPixelShaders.usf
在DeferredLightPixelMain()中添加逻辑:
1. 非卡通材质正常渲染。
2. 材质材质只有在LightingChannel = 2时才会计算卡通光影效果。
```c++
bool UseToonShadow = IsToonShadingModel(ScreenSpaceData.GBuffer.ShadingModelID);
// LightingChannel Toon Shading only calculate light of LightingChannel = 2
BRANCH if (!UseToonShadow || (UseToonShadow && DeferredLightUniforms.LightingChannelMask & 0x4))
{
const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);
FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);
UpdateLightDataColor(LightData, InputParams, DerivedParams);
#if USE_HAIR_COMPLEX_TRANSMITTANCE
if (ScreenSpaceData.GBuffer.ShadingModelID == SHADINGMODELID_HAIR && ShouldUseHairComplexTransmittance(ScreenSpaceData.GBuffer))
{
LightData.HairTransmittance = EvaluateDualScattering(ScreenSpaceData.GBuffer, DerivedParams.CameraVector, -DeferredLightUniforms.Direction);
}
#endif
float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8);
float SurfaceShadow = 1.0f;
float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);
float4 Radiance = GetDynamicLighting(DerivedParams.TranslatedWorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, LightAttenuation, Dither, uint2(InputParams.PixelPos), SurfaceShadow);
OutColor += Radiance;
}
```
# PostProcess
## ToneMapping
c++部分主要修改了:
1. PostProcessing.cpp
2. PostProcessTonemap.cpp
3. PostProcessTonemap.h
***实现向ToneMaper Shader传递 `TRDGUniformBufferRef<FSceneTextureUniformParameters>`的功能***
之后再PostProcessTonemap.usf中对**CustomStencil**进行判断如果为true则直接返回之前渲染结果。实际上BufferVisualization里根本看不出来。
```c++
#include "DeferredShadingCommon.ush"
// pixel shader entry point
void MainPS(
in noperspective float2 UV : TEXCOORD0,
in noperspective float2 InVignette : TEXCOORD1,
in noperspective float4 GrainUV : TEXCOORD2,
in noperspective float2 ScreenPos : TEXCOORD3,
in noperspective float2 FullViewUV : TEXCOORD4,
float4 SvPosition : SV_POSITION, // after all interpolators
out float4 OutColor : SV_Target0
#if OUTPUT_LUMINANCE
, out float OutLuminance: SV_Target1
#endif
)
{
float Luminance;
FGBufferData SamplerBuffer = GetGBufferData(UV * View.ResolutionFractionAndInv.x, false);
if (SamplerBuffer.CustomStencil > 1.0f && abs(SamplerBuffer.CustomDepth - SamplerBuffer.Depth) < 1)
{
OutColor = SampleSceneColor(UV);
}
else
{
OutColor = TonemapCommonPS(UV, InVignette, GrainUV, ScreenPos, FullViewUV, SvPosition, Luminance);
}
#if OUTPUT_LUMINANCE
OutLuminance = Luminance;
#endif
}
```
## PostProcessCombineLUT.usf
主要移植了UE4版本的LUT以此保证效果统一。
# 其他
## GpuSkinCacheComputeShader.usf
注释2行代码用处不明。
```c++
#if GPUSKIN_MORPH_BLEND
{
Intermediates.UnpackedPosition += Unpacked.DeltaPosition;
// calc new normal by offseting it with the delta
LocalTangentZ = normalize( LocalTangentZ + Unpacked.DeltaTangentZ);
// derive the new tangent by orthonormalizing the new normal against
// the base tangent vector (assuming these are normalized)
LocalTangentX = normalize( LocalTangentX - (dot(LocalTangentX, LocalTangentZ) * LocalTangentZ) );
}#else
#if GPUSKIN_APEX_CLOTH
```
=>
```c++
#if GPUSKIN_MORPH_BLEND
{
Intermediates.UnpackedPosition += Unpacked.DeltaPosition;
// calc new normal by offseting it with the delta
//LocalTangentZ = normalize( LocalTangentZ + Unpacked.DeltaTangentZ);
// derive the new tangent by orthonormalizing the new normal against
// the base tangent vector (assuming these are normalized)
//LocalTangentX = normalize( LocalTangentX - (dot(LocalTangentX, LocalTangentZ) * LocalTangentZ) );
}#else
#if GPUSKIN_APEX_CLOTH
```

View File

@@ -0,0 +1,314 @@
# 相关资产路径
Content/ResArt/CommandMaterial
- [x] [[#Functions]]
- [x] [[#MatCap]]
- [x] [[#Materials]]
- [ ] [[#MaterialInstance]]
- [x] [[#Outline]]
- [x] [[#Textures]]
# Functions
- [x] [[#ShadingModels]]
- MF_ToonPBRShadingModel
- MF_ToonBaseShadingModel
- MF_ToonSkinShadingModel
- MF_ToonHairShadingModel
- [x] [[#Effects]]
- MF_Dissolve
- MF_EdgeLight
- MF_Fur
- [x] Tools
- MF_DecodeArrayIDAndAlpha分离输入浮点数的整数与小数部分。整数部分作为TextureArrayID小数部分作为Alpha参数。
- 主要用于MF_FaceOverlay的**Face Overlay Color**效果与M_Penetrate的**Eye Overlay Color**效果。
- MF_Hash11
- MF_Hash12
- MF_Hash13
- MF_Hash22
- MF_Hash23
- [x] ***CameraLightCollection***各个角色的主光照颜色、边缘光颜色与主光亮度以及LightDir。
- MF_CharacterMainLightIntensity使用CustomNode编写的函数通过RoleID进行Switch Case计算对应角色的MainLightColor * MainLightIntensity这些一般在Sequence中进行修改。
- MF_ApplyToonHairSpecular头发高光计算被M_ToonBase_V02调用。
- ***MF_CharacterEffects***基本所有角色相关材质都有使用。主要调用了MF_EdgeLight、MF_Dissolves实现了**边缘光与溶解效果。
- MF_CharacterRimLightIntensity使用CustomNode编写的函数通过RoleID进行Switch Case计算对应角色的RimLightColor * RimLightIntensity这些一般在Sequence中进行修改。
- MF_FaceHighlightAndShadow使用Shadow贴图渲染脸上的阴影效果通过Dot(LightVector,FaceRight)判断左右)以及边缘高光(通过Dot(LightVector,FaceFront)作为Mask)被M_ToonFace调用但没有使用脸部阴影效果只使用了高光实际看不出来
- 其中的FaceRight、FaceFront、FaceLightDir**使用了CustomPrimitiveData**。
- MF_FaceOverlay脸部材质额外的BaseColorTexture叠加效果猜测是用来制作一些特殊表情的腮红效果被M_ToonFace调用。
- MF_Inputs镭射材质效果只被M_ToonLaserPBR、MI_Leishezhi调用。
- MF_MatcapMatcap效果输出2种贴图Multip 与 Add效果被MF_ToonPBRInput调用。
- **MF_Matcap_Add**MF_Matcap的升级版。
- OutputAdd = LightMap * LightMatcap
- OutputEmissive = Matcap Texture 01 + Matcap Texture 02 + Matcap Texture 03 + Emissive Matcap * Emissive Texture
- OutputNeckShadow = lerp( lerp(1.0, Matcap Color 04, Matcap Texture 04), 1, NeckShadow)
- OutputInnerline = Innerline Matcap
- MF_NormalMapIntensityNormal强度调整被大量材质引用。
- ***MF_SceneEffects***调用了MF_Dissolve实现了溶解效果。但备用他的材质并不多。
- MF_ShiftTangentKajiya-Kay中的ShiftTangent被M_ToonHair_V01调用。
- MF_StrandSpecKajiya-Kay中的高光计算逻辑被M_ToonHair_V01调用。
- MF_SurfaceSurface材质相关属性逻辑被MF_ToonPBRInput、MF_ToonBaseInput调用。
- **MF_Surface_V02**Surface材质相关属性逻辑被MF_ToonBaseInput_V02调用。与MF_Surface相比少了Specular输出。
- MF_TextureBooming材质没有上线。
- **MF_ToonBaseInput**通用ToonBase材质逻辑函数。集合了MF_CharacterMainLightIntensity、MF_Matcap_Add、MF_Surface、MF_ToonBaseShadingModel材质函数以及一些变量材质设置。被**M_ToonBase_V02_Penetrate**、**M_ToonBase_V02_Test**调用。
- ***MF_ToonBaseInput_V02***通用ToonBase材质逻辑函数V02。集合了MF_CharacterMainLightIntensity、MF_Matcap_Add、**MF_Surface_V02**、MF_ToonBaseShadingModel材质函数以及一些变量材质设置。被**M_ToonBase_V02**、**M_NaiLin_AnotherWorld02**、**M_EggGym_Flower**调用。
- **MF_ToonHairSpecularMaskUV**计算Hair高光贴图UV被MF_ApplyToonHairSpecular**M_ToonBase_V02**)调用。
- 使用dot( float3(0,0,1.0f), CaemraVector)的数值来对**HairMask的采样UVV轴** 进行偏移,以此实现高光偏移效果。
- **MF_ToonPBRInput**通用ToonPBR材质逻辑函数。集合了MF_CharacterMainLightIntensity、MF_Matcap、MF_Surface、**MF_ToonPBRInput**l材质函数以及一些变量材质设置。被**M_Penetrate**、**M_ToonBase_V01**、**M_ToonFace**、**M_ToonHair_V01**、**M_ToonSkin**、**M_BeiLa_Skin_AnotherWorld**、**M_Wave**。
- ***MF_TranslucentDOF***Translucent材质的景深效果***没有看懂***。被MF_Input、**MF_Surface**、**MF_Surface_V02**、M_ToonFacee_old、M_ToonLaserPBR调用。
- MF_VectorRotateAboutAxis向量旋转函数。被MF_WorldSpaceStarring调用。
- MF_WorldSpaceStarring被M_NaiLin_AnotherWorld02调用。
- SceneEffectsCollection场景效果材质参数集**可能已经废弃因为UE5大世界不支持关卡流**。会被MF_SceneEffects、BP_EmptyToStageA以及其他材质调用。
## ShadingModels
采用CustomNode构造FMaterialAttributesde的之后传递到MaterialAttribute模式的材质中其他骚操作还有
1. 使用宏开启MRT5`#define PIXELSHADEROUTPUT_MRT5 1`
2. 设置ShadingModelID`Result.ShadingModel = 14;`
3. 使用FToonShadingPerMaterialCustomData ToonShadingPerMaterialCustomData位于Common.ush)来传递卡通渲染用数据之后在BasePassPixelShader.ush中将数据塞入GBuffer中。
### MF_ToonPBRShadingModel
```c++
FMaterialAttributes Result;
Result.BaseColor = float3(1.0, 1.0, 1.0);
Result.Metallic = 0.0;
Result.Specular = 0.0;
Result.Roughness = 0.0;
Result.Anisotropy = 0.0;
Result.EmissiveColor = float3(0.0, 0.0, 0.0);
Result.Opacity = 1.0;
Result.OpacityMask = 1.0;
Result.Normal = float3(0.0, 0.0, 1.0);
Result.Tangent = float3(1.0, 0.0, 0.0);
Result.WorldPositionOffset = float3(0.0, 0.0, 0.0);
Result.SubsurfaceColor = float3(1.0, 1.0, 1.0);
Result.ClearCoat = 1.0;
Result.ClearCoatRoughness = 0.1;
Result.AmbientOcclusion = 1.0;
Result.Refraction = float3(0.0, 0.0, 0.0);
Result.PixelDepthOffset = 0.0;
Result.ShadingModel = 1;
Result.CustomizedUV0 = float2(0.0, 0.0);
Result.CustomizedUV1 = float2(0.0, 0.0);
Result.CustomizedUV2 = float2(0.0, 0.0);
Result.CustomizedUV3 = float2(0.0, 0.0);
Result.CustomizedUV4 = float2(0.0, 0.0);
Result.CustomizedUV5 = float2(0.0, 0.0);
Result.CustomizedUV6 = float2(0.0, 0.0);
Result.CustomizedUV7 = float2(0.0, 0.0);
Result.BentNormal = float3(0.0, 0.0, 1.0);
Result.ClearCoatBottomNormal = float3(0.0, 0.0, 1.0);
Result.CustomEyeTangent = float3(0.0, 0.0, 0.0);
#define PIXELSHADEROUTPUT_MRT5 1
Result.ShadingModel = 14;
ToonShadingPerMaterialCustomData.ToonSpecularColor = saturate(SpecularColor.rgb);
ToonShadingPerMaterialCustomData.ToonShadowColor = saturate(ShadowColor.rgb);
ToonShadingPerMaterialCustomData.ToonShadowLocation = saturate(CutPosition);
ToonShadingPerMaterialCustomData.ToonShadowSmoothness = saturate(CutSmoothness);
return Result;
```
### MF_ToonBaseShadingModel
```c++
FMaterialAttributes Result;
Result.BaseColor = float3(1.0, 1.0, 1.0);
Result.Metallic = 0.0;
Result.Specular = 0.0;
Result.Roughness = 0.0;
Result.Anisotropy = 0.0;
Result.EmissiveColor = float3(0.0, 0.0, 0.0);
Result.Opacity = 1.0;
Result.OpacityMask = 1.0;
Result.Normal = float3(0.0, 0.0, 1.0);
Result.Tangent = float3(1.0, 0.0, 0.0);
Result.WorldPositionOffset = float3(0.0, 0.0, 0.0);
Result.SubsurfaceColor = float3(1.0, 1.0, 1.0);
Result.ClearCoat = 1.0;
Result.ClearCoatRoughness = 0.1;
Result.AmbientOcclusion = 1.0;
Result.Refraction = float3(0.0, 0.0, 0.0);
Result.PixelDepthOffset = 0.0;
Result.ShadingModel = 1;
Result.CustomizedUV0 = float2(0.0, 0.0);
Result.CustomizedUV1 = float2(0.0, 0.0);
Result.CustomizedUV2 = float2(0.0, 0.0);
Result.CustomizedUV3 = float2(0.0, 0.0);
Result.CustomizedUV4 = float2(0.0, 0.0);
Result.CustomizedUV5 = float2(0.0, 0.0);
Result.CustomizedUV6 = float2(0.0, 0.0);
Result.CustomizedUV7 = float2(0.0, 0.0);
Result.BentNormal = float3(0.0, 0.0, 1.0);
Result.ClearCoatBottomNormal = float3(0.0, 0.0, 1.0);
Result.CustomEyeTangent = float3(0.0, 0.0, 0.0);
#define PIXELSHADEROUTPUT_MRT5 1
Result.ShadingModel = 13;
ToonShadingPerMaterialCustomData.ToonShadowColor = saturate(ShadowColor.rgb);
ToonShadingPerMaterialCustomData.ToonShadowLocation = clamp(ShadowLocation, 0, SpecularLocation);
ToonShadingPerMaterialCustomData.ToonShadowSmoothness = saturate(ShadowSmoothness);
ToonShadingPerMaterialCustomData.ToonForceShadow = saturate(ForceShadow);
ToonShadingPerMaterialCustomData.ToonSecondaryShadowColor = saturate(SecondaryShadowColor.rgb);
ToonShadingPerMaterialCustomData.ToonSecondaryShadowLocation = clamp(SecondaryShadowLocation, 0, SpecularLocation);
ToonShadingPerMaterialCustomData.ToonSecondaryShadowSmoothness = saturate(SecondaryShadowSmoothness);
return Result;
```
### MF_ToonSkinShadingModel
```c++
FMaterialAttributes Result;
Result.BaseColor = float3(1.0, 1.0, 1.0);
Result.Metallic = 0.0;
Result.Specular = 0.0;
Result.Roughness = 0.0;
Result.Anisotropy = 0.0;
Result.EmissiveColor = float3(0.0, 0.0, 0.0);
Result.Opacity = 1.0;
Result.OpacityMask = 1.0;
Result.Normal = float3(0.0, 0.0, 1.0);
Result.Tangent = float3(1.0, 0.0, 0.0);
Result.WorldPositionOffset = float3(0.0, 0.0, 0.0);
Result.SubsurfaceColor = float3(1.0, 1.0, 1.0);
Result.ClearCoat = 1.0;
Result.ClearCoatRoughness = 0.1;
Result.AmbientOcclusion = 1.0;
Result.Refraction = float3(0.0, 0.0, 0.0);
Result.PixelDepthOffset = 0.0;
Result.ShadingModel = 1;
Result.CustomizedUV0 = float2(0.0, 0.0);
Result.CustomizedUV1 = float2(0.0, 0.0);
Result.CustomizedUV2 = float2(0.0, 0.0);
Result.CustomizedUV3 = float2(0.0, 0.0);
Result.CustomizedUV4 = float2(0.0, 0.0);
Result.CustomizedUV5 = float2(0.0, 0.0);
Result.CustomizedUV6 = float2(0.0, 0.0);
Result.CustomizedUV7 = float2(0.0, 0.0);
Result.BentNormal = float3(0.0, 0.0, 1.0);
Result.ClearCoatBottomNormal = float3(0.0, 0.0, 1.0);
Result.CustomEyeTangent = float3(0.0, 0.0, 0.0);
#define PIXELSHADEROUTPUT_MRT5 1
Result.ShadingModel = 15;
ToonShadingPerMaterialCustomData.ToonShadowColor = saturate(ShadowColor.rgb);
ToonShadingPerMaterialCustomData.ToonShadowLocation = saturate(CutPosition);
ToonShadingPerMaterialCustomData.ToonShadowSmoothness = saturate(CutSmoothness);
return Result;
```
### MF_ToonHairShadingModel
```c++
FMaterialAttributes Result;
Result.BaseColor = float3(1.0, 1.0, 1.0);
Result.Metallic = 0.0;
Result.Specular = 0.0;
Result.Roughness = 0.0;
Result.Anisotropy = 0.0;
Result.EmissiveColor = float3(0.0, 0.0, 0.0);
Result.Opacity = 1.0;
Result.OpacityMask = 1.0;
Result.Normal = float3(0.0, 0.0, 1.0);
Result.Tangent = float3(1.0, 0.0, 0.0);
Result.WorldPositionOffset = float3(0.0, 0.0, 0.0);
Result.SubsurfaceColor = float3(1.0, 1.0, 1.0);
Result.ClearCoat = 1.0;
Result.ClearCoatRoughness = 0.1;
Result.AmbientOcclusion = 1.0;
Result.Refraction = float3(0.0, 0.0, 0.0);
Result.PixelDepthOffset = 0.0;
Result.ShadingModel = 1;
Result.CustomizedUV0 = float2(0.0, 0.0);
Result.CustomizedUV1 = float2(0.0, 0.0);
Result.CustomizedUV2 = float2(0.0, 0.0);
Result.CustomizedUV3 = float2(0.0, 0.0);
Result.CustomizedUV4 = float2(0.0, 0.0);
Result.CustomizedUV5 = float2(0.0, 0.0);
Result.CustomizedUV6 = float2(0.0, 0.0);
Result.CustomizedUV7 = float2(0.0, 0.0);
Result.BentNormal = float3(0.0, 0.0, 1.0);
Result.ClearCoatBottomNormal = float3(0.0, 0.0, 1.0);
Result.CustomEyeTangent = float3(0.0, 0.0, 0.0);
Result.ShadingModel = 14;
Result.Anisotropy = 1.0;
ToonShadingPerMaterialCustomData.CustomData0 = saturate(float4(ShadowColor.rgb, ShadowSmoothness));
ToonShadingPerMaterialCustomData.CustomData1 = saturate(float4(SpecularAbsorbance, SpecularRangeParam.xyz));
return Result;
```
## Effects
- **MF_EdgeLight**边缘光效果通过UE菲尼尔函数以及一些贴图、变量实现效果。主要被***MF_CharacterEffects***引用。
- **MF_Dissolve**:溶解过度效果。主要被***MF_CharacterEffects***、***MF_SceneEffects***引用。
- MF_Fur用于制作Fur效果的材质函数被**ToonShading_Standard_nooffset_fur**引用。主要还是靠***GFur插件***,没有参考意义,升级难度+1。
# Matcap
存放大量Matcap用的**球形环境贴图**。除此之外`/ResArt/CommonMaterial/Materials/V02/MatCap/`也存储了Matcap贴图。
## 球形全景图制作方法
【如何将无人机拍摄的球形全景图还原成球形视图】 https://www.bilibili.com/video/BV1yz411q7Eg/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
# Materials
>所有角色与新服装都迁移到V02版本V01已废弃。
- Special
- M_BeiLa_Skin_AnotherWorld特别定制的材质。
- [ ] V02
- Special
- M_NaiLin_AnotherWorld02特别定制的材质。
- ***[[#M_ToonBase_V02]]*****默认的ShadingModel为13**也就是SHADINGMODELID_TOON_BASE。
- ~~M_ToonBase_V02_Test~~测试用主要的差别是使用的是MF_ToonBaseInput里面用的是旧版的MF_Surface
- MI_ToonBase_V02
- MI_ToonSkin_V02
- MI_ToonFace_V02
- MI_ToonHair_V02
- MI_Brow_V02
- MI_Eye_V02
- MI_EyeGlass_V02
- MI_EyeHighlight_V02
- MI_EyeShadow_V02
- MI_MakeUp_V02
- MI_TeethTongue_V02
- [x] **M_Eye_Highlight**
- M_Hide隐藏模型用材质。
- [x] M_Penetrate
- [x] **M_ToonBase_V01**主要的逻辑是MF_ToonPBRInput => MF_CharacterEffects。**默认的ShadingModel为14**,也就是**SHADINGMODELID_TOON_PBR**。
- [x] M_ToonBase_V02_Penetrate带有Penetrate功能的M_ToonBase_V01。
- [x] **M_ToonFace**
- [x] M_ToonFace_old
- [x] **M_ToonHair_V01**
- [x] **M_ToonSkin**
## M_ToonBase_V02
与M_ToonBase_V01相比。最主要的逻辑区别在于
1. MF_ToonPBRInput => MF_ToonBaseInput_V02
1. MF_Matcap_Add => MF_Matcap。不输出Specular转而将高光结果输出在BaseColor与Emissive中。
2. MF_ToonPBRShadingModel => MF_ToonBaseShadingModel。
1. 移除Specular
2. 增加ToonShadowSmoothness
3. 增加ToonSecondaryShadow
4. ShadingModel为13也就是**SHADINGMODELID_TOON_BASE**。14 => 13。
2. 增加MF_ApplyToonHairSpecular()计算头发高光并且将结果叠加到Emissive上。
3. 增加Penetrate逻辑结果加上WPO上。
4. 增加Refraction逻辑通过Normal以及菲尼尔节点插值以此设置Refraction。
# MaterialInstance
# Outline
主要存储了Outline主材质以及所有角色所用到的Outline材质统一放到对应角色文件夹中。描边材质使用***M_Outline_V03***或***MI_Outline_V03***即可。
1. 材质ShadingModel为Unlit、BlendMode为Maskd。
2. 所有描边材质都有**MF_CharacterEffects**、MF_CharacterMainLightIntensity。所以都支持溶解效果以及调整每个角色的描边光照效果连接的是材质Emissive引脚
- M_Outline_V01 & M_Outline_V02
- **WPO**引脚逻辑:**描边粗细控制部分逻辑**和V01基本相同除了V01有Minimum Line Thickness这个粗细最小值。剩下的其他逻辑一模一样。
- ***M_Outline_V03***
- **OpacityMask**引脚逻辑与V01、V02略有不同。会将BaseTexture贴图的a通道乘以HideMask然而并没有材质使用。
- **WPO**引脚逻辑V03版本的**描边粗细控制部分逻辑**做了更加合理的改动。VertexColor的RGB通道代替Backface外扩时的VertexNormalWS这样不会影响光照效果。
- MaterialInstance
- MI_Outline_V03
- MI_Outline_Face_V03
- MI_Outline_Hair_V03
- MI_Outline_Skin_V03
- Dissolve继承自MaterialInstance文件夹中的对应材质实例**勾选了EnableDissolve**。
# Textures
存储一些默认贴图与一些测试用(无用)贴图。

View File

@@ -0,0 +1,12 @@
# 面捕问题解决
## c++
~~IdolAnimInstance~~:没相关代码。
~~MotionSyncComponent~~
~~UOVRLipSyncActorComponentBase~~
~~MotionReceiverActor.cpp~~
## ts
TsArkitDataReceiver
TsMotionRetargetComponent
TsMotionSyncComponent

View File

@@ -0,0 +1,16 @@
# 环境
- Unity2020.1.16
- XCode版本对应要求:https://developer.apple.com/cn/support/xcode/
- 曹老师说无需证书就可以打包到手机上。
# 证书
证书密码123456
## 具体操作
https://blog.csdn.net/ListJson/article/details/128044539
1. 生成CRS文件用于申请证书钥匙串 - 钥匙串访问 - 证书助理 - 从证书颁发机构请求证书
2. 将证书导入Mac从苹果开发者网站上申请Development与Distribution证书并且导入Mac系统需要拖入钥匙串的登录里
3. 信任证书:在钥匙串里双击对应证书,点开信任选项卡,设置为始终信任。
4. 导出P12证书。
# 打包

View File

@@ -0,0 +1,132 @@
# 项目1北京市科委研发项目
需要在虚幻里访问api获得模型文件(fbx模型 png贴图)和场景描述(json) 在runtime下加载后用于扣绿虚拍系统
# 项目2国家数字资产平台
根据访问需求,接通云游戏(使用蔚领云游戏SDK)然后收到前端的资产uuid加载资产渲染。
## 第一阶段
用户访问一个web平台点击某个资产(模型or场景)
会弹出一个页面页面类似虚幻metahuman编辑器一样是视频流的形式
在视频流中是一个runtime窗口可以渲染目标资产
## 第二阶段
可以在前端点击资产添加runtime窗口里会添加资产然后runtime窗口里有简单编辑器可以摆镜头可以简单摆放资产
# Default材质
标准pbr(半透明)
提供参数
1. base color (tex)
2. basecolor tilt (v3) 乘basevolor
4. opacity(tex linear)
5. opacity power(scale)乘到opacity
6. roughness(tex linear color)
7. roughness tilt(scale) 乘roughness
8. metalic (tex linear color)
9. metalic tilt(scale)
10. ao (tex linear color)
11. emisive(tex)
12. emisive power(scale) 乘emisive
13. normal(tex)
# FaceSet
https://github.com/zenustech/zeno/blob/b4ed8740810f20cf0612d615d9505468b2d2a4d1/projects/FBX/FBXSDK.cpp#L477
```c++
int mat_count = 0;
if (pMesh->GetElementMaterialCount() > 0) {
for (auto i = 0; i < numPolygons; ++i) {
faceset[i] = pMesh->GetElementMaterial()->GetIndexArray().GetAt(i);
}
mat_count = pNode->GetMaterialCount();
for (auto i = 0; i < mat_count; i++) {
FbxSurfaceMaterial* material = pNode->GetMaterial(i);
ud.set2(format("faceset_{}", i), material->GetName());
}
}
```
UE中相关逻辑位于FbxMesh.cpp
```c++
void ExtractMeshMaterials(FFbxParser& Parser, FbxMesh* Mesh, FbxNode* MeshNode, TFunction<void(const FString& MaterialName, const FString& MaterialUid, const int32 MeshMaterialIndex)> CollectMaterial)
{
if (!Mesh || !MeshNode)
{
return;
}
//Grab all Material indexes use by the mesh
TArray<int32> MaterialIndexes;
int32 PolygonCount = Mesh->GetPolygonCount();
if (FbxGeometryElementMaterial* GeometryElementMaterial = Mesh->GetElementMaterial())
{
FbxLayerElementArrayTemplate<int32>& IndexArray = GeometryElementMaterial->GetIndexArray();
switch (GeometryElementMaterial->GetMappingMode())
{
case FbxGeometryElement::eByPolygon:
{
if (IndexArray.GetCount() == PolygonCount)
{
for (int32 PolygonIndex = 0; PolygonIndex < PolygonCount; ++PolygonIndex)
{
MaterialIndexes.AddUnique(IndexArray.GetAt(PolygonIndex));
}
}
}
break;
case FbxGeometryElement::eAllSame:
{
if (IndexArray.GetCount() > 0)
{
MaterialIndexes.AddUnique(IndexArray.GetAt(0));
}
}
break;
}
}
const int32 MaterialCount = MeshNode->GetMaterialCount();
TMap<FbxSurfaceMaterial*, int32> UniqueSlotNames;
UniqueSlotNames.Reserve(MaterialCount);
bool bAddAllNodeMaterials = (MaterialIndexes.Num() == 0);
for (int32 MaterialIndex = 0; MaterialIndex < MaterialCount; ++MaterialIndex)
{
if (FbxSurfaceMaterial* FbxMaterial = MeshNode->GetMaterial(MaterialIndex))
{
int32& SlotMaterialCount = UniqueSlotNames.FindOrAdd(FbxMaterial);
FString MaterialName = Parser.GetFbxHelper()->GetFbxObjectName(FbxMaterial);
FString MaterialUid = TEXT("\\Material\\") + MaterialName;
if (bAddAllNodeMaterials || MaterialIndexes.Contains(MaterialIndex))
{
if (SlotMaterialCount > 0)
{
MaterialName += TEXT("_Section") + FString::FromInt(SlotMaterialCount);
}
SlotMaterialCount++;
CollectMaterial(MaterialName, MaterialUid, MaterialIndex);
}
}
}
}
```
#
AFBXMeshActor::AddMesh
UFBXSceneImporter::ImportFBX()
=>
UFBXSceneImporter::ProcessNode()
=>
UFBXSceneImporter::ProcessMesh()
=>
UFBXSceneImporter::CreateNodeMaterials()
=>
FBXImportSettings->GetMaterialProperties(FbxMaterial, Node->GetName());
## FaceSet
FbxGeometryElementMaterial* GetElementMaterial
FbxGeometryElementMaterial(FbxLayerElementMaterial)

View File

@@ -0,0 +1,119 @@
# 其他软件的思路
## MeshY
1. 模型+三视图+关键字&标签 => 贴图贴上模型,并且生成对应的贴图
1. LJJ可行性与实现意义并不大因为需要一个相对准确的三视图。
2. DCC软件已经有一个这样的工具速度差不多优势并不大 https://www.youtube.com/watch?v=Gh_LzoliD6A
2. 文字&关键字&标签 => 模型
1. 意义比较大,而且已经有插件做到了这一点。
1. https://youtu.be/z9XjdcgP8TA?si=eEFS9AZ_2C0yud-O&t=206
2. AI生成点云数据
1. https://www.youtube.com/watch?v=5uF5-iIs-yY&t=113s
2. https://www.youtube.com/watch?v=OEfV3RRpmRE
## 绘画软件SubstancePatiner
个人认为最好是
- Substance Painter 通过关键字生成笔刷Mask贴图或者材质笔刷。
- 模仿Substance Painter 对模型的某个材质或者某个区域添加关键字。
在此基础上可以通过给指定区域绘制Mask添加标签以及细致标签可以达到更加精确的效果。但我们还需要选择开发基础软件我推荐Blender与UE
# 宿主软件选择&开发思路&开发计划
从开发效率方面考虑建议先在软件现成功能的基础上进行开发再原型确定之后再分别移植到其他的软件上。这里推荐UnrealEngine与Blender。同时Blender的SendToUnrealEngine插件相比其他DCC Send To UE插件功能更好。所以建议如下
- 模仿普罗米修斯建立一个资产管理工具&数据及控制中心下文简称MetaCenter
- 建议使用Electron进行开发重界面轻功能推荐使用Electron开发速度快前后端人员可以上手其他还是有React Native等
- 其优势在于有良好的人机学习环境用户在整理资产的过程就是AI学习的过程。同时用户社区分享预设也可以起到一个评分系统的作用。
- 同时也可以作为今后给资产打Tag的基础工具。
- 建议让杨萱羽花时间去摸索一下,总结功能以及工作流程。
- 规避资产库版权问题[[#资产库问题]]
- Blender基础上开发AI辅助绘制贴图插件需要知道Blender的贴图绘制距离SP还差哪些功能
- 推荐的参考对象是[dDo](https://www.bilibili.com/video/BV18K4y1b7qe/?spm_id_from=333.337.search-card.all.click&vd_source=d47c0bb42f9c72fd7d74562185cee290)以多种特征贴图来生成细节以及SubstancePainter的AI功能。
- 关键字生成笔刷Mask贴图或者材质笔刷。
- 通过对模型的某个材质或者某个区域添加关键字的方式来生成区域贴图。
- 建议找一个对Blender以及Python都很了解的人去评估一下。
- UE基础上开发场景摆放&语义控制&连接MetaCenter
- 具体参考polygonflow.io以及普罗米修斯
- 图片生成点云功能方便设计以及搭建场景(影视向)
- 第二期开发可以考虑VR摆场景
## 插件承载载体推荐使用Blender
Blender具有一个比较完善的插件市场
1. Blender具有比较完整的绘画以及3D工具
1. Convert Ai generated 2D images to 3D models for use in Blender and Gravity Sketch. https://www.youtube.com/watch?v=Wf-OmHyFduo
### 开发思路
假设一个物体是由很多个小物体组成的我们要给房子绘制贴图。这里我们需要考虑提供哪些信息给AI并且考虑如何做加法/减法来达到最终的目的(调整以下行为的顺序):
- 给AI一个3D模型
- 参考dDo给剑不同材质、颜色区域指定不同的纯色。AI无法判断整体但对细致的区域学习较快
- 给每个纯色区域一个或者多个关键字
- 给纯色区域的过渡区域一个关键词
- 以辅助绘制的方式在模型上绘制BaseColor或者NormalMap
- 在模型上绘制 重新计算区域让AI重新生成
- 给AI参考图3视图 =>大致印象参考图)
优势在于:
1. 有良好的人机学习环境。
2. 递进式的创建工作。
### 风格化贴图
对大规模卡通&风格化资产打标签来让AI学习。 资产来源可以是Taobao剑网三、龙之谷等以及游戏提取资产。
## UE插件方案参考
***普罗米修斯的方案很好实现了用户与AI模型的交互在用户的使用就可以不断训练AI。***
1. 根据照片&关键词快速从 Quixel或者其他免费资产库中找到资产并且导入UE中甚至摆放好
1. www.polygonflow.io 【【AI教程】地编又要降工资了UE5地编全新AI辅助工具问世】 【精准空降到 01:06】 https://www.bilibili.com/video/BV1VN411p7FF/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=66
2. 普罗米修斯
1. Meta Data(外部工具编辑资产的Meta数据)
1. 【普罗米修斯AI插件在虚幻引擎的基础教程Artificial Intelligence That Builds Virtual Worlds】 【精准空降到 06:15】 https://www.bilibili.com/video/BV1gM411K7Up/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=375
2. 【普罗米修斯AI插件在虚幻引擎的基础教程Artificial Intelligence That Builds Virtual Worlds】 【精准空降到 10:34】 https://www.bilibili.com/video/BV1gM411K7Up/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=634
3. https://youtu.be/hA0MsGWvmzs?si=3WK8T4WfNqj71rRw&t=1386
2. 资源添加
1. https://youtu.be/z9XjdcgP8TA?si=2oIHdtVrZnF8P9bx&t=101
3. 关键词语生成资产摆放
1. https://youtu.be/z9XjdcgP8TA?si=eEFS9AZ_2C0yud-O&t=206
4. 识别本地资产并且创建标签
1. https://youtu.be/hA0MsGWvmzs?si=WxHYO4H9bSeDQ4lN&t=546
5. 资产智能摆放
1. https://youtu.be/hA0MsGWvmzs?si=oLZYS8JO4wGV-Xzz&t=716
6. 100%程序化流程(代表所有操作都是可逆的或者是节点化的)
1. https://youtu.be/hA0MsGWvmzs?si=j2ISS0gL6LKzeJfE&t=1067
7. 外部程序 <==> UE 相互拖拽
1. https://youtu.be/hA0MsGWvmzs?si=Irn_TFy0Mri2furg&t=1669
8. 社区互动
1. https://youtu.be/hA0MsGWvmzs?si=m5iQ_tAxd9UOu667&t=1701
# 问题&解答
## 资产库问题
普罗米修斯的方案是组织用户自己的资产库polygonflow是UE插件连接到MegaScane Bridge可以规避版权问题。
## 其他资产来源&应用
1. taobao可以购买龙之谷等以前游戏的资产。
2. SubstancePainter AI笔刷 https://www.artstation.com/artwork/038Qz5
3. https://substance3d.adobe.com/magazine/ai-power-2d-painting-a-massive-boost-for-substance-alchemist/
4. Stylized Environment Texturing in Mixer:https://www.youtube.com/watch?v=kg4YFS6b0ek
---
# 其他待讨论的问题
- [ ] 目标是什么什么应用场景使用AI生产模型以及贴图大概需要多少样本样本可能不够
- [ ] 是否以最终效果作为判断依据以蓝色协议这种二次元游戏来说他定制了渲染管线贴图除了颜色贴图还有一些控制渲染变量用的贴图、以及一些不方便导出的渲染变量。环境物体主要的颜色贴图、法线贴图、Roughness、Metallic。**角色贴图**会有一些卡通效果控制类的,那些贴图意义不大。(环境物体可能还会做一次SNN滤镜) ![](./SNN.png)
- [ ] 使用材质修改颜色的换皮怪是否会影响最后的训练结果?
- [ ] 最终输出哪些东西? 与游戏目录结构相同的文件目录以及对应贴图模型文件、附带一个Model=>Textures JSON? 场景StaticMesh是否包含骨骼模型
- [ ] 是否需要Motion
个人认为直接上手二次元比较难还是倾向于PBR或是偏向pbr的卡通渲染&风格化渲染。
1. DDO绘制流程:https://www.bilibili.com/video/BV18K4y1b7qe/?spm_id_from=333.337.search-card.all.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
2. PBR资产库比较多一些AI场景工具直接根据图片来查找Quixel或者其他资产库的资产并且下载到项目里。
# 模型贴图对应方式
Model => MaterialInstance => Textures
{
[
{
Model:/Model/1.fbx,
Textures:[
/Model/Textures/1.tga,
/Model/Textures/2.tga,
/Model/Textures/3.tga,
]
}
]
}

View File

@@ -0,0 +1,36 @@
# 待选目标
1. UE5自带的动画重定向功能
2. Swift Motion Toolkit
1. https://www.unrealengine.com/marketplace/zh-CN/product/swift-motion-toolkit?sessionInvalidated=true
3. ~~UE4自带的动画重定向功能~~
4. ~~VRM4U实现的动画重定向功能~~
## Swift Motion Toolkit
Showcase:
[Mixamo Animation to Manny](https://redirect.epicgames.com/?redirectTo=https://www.youtube.com/embed/_p9ttnPKMYI)
[Mocap dancing to Mannequin](https://redirect.epicgames.com/?redirectTo=https://www.youtube.com/embed/dRPSrGvM67U)
[Mocap aiming to Mannequin](https://redirect.epicgames.com/?redirectTo=https://www.youtube.com/embed/ZWKmjRFj-kEhttps://www.youtube.com/embed/ZWKmjRFj-kE)
[Mocap plane to Manny](https://redirect.epicgames.com/?redirectTo=https://www.youtube.com/embed/ZWKmjRFj-kE)
[Video tutorials](https://redirect.epicgames.com/?redirectTo=https://www.youtube.com/playlist?list=PLt62Gv_wadqXZW6YQkZCXNoVAg8s9NxwI)
[Documents](https://redirect.epicgames.com/?redirectTo=https://kontiki.info/swift_motion_toolkit)
### 技术细节
Features:
-  Import mo-cap animation in BVH format and provide a preview.
-  Retarget mo-cap animation to skeletal mesh asset. 
-  Supports more precise restoration of the position of end joints using IK.
-  Supports synchronizing animation to root and IK bones.
- Supports baking vertical or (and) horizontal root displacements into animations.
- Support for selectively preserving root rotation.
- Support for converting animations with root motion to in-place animations. 
# 项目需求
1. 研究UE5的骨骼链&IK 动画重定向算法。
2. 研究Swift Motion Toolkit的动画重定向算法。
3. 比较两者算法优劣并从Runtime的角度提出改进思路。
4. 搜索相关动画重定向论文,编写算法解析文档。
## 重定向过程简述(输入&输出)
1. 提供SourceSkeleton、TargetSkeleton2个骨骼FBX文件以及若干SourceAnimation动画数据(基于SourceSkeleton)FBX文件。
2. 通过计算SourceSkeleton、TargetSkeleton差异将SourceAnimation动画数据转换成TargetSkeleton版本的动画数据。
3. 输出成FBX文件。
# 怎么对接

View File

@@ -0,0 +1,107 @@
# 问题
使用Ubantu官方安装的系统编译的v8环境放入Puerts插件后可以正常运行使用 adamrehn/ue4-runtime:22.04-vulkan-x11镜像下载编译环境后编译v8环境并且放入puerts插件运行后会报错。初步认为是编译环境有问题所致。可能是因为参与编译的libatomic.so.1库是32位的。
![[Puerts启动错误.png]]
# 需求
1. 参考 adamrehn/ue4-runtime:22.04-vulkan-x11重新构建容器
2. 安装下列环境以进行Puerts的V8环境编译
```bash
sudo apt-get install clang
sudo apt-get install libc++-dev
sudo apt-get install libc++abi-dev
sudo apt-get install make
curl -sL https://deb.nodesource.com/setup_16.x | sudo -e bash -
sudo apt-get install -y nodejs
sudo apt-get install git
```
```bash
# 下载编译脚本,脚本需要手动修改一下
cd /home/user/Projects/
git clone https://github.com/puerts/backend-nodejs.git
cd /home/user/Projects/backend-nodejs/node-script
npm install -s commander
/home/user/Projects/backend-nodejs/linux.sh
```
**修改过的编译脚本**
```sh
WORKSPACE=/home/user/Projects/backend-nodejs
HOMEPATH=~
VERSION=$1
cd $HOMEPATH
# git clone https://github.com/nodejs/node.git
cd node
# git fetch origin v$VERSION
# git checkout v$VERSION
echo "=====[Patching Node.js]====="
node $WORKSPACE/node-script/do-gitpatch.js -p $WORKSPACE/patchs/lib_uv_add_on_watcher_queue_updated_v16.16.0.patch
node $WORKSPACE/node-script/add_arraybuffer_new_without_stl.js deps/v8
node $WORKSPACE/node-script/make_v8_inspector_export.js
echo "=====[Building Node.js]====="
export CC=clang
export CXX=clang++
export CXXFLAGS="-stdlib=libc++"
export LDFLAGS="-stdlib=libc++"
./configure --shared
make -j8
# 这后面可以的可以不要执行需要libnode.so.93 libnode.so
mkdir -p ../puerts-node/nodejs/include
mkdir -p ../puerts-node/nodejs/deps/uv/include
mkdir -p ../puerts-node/nodejs/deps/v8/include
cp src/node.h ../puerts-node/nodejs/include
cp src/node_version.h ../puerts-node/nodejs/include
cp -r deps/uv/include ../puerts-node/nodejs/deps/uv
cp -r deps/v8/include ../puerts-node/nodejs/deps/v8
mkdir -p ../puerts-node/nodejs/lib/Linux/
cp out/Release/libnode.so.* ../puerts-node/nodejs/lib/Linux/
cd ../puerts-node/nodejs/lib/Linux/
ln -s libnode.so.93 libnode.so
cd -
```
## 原容器存在问题
adamrehn/ue4-runtime:22.04-vulkan-x11存在的问题
1. 缺少相关编译环境。
2. 没有设置环境变量不知道root、ue4账号密码。
3. 参与编译的libatomic.so.1库是32位的。
经过测试使用官方镜像安装的Ubantu可以正常进行编译UE项目以及Puerts v8环境并且可以正常打开项目原容器可以编译UE项目但因为编译的Puerts v8环境有一些问题导致无法运行Puerts。
## 启动命令参考
```bash
运行UE用
sudo docker run --gpus=all --rm -e DISPLAY -ti \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
-v /home/user/Linux_Unreal_Engine_5.1.1:/home/ue4/UnrealEngine \
-v /home/user/Projects/AIMotionRender:/project \
adamrehn/ue4-runtime:22.04-vulkan-x11
编译v8环境用
sudo docker run -u 0 --gpus=all --rm -e DISPLAY -ti \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
-v /home/user/Linux_Unreal_Engine_5.1.1:/home/ue4/UnrealEngine \
-v /home/user/Projects/AIMotionRender:/project \
-v /home/user/node:/home/user/node \
-v /home/user/Projects/backend-nodejs:/home/user/Projects/backend-nodejs \
adamrehn/ue4-runtime:22.04-vulkan-x11
```
# UE项目测试方法
将编译出来的libnode.so.93 libnode.so放入/home/user/Projects/AIMotionRender/Plugins/Puerts/ThirdParty/nodejs_16/lib/Linux 目录下,之后就可以启动容器
/home/ue4/UnrealEngine/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh /project/AIMotionRender.uproject -game -engine -rocket -progress -VSCode
编译命令:
/home/ue4/UnrealEngine/Engine/Build/BatchFiles/Linux/Build.sh Development Linux -Project="/project/AIMotionRender.uproject" -TargetType=Editor -Progress -NoEngineChanges -NoHotReloadFromIDE
启动命令:
/home/ue4/UnrealEngine/Engine/Binaries/Linux/UnrealEditor -log "/project/AIMotionRender.uproject"
启动过程没有报错就可以了。

View File

@@ -0,0 +1,408 @@
# 动画方案
预制开始/等待动画 -> VMC推流动画 -> 预制结束/等待动画
## VMC推流
[[AnimNode & VMC笔记]]
## 迭代动画状态机方案
1. 由ChatGPT模型AI使用之前录制动画素材拼凑出N组排列组合。
2. 动画资产以及排列数据进行定期热更新。(自动 | 人工)
3. 实时直播时由ChatGPT发送指定排列组合的名称或者ID给客户端之后客户端播放对应的排列组合动画。
# 推流方案
推流视频:
- https://www.bilibili.com/video/BV1ub4y1Y74K/?spm_id_from=333.337.search-card.all.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
- https://www.youtube.com/watch?v=ufU9me5pDYE&t=2s
# 协议
## OSC
一种基于UDP的**远程控制协议**传输的数据主要分为Bundle 与 Message。
- OSC:https://opensoundcontrol.stanford.edu/index.html
- Nodejs的OSC实现:https://www.npmjs.com/package/osc
- 案例代码库:https://github.com/colinbdclark/osc.js-examples
### 反序列化步骤
1. 调用`ReadOSC()`
2. ReadOSCString读取Address。主要分为`#bundle``#message`
3. `#bundle`
1. 读取uint64 Time。
2. 调用`ReadOSC()`,递归序列化之后的数据。
4. `#message`:基础数据反序列化逻辑
1. 读取FString Semantics里面每个字符代表之后基础数据的类型。
2. 根据基础数据类型进行反序列化一个数据生成一个FUEOSCElement。
```c++
UENUM()
enum class EUEOSCElementType : uint8
{
OET_Int32 UMETA(DisplayName = "Int32"),
OET_Float UMETA(DisplayName = "Float"),
OET_String UMETA(DisplayName = "String"),
OET_Blob UMETA(DisplayName = "Blob"),
OET_Bool UMETA(DisplayName = "Bool"),
OET_Nil UMETA(DisplayName = "Nil")
};
```
### UEOSC实现分析
一个数据包的格式为:
```c++
USTRUCT(BlueprintType)
struct UEOSC_API FUEOSCMessage
{
GENERATED_USTRUCT_BODY()
public:
FString Address;
TArray<FUEOSCElement> Elements;
};
```
其中`FUEOSCElement`存储的具体数据,里面是一个结构体存储着基础数据类型数据以及数据类型枚举。
#### 基础数据类型
数据以结构体形式进行序列化/反系列化。可携带的数据类型为:
- int32、int64、uint64
- float32
- String(`FName`)
- blob(`TArray<uint8>`)
- bool
## VMC
全名为Virtual Motion Capture Protocol一种基于OSC的虚拟偶像动作输出传输协议。
**存在问题**
1. 数据没有压缩,不适合互联网传输。
2. 基于OSC这种UDP协议数据没有可靠性。
### 协议分析
- VMC协议可视化工具:https://github.com/gpsnmeajp/VMCProtocolMonitor
- https://protocol.vmc.info/specification
- performer-spec:https://protocol.vmc.info/performer-spec
- marionette-spec:https://protocol.vmc.info/marionette-spec
VMC协议基本上实现了开放声音控制OSC单向UDP通信来进行通信。
因此VMC 协议定义了自己的术语:
- **木偶**(**Marionette**)
- 接收动作、绘制等。(必填)
- 它的存在最终是为了在屏幕、视频、通讯上产生结果。
示例EVMC4U、VMC4UE、其他运动接收兼容应用程序
- **表演者**(**performer**)
- 主要处理运动并向 Marionette 发送**全身骨骼信息 (IK)** 和**辅助信息**。(必填)
- 例如虚拟动作捕捉、Waidayo、VSeeFace、MocapForAll、TDPT 等)
- **助理**(**Assistant**)
- 主要不处理动作并向表演者发送辅助信息。(可选)
- 仅负责**发送辅助**信息。(**一些骨骼、跟踪器姿势、面部表情**等)
例如face2vmc 模式下的 Waidayo、Sknuckle、Simple Motion Tracker、Uni-studio 等)
具体沟通规定如下:
- 通信时使用适当类型的 OSC。
- 字符串采用 UTF-8 编码,可以用日语发送。
- 至于端口号Marionette 将监听端口39539而 Performer 将监听端口39540但从 UX 角度来看,我们建议您更改发送地址
和接收端口。
- 数据包在适当的范围内1500 字节以内)进行捆绑,并且应由接收方进行适当的处​​理。
- 传输周期以发送方的任意间隔执行。并非所有消息都会在每个周期发送。
另外,发送方应该能够调整发送周期的间隔,或者以足够低的频率发送。
- 接收方应丢弃不必要的消息。您不必处理所有消息。
- 发送或接收哪些消息取决于两者的实现。
- 未知地址,应忽略太多参数。
- 如果您发现参数太少或类型与扩展规范中定义的参数不同,请将它们视为错误或忽略它们。
![800](https://protocol.vmc.info/flow.gif)
![500](https://protocol.vmc.info/layer.png)
### performer-spec
这是`Marionette→Performer`或`Assistent→Performer`流程中的发送数据的规范。
主要有:
- 虚拟设备转换
- 帧周期
- 虚拟 MIDI CC 值输入
- 虚拟摄像机变换和 FOV
- VRM BlendShapeProxyValue
- 眼动追踪目标位置
- [事件发送]信息发送请求(Request Information)
- [事件传输] 响应字符串
- [事件发送] 校准(准备)请求(校准/校准就绪请求)
- [事件发送]请求加载设置文件
- 通过信息
- DirectionalLight 位置/颜色DirectionalLight 变换和颜色)
- [事件传输] 快捷调用Call Shortcut
#### 虚拟设备转换
主要为虚拟头显、控制器Controller和追踪器Track。(HMD被视为跟踪器)
结构为:虚拟序列号 -> Position ->Quaternion
```json
V2.3
/VMC/Ext/Hmd/Pos (string){serial} (float){p.x} (float){p.y} (float){p.z} (float){q.x} (float){q.y} (float){q.z} (float){q.w}
/VMC/Ext/Con/Pos (string){serial} (float){p.x} (float){p.y} (float){p.z} (float){q.x} (float){q.y} (float){q.z} (float){q.w}
/VMC/Ext/Tra/Pos (string){serial} (float){p.x} (float){p.y} (float){p.z} (float){q.x} (float){q.y} (float){q.z} (float){q.w}
```
#### 帧周期
设置虚拟动作捕捉的数据传输间隔。 以 1/x 帧间隔发送。
```json
V2.3
/VMC/Ext/Set/Period (int){Status} (int){Root} (int){Bone} (int){BlendShape} (int){Camera} (int){Devices}
```
### marionette-spec
这是`Performer → Marionette`流程中的发送数据的规范。
- 内容:基础状态描述,校准状态、校准模式、追踪状态
- 发送者相对时间Time
- 根变换
- 骨骼变换
- VRM BlendShapeProxyValue
- 相机位置/FOV相机变换和FOV
- 控制器输入
- [事件传输]键盘输入
- [事件传输] MIDI 音符输入
- [事件传输] MIDI CC 值输入
- [事件传输] MIDI CC 按钮输入
- 设备改造
- [低频] 接收使能
- [低频] DirectionalLight 位置/颜色DirectionalLight 变换和颜色)
- [低频]本地VRM信息
- [低频]远程VRM基本信息
- [低频] 选项字符串
- [低频]背景色
- [低频]窗口属性信息
- [低频]加载设置路径
- 通过信息
### 根骨骼变换
```json
v2.0
/VMC/Ext/Root/Pos (string){name} (float){p.x} (float){p.y} (float){p.z} (float){q.x} (float){q.y} (float){q.z} (float){q.w}
v2.1
/VMC/Ext/Root/Pos (string){name} (float){p.x} (float){p.y} (float){p.z} (float){q.x} (float){q.y} (float){q.z} (float){q.w} (float){s.x} (float){s.y} (float){s.z} (float){o.x} (float){o.y} (float){o.z}
```
p=位置 q=旋转(四元数) s=MR 合成的比例 o=MR 合成的偏移
作为模型根的对象的绝对姿势
名称固定为“root”。 建议将
前半部分视为Position后半部分视为接收侧Loal姿势的四元数以与Bone匹配
从 v2.1 开始,添加了 MR 合成的比例。
通过使用它,可以将虚拟人物的位置和大小调整为实际的身体尺寸。
### 骨骼变换
```json
/VMC/Ext/Bone/Pos (string){name} (float){p.x} (float){p.y} (float){p.z} (float){q.x} (float){q.y} (float){q.z} (float){q.w}
```
作为模型根的对象的局部姿势名称
是UnityEngine沿HumanBodyBones的类型名称
前半部分是Position后半部分是Quaternion
所有 HumanBodyBone 都将被发送。还包括 LastBone。 这还将传输手指运动和眼骨。
## UE Remote Control
https://docs.unrealengine.com/5.1/en-US/remote-control-for-unreal-engine/
基于WebSocket
# VMC4UE的实现
## AnimNode
实现:
- FAnimNode_ModifyVMC4UEBones
- FAnimNode_ModifyVMC4UEMorph
![[AnimNode & VMC笔记]]
# VMC APP代码参考
- [VirtualMotionCaptureProtocol](https://github.com/sh-akira/VirtualMotionCaptureProtocol)提供了最基础的实现。
- ~~[EasyVirtualMotionCaptureForUnity](https://github.com/gpsnmeajp/EasyVirtualMotionCaptureForUnity)~~
- ThirdParts
- https://github.com/digital-standard/ThreeDPoseTracker
- 逻辑在VMCPBonesSender.cs、uOscClientTDP.cs
- BufferSize = 8192;int MaxQueueSize = 100;
## VirtualMotionCaptureProtocol
Message方式
```c#
void Update()
{
//モデルが更新されたときのみ読み込み
if (Model != null && OldModel != Model)
{
animator = Model.GetComponent<Animator>();
blendShapeProxy = Model.GetComponent<VRMBlendShapeProxy>();
OldModel = Model;
}
if (Model != null && animator != null && uClient != null)
{
//Root
var RootTransform = Model.transform;
if (RootTransform != null)
{
uClient.Send("/VMC/Ext/Root/Pos",
"root",
RootTransform.position.x, RootTransform.position.y, RootTransform.position.z,
RootTransform.rotation.x, RootTransform.rotation.y, RootTransform.rotation.z, RootTransform.rotation.w);
}
//Bones
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone != HumanBodyBones.LastBone)
{
var Transform = animator.GetBoneTransform(bone);
if (Transform != null)
{
uClient.Send("/VMC/Ext/Bone/Pos",
bone.ToString(),
Transform.localPosition.x, Transform.localPosition.y, Transform.localPosition.z,
Transform.localRotation.x, Transform.localRotation.y, Transform.localRotation.z, Transform.localRotation.w);
}
}
}
//ボーン位置を仮想トラッカーとして送信
SendBoneTransformForTracker(HumanBodyBones.Head, "Head");
SendBoneTransformForTracker(HumanBodyBones.Spine, "Spine");
SendBoneTransformForTracker(HumanBodyBones.LeftHand, "LeftHand");
SendBoneTransformForTracker(HumanBodyBones.RightHand, "RightHand");
SendBoneTransformForTracker(HumanBodyBones.LeftFoot, "LeftFoot");
SendBoneTransformForTracker(HumanBodyBones.RightFoot, "RightFoot");
//BlendShape
if (blendShapeProxy != null)
{
foreach (var b in blendShapeProxy.GetValues())
{
uClient.Send("/VMC/Ext/Blend/Val",
b.Key.ToString(),
(float)b.Value
);
}
uClient.Send("/VMC/Ext/Blend/Apply");
}
//Available
uClient.Send("/VMC/Ext/OK", 1);
}
else
{
uClient.Send("/VMC/Ext/OK", 0);
}
uClient.Send("/VMC/Ext/T", Time.time);
//Load request
uClient.Send("/VMC/Ext/VRM", filepath, "");
}
void SendBoneTransformForTracker(HumanBodyBones bone, string DeviceSerial)
{
var DeviceTransform = animator.GetBoneTransform(bone);
if (DeviceTransform != null) {
uClient.Send("/VMC/Ext/Tra/Pos",
(string)DeviceSerial,
(float)DeviceTransform.position.x,
(float)DeviceTransform.position.y,
(float)DeviceTransform.position.z,
(float)DeviceTransform.rotation.x,
(float)DeviceTransform.rotation.y,
(float)DeviceTransform.rotation.z,
(float)DeviceTransform.rotation.w);
}
}
```
Bundle将数据打包成一个Bundle创建Bundle时会填入一个时间戳。之后
```c#
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
...
 boneBundle.Add(new Message("/VMC/Ext/Bone/Pos",
                            bone.ToString(),
                            Transform.localPosition.x, Transform.localPosition.y, Transform.localPosition.z,
                            Transform.localRotation.x, Transform.localRotation.y, Transform.localRotation.z, Transform.localRotation.w));
...
}
```
Bundle方式
```c#
void Update()
{
//Only model updated
if (Model != null && OldModel != Model)
{
animator = Model.GetComponent<Animator>();
blendShapeProxy = Model.GetComponent<VRMBlendShapeProxy>();
OldModel = Model;
}
if (Model != null && animator != null && uClient != null)
{
//Root
var RootTransform = Model.transform;
if (RootTransform != null)
{
uClient.Send("/VMC/Ext/Root/Pos",
"root",
RootTransform.position.x, RootTransform.position.y, RootTransform.position.z,
RootTransform.rotation.x, RootTransform.rotation.y, RootTransform.rotation.z, RootTransform.rotation.w);
}
//Bones
var boneBundle = new Bundle(Timestamp.Now);
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone != HumanBodyBones.LastBone)
{
var Transform = animator.GetBoneTransform(bone);
if (Transform != null)
{
boneBundle.Add(new Message("/VMC/Ext/Bone/Pos",
bone.ToString(),
Transform.localPosition.x, Transform.localPosition.y, Transform.localPosition.z,
Transform.localRotation.x, Transform.localRotation.y, Transform.localRotation.z, Transform.localRotation.w));
}
}
}
uClient.Send(boneBundle);
//Virtual Tracker send from bone position
var trackerBundle = new Bundle(Timestamp.Now);
SendBoneTransformForTracker(ref trackerBundle, HumanBodyBones.Head, "Head");
SendBoneTransformForTracker(ref trackerBundle, HumanBodyBones.Spine, "Spine");
SendBoneTransformForTracker(ref trackerBundle, HumanBodyBones.LeftHand, "LeftHand");
SendBoneTransformForTracker(ref trackerBundle, HumanBodyBones.RightHand, "RightHand");
SendBoneTransformForTracker(ref trackerBundle, HumanBodyBones.LeftFoot, "LeftFoot");
SendBoneTransformForTracker(ref trackerBundle, HumanBodyBones.RightFoot, "RightFoot");
uClient.Send(trackerBundle);
//BlendShape
if (blendShapeProxy != null)
{
var blendShapeBundle = new Bundle(Timestamp.Now);
foreach (var b in blendShapeProxy.GetValues())
{
blendShapeBundle.Add(new Message("/VMC/Ext/Blend/Val",
b.Key.ToString(),
(float)b.Value
));
}
blendShapeBundle.Add(new Message("/VMC/Ext/Blend/Apply"));
uClient.Send(blendShapeBundle);
}
//Available
uClient.Send("/VMC/Ext/OK", 1);
}
else
{
uClient.Send("/VMC/Ext/OK", 0);
}
uClient.Send("/VMC/Ext/T", Time.time);
//Load request
uClient.Send("/VMC/Ext/VRM", vrmfilepath, "");
}
```

View File

@@ -0,0 +1,94 @@
# 各个服务负责人
- TTS文字=>音频)
- 张守高
- 动作 (文字=>动作)
- 张渤林
- 孙佳俊
- 口型(音频=>表情数据)
- 张渤林
- 常清
# 存在问题
1. 目前是否以文件的方式实现。
2. 文字、声音、表情与动作的关联性。(主要是声音、表情与动作)
3. 生成资产数据存在顺序依赖问题
1. 文字 => 声音 => 表情(口型)
2. 文字 => 动作
# 推流设计思路
1. 服务器发送的数据都使用一个基于玩家发送对话生成的UUID作为时间戳或许还需要一个**用户ID**)。
2. 数据采用Stream式发送。动作与口型使用VMC来传输音频使用RTMP协议来传输。其中推流模式分为2种实现
1. Stream式实时接受所有数据。
2. Stream式缓存预读。
3. 架构
1. 根据上面说得2种模式在服务端控制时间戳的重置**数据截断,并且发送给客户端进行截断与时间轴统一操作**)。
1. Puerts的Nodejs负责接收数据以及管理状态机来管理接受的推流数据以及是否播放。**相关核心函数写在C++中**
2. AIVirtualIdolServer端接受到动作数据之后发送给AIVirtualIdol。
3. RTMP的音频推流AI端需要部署RTMP推流器AIVirtualIdolServer部署RTMP ServerAIVirtualIdol接受Server数据后播放。
UE中的RTMP实现
参考RTMP
- RTMP协议 01 入门:https://www.jianshu.com/p/715f37b1202f
- RTMP协议 02 视频Chunk和音频Chunk到底长啥样?:https://www.jianshu.com/p/cc813ba41caa
- RTMP协议 03 RTMP设计思想:https://www.jianshu.com/p/9459606c2025
- RTMP协议 04 RTMP播放基本流程:https://www.jianshu.com/p/4577a61af0c7
- RTMP协议 05 时间戳:https://www.jianshu.com/p/119075482a54
## RTMP
### Message & Chunk
RTMP中一个重要的概念就是消息。
![600](https://upload-images.jianshu.io/upload_images/1720840-a4e1d3c0d08d671e.png?imageMogr2/auto-orient/strip|imageView2/2/w/870/format/webp)
![600](https://upload-images.jianshu.io/upload_images/1720840-f291fa10767d860b.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
![800](https://upload-images.jianshu.io/upload_images/1720840-76439153b7b7aec6.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
**网络中实际发送的内容**
![600](https://upload-images.jianshu.io/upload_images/1720840-9dcadc0699c290c0.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
Chunk Format
Message被切割成一个或多个Chunk然后在网络上进行发送。
当发送时一个chunk发送完毕后才可以发送下一个chunk。
![600](https://upload-images.jianshu.io/upload_images/1720840-a476d0c147ffa144.png?imageMogr2/auto-orient/strip|imageView2/2/w/897/format/webp)
Message被拆分成一个或多个Chunk然后在网络上发送
拆分的时候,**默认的Chunk Size是128字节**以Message大小为300字节举例进行拆分。
```undefined
300 = 128 + 128 + 44
```
![600](https://upload-images.jianshu.io/upload_images/1720840-5f7e632acaf568ab.png?imageMogr2/auto-orient/strip|imageView2/2/w/955/format/webp)
作者FlyingPenguin
链接https://www.jianshu.com/p/715f37b1202f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
# 开发计划
1. C++实现RecivedDataManagerComponent管理音频、口型&动作数据、文字。
2. Puerts实现数据接收逻辑。
3. 实现声音播放逻辑。
4. 实现口型&动作动画节点。
5. 实现动画状态机结构初版。
# 声音
## Runtime Import Sound
使用`RuntimeAudioImporter`插件里面的解码使用了第三方库https://github.com/mackron/dr_libs。导入Sound使用了`UImportedSoundWave::PopulateAudioDataFromDecodedInfo()`
## UE的做法
```cpp
/*SoundStreaming->RawPCMDataSize = DataSize;
SoundStreaming->RawPCMData = AudioData;*/
```
# 表情
在状态机里生成完成之后通过BP_Player的接口ATF_TO_BS传递数据到动画蓝图`NV2_Full_Skeleton_AnimBlueprint`中。之通过动画蓝图的Pose读取曲线并且播放动画。

View File

@@ -0,0 +1,122 @@
# AI虚拟直播间需求&任务整理
## 实现目标&内容&工期
>拿给EOE老板看的Demo版本
1. 控制AI行为以及数据交互的导播台应用Electron + Server
1. 实现目标
1. =>AI服务器
1. 能够发送文字、语音数据以及其他指令到AI服务器。
2. 能够接收文字、音频、CSV、FBX数据。
2. =>渲染机器
1. 能够转发音频、CSV、FBX数据到渲染机中。
2. 实现内容基础Electron用户界面以及对应的Server第一版放在一起后续分离工期 2周左右。
1. 转发数据逻辑
2. 用户界面
3. 未来TODO
1. 自定VMC协议用来传输音频等其他数据
2. 渲染机UE插件
1. 导播台应用=>
1. 制作角色蓝图、动画蓝图以及状态机可以播放音频、CSV、FBX。
2. 接收导播台AI服务器的控制指令
2. 实现内容:编写插件实现对应的数据接收节点。
- 闲时模式
- 循环动画+随机动画
- 语音模式(说话与唱歌)
- 说话
- 表情、口型、声音、身体动作Gesture
- 唱歌N套预先录制
- 表情、口型、声音、身体动作Gesture
  - 指令模式
- 文字输入
- 跳舞
- 动作
- AI数据混合
需要的资产:
1. N套预制唱歌资产
2. 闲时模式的相关资产
## 状态机设计
1. 相关数据都存储在角色类AAIVirtualIdolCharacter中。
1. Idol状态相关控制数据。
2. 实时 - 表情指令的预制渐入渐出曲线以及表情文件。
2. EOE歌曲可以制作专门的UDataAsset以方便播放。
### 状态
- 可切换状态待开发功能后续的AI偶像陪伴项目会需要。
- 不可切状态必须等到当前状态执行完才能执行其他Action并且执行完后会回到Idol状态
- 播放状态(预制资产)。角色开始唱歌&跳舞,可以选择只跳舞。
- 聊天状态AI实时生成。角色做出语音、动作、表情&口型。
### 指令
- 实时指令:用于即使切换角色面部表情&口型或者播放一些场景效果。一般不会改变与打断当前状态。
- [ ] 表情系列指令(预制表情)
- [ ] 微笑
- [ ] 皱眉
- 强制指令:用于导播强制中断有问题的表演。
- [x] 强制进入Idle状态。
- 状态切换指令待开发功能后续的AI偶像陪伴项目会需要。用于定制当前状态完成后执行的下一个状态可以打断当前状态并开始执行下一个状态。
## 资产制作
### 歌曲
1. 动捕Motion&Facial FBX
2. 歌曲音频
# AI虚拟偶像陪伴
## 阶段技术需求 & 实现目标
### 第零阶段快速简历可供AI迭代的基础程序
1. Express Http服务器。
1. 提供静态文件下载服务。
2. 建立一个获取所有Uasset文件的url。
2. UE客户端
1. 动画蓝图中建立多个子AnimGraph使用动态方式挂载。
### 第一阶段(建立高可用、迭代性的基础架构)
需求功能(优先&难易度排序):
1. 资产 & 逻辑脚本热更新逻辑 => Puerts热更新逻辑。
2. 客户端发送文字信息给服务端。=> 构建一个Http聊天服务器。
3. 服务端控制虚拟角色行为 => ~~RPC事件同步~~ Http服务器WebSocket连接Puerts间接控制方案。**后续可能需要改成帧同步方案**。
4. 客户端在动画蓝图中实时混合**新下载**的**动画资产**。
5. 客户端在动画蓝图中实时混合**实时推流**的**动画数据**,以及播放**AI生成语音**。
#### 技术细节
- 客户端:
- 使用Puerts控制逻辑。
- 使用Puerts热更新逻辑 & 资产。
- 使用下载Pak重启后批量读取方案。
- 使用ModuleGameFeature框架进行网络缓存的方案。堡垒之夜目前使用
- 使用Puerts宿主环境Nodejs构建Http服务以此与服务端通信。优点是不会卡住游戏线程
- IOS得进行测试是否可以使用这个方案。
- * 实现 **动画数据**推流功能。
- 服务端:客户端同步采用事件同步 + 缓存动画数据的方式实现
- Demo期间使用Nodejs进行打底。采用Nodejs + Express搭建后台管理页面采用VUE3。
- 序列化使用 Protobuf
- RPC协议gRPC ?
- Http聊天服务器。
- 账号权限判断。
- Pak文件 / ModuleGameFeature缓存方案所用的文件服务器。
- 文件上传功能(语音数据)。
- * 实现 **动画数据**推流功能。
使用其他框架?
- https://github.com/node-pinus/pinus
#### 现阶段问题:
我需要知道:
1. 玩家发送文字信息后,虚拟角色是否会发出语音?
2. AI如何对虚拟角色行为树进行迭代仅仅是迭代行为树中的某一个行为么
3. 如何针对某一个演员的指定行为进行迭代?录制一定的演员表演动作动画数据进行迭代?
### 第二阶段使用借助AI配合UE动画系统迭代动画效果
需求功能(优先&难易度排序):
1. 构建一个可以不断热更、优化AI系统迭代动画资产的动画框架。
1. MotionMarching
2. Motion匹配。
2. 游戏性提升。
# 其他资料
- Node-Pinus游戏服务器框架:https://github.com/node-pinus/pinus
- 案例:https://github.com/node-pinus/pinus/tree/master/examples/simple-example
- Pomelo的wiki:https://github.com/NetEase/pomelo/wiki/Home-in-Chinese
- Nodejs RPC:https://zhuanlan.zhihu.com/p/598460945

18
02-Note/DAWA/DAWA.md Normal file
View File

@@ -0,0 +1,18 @@
# DAWA Overview
```ccard
type: folder_brief_live
imagePrefix: '08-Assets/Images/BingWallpaper'
noteOnly: false
style: card
```
[[柔体模拟开发计划]]
[[UE卡通渲染改进]]
# 流程架设计划
1. 共享DDC
2. 推广Lyra的UEditorValidator Asset有效性检测插件。
3. 联机编译Shader
4. DDNS 与 远程
5. NAS 网盘
6. 版本管理系统 PlasticSCM 与 Perforce
7. UnrealGameSync 以及 CI/CL系统

View File

@@ -0,0 +1,8 @@
## 参数检查
### 灯光
- [ ] 开启RayTracing之后检查所有Map的SpotLight、RectLight、SkyLight、DirectionalLight(Angle较大时)的采样值。
## 参数预设与参数文档
- [ ] MovieRenderQueue的预设以及相关参数文档
- [ ] TAA相关的参数文档

View File

@@ -0,0 +1,92 @@
## MUTools Maya部分笔记
1. AddMesh()
2. SendCommand()
### 导入Maya文件命令Mel
>file -f -o "C:/Users/admin/Desktop/test.mb";
### Maya插件获取场景数据
DBU_ScenePrimitiveCollector::AssembleData具有2个分支UpdateOutline与CollectFeedback同时负责序列化
1. AdditionalMessage
2. 写入文件头虽然这个写入步骤在UpdateOutline之后但在写Outline数据时偏移了sizeof(UPDATEMSG)
3. UpdateOutline
1. OutlineEntityList
1. NameStream中每个节点名称字符串的偏移
2. ChildNode数目
2. UPDATEMSG MSGHEAD 文件头信息。
3. NameStream 存储每个节点名称的流
4. CollectFeedback
1. UpdateEntityList
2. MeshStream->AddrList
3. MeshStream->ByteStream
UE中的反序列化根据文件头信息的HasOutline与HasFeedbackData有以下两个分支
1. 读取文件头信息
2. HasOutline
1. OutlineRootData
2. OutlineData
3. NamesData 节点名称流
3. HasFeedbackData
1. NamesData
2. UpdateEntities
3. AddrList
### 更新大纲Mesh数据
UpdateOutline只有Maya大纲的名称与当前节点的子节点数目数据。其他模型信息集中在CollectFeedback阶段。
DBU_AddMesh来管理列表监视每个Mesh状态如果entity的meshDirty=true则将其加入`vector<MeshAndID> MeshList`。最后使用
```c++
MeshStream = new AssembleMeshStream(MeshList);
```
来生成这个MeshStream。来进行顶点、UV、法线、顶点处理
```c++
bool DBU_ScenePrimitiveCollector::AssembleData(MString& Result,vector<char> &ByteStream, bool CollectFeedback,bool UpdateOutline, MString AdditionalMessage)
{
MStatus status;
//if (DBU_AddMesh::First == nullptr)
// CollectFeedback = false;
if (CollectFeedback)
{
std::chrono::steady_clock::time_point _start_time = std::chrono::steady_clock::now();
DBU_AddMesh::LastSentUpdateList.clear();
DBU_AddMesh* curr = DBU_AddMesh::First;
while (curr)
{
curr->CollectEntity();
auto Next = curr->Next;
curr = Next;
}
```
DBU_AddMesh::CollectEntity()
DBU_AddMesh节点在连接时候将srcPlug的DAG路径加入NodeRegisteredDAGPaths。
MPlug & srcPlug->MObject FromWhat->MFnDagNode FromNode->MDagPath dagPath
### MUTools UE
通过MeshList的Index进行同步。
点击Outline Import后的逻辑 为发送遍历Entiry 并且发送Mel
$indices = { XXXX };
{
int $i;
for($i=0;$i<size($indices);$i++)
{
DBU_AddSynchronize(DBU_GetSentOutlinerEntityPath $indices[$i]);
}
}
print `lsType "DBU_AddMesh"`;
调用DBU_AddSynchronize(DBU_GetSentOutlinerEntityPath)节点

View File

@@ -0,0 +1,644 @@
# 有待改进的地方
1. 角色卡通渲染:建议之后自研卡通渲染引擎。
2. DMX Light Beam性能问题。
3. 因为成片后续会进行重新渲染,建议编写一个插件 记录UE各种事件录像功能+记录摄像机轨迹,方便后期进行操作。
# 优化记录
## DMX
渲染模型&材质:
- SM_Strobe_Lens M_MI_Lens_Strobe不透明材质指令数254
- SM_Beam_RM M_Beam_Master
- BP_StaticMatrix M_MatrixBeam_Master
### Beam材质
变量:
- NSliceNearPlane 交点世界坐标
- NDepth
- FSliceFarPlane 交点世界坐标
- FDepth
MF_WSIntersection如果没有相交RelIntersectPos为IntersectDistance为。
### 优化方案
1. DMX模型改成StaticMeshInstanceGun的模型是Masked建议改成不透明。
2. 关闭DMX灯光中的照明效果因为不会对着模型照明。
1. 场景中的组件勾选**Disable Lights**
3. StaticMeshLens 组件可以按照距离进行关闭。BP_StaticStrobe_C、BP_MovingHead_C
1. 场景中的组件中的StaticMeshLens去掉**Visible**。
2. Sequence中手动设置**Visible**。
4. Beam材质
1. 给BeamRayMarch的DMX Max Light Distance 乘上一个变量倍率之后适当减少DMX Max Light Distance大小建议的倍率为1.2~1.5长度为1/1.2~1/1.5 给BeamRayMarch的DMX Zoom 乘上一个变量倍率Zoom 5=>4.5~3并且将倍率调整成合适数值。
2. 调整Zoom Quilty 1=>1.5~2并且适当调整DMX Lens Radius的数值减少断点问题。
5.
## 优化目标
XR 2k 2048*1080 50帧 => 3440 * 1440 50帧0
## 优化场景
E:\Project\yanjingdan\BengHuai\Ture\Content\UnWorld\BengHuai_True\Maps
E:\Project\yanjingdan\BengHuai\L_Dacapo\content\UnWorld\BengHuai_Dacapo\Maps\L_Dacapo5.umap
E:\Project\yanjingdan\BengHuai\BengHuai\Content\UnWorld\BengHuai_Tiantai\Maps\L_BengHuai_Tiantai.umap
步骤:
1. 场景静态模型设置成Nanite以下模型需要额外考虑是否开启Nanite
1. 带有世界位移与Masked的模型比如花 草 模型。
2. 将一些不会动的模型都设置成Static。
3. 告知拍摄镜头需求,是全景还是需要360°都有拍摄要求。
4. 确定场景之后不会进行改动之后使用MergeActor将模型中的相同材质进行合并将场景中的静态模型设置成Instance。
### L_BengHuai_Tiantai
问题:
~~1. 模型问题
1. 目前比较大的问题在于模型较多、面数较高且场景静态模型没有设置成Nanite。使得剔除与BasePass渲染延迟过高。~~
#### Model
~~1. 场景中大量巨大无异议的球名字为Sphere~SphereX~~
~~2. 图元较多的关卡
1. L_Main_AllCity_Chengbao_2~~
- dd_polySurface
- BookOpen
- Box001~Box003
-
#### Light&Shadow
1. SpotLightDingGuang
1. 去掉Cast Shadow只影响台阶的阴影)
2. 勾选Distance Field Shadows。
2. RectLight 81~93
1. 去掉Cast Shadow。
2. 将其替换成SourceLength 匹配的PointLight。
3. 将其他面光源替换成点光源或者聚光灯可以减少0.1~0.2ms,优先级低)。
#### Translucent
1. 修改EasyFog的材质(大纲搜索EasyFog从Translucent=>Additive。并且重新调整亮度。
2. 大纲搜索EnvEmitter调整大小并使用Cutoff工具对齐进行裁剪。覆盖范围过大。修改材质MI_Env_Inst~MI_Env_Inst22从Translucent=>Additive。并且重新调整亮度。
#### 2023.6.5优化建议
1. 舞台观众、荧光棒去掉CastShadow同时考虑使用Nanaite
2. 浮游炮材质重Masked=>不透明
3. 烟花材质重Translucent=>Additive
4. Sequence进入舞台镜头时对看不见的建筑物进行隐藏操作。
5. 主舞台所有模型进行Merge并且设置成Nanite。
6. 主舞台DMX灯光开启DisableLight。
7. 调整Lumen渲染参数或者后处理盒子参数
- Lumen Scene Lighting Quality 2 =>0.25
- Lumen Scene Detail4=>1
- Lumen Scene View Distance20000=>1000~3000
- Max Trace Distance20000=>1000~3000
### L_Dacapo5
问题:
1. 模型问题
1. 模型较多、面数较高且场景静态模型没有设置成Nanite。使得剔除与BasePass渲染延迟过高。
2. 花草不是Instance
3. 花卉、草的的材质有优化空间。
2. 阴影
1. 有2个方向光投射阴影关闭其中一个。并且使用距离场阴影。
3. Lumen
1. 设置Lumen参数去除草地对GI的计算。
4. 天空上的透明体积雾效果
BasePass渲染延迟较高的模型
HUABAN_toon hua 123 instances 19501-19515 4775.936
grass0 hua 123 instances 19481-19489 4387.84
MI_grass_01_Inst_gbl SM_grass_01_gbl_lv 31 instances 14016-14025 2996.224
HUABAN_PINK_toon1 hua_pink 35 instances 15286-15291 1172.416
grass0 hua_pink 35 instances 19491-19499 1107.968
HUAXIN_toon_toon1 hua 123 instances 15266-15274 572.416
MM_Grass HUA002 2 instances 14405-14415 440.32
M_plants SM_sf_flower_05 13548-13551 193.536
M_plants SM_sf_flower_05 13739-13742 191.488
M_plants SM_sf_flower_05 13734-13737 167.936
HUAXIN_toon_toon1 hua_pink 35 instances 15276-15284 164.832
M_plants SM_sf_flower_05 13669-13672 128.00
M_plants SM_sf_flower_05 3 instances 13664-13667 124.928
### L_True
1. 模型问题
1. Nanite与Instance主要是电视机
2. VolumetricSpotlightEngine 是拿来干什么的?
3. 灯光
1. 关闭投射阴影(尤其是点光源,包括天光)
1. 天光
2. SpotLight11
3. SpotLight12
4. SpotLight2
5. DirectionalLight
6. PointLight1~6
2. 调整灯光投射范围(有一些太远了)
4. 13_Mat_DianShiJi14 贴花看不出有什么作用。
5. 黑色遮罩的几个Plane是干什么的
6. 中间的几个透明片删掉
7. 中间数据库的材质重Tranlucent => Additive
8. 几个透明的粒子效果将材质从Tranlucent => Additive 鬼影问题很大)
BasePass渲染延迟较高的模型
monitorE_col_High_A_Mat2 monitorE_High_Geo_Grp 90 instances 12336-12351 2444.288
TTest monitorE_High_Geo_Grp 28 instances 12006-12014 761.856
monitorE_col_High_A_Mat2 monitorE_High_Geo_Grp 12 instances 12353-12357 337.92
monitorE_col_High_A_Mat monitorE_High_Geo_Grp 11 instances 12837-12852 308.224
monitorF_col_high_A_Mat monitorF_High_Geo_Grp 107 instances 12870-12884 197.632
TTest monitorE_High_Geo_Grp 6 instances 11961-11969 183.296
monitorG_col_High_A_Mat3 monitorG_High_Geo_Grp 66 instances 11542-11552 159.7
monitorA_col_High_Mat3 monitorA_High_Geo_Grp 168 instances 12576-12590 153.536
monitorD_col_High_A_Mat monitorD_High_Geo_Grp 27 instances 12283-12297 135.168
monitorG_col_High_A_Mat1 monitorG_High_Geo_Grp 47 instances 11448-11480 115.648
## L_XBLA xiubolian休伯利安
1. 移除没有明显投影效果的灯光。
2. 重写DMX插件CPU GPU 负载压力较大。
3. Separate Translucency去除勾选。
### 模型优化
1. 将场景中的静态模型都设置成Nanite。
2. 将BP_FeiChuan模型进行合并。
3. 如果飞船模型如果需要CastShadow则需要在模型里勾选Generate Distance Field。
#### xbla
开启Nanite以及勾选Generate Distance Field。
MininumResidency2MB
Fallback Relative Error1.0=> 0.1~0.0
#### 关闭FeiChuang蓝图中StaticMesh的Shadow
### Light&Shadow
- [ ] 远处的几个小飞船 去掉CastShadow选项。
- DirectionalLight
- Num Dynamic Shadow Cascades5=>3
- Dynamic Shadow Distance MovableLight20000=>5000
- DistanceField Shadow Distance51200=>5000
- Shadow Resolution Scale1=>0.1~0.5
- SpotLight3
- 关闭CastShadow(3与4至少关一个)
- Attenuation Radius11052.831055=> 2040
- Shadow Resolution Scale1=>0.5
- 第二次修改
- Outer Cone Angle 44=>40
- Shadow Resolution Scale0.5=>0.3
- SpotLight4
- 关闭CastShadow(3与4至少关一个)
- Attenuation Radius12052.831055=>4000.0
- 点光源
- 关闭阴影
- 降低Attenuation Radius到合适范围。
- 删除机翼处重新的点光源pointLight81
### Lumen
调整Lumen渲染参数或者后处理盒子参数
Lumen Scene Lighting Quality 2 =>1
Lumen Scene Detail4=>1
Lumen Scene View Distance20000=>5000
Max Trace Distance20000=>5000
### Translucent
#### Nebula
以下几个建议删除:
BP_NebulaProceduralGenerator
BP_NebulaProceduralGenerator2
BP_NebulaProceduralGenerator3N
BP_NebulaProceduralGeneratorMove
BP_NebulaProceduralGeneratorMove2
- BP_NebulaProceduralGenerator5
- Volumetric Sphere Amount10=>1~5推荐1
- 效果会大变
- Min Sphere Radius (Km)0.25=> 0.1~0.2
- Max Sphere Radius (Km)0.75=> 0.6~0.7
- BP_NebulaProceduralGenerator6
- Volumetric Sphere Amount10=>1~3推荐1
- QualityHight => Low~Medium
- 效果会大变
- Min Sphere Radius (Km)0.25=> 0.1~0.2
- Max Sphere Radius (Km)0.75=> 0.6~0.7
远景星云考虑使用SceneCapture渲染出来再贴到一个片上。
#### StarCluster
建议使用Niagara重做一个透明区域太多了。
## Domineer_Showcase
SequenceSeq_Seq_Domin_XR
鬼影问题:
1. 修改材质从Masked=> Translucent并且开启材质的Responsive AA。
## NightGlow
### ~~CustomDepth~~
- MI_Icelandic_Rock_Cliff_ucdmcawdy_4K_Inst2_Normal
没有特殊需要把PostProcessing-Custom Depth-Stencil Pass改成Disable。
### Shadow
- ~~SpotLight ~~
- ~~SpotLight2 ~~
- ~~SpotLight4
- ~~geogrp29下面的灯~~
- stone_Anim2
CasShadow关了。
### ~~Lumen~~
- Lumen Scene Lighting Quality 1=>0.25
- Lumen Scene View Distance20000=>1000
- Max Trace Distance20000=>1000
或者考虑换成烘焙阴影(不太合适)。
### Nanite
特效Mesh
~~- NS_Storns
- SM_Strom_piece_1884
- SM_Strom_piece_1875
- SM_Strom_piece_1900~~
### 场景模型控制 & WuTai破碎
- 在舞台模型出现后使用Sequence将wutai破碎下面的破碎模型都隐藏了。
- 将外部六棱柱与内部六棱柱分开导出并将内部六棱柱转换成Instance Static Mesh。
- 使用StaticMesh替换Landscape。
### 透明物体
- EasyFog材质从Translucent=>Additive
- SM_Cloud_Single的材质从Translucent=>Additive
- geogrp的NewMaterial1_Inst从Translucent=>Additive
### 2023.6.11
- GameThread 13.4ms
- ~~WorldTick 4.3ms =>3)~~
- ~~进地形改成StaticMesh选中地形File => Selected (1.3ms)~~
- ~~减少NiagaraActor数量。~~
- RenderThread 43.1ms
- InitViews 9.8ms
- GatherDynamicMeshElements 7.4 ms
#### 理论上WuTai优化极限
- 关闭光线追踪
- 体积云参数调节
-
删除模型测试:
- 原始状态延迟29~32
- 删除WuTai FbxScene_zhuzi 21~22
- 删除FbxScene_zhuzi2~8 28~30
- WuTai 全部删除 23~25
- Wutai1 几乎没有变化
- Wutai2 26~28
- Wutai3 26~28
替换模型之后19~21ms
##### 可以Instance的WuTai网格
删除以下网格并且使用 替换
- piece_0
- piece_1
- piece_2**
- piece_4**
- piece_6**
- piece_7
- piece_8
- piece_9
- piece_10**
- piece_13**
- piece_14
- piece_15**
- piece_18**
- piece_21**
- piece_23**
- piece_24
- piece_25
- piece_26**
- piece_28**
- piece_29
- piece_30
- piece_31
- piece_32**
- piece_35**
- piece_36**
- piece_38**
- piece_40**
- piece_41
- piece_42**
- piece_45**
- piece_46
- piece_47
- piece_48**
- piece_51**
- piece_53**
- piece_54**
- piece_56**
- piece_57
- piece_58
- piece_59
- piece_60
- piece_61
- piece_62**
- piece_64**
- piece_65
- piece_66
- piece_67
- piece_68
- piece_69
- piece_70
- piece_71
- piece_72
- piece_73
- piece_74
- piece_75
- piece_76
- piece_77
- piece_78**
- piece_81**
- piece_82**
- piece_84**
- piece_85
- piece_86
- piece_87
- piece_88
- piece_89
- piece_90**
- piece_93**
- piece_94**
- piece_97**
- piece_98
- piece_99
- piece_100
- piece_101
- piece_102
- piece_103**
- piece_105**
- piece_107**
- piece_108
- piece_109**
- piece_112**
- piece_115**
- piece_117**
- piece_118
- piece_119
- piece_120
- piece_121
- piece_122**
- piece_124**
- piece_125
- piece_126**
- piece_104
- piece_12
- piece_44
- piece_92
- piece_123
- piece_63
- piece_5
- piece_11
- piece_33
- piece_111
- piece_79
- piece_17
- piece_22
- piece_37
- piece_43
- piece_114
- piece_27
- piece_34
- piece_106
- piece_116
- piece_110
- piece_16
- piece_19
- piece_83
- piece_95
- piece_20
- piece_49
- piece_50
- piece_91
- piece_113
## L_Dacapo6
### CPU
- CPU粒子删除看不到的Fog
- NewBlueprint
- P_Wind
- BP_AddCommand
- BP_LightControl
### GPU
#### 植被
镜头:
- 1612 GPU 37ms
- 2568 GPU 38ms
- 3233 GPU 38ms
1. 删除数量为0的植被
2. 将所有植被模型设置为Nanite。~~地形启用Nanite。~~
1. 转Nanite之前需要修改材质函数MF_Grow将里面的ObjectPosition节点改成float3(0,0,0) => TransformPosition(Instance&ParticleSpace)这2个节点
3. r.Nanite.AllowWPODistanceDisable
4. r.Lumen.DiffuseIndirect.MeshSDF.RadiusThreshold 100
5. 隐藏额外的灯光006~013并且将非卡通角色照明灯光的ToonIntensity设置为0
6. 将DMX灯光相关的模型用Sequence控制隐藏一下。
7. 将建筑物都使用默认材质进行替换。不要使用卡通材质。
8. r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange 1000
9. SM_Beam_RM 系列用Sequence设置可见性。
### 删除额外的东西
- 删除额外Landscape2使用StaticMesh代替
### Sequence 场景可见性优化
Squence设置物体可见性
- NewBlueprint
### Lumen
PostProcessVolumn => Lumen
- Lumen Scene Lighting Quality 2 => 1
- Lumen Scene Detail2 => 1
- Final Gather Quality3 => 1
- Lumen Scene View Distance20000 => 5000
- Max Trace Distance20000 => 5000
### 2023.6.10
- 删除场景中的垃圾
- NewBlueprint系列
- 需确认用途
- SM_Clouds_346_3
- SM_Clouds_346_01
- 替换成StaticMeshInstance 并且转成Nanite
- ZhuDingHongEXP~ZhuDingHongEXP1659
- stone目录下的模型 SM_ruins_20~SM_ruins_87
- 新建文件夹1目录下的Cube~Cube18
- JuHua1244
- SHIKUAI_对象001
- SM_cliff_stone
- SM_GrassClumpFonce
- ZhuDingHongEXP2026
- 优化粒子 Fx_WaterFall
- 减少粒子数量以及生命。
- 删除所有镜头都看不到的粒子。大致可以删掉一半一些只有单个镜头可以看到的可以用Sequence手动K一下可见性。
- 替换粒子改成GPU或者面片或者直接删除
- P_Wind
- GameThread
- 删除额外的2个地形并使用一个地形进行还原。
- RenderThread
- InitViews
- 几个角色脸部表情骨骼体 CastShadow 去掉
-
## L_Rubia
- PlanearReflection 删除,因为已经使用了屏幕空间反射。
- 因为场景都是透明物体切没有Nanite会导致VSM无法缓存所以需要使用ShadowMap。
## L_LiangZiZhiHai
### 模型转Nanite
- zhuwutai_Cube25
- wutai003
- SM_BENHUAI_BOX_1
- SM_BENHUAI_BOX_2
- SM_BENHUAI_BOX_6
- SM_BENHUAI_BOX_5_DJ
- SM_BOX_HEZI_HEKUAI
- SM_BOX_HEZI_HEKUAI_GanCui
- SM_GaoSuiBox
- SM_KongZhonXuanFU
- SM_LiaBianBox
- SM_ShuiJing_box_ID3
### 灯光&Shadow
- 关闭点光源阴影
### 透明材质Translucent => Additive
- M_StaticMeshSkinParticle_Inst
- MI_Env_Inst2
- M_XuanWo_Inst1
- Stage_SeaOfQuanta_ErodedLand_UI06_Mat_Inst
- M_BP_EasyFog_2
- M_SkyWhiteJianBian_Inst1
### Lumen
PostProcessVolumn => Lumen
- Lumen Scene Lighting Quality 1 => 0.25
- Lumen Scene View Distance20000 => 1000
- Max Trace Distance20000 => 1000
### 粒子(严重影响性能!考虑删除或者优化)
- EnvEmitter_System02
- BP_EasyFog_2
### 合并模型
将场景的中的圆环状模型、Box模型合并一下。
## L_JianZhong
### Shadow
- Ultra_Dynamic_Sky中的方向光
- Dynamic Shadow Distance MovableLight20000 => 10000
- Num Dynamic Shadow Cascades5 => 1
- Distance Field ShadowsTrue => False
- 关闭阴影
- SpotLight17
- SpotLight18
- SpotLight
### 模型优化
- 将场景中的石头、后景石头、剑以及剑下面的石头都转成Instance
- 场景中的模型转成Nanite。
- 相机背后看不到的模型都删除了。
- 删除场景中的CrystalFlw模型
- 将场景中的重复模型使用单个模型重新摆放最后设置成Instance。
### Shadow
- 关闭阴影
- PointLight
- PointLight4
- PointLight5
- PointLight6
- SpotLight2
- SkyLight
- VolumetricSpotlightEngine4的SpotLight
- 删除灯光
- PointLight2
- PointLight3
- DirectionalLight
- Dynamic Shadow Distance MovableLight20000 =>4000
- Num Dynamic Shadow Cascades5 => 1
- DistanceField Shadow Distance4000
- DistanceField Trace Distance25000 =>2000~4000
- Shadow Resolution Scale1 => 0.1
### Lumen
PostProcessVolumn => Lumen
- Lumen Scene Lighting Quality 2 => 0.25
- Lumen Scene Detail2 => 1
- Final Gather Quality3 => 1
- Lumen Scene View Distance20000 => 1000
- Max Trace Distance20000 => 1000
ProjectSettings - Lumen 以下设置仅提高1~2帧
- Use Hardware Ray Tracing when available
- High Quality Translucency Reflections
- Software Ray Tracing ModeDetailTracing => GlobalTracing
- r.Lumen.TraceMeshSDFs.Allow = 0
#### 添加DefaultScalability.ini
将Hight级别的Lumen设置覆盖到Epic级别设置。
### Fog
- ExponentialHeightFog1关闭体积雾。
### 粒子 转换成GPU粒子或者尽量减少CPU粒子数量
- NS_TextRain_Copy (字符雨生成器)
- NS_Test
- NS_Test_25
- NS_STM_SkinParticle
- M_StaticMeshSkinParticle_Inst
### 模型
- SkyBox (删除)
- 透明片Plane22~Plane41使用的材质的透明度是0考虑删除。
- 隐藏电路板Plane~138
- 黑色遮罩Plane4~Plane19
- **Nanite & 转成Instance**
- box1
- monitorG_High_Geo_Grp_2
- **转Nanite & 合并 || 删除看不到**
- monitorG_High_Geo_Grp_2
- SM_DIANSHIJI_HeCheng100
### Sequence中控制隐藏粒子与看不到的模型电视机
- monitorF_High_Geo_Grp12~1516
### 材质Translucent=>Additive
- M_MovingLineTest_Inst1_TRUE
- M_MovingLineTest_Inst1_TRUE1
- M_MovingLineTest_Copy
- BengHuai_True/Material
- M_BeiJing
- M_BeiJing_Inst
- M_OP~M_OP3
### 2023.6.13
1. 电视机模型转Instance。
2. 减少字符雨数量
3. 关闭光线追踪
4. ExponentialHeightFog1关闭体积雾。
#### ~~电视机贴图有拖影问题~~
## DLSS & TSR & FSR 测试
测试场景L_True分辨率4K 100%笔记本3070镜头sequence 604 的1840帧
- TSR ScreenPercentage66 GPU帧数27~29
- TAA ScreenPercentage100 GPU帧数36~37
- FSR+TAA ScreenPercentage66 GPU帧数26~28
- DLSS+TAA ScreenPercentage66 GPU帧数26~28
可以在DefaultEngine.ini里面添加渲染分辨率
[/Script/Engine.RendererSettings]
r.screenpercentage=66
r.screenpercentage=50
## L_BengHuai_Oaths_01
### 灯光
## L_BengHuai_Oaths_Close
### 灯光
# Lumen
r.Lumen.ScreenProbeGather.AdaptiveProbeAllocationFraction =0.5 =>0.1

View File

@@ -0,0 +1,17 @@
# 前言
- Document:https://drive.google.com/file/d/1zgNpEdIRJxYInLf7WppvjQuUKup6tJAV/view
# 3C
- BP_GameInstance联机相关逻辑。
- BP_FightingGameMode
- BP_GameState
- BP_PlayerState
- 逻辑
- 载入所有角色选人阶段资源1~16
- 根据选择的模式调用SetInitialPositionFor1on1()或者SetInitialPositionFor3On3()之后创建BP_GlobalCamera
- 3个接口实现BPIStartCameraForBattle、BPISetChange-SideForCamera、BPISetInitial-PositionForArcade
- BP_Sub_Controller => BP_Common_Controller
## Character
每个角色的逻辑都单独写在一个Character蓝图类中。

View File

@@ -0,0 +1,200 @@
---
title: UE5制片流程相关想法
date: 2022-08-25 13:55:15
tags: Film
rating: ⭐️
---
## 渲染相关
1. 场景检查工具
1. 灯光
2. 贴图
2. 预渲染相关
1. GPULightMass。
2. 模型/场景距离场生成问题与查看。
3. Lumen
1. 相关参数以及命令行参数。
2. 物理灯光流程。
4. 虚拟阴影贴图
5. 性能优化建议
1. 尽可能的使用MaterialInstance而非直接复制Material。
2. 尽可能使用面光源而非灯阵。
6. 修改引擎
1. 让指定角色不接受VLM的影响
2. 定制一种灯光只对某个指定的SkeletalMesh起作用不对环境模型起作用同时不产生球谐效果
7. MassAI群集系统
8. 机器学习变形器UE5.1出)
9. 其他想法
## 地编相关
1. 虚拟贴图
2. Nanite
3. USD工作流
4. 摆放工具与其他工具链
## 其他建议
1. 中午休息时将电脑改成睡眠模式:对自己的身体、电费、空调费、硬件寿命都好。
## 具体
### 动画组
1. 学习相关基础编辑器使用。
2. **如何导入与重定向商城与动作库资源**
1. 导入与重定向HumanIK骨骼、Biped、其他动作库资产。
3. **重点学习ControlRig与Sequence**
1. 对角色、生物进行绑定。
2. 使用ControlRig与Sequence制作动画。
3. 使用Sequence的Animation轨道以类似动画层的方式来融合动画效果来制作动画。
4. 使用Sequence以录制动画的方式来制作动画资产。
4. 测试Unreal Live Link for Autodesk Maya以其他LiveLink流程
1. 测试编写文档以方便一起讨论实现新的工作流。
#### 快捷键
快捷键是可以导出与导入预设的。
- F聚焦
- Shift+F所有View聚焦
- Alt+鼠标左右键效果与Maya一样。
- Alt+鼠标中键效果与Maya相反。但可以在EditorPreferences-LevelEditor-Viewports-Control中勾选`Invert Middle Mouse Pan`
#### 基础
1. Skeletal、曲线、AnimationSequence资产编辑器。
2. Sequencd
3. 动作库使用
#### ControlRig
- ControlRig https://docs.unrealengine.com/5.0/en-US/control-rig-in-unreal-engine/
- 入门教程合集https://www.youtube.com/watch?v=RDYWbA01k60&list=PLSbIBa4Ejxt_A55gIHQs3wmj5G7VeYESv
- 案例教程https://www.youtube.com/watch?v=r1fHOS4XaeE
- IK Rig类似Maya的HumanIK的重定向系统:https://docs.unrealengine.com/5.0/en-US/unreal-engine-ik-rig/
可以在ContentExample中找到相关的教学用的资产。
##### 类似动画层的功能
https://www.youtube.com/watch?v=4ULgKXTf1jE
#### 动画蓝图
动画蓝图学习:https://docs.unrealengine.com/5.0/zh-CN/animation-blueprints-in-unreal-engine/
#### LiveLink
主要是看Unreal Live Link for Autodesk Maya插件可能地编也能用。
- EPIC Maya LiveLink https://www.unrealengine.com/marketplace/zh-CN/product/maya-livelink
- Unreal Live Link for Autodesk Maya https://www.unrealengine.com/marketplace/zh-CN/product/f2e37c4b943f4337b352e1639fa4ebe3
#### ~~MassAI~~(次要 需要编程)
https://www.youtube.com/watch?v=f9q8A-9DvPo
#### ML变形器 - Maya数据生成插件
https://docs.unrealengine.com/5.0/en-US/using-the-machine-learning-deformer-in-unreal-engine/
https://www.unrealengine.com/marketplace/zh-CN/product/ml-deformer
### 特效
1. 学习Niagara功能。可以大致看懂ContentExample等其他项目中的特效并能移植到番剧中使用。
2. 学习fluid-flux、riverology插件使用。
3. 学习Niagara的流体功能。
#### Niagara效果
学习官方案例关卡中Niagara的所有效果。并且将可复用效果分离出来并且进行资产标签分类。
https://www.bilibili.com/video/BV1Xy4y1m7QS?spm_id_from=333.337.search-card.all.click
#### FluidNinja插件
https://www.youtube.com/c/AndrasKetzer/videos
#### 其他插件(白嫖或者看一下朱总账户有没有)
https://www.unrealengine.com/marketplace/zh-CN/product/uiws-unified-interactive-water-system
https://www.unrealengine.com/marketplace/zh-CN/product/fluid-flux
https://www.unrealengine.com/marketplace/zh-CN/product/riverology
#### 流体
https://www.youtube.com/watch?v=k7WLE2kM4po&t
#### 程序化生成城市
https://www.youtube.com/watch?v=usJrcwN6T4I
### 角色
1. 解析UE5 黑客帝国Demo中角色所用用到的技术与流程。并且寻找番剧中可以使用的技术。
- 黑客帝国Demo Character制作流程https://www.youtube.com/watch?v=h_dJtk3BCyg
### 资产
1. UE5 的一些技巧比较杂什么方面的都有https://www.youtube.com/watch?v=QXuHzH0IyRE
2. 学习USD工作流如果评估为有价值则最后组织同事进行测试。
3. 学习Multi-User Editing的使用如果评估为有价值则最后组织同事进行测试。
4. 测试Unreal Live Link for Autodesk Maya插件。
- USD 工作流https://www.youtube.com/watch?v=Ddu7TAICAXw&t
- Unreal Live Link for Autodesk Maya https://www.unrealengine.com/marketplace/zh-CN/product/f2e37c4b943f4337b352e1639fa4ebe3 https://www.youtube.com/watch?v=5iTbnk5Zmak
### 地编
1. 熟悉常用快捷键以提高效率https://www.unrealengine.com/zh-CN/tech-blog/designer-s-guide-to-unreal-engine-keyboard-shortcuts
2. 熟悉UE的相关系统体积雾、昼夜系统、Cloud System 、Water System。
3. 测试虚拟贴图在地形系统中的应用。(搞明白相关参数对性能的影响)
4. **测试Nanite**搞明白相关参数对性能、Lumen与其他显示效果的影响
5. 使用UE的建模功能与雕刻工具。
6. 寻找商城里相关的快速摆放物品插件。
#### 商城插件寻找
寻找一下商城相关的快速摆放物品插件(朱总账号可以白嫖插件)
#### 虚拟贴图(需要固态硬盘支持)
https://docs.unrealengine.com/5.0/zh-CN/virtual-texturing-in-unreal-engine/
#### Nanite需要固态硬盘支持
https://www.youtube.com/watch?v=xUUSsXswyZM
https://docs.unrealengine.com/5.0/zh-CN/nanite-virtualized-geometry-in-unreal-engine/
### 渲染
1. UE5.0 几种渲染方案测试实时以及MoveRenderQueue。包括Lumen、RayTracing、PathTracing。已知的存在问题
1. Lumen的综合测试。
1. 使用自带的比色卡模型+室内与室外场景进行测试。
2. 了解材质参数、Lumen参数、后期盒子参数对反射、GI的影响。
3. 测试相关性能,以得出相关
2. 已知的Lumen的问题
- Lumen不支持双面与半透明物体5.1会支持半透明)
- 目前Lumen的反射还有一些问题反射精度
- Raytracing反射目前无法反射GI效果反射结果没有GI
- PathTracing不支持某些效果
1. 虚拟阴影贴图测试
2. 物理灯光流程测试。即使用真实环境的曝光度进行打光。选择清晨、正午、下午、黄昏、夜晚。晴天、阴天
3. 室内家装效果图流程测试。
1. GPULightMass
2. 虚拟光照贴图(需要固态硬盘)
#### LumenGI与反射
- Lumen功能与作用介绍https://www.youtube.com/watch?v=Dc1PPYl2uxA
- Lumen基础教学https://dev.epicgames.com/community/learning/courses/6Oz/unreal-engine-lumen/vyV2/lumen
- Lumen Explained - IMPORTANT Tips for UE5非官方https://www.youtube.com/watch?v=1e6oOiKh91U
- Lumen Tiphttps://www.youtube.com/watch?time_continue=267&v=wTYM9TfckOQ&feature=emb_logo
- 参考视频中调节的参数https://www.bilibili.com/video/BV1YV4y1J7Zv?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290
##### 使用软件支持跑Lumen
Lumen分硬件支持与软件支持软件支持需要用到距离场所以如果出现一些渲染错误需要检查一下距离场。大概率还会用到体积光照贴图这个可以使用GPULightMass生成。
1. 体积光照贴图。尝试使用GPULightMass生成。
2. 模型/场景距离场。生成问题与查看。
#### 局部曝光
#### MRQ的路径追踪
#### 性能优化建议
1. 尽可能的使用MaterialInstance而非直接复制Material。
2. 尽可能使用面光源而非灯阵。
#### 番剧中灯光效果在UE5中的实践与应用室内吴陆佳
尽量使用面光源、
一组UE5场景
#### 番剧中灯光效果在UE5中的实践与应用室外陈飞宇、唐登强
## 本人工作
一些自动化任务在[[远程办公与自动化逻辑]]
1. 制作In-House插件。
1. 项目缓存、引擎缓存位置检测与设定。
2. 渲染模板与参数调节面板
2. 移植Lyra的AssetValid检测插件。
3. NAS搭建。
4. DDC共享缓存与联机构建Shader。
5. 版本管理。
6. 将可复用资产归类之后封装成插件。放在服务器上。UE目前有一个功能可以远程自动下载插件。
7. 修改引擎
1. 让指定角色不接受VLM的影响
2. 定制一种灯光只对某个指定的SkeletalMesh起作用不对环境模型起作用同时不产生球谐效果
8. MRQ使用MRQ的切片渲染功能、或者联机部署

View File

@@ -0,0 +1,17 @@
# 前言
https://gitlab.com/RazTools/Studio
# 提取方法
1. 下载AssetStudio并且运行AssetStudioGUI.exe
2. 点击`Options - SpecifyGame`将游戏设置成GI。
3. 在系统盘之外的任意位置创建一个文件夹作为`输出用的文件夹`
4. 点击`File - LoadFolder`,载入原神的`GenshinImpact_Data\StreamingAssets\AssetBundles\blocks`文件夹会花费很长时间。Process Assets会花费非常长的时间。大约40min
5. ~~点击Misc - BuildAssetMap~~ (已确认执行该步骤会耗费10小时以上的时间并且最后会卡死判断为程序bug)
6. 在FilterType中设置想要输出的类型
7. 点击`Export - Filtered Assets`即可进行输出。
## 输出设置
`Options - ExportOptions`中可以设置输出贴图格式建议设置成Tag。
# 音频提取
https://github.com/Escartem/AnimeWwise?tab=readme-ov-file

View File

@@ -0,0 +1,135 @@
## 优化记录
1. 灯光优化
   1. 主场景(城楼、栈桥与城门部分)
      1. 主光源
         1. 将DynamicShadowDistanceMoveableLight调整到合适范围近景细节阴影40000 => 4000~15000范围
         2. 调整DistanceFieldShadowDistance远景的软阴影效果20000 => 4000~15000如果远景阴影效果有问题再调整DistanceFieldTraceDistance100000.0 => 100000.0~40000000.0
         3. 调整Num Dynamic Shadow Cascades4=> 1~3保证阴影效果变化不大的情况下
      2. 其他辅助光源
         1. 保证灯光的半径与角度刚好覆盖想要影响的物体。
         2. 补光灯可以尝试低亮度不投射阴影或者高亮度+贴花,尽可能避免高亮度加投射阴影。
         3. 如果需要投射阴影请适当调整阴影分辨率1=> 0.05~0.8
   2. 不太容易看的场景(坑底与中间浮岛下面部分)
      1. 尽量保证只有灯光效果关闭阴影效果。如果需要阴影就使用SpotLight不投射阴影可以用点光源。
      2. 尽可能减少灯光数量。即坑底部分的照明可以选择使用贴花Fake
   3. 远景山脉阴影使用Fake贴花。避免使用方向光来投射阴影。
   1. 如果需要阴影可以将Num Dynamic Shadow Cascades 4=>0并且调整调整DistanceFieldShadowDistance。直接使用距离场阴影。
2. 场景优化
   1. 粒子可以调节Cut Off的裁剪系数Spawn TimeSpawn Rate等等可以在保证效果的同时来降低粒子数量的参数
   2. 模型
   1. ~~制作LOD或者HLOD。~~ 使用Nanite
   2. 考虑使用HSIM或者 UE5出的Light Weight Instances https://www.youtube.com/watch?v=QGEV8YjBGHQ
   1. 启用Light Weight Instances插件。
   2. 创建一个蓝图类LightWeightInstanceStaticMeshManager类并且拖入场景中。
   3. Manager设置RepresentedClass指定新建的Actor这里假定名字为AA里面只有StaticMeshComponent
   4. 设置InstancedStaticMeshComponentStaticMesh与Materials。
   5. 往场景里放入多个AA并且选中之后在菜单栏的Actor-Convert Actors to LightWeightinstances
   3. 远景资产
   1. 删除场景外的看不见的模型
   2. 删除多余的地形
3. Nanaite
1. 尽可能得将模型转化成
2. 使用Nanite Tools检查哪些模型的材质有问题并解决。
### 优化记录
#### 方向光
##### 2022.9.20
修改远景4个DirectionalLight 动态ShadowMap阴影可以为距离场阴影或者静态阴影。
- DirectionalLight
  - 级联4=》2
  - DynamicShadowDistanceMoveableLight 40000=》5000
  - DistanceFieldShadowDistance40000=》10000
- DirectionalLight2
  - 级联4=》2
  - DynamicShadowDistanceMoveableLight 40000=》25000
  - DistanceFieldShadowDistance10000=》1000000.0
  - DistanceFieldTraceDistance  =>2500000.0
- DirectionalLight4
  - 级联5=》0
  - DynamicShadowDistanceMoveableLight 40000000.0=》4000.
  - DistanceFieldShadowDistance1000000.0 =》1000000.0
  - DistanceFieldTraceDistance  100000.0=》40000000.0
##### 2022.9.21
- [x] DirectionalLight2
  - 删除
- [ ] PointLight3
- 亮度150=》50
- 关闭CastShadow
- [ ] SpotLight11
- ShadowResolutionScale1=》0.1
- 或者考虑降低角度,只对着城楼。
- [ ] RectLight7
- 建议调整范围 =》降低至能刚好包裹城门的区域。
- 亮度 500=>100
- 关闭阴影
- 或者删了
- [ ] SpotLight25
- 降低半径2000 角度 60
- 阴影分辨率1=>0.5
- [ ] SpotLight26
- 降低半径到2500角度降低到45亮度提高=》10000 Shadow分辨率 1=》0.5
- [ ] SpotLight27
- 降低半径到2300角度降低到45亮度提高=》12000Shadow分辨率 8=》0.5
- [ ] PointLight56
- 关闭CastShadow
- 降低半径到1600
- [ ] PointLight57
- 关闭CastShadow 或者降低阴影分辨率 0.1=》0.01
- 降低半径到700
- [ ] PointLight74
- 关闭CastShadow
- 或者换成SpotLight
- [ ] RectLight14
- 降低半径到2860
- 关闭CastShadow
- 或者换成SpotLight
### 后处理
- AmbientCubeMapIntensity 1=》0
- Ambient OcclusionIntensity 0.7=>0
### 粒子优化
- Niagara Cutout
- https://www.youtube.com/watch?v=_T-BTiMF7XA
- https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/Niagara/EmitterReference/RenderModules/
- 4层 雾气=> 3层
- 粒子系统的 LifeTime 25=>14
### 场景优化
- 删除场景外看不到的模型
- 删除多出的Landscape2
- 给树木等物体制作Impostors
## 优先级顺序
- [x] Stat数据查看
- 查看Stat Unit 数值确定是GPU还是CPU瓶颈
- 查看Stat SceneRendering
- ctrl+shift+ r.showmaterialdrawevents -1 r.rhicmdbypass 1 r.rhithread.enable 0
- [x] 动态灯光阴影尝试增加级联数与其他参数。使用低分辨率贴图来渲染阴影使用RenderDoc查看分辨率
- [x] 远景模型距离场检查。使用DFAO关闭SSAO
- [x] 尝试干掉点光源制作Fake灯光 贴花来代替各种灯光与阴影效果
- [x] 检查材质复杂度
- 烘焙材质,将复杂材质烘焙结果到贴图
- [x] ~~检查模型面数以及LOD碰撞盒~~ 改用Nanite来解决
- [x] 草地使用HISM或者UE5出的Light Weight Instances
- https://www.youtube.com/watch?v=QGEV8YjBGHQ
- [x] 粒子优化工具:剪裁粒子面积。
- Unreal4引擎对粒子系统半透明渲染的Overdraw做了优化。在ProjectSetting->Rendering > Optimization中有一个渲染Enable Particle Cutouts by Default选项
- [x] MergeActor工具合并DrawCall
- ~~开启DLSS插件~~
- ~~设置Dynamic分辨率~~
## 制作流程改变
对于性能要求的项目。
1. 制作前先让TA知道目标硬件帧数。
2. 美术摆放场景后检查**距离场**。
3. 美术手动合并材质。
4. 吃透Nanite以及LOD属性设置
5. 搭建VPN、版本管理等方便远程协作的服务。
## 优化笔记
![[UE5优化方法与实践笔记]]

View File

@@ -0,0 +1,25 @@
## 使用的模拟算法
FEM + 肌肉(变量):
PBDPosition Based Dynamics
MPM
## 技术路线
1. NVIDIA flex移植。
- 优点有完备的UE组件实现提供编辑器等工具
- 缺点没有UE5版本UE4版本也是民间人士实现的。
3. Niagara PBD解算。
- 优点在ContentExample中有实现PBD解算器。
- 缺点:没有其他组件的实现。
5. UE5 Chaos。
- 优点:与其他组件可以数据交互。
- 缺点没有具体的PBD柔体实现。
7. ZENO可以查看节点源码
8. Houdini xPBD Vellum学习节点的解算器代码主要是OPENCL
9. ~~AMD FEMFX库。~~
10. 保底路线:骨骼模拟。
## 开发执行计划
因为不管上述的哪个方案都无法直接制作出想要的柔体模拟效果,所以:
1. 先学习Houdini搭建可行的离线模拟方案FEM、PBD保证技术路线方向的可行性正确性。
2. 学习PBD、FEM的算法。
3. 在UE中复现出现。

View File

@@ -0,0 +1,13 @@
# 摄像机控制逻辑
调用逻辑位于`BP_Sub_Controller`中的若干接口:
- Event
- Init_Camera
- BPI OFF SP Camera
- BPI SP Camera ON
- BPI USM Camera ON
- BPI Initi SP&USM Camera
-
- Interface
...
具体实现逻辑位于Character中。

View File

@@ -0,0 +1,94 @@
# Class
## FightGameAction
FightGameAction => GameAction => HitBoxAction => Actor
CustomEvent
- 继承事件:
- ActionStart取得当前Pawn并且赋予FgPawn变量以及当前AnimInstance赋予PawnAnim。
- SpecialEvent调用IsParry()来判断是否是格挡事件如果是就调用Parry事件。
- StrikeEvent
1. StrikeFeedback触发对应的反馈效果。包括粒子、声音、力、镜头摇晃。
2. ApplyHitDamage应用伤害。
- OtherEvent其他Hit逻辑。
- 新事件:
- Parry格挡事件。
- Freeze硬直效果。
- ResetFreeze重置硬直效果。
### HitBoxAction
Component
- HitBoxEditor编辑用组件用于编辑每帧Action时切换指定Index的帧。***ComponentTag"hb.Editor"***
- FrameSlider
- FrameIdTx
- AnimSlider
Method
- Initialize()
1. SetOwner()
2. SetFrameList()遍历所有HitBoxFrameComponent并且根据**组件名称**取得FrameID
3. ActionStart()调用ActionStart()事件。
CustomEvent
1. ActionStart
2. HitBox
3. OtherHits
Editor相关
CustomEvent
1. InitializeEditor**根据FrameSlider的Location来设置HitBoxFrame**该逻辑会在ConstructScript中被调用每移动一次就会被调用一次。
2. EditorSliders根据FrameSlider、AnimSlider的当前帧数根据相对Location来记录FrameId来设置显示文字。
3. DestroyEditor
### GameAction
主要实现了HitBox事件。判断是否是StrikeEvent与SpcialEvent后调用StrikeEvent()与SpcialEvent()。
CustomEvent
1. StrikeEvent
2. SpcialEvent
Editor相关
CustomEvent
1. InitializeEditor在父类事件的基础上用相对位移来存储开始播放Montage的初始位置设置到**SkeleMesh->AnimationData->InitialPosition**。
## ActionComponent
>主要使用这个组件来管理当前Action设置HitBoxAction类给ChildActor并且调用HitBoxAction的Initialize并且绑定ActionEvent。
Event
- BeginPlay设置ChildActorComponent到ChildActor变量上。可根据Tag来选择ChildActorComponent。
- EndPlay触发DestoryAction事件。
CustomEvent
- ***SetAction***设置新的HitBoxAction类以及Frame Id。
- SetActionClass()其中SetActionClass的逻辑会调用HitBoxAction的Initialize并且绑定ActionEvent。
- 调用Action类的SetActiveFrame()。
- NewAction依次调用DestoryAction、SetAction事件。
- DestoryAction将ChildActor置空。
## HitBoxFrame
HitBoxFrame => SceneComponent
CustomEvent
- InitializeFrame取得所有ChildrenComponent(HitBox)并且加入BodyList中。并且对每个HitBox调用InitializeHitBox()
- UpdateFrame判断FrameId销毁旧OldFrame创建新的。
- DestroyFrame
# AnimNotify
## FrameNotify
- RecivedNotify调用SetNewAction()
1. 通过SkeletalMeshComponent取得OwnerActor
2. 取得ActionComponent
3. 根据StartAction执行对应的逻辑
1. trueActionComponent->NewAction()
2. false, 判断ActionComponent中的Action类是否与AnimNotify的Action类相同如相同执行ActionComponent->SetAction()
## FrameState
- Recived_NotifyBegin逻辑与FrameNotify的逻辑相同。
- Recived_NotifyTick每帧判断ActionComponent的FrameId与Class是否与FrameState是否相同如果不相同就调用ActionComponent->SetNewAction()
## MovementModeState
用于设置角色的MovementMode
## RunMontageSection
用于让Montage跳转Section。

View File

@@ -0,0 +1,103 @@
# 阶段计划
1. [ ] 使用FightingGameTemplateProject的场景、动作资产、构建固定相机单角色进行各种Combo的Demo。
2. [ ] 细化各种攻击判断逻辑、细化GAS逻辑以及DataTable填表逻辑。
# 开发计划
1. 改进HitBoxBlueprint项目
1. 解决编辑器Actor中帧数与动画帧数不同步的问题
2.
2. Perforce & CI/CD 搭建
3. 使用FightingGameTemplateProject的场景、动作资产、构建固定相机单角色进行各种Combo。
4. 使用LogicDriver、GAS使用GASDocument构建基础逻辑。
1. 使用LogicDriver构建出招以及各种动作状态机。
2. 使用GAS构建技能与Buffer功能。
3. 使用增强输入定制出招表。
5. 将HitBoxBlueprint生成的HitBox数据改成DataAsset样式
6. 实现InputBuffer 输入缓存系统。可以参考商城的 Ninja Input插件
7.
# GAShooter移植计划
## c++
- [x] GSEngineSubsystem.h
- [x] GSBlueprintFunctionLibrary.h
- [x] ~~AI~~
- [ ] Characters
- [ ] Abilities
- [ ] AbilityTasks
- [x] GSAT_MoveSceneCompRelLocation.h
- [x] GSAT_PlayMontageAndWaitForEvent.h
- [x] GSAT_PlayMontageForMeshAndWaitForEvent.h
- [x] GSAT_ServerWaitForClientTargetData.h
- [ ] ~~GSAT_WaitChangeFOV.h~~
- [x] GSAT_WaitDelayOneFrame.h
- [x] GSAT_WaitInputPressWithTags.h
- [x] GSAT_WaitInteractableTarget.h
- [x] GSAT_WaitTargetDataUsingActor.h
- [x] AttributeSets
- [ ] ~~GSAmmoAttributeSet.h~~
- [x] **GSAttributeSetBase.h**
- [x] AsyncTaskAttributeChanged.h
- [x] AsyncTaskGameplayTagAddedRemoved.h
- [x] GSAbilitySystemComponent.h
- [x] GSAbilitySystemGlobals.h
- [x] GSAbilityTypes.h
- [x] GSDamageExecutionCalc.h
- [x] GSGA_CharacterJump.h
- [x] GSGameplayAbility.h
- [x] GSGameplayCueManager.h
- [x] GSGameplayEffectTypes.h
- [x] GSGATA_LineTrace.h
- [x] GSGATA_SphereTrace.h
- [x] GSGATA_Trace.h
- [x] GSInteractable.h
- [x] GSTargetType.h
- [x] ~~Animation~~
- [ ] Heroes
- [ ] ~~GSHeroCharacter.h~~
- [x] GSASCActorBase.h
- [ ] GSCharacterBase.h
- [ ] ~~GSCharacterMovementComponent.h~~
- [x] Items
- [x] GSPickup.h
- [ ] Player
- [x] GSPlayerState.h
- [x] GSPlayerController.h
- [ ] UI
- [ ] Weapons
- [x] GSProjectile.h
- [ ] ~~GSWeapon.h~~
## Content
- [ ] ~~FPWeapon~~
- [ ] GASShooter
- [ ] Blueprints
- [x] AnimNotifes
- [ ] BP_Chest
- [x] BP_DamageVolume
- [ ] ~~BP_GameMode~~
- [x] BP_HealthManaStaminaShieldRegenVolume
- [x] BP_HealthManaStaminaShieldVolume
- [ ] ~~BP_PlayerController~~
- [ ] ~~BP_SpectatorPawn~~
- [x] GE_ChestOpen
- [x] GE_DamageVolume
- [x] GE_DoorOpen
- [x] GE_HealthManaStaminaShieldRegenVolume
- [x] GE_HealthManaStaminaShieldVolume
- [ ] Characters
- [ ] Hero
- [ ] Abilities
- [x] GA_InteractActive
- [x] GA_InteractPassive
- [x] GE_BleedingOut
- [x] GE_Dead
- [x] GE_Interacting
- [x] GE_InteractingRemoval
- [x] GE_KnockedDown
- [x] Shared
- [ ] ~~Items~~
- [ ] ~~Maps~~
- [ ] ~~UI~~
- [ ] ~~Weapons~~
- [x] InfinityBladeFireLands
- [ ] ~~ShooterGame~~
## GameplayTags

View File

@@ -0,0 +1,134 @@
# 同步
git同步教程
https://zhuanlan.zhihu.com/p/531516583
https://cloud.tencent.com/developer/article/1877515
## 手机同步方案
目前Obsidian 除了自带的同步外,仅支持加载手机本地目录(安卓以及苹果),或苹果的 iCloud 服务,再次特别推荐官方的带有详细文件历史同步的同步功能。不过既然 Obsidian 手机版支持加载手机本地目录,对于安卓手机,可以采用 Folder Sync 来同步坚果云、OneDrive以及其它 WebDav 网盘的文件到自己的安卓手机
# 推荐模板
- https://github.com/cumany/Blue-topaz-examples ![](https://user-images.githubusercontent.com/42957010/184872220-f9b52079-8695-46a7-b517-0588426aa7b5.jpg)
- 视频教程https://space.bilibili.com/1970226/channel/series
- https://github.com/sheldonxxd/obsidian_vault_template_for_researcher
- 介绍视频https://www.bilibili.com/video/BV1XR4y1w7jp/?spm_id_from=333.1007.top_right_bar_window_default_collection.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
# 显示编辑
格式编辑器插件:
obsidian-editing-toolbar插件https://github.com/cumany/obsidian-editing-toolbar
1. 安装 [BRAT Plugin](https://obsidian.md/plugins?id=obsidian42-brat)去安装。之后安装 `cumany/obsidian-editing-toolbar`
2. 手动下载与安装https://github.com/obsidian-canzi/Enhanced-editing
3. 在obsidian-editing-toolbar菜单中添加操作
# 插件
- Obsidian-outliner
- 日历
- Obsidian Big Calendar
- Calendar
- 同步
- Obsidian Git
- 看板
- Kanban
- rollover daily todos0
- 表格
- Advanced Tables
- mind map
- Obsidian Memos
- templater
- Media Extended
- 代码高亮
- Editor Syntax Highlight
- Highlightr
- Dataview
- Advanced Tables
- Collapse All
- Customizable Sidebar
- MOC
- Zoottelkeeper **Plugin**
- 手绘流程图
- Excalidraw
- 笔记拆分将多个2级标题进行拆分Note Refactor
### 其他人分享的插件列表
https://www.zhihu.com/zvideo/1465842922323636224
https://zhuanlan.zhihu.com/p/410202700
- cm-editor-syntax-highlight-obsidian
- note-refactor-obsidian
- workbench-obsidian
- markdown-prettifier
- obsidian-shortcuts-for-starred-files
- obsidian-journey-plugin
- tag-wrangler
- obsidian-footnotes
- nldates-obsidian
- pane-relief
- url-into-selection
- calendar
- obsidian-advanced-uri
- remember-cursor-position
- mrj-text-expand
- obsidian-hotkeys-for-templates
- recent-files-obsidian
- obsidian-charts
- obsidian-auto-link-title
- obsidian-leaflet-plugin
- obsidian-admonition
- obsidian_focus_mode
- text-snippets-obsidian
- buttons
- obsidian-hider
- obsidian-markdown-furigana
- obsidian-pangu
- obsidian-pandoc-plugin
- obsidian-powerthesaurus
- obsidian-outliner
- obsidian-tracker
- supercharged-links-obsidian
- media-extended
- privacy-glasses
- obsidian-style-settings
- obsidian-sortable
- maximise-active-pane-obsidian
- dataview
- better-fn
- obsidian-electron-window-tweaker
- obsidian-tasks-plugin
- mrj-jump-to-link
- quickadd
- oz-image-plugin
- metaedit
- obsidian-pandoc
- obsidian-map-view
- smart-random-note
- obsidian-dice-roller
- obsidian-excalidraw-plugin
- mx-bili-plugin
- hotkey-helper
- obsidian-hotkeys-for-specific-files
- graph-view-blink
- sliding-panes-obsidian
- alx-folder-note
- longform
- obsidian-wordnet-plugin
- obsidian-copy-block-link
- emoji-shortcodes
- obsidian-jump-to-date-plugin
- templater-obsidian
- obsidian-markmind
- obsidian-relative-find
- obsidian-code-copy
- obsidian-banners
- calendar-beta
- file-explorer-note-count
- obsidian-drag-and-drop-blocks
- obsidian-hackernews
- quick-explorer
- readwise-official
- obsidian-annotator
- obsidian-kanban
- customizable-sidebar
- folder-note-core
## 甘特图案例
![[Obsidian甘特图笔记]]

View File

@@ -0,0 +1,25 @@
`
```mermaid
gantt
title 本周项目事务甘特图
dateFormat YYYY-MM-DD HH:mm:ss
axisFormat %m-%d
section obsidian
这是一个测试 :t1, 2022-03-28 14:32:45, 2d 10min
section 测试
做饭 :t2, 2022-03-29 14:32:45, 3d 30min
洗碗 :t3, 2022-03-30 16:32:45, 1d 30min
```
`
```mermaid
gantt
title 本周项目事务甘特图
dateFormat YYYY-MM-DD HH:mm:ss
axisFormat %m-%d
section obsidian
这是一个测试 :t1, 2022-03-28 14:32:45, 2d 10min
section 测试
做饭 :t2, 2022-03-29 14:32:45, 3d 30min
洗碗 :t3, 2022-03-30 16:32:45, 1d 30min
```

View File

@@ -0,0 +1 @@
# 前言

View File

@@ -0,0 +1,72 @@
---
title: UE5你可能不知道的35个新功能
date: 2022-11-10 11:28:08
excerpt:
tags:
rating: ⭐
---
## 前言
[35 UE5 Features You Probably Don't Know About](https://www.youtube.com/watch?v=k2IP5DYQ0-0)
## ConsoleVariable
### ConsoleVariablesEditor
一种用于快速切换不同数值ConsoleVariables的工具。方便TA/图程调节参数。
首先需要在启用插件`ConsoleVariablesEditor`
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ConsoleVariablesEditor_1.png)
之后就可以在Window-ConsoleVariables打开这个工具了。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ConsoleVariablesEditor_5.png)
主要的操作方法就是点击左上角的按钮添加ConsoleVariables。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ConsoleVariablesEditor_2.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ConsoleVariablesEditor_3.png)
之后还可以将其保存成预设,方便进行切换。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ConsoleVariablesEditor_4.png)
### ABTest
一种方便比较2种ConsoleVariables效果差别的工具开启后会以一定频率不断进行切换参数以此方便开发者进行效果比较。
- abtest (Console Command) (value1) (value2)
- abtest stop
比如:
> abtest r.VolumetricFog 0 1
## Asset
### Asset Reload
可以在资产上右键-AssetAction-Reload直接加载本地磁盘或者当前版本管理版本的资产而无需重启整个编辑器。
### Asset高级搜索语法
比如:
>(NaniteEnable = False) AND Triangles > 5000)
可以查询的信息主要为鼠标放在资产上显示的信息。
### Asset List显示
在ContentBrower右边的Settings中点击List即可显示成列表模式。
## UE建模工具
- CubeGrid使用Cube画刷进行原型关卡的搭建。
- FixPivotTool调整Pivot位置。
### GeometryScript
通过蓝图脚本进行一些建模操作,比如对场景中的物品进行布尔运算啥的。
### TextureBaking
位于UE建模工具的Baking中。有BakeTX、BakeVtx与BakeAll。
## Niagara
Baker可以将Niagara制作的特效优化成Flip效果。
## Chaos
ChaosCacheManager组件可以将ChaosCache设置成Pose之后通过设置StartTime来制作一些时间逆转的效果。
## Sequence
Sequence摄像机与玩家摄像机混合过渡效果。在摄像机轨道上右键点击Can Blend。之后进入某个区域播放Sequence就有这种下够哦。
## 其他
- 开启屏幕空间旋转编辑器设置中勾选Enable Screen Rotation
- Procedural Foliage Volumn
- 可以通过ShadowPassSwitch材质节点控制某一些区域的模型不投射阴影。
- 在Simulation之后选中对应的物体之后按K就可以保留物理模拟后的结果适场景美术。

View File

@@ -0,0 +1,74 @@
# 灼华互娱
## 头发
他们的解决方案是:
1、Nvidia的HairWorks
2、在DCC中解算面片头发动画之后导出ABC缓存。
HairWorks是基于dx11的使用了HairWorks就无法使用dx12的Rtx。不过4.24新出的Hair Rendering功能可以代替之前HairWorks。
## ABC缓存使用
主要使用ABC缓存结算头发与布料但ABC缓存很可能因为过大而导致导入崩溃。所以灼华互娱对DCC的ABC导出插件进行魔改从而减少ABC缓存大小。但个人认为最佳的解决方案还是重新实现导出缓存格式不过这需要实现DCC导出插件与Unreal4导入插件引擎没有必要使用高精度数据工作相对来说会大一些。
## Niagara与Houdini结合
灼华互娱采用的方案是:
使用Niagara制作普通粒子特效。
对于需要解算巨量粒子则使用Houdini生成粒子缓存导入Ue4中。在Ue4中生成模型Instance作为粒子。
## 资产管理
灼华互娱使用USD(通用场景描述) 皮克斯公司开发的开源工具,来制作整个资产管理系统。从而在流程上实现了很多自动化处理。
比如各种资产同步比如Maya或者houdini工程进行修改就会自动导入Ue4。之后自动导入服务器。
# ASher的演讲 Ue4中地形及环境特效的制作与表现
主要介绍了Volumetric Fog/Cloud、LansmassBrush、Erosion、Niagara、VolumeMaterial。
个人感觉4.24的地形侵蚀工具相当不错可以通过模拟流体来创建河流侵蚀河岸效果。也可以增加地形的细节以增加可信度。而且可以导出高度图、侵蚀图、泥沙流动图等你可以用这些贴图制作别的物体Mask。甚至是导入niagara制作特效。
另外你也可以使用这个系统与niagara制作海水拍打岸边的岩石所造成的的水花。
## 体积插件可以从github下载编译
配合4.24出的物理大气系统可以轻松渲染出一个很不错的天空。它在dev-rendering分支中位于/Engine/Plugins/Experimental/Volumetrics。
## 体积雾
### 体积雾材质
材质使用的是案例关卡中的雾气材质。具体的材质节点可以参考Shaderbits.com。
至于雾气会绕开Mesh雾气这个应该就是根据距离场再使用类似FlowMap的方式来解决的。
对于天空中的云,通过输出地形高度图来影响。
体积雾阴影需要trick通过体积雾的高度图来计算normal之后通过方向光与法线的dot来计算自阴影。
### 在Sequencer里控制时间
use manual time控制时间。
### 使用命令行调整体积雾质量
r.volumetricFog.TemporalReprojection 0
r.volumetricFog.GridSizeZ 1024
### Temp SuperAA相关
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/13917C7C77354910BD652768B792F3D3.octet-stream)
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/B85557DC9821473FB8B98FB9B1B2FD06.octet-stream)
使用这个方法可以解决物体与地形的边界过于明显的问题。另一种解决方法是:
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/DEC240EAC32940248376B36141EDDDA9.octet-stream)
Lerp的Alpha还可以使用HeightLerp、相对高度甚至是距离场来设置。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/6EFC0D2DAAC8454797B8E4118A433B56.octet-stream)
地形的融合注意使用Material LOD
http://riteofilk.com/2017/07/29/blurring-the-edge-blending-meshes-with-terrain-in/
Dither TAA节点可以通过以下视频了解
https://www.youtube.com/watch?v=PKX_v4RDkPc
Dither TAA的另一个技巧就是让物体产生半透明的效果将DitherTAA节点连接到Mask上
### Quiexl 扫描库贴图设置技巧
使用以下设置会让画面变得锐利一些:
r.ToneMapper.Sharpen 3
r.ScreenPercentage 120
贴图设置Mipmap设置为Sharpen5。

View File

@@ -0,0 +1,100 @@
---
title: 杭州UnrealCircle2022笔记
date: 2022-08-09 13:55:15
tags:
rating: ⭐️⭐️
excerpt:
---
# 杭州UnrealCircle
## ChaosCloth
1. 与引擎其他模块协作,比如**动画系统**
2. 基于PositionBase稳定的表现效果。应该是基于粒子的。
3. 允许使用AnimDriveMask 动画蓝图节点以及参数设置。
4. 支持蓝图设置参数以及对应的Debug工具。
5. 支持老算法NvCloth的结算结果。
6. Chaos没有碰撞体数量限制NvCloth<32新增只会影响Cloth的TaperedCapsules
- 对角色的Clothing菜单里的勾选与环境进行碰撞即可实现与环境碰撞collision with environment
- MeshSkinning->Mesh Skinning选项对与高面Cloth的优化方案
- CVARs Debug命令与PIE参数可视化
- p.ChaosClothEditor.Debugxxx
- p.ClothPhysics.WaitForParallelClothTask 1
- p.ChaosDebugView.DebugRaw;
- Lo-Hi值与Mask值相关。
## 博采传媒 基于Unreal的虚拟制片
剧本->资产制作->剧本可视化 制作预演短片。
Previz-》Techviz-》虚拟制景-》LED环幕拍摄
博采制作了一个半圆柱形的LED棚来解决工业光魔 棚的缺陷。杭州750平米5米高;安吉900平米8米高。
一个月制作 0特效 科幻短片LED棚实拍。
优点:可以节约实景道具,同时获得结果接近的光影效果。
难点如何将LED屏幕的光打到角色身上。比如拍摄夜景时控制DMX灯光与LED屏场景同步来对角色进行补光。
道具研发:比如模拟棚内汽车颠簸的承载系统。
#### 需要大量拍摄技巧
为了保证演员脸上的间接照明演员必须里LED屏幕非常接近(2m,夹角150°)。
个人想法:是否可以使用无人机矩阵进行补光处理。
- 小场景丁达尔光
- 小置景大场景:需要小场景与背景结合经验。
- 丁达尔光:可以直接做出丁达尔光的场景。
- 摄像机仰拍:部分镜头因为圆顶屏幕的限制需要使用仰拍视角来规避。
- 虚拟拍摄与DMX拍摄
## 虚幻引擎在短视频中的应用
可以购买LED电视代替LED屏低配版
## 虚幻录像系统
视频地址https://www.bilibili.com/video/BV1Z34y1n72n?share_source=copy_web
#### 概念
- ReplaySubsystem一个全局的回放子系统封装核心接口并且暴露给上层使用。
- DemoNetdriver继承自NetDriver宏观控制回放的录制与播放。
- DemoNetConnection继承自NetConnection自定义实现回放数据的发送位置。
- ReplayHelper防撞一些放回处理数据的接口将回放逻辑与DemoNetDriver进行解耦。
- XXXNetworkReplayStreamer序列化数据的存储类。
#### Streamer流送有5种方式
1. Null
2. Http
3. 内存
4. 文件
5. 还有一个忘记了
#### 实现快进
1. CheckPoint存档点即一个完整的世界快照每隔一段时间记录一个。
2. Stream一段连续时间的数据流存储着从上一个Checkpoint到当前的所有录制数据。
3. Event记录一些特殊的自定义事件。
#### 录像系统的Streamer使用方式
- UGameInstance* GameInstance=GetWorld()->GetGameInstrance();
- GameInstance->StarrtRecordingRecord()
- GameInstance->PlayReplay()
- 在CS模块文件中增加**NetworkReplayStreaming**与**LocalFileNetworkReplayStreaming**模块按照需求还可以增加InMemoryNetworkReplayStreaming与HttpNetworkReplayStreaming。
#### 版本兼容
- SendProperties_BackwardsCompatible_r/ReciveProperties_BackwardsBackwardsCompatible_r
- 跨版本兼容将本版蹦属性的名称等信息一并写入NetFieldExports结构体再进行序列化
- SendProperties_r/RecevieProperties_r
- 不考虑兼容只序列化某个属性的Index以及内容不记录属性名称。
#### 关卡设计思路
- 游戏中的死亡回放要使用内存Streamer。
- 在进行比赛的同时播放死亡录像采用DuplicatedLevelCollection方案实现。
- 对Level进行划分DynamicSourceLevel原始世界、StaticLevel存放静态Actor、DynamicDuplicatedLevels存放回放世界。
- 使用ActorGUID来区分是服务器Actor还是本地Actor。
#### 观战系统
FHttpNetworkReplayStreamer通过服务器发送一个拷贝到观战服务器中。
#### 优化
- 尽量避免RPC而多使用属性同步
- 使用PlayerState不适用Controller来存储同步数据
- 服务器与客户端都可以录制回放
- 建立对象池来避免加载、Spawn、Destroy、Actor GC的卡顿问题
- 回放世界与真实世界是同一个物理场景,需要避免碰撞
- 避免在回放世界打开物理
- 通过设置PxFilterFlags FCollisionFilterData并修改引擎的碰撞规则处理

View File

@@ -0,0 +1,118 @@
---
title: 究极风暴渲染技术
date: 2022-08-09 13:55:15
tags:
rating: ⭐️
excerpt:
---
此文为转载  转载地址此文中已注明,本地址转载请注明 http://blog.sina.com.cn/diorsolo 
重视画面效果的DOF        图A是应用DOF前的状态。在这个基础上由于适用了从ZBuffer中抽出的(unfocusmask)虽然再现出了DOF但是因为在角色上有轮廓线Outline模糊的部分和不模糊的部分的差距鲜明的显出了轮廓若隐若现图C。因此像图D那样在unfocusmask本体上加上Blur来抑制上述的不合适图E。实际仔细看来本来的焦点应该符合背景而显得模糊但考虑图像的融合还是采用了这种方法。顺便说一句Blur使用了高斯filter来处理可以很轻松的完成。【千里马肝注这的确是一个让unfocus部分与focus部分更柔和融合的小trick。】
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/C936EB7747674CEEAE7990DA20272D9B.jpeg)
### 火焰的特效
      图A里是佐助的[豪火球忍术]图B是实际的模型构成。这个特效可以由中心的球体模型图C平面的正面化模型图DE对应移动的生成平面多边形的轨迹模型图F这三个部分构成把这些合成后可以实现拥有2D的样子并在3D空间中没有不协调感的特效。还有适用于Flare或轨迹的动画用贴图如图所示各自八张的循环,每个一种但是担任特效的下田星儿先生表示最初是做成了平均性形状的多边形在实际组合时让多边形变形即使用一个种类的循环动画也能够表现出形态丰富的火焰。顺便说一句这个特效为了再现动画版的时间虽然是一秒内8到15个画面但游戏本身是按30fps活动。试验性的尝试过60fps和15fps但在60fps中不能流畅的做出活动的动画一旦用15fps做出动画作为游戏的反应就变差了。
      
http://www.opengpu.org/forum.php?mod=viewthread&tid=6609&extra=page=1 转载原文地址尊重作者劳动
## 为了让动画片和游戏没有界限
      本作是在06年初时开发的当时Xbox360刚在日本发售PS3的发售还要有些时候这时不论是谁都在思考着今后的次时代游戏的开发战略。在这样的状况中负责开发CyberConnect2公司的董事社长松山洋先生决心把[伴随着次世代机的图形性能飞跃的提高,各种开发经费也高涨]这个问题,通过以全世界的市场作为目标进行开发而得到突破。
      为了实现这个坚固的理念,深思熟虑过我们自己对于世界的优势,导出的方针就是[把日本独有的动画和漫画的表现贯穿始终],那就成为了所谓的[把动画和游戏的界限消除]的具体目标。为了这个目标首先编成了10人的研究开发组作为先头部队这期间有7名设计师进入开发组内从事制作被称为[超动画]的[贯穿始终]的视觉表现的预想影像经过了半年时间终于在3dsMax上做了出来。
      
      为了达成目标如动画般的画面表现,本作的动画全部用手工完成,一方面是松山先生回想起那些庞大的工作量就说[总之是动作组MotionTeam一直很努力才顺利前进],另一方面动作指导的石桥洋平先生也回答说[确实太辛苦了,但是有研究开发时适当策略可供参考,所以可以冷静的应对]。确实是可靠的言词。
      还有本作是有动画版权的游戏所以要由原版权公司检查。具体上和动画设定的出入对于游戏中存在的IF假象剧情原创忍术的[奥义]的表现进行检查。这些都给予了细心的注意,动画师不时地一边参照动画影像一边进行工作,还要根据每个负责人的文字描述做出概念,之后由导演进行核对,整个工程全部的阶段都要在内部核对,要做到尽可能不返工的程度,用这样精炼的制作来接受原版权公司的监督。职员们这样的努力,已经出色的成为本作的一个理念[兼顾画面的观赏性和作为游戏的游戏性这样的两个目标]。事实上在本作的预告片公开时,也能看到用户和游戏业界对于过高的游戏品质比起动画业界发出了更多的赞赏声。
      最后,通过采访最有印象的是,开发职员要面对把原作动画作为初始的动画本身,从言词的细微传来了敬意和热爱。从下页开始要介绍这种丝毫不妥协的热爱,平时挑战各种各样限制本作构思和技术平衡的真髓。
### 让人感觉不到CG噪音的简单角色表现
动画作画的关键就是对信息量的控制。用少量的信息表现出强烈的个性,怎么才能做出活灵活现的角色呢?
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/704A99E2037A4B2BBA320465980DA40E.jpeg)
### 角色模型的LOD
      角色的建模使用3dsMax。图里是主人公漩涡鸣人的身体和头的建模从左开始是高、中、低模。多边形数量各自为高模16000头部2400、中模8000头部1400、低模4000头部700基本上由从镜头的距离算起5米以内、5到7米、8米以上来区分以此进行LOD处理。梅田公一先生表示这是为了在减轻描画处理负荷的同时表现出动画特有的[远处的东西被画出线条是经过整理的形态]。还有,有时有必要让人清楚的看到表演性质的脸,还会发生由于大量的角色登场引起的负荷上升那样局面的情况,要根据情况强制性的切换高模或是低模。还有,脸上使用到了很多的多边形,本作中使用法术时结印的手指也分配到了相当精细的多边形。
【千里马肝注:临、兵、斗、者、皆、阵、烈、在、前。这九个字源于东晋葛洪的《抱朴子*登涉篇》,(葛洪者,乃东晋时结合儒家思想改造道教,宣扬采药炼丹、长生不老的人,至此,道教变成为封建统治服务的宗教)“临兵斗者,皆数组前行,常当视之,无所不辟。”  意思是说,常念这九个字,就可以辟除一些邪恶。在抄录这九个字时,把“数、组、前、行”误抄成“阵、列、在、前”而沿用至今。这九个字分别的意思是:临,代表身心稳定。兵,代表能量。斗,代表宇宙共鸣。者,代表复原。皆,代表危机感应。阵,代表心电感应或隐身。列,代表时空控制。在,代表对五元素的控制。(五元素:就是我们熟知的金、木、水、火、土。)前,代表光明。】
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/8000D42FA9A44E93B15A6AE23283AAA2.jpeg)
### 重视画面效果的生动配色阴影
      公司内部以前使用过的乘法CelShader绘制出皮肤的阴影部分的褪色效果是个难点见上图A。在本作中为了解决那个问题模仿了Photoshop的softlight图层效果开发出了CelShader见上图B。用这个方法虽然确实在皮肤的影子上能得到良好的结果但同时衣服等东西原本的色彩很高的部分影子也变的明亮结果作为影子就会出现破绽。于是开发出了新的彩度高的像素上使用乘算彩度低的像素上用softlight的把影子颜色在直线上混合描绘的shader实现了取得平衡的阴影见上图C。另外项目主管的西川裕贵先生表示因为这个shader保持相当的的彩度根据动画上经常用的色调也可以适用在阴影的表现上例如说把夜里的阴影做成青色时也很有用。
【千里马肝注:动画在绘制之前,会由一位经验丰富的美术绘制一套被称作“色彩指定”,简称“色指”的图片,其中对于角色的肤色、头发色、眼睛、服饰等的颜色进行规定,包括该部分在阴影下的颜色、高光的颜色等等。这样严格要求的结果是,当动画关键帧被绘制完成后,流水线上的美术必须严格按照色指进行上色,于是出来的动画效果就如同一个人画出来的一样。】
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/323A8D58845A421495538CCD55D2C04C.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/DCB6EECE80484ACBA8C6CC0C0F14A8EF.jpeg)
### 整理出动画风格的阴影
      虽然图A把高模做出了GlowShading但是如果使用这个光源设定进行CelShading光照会像图B那样忠实的表现出阴影来导致表情变的非常僵硬。这样就达不到动画片的效果所以需要像图C和D那样让模型膨胀再整理法线作成调整过的模型取得了法线信息后把那些适用在原模型上就可以再现出图E那样动画风格的柔软的表情。还有在实际主机上的再现是开发了专用的插件对应完成的。这个Shading技术可以说是初期研究开发阶段很大的一个成果。
【千里马肝注这种手法之前在《偶像大师1、2》中也出现过具体实现还是第一次见到。需要试验一下这种膨胀的方式当光源位置变化时会是什么效果
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/2DE1377209674C0F933535642A1953D8.jpeg)
### 必要的轮廓线(Outline)抽出法
      作为轮廓线的生成法从PS2的时候就是被经常使用的方法把模型按法线的方向推出很大的一圈再把多边形的法线反向作为轮廓的绘制手法图A。用这个方法因为对于全部的多边形都适用一样的处理头发等的轮廓也忠实的绘制出来表现出期望中的动画风格的质感。还有一种生成法一次性把模型渲染到2Dbuffer中进行post处理是一种抽出模型的轮廓silhouette的绘制手法图B。用这个手法虽然使用silhouette生成了简单的的轮廓线Outline但不能在silhouette的内侧生成。所以本作的Celshading上附加了轮廓线Outline选择的选项由于设计师会对应状况选择绘制方法实现了这两个手法的并用。
      
【千里马肝注图A的方法不用多说图B的方法可以将normal或depth渲染到rendertarget中然后进行Sobelfilter处理。作为卡通渲染有时的确不希望过多的细节被表现出来这里本作使用2种可选项是个好办法。更多的信息请见RenderMonkey中的NPR。】
### 动画中加入了其所特有的夸张
动画的深奥妙趣,就是充分利用省略和强调张驰。
那么如何通过CG来表现这些并灵活的运用呢
这里来披露一些吧。
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/66E0601B773C4C0897688A5B49D19040.jpeg)
### 索具(Rigging)的准备配置(Setup)
       角色的索具装配使用了Biped。骨骼的构成被称为全角色共通的基础的躯干部分有52个各种调整用的自由骨骼是22个面部是67个服装和头发等摇动的物体用的延伸骨骼是30到80个全部大概每个角色可以算出是170到220个。还有做成的动画数漩涡鸣人的情况在自由战斗时是125个极限任务模式里是90个。
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/173B3E3372CF466CBAF62D2B94B0A042.jpeg)
### 动画拖影AnimeBlur的实现
      作为动画片固有的速度表现速度过快的会有轮廓被拖长的情况。在本作中把三角锤的模型重叠图A,提前加入角色的末端部分图B,把骨骼拉出来图C实现了这个效果图D。在骨架上加入了弹簧控制根据动作的速度把拉出的长度自动的变化虽然做出的动作也有些和表演意图不同但为了取得平衡全部的人都加入了。
【千里马肝注: 关于弹簧控制,请搜索:虎克定律。】
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/D0BA456FAE4A4D8E8BD494880593C096.jpeg)
### 面部配置Setup的基础
       面部正如前述那样由67个骨骼控制图A。作为本作特有的设定下唇的下骨骼占到脸的三分只一。这是为了可以表现出即使是动画独特的张嘴下巴的位置也不会改变或是表现出图B那样脸有很大的扭曲。还有一看就明白的真实系的面部配置中大多被设定的脸颊或鼻根的骨骼极少。这样着色在平面上难以反映变化所以并不是要设定最低限度的骨骼而代替那些皱纹表现的要用专用的3D模型图C来实现图D。顺便说一句图C的两个白色圆板是眼睛的高光这样可以把皱纹以及高光从shader上分离容易按意图做出表情
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/0D143BF89E814B7DA84E3BFFE8BA9F94.jpeg)
### 动画独特的不合常态的关节
       图A作为人的动作当然相当的正确但是按动画来看就太过死板缺乏趣感。正是像图B那样柔软有弹性的形状变化才符合动画片。为了实现那些要用基础的骨骼图A制作动作后再把子阶层模型顶点网格通过自由骨骼对权重进行控制图C的红色部分这样就可以制作动画独特的柔软性。
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/2822B5D78276433D8CDAEF4A086D4395.jpeg)
### 大胆夸张的动画风格构图
       在实际的镜头中一旦让被摄影对象的人完全的进入画面怎样努力也如图A那样有透视的限制。所以为了得到如图B那样动画型的夸张的构图要通过Biped的骨架的辅助动画中加算上Scale让其在物理性上巨大化。
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/9B85F54DB0CB464690D1E2C433A5BDAF.jpeg)
### 面部的作业环境
       使用了以前的目标变形MorphTarget是因为要在本作中变更骨骼如图那样准备了选择器工作中可以迅速的对应新的手法图A。还有由于这个变更表情的数据库管理化就成了问题但是可以把表情的骨骼和标准的姿态的骨骼的差值做成文本档案化解决了输出脚本的制作问题。由于这样通过目标变形能够混合两个表情的同时还可以转移到拥有同一个骨架构造的其他角色上做出类似的表情对工作的效率化还有风格的统一等有很大的作用。
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/DA0CEEBCE9C2478DA77C99B1741522C5.jpeg)
### 背景的概要
      上图是战斗舞台的其中一个角色的移动可能范围按实际尺寸是直径50米最远景是500米左右大约用了5万多边形做成。担当背景制作的四所铁矢先生表示开发当初好像有40万到50万多边形一边要维持品质一边要把多边形消减到能够实时绘制处理的水平很辛苦呢
![《火影忍者:究级风暴》渲染技术究极解析](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/10CA18F8717F457CA88A232FB38E5A58.jpeg)
### 丰富的河流表现
       让水面上反射出美丽阳光的河流图A,如何能用一张平面的多边形作出来图B。使用的贴图有color图Cheight图Dnormal图Eshadow图F环境图G5个种类水的颜色在shader参数里设定在颜色上也可以增加高度的信息。环境贴图拥有alpha通道,加入了模拟的HDR信息图H绘制太阳一类的效果。像素描绘的结果会显出很强的GlareFilter效果花了很多工夫。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/9EFCA4B955C84964A830832998D90916.jpeg)
### 背景的贴图
      在贴图上使用了Albedo贴图图A和阴影贴图图B。根据适用的地方在2048X2048到256X256像素的范围内选择尺寸使用DXTC格式总计30MB的程度。还有用Photoshop的插件做成Mipmap来使用图C。虽然由于这些贴图的容量会增加33%但和没使用时比起来GPU的处理负荷减轻了接近一半那个效果是非常大的。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/212029DE080A4C709ACC71E7DC81E798.jpeg)
### 背景的作业环境
      公司内部在构筑开发环境上也倾注了全力在背景制作中使用了30台以上的wacom的21寸液晶绘图板。更有应该注意的是在负责人的头上有个防止面对监视器时映入灯光的特别定作的圆盘形的遮光罩。这是日产汽车和slik共同开发的产品由于这个抑制了照明实现了没有那么明亮的作业环境。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/NARUTO/04F65D2BA78243F0BE323125FB99F78F.jpeg)

View File

@@ -0,0 +1,27 @@
## 参考案例
https://www.bilibili.com/video/BV1tp4y1r7A7
Engine/Source/Runtime/LinvLinkInterface
## 添加数据类型
>在Roles文件夹中得到XXXType.h与XXXRole.h中增加自定义数据类型与Roles规则。
- FLiveLinkBaseStaticData静态变量
- FLiveLinkBaseFrameData每帧变量
- FLiveLinkBaseBlueprintData:蓝图数据,用于在蓝图中访问对应的数据。
## 添加规则
- GetDisplayName():定义自己的LiveLink名称。必须定义且唯一。
- GetXXXDataStruct():返回XXXType.h中定义的结构体。
- InitializeBlueprintData():初始化数据并且赋值到FLiveLinkBaseBlueprintData中设置的变量里。
## 编写LiveLinkProvider(数据发送端)
>在Provider中编写传递数据过程
## 编写LiveLinkController
IsRoleSupportedd():判断Role是否为指定Role,即判断数据为什么类型Role的数据。
Tick():编写每帧处理逻辑。
## 使用
在LiveLink管理器中连接Provider
在Actor中挂载LiveLinkController组件

View File

@@ -0,0 +1,26 @@
### 虚幻引擎4全平台热更新方案
https://www.bilibili.com/video/BV1ir4y1c76g
### 虚幻引擎中基于物理的动画
https://www.bilibili.com/video/BV1UV411a7Jq
主要讲解使用的计算功能与思路。
### 深入解读Niagara智能粒子效果
https://www.bilibili.com/video/BV1At4y1v7EQ
### 虚幻引擎的Niagara系统与实时VFX的演进Niagara功能节点介绍
https://www.bilibili.com/video/BV13541157SU
### 纯虚幻引擎流程的卡通烟雾特效
https://www.bilibili.com/video/BV1fV411a7Se
一句话是要用Niagara模拟粒子之后使用CaptureSceneRender获知速度、透明度、Alpha等信息的flip之后再制作粒子效果。
### 用虚幻示意图形UMGUI设计器优化并构建3A级UI
https://www.bilibili.com/video/BV1EK4y1j7VB
UMG的优化
### 用Unreal Insights收集、分析及可视化你的数据
https://www.bilibili.com/video/BV1Ay4y1q7Kj
### 利用虚幻引擎自动化框架进行性能测试
https://www.bilibili.com/video/BV1Ca4y1p7Bq

View File

@@ -0,0 +1,18 @@
## 视频地址
https://www.bilibili.com/video/BV19p4y167gh
## 为什么需要CL
- 多操作系统开发需要
- 构建与发布产品(不同环境)
- 构建DLL并且上传到哨兵程序中
- 构建自定义引擎
- 运行自动测试程序
- 检测代码与分支的有效性
- 为每个设置构建对应的版本
## CL工具
- TeamCity
- Jenkins
- Bamboo
## CD

View File

@@ -0,0 +1,40 @@
## 视频地址
https://www.bilibili.com/video/BV1ey4y1q7s4
## ComputerShadder
### 支持原子操作
InterLockedAdd(),Min()/Max(),Exchange()
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ComputeShaderAtomic.png)
### 线程同步
GroupMemoryBarrierWithGroupSync()![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ComputeShaderSync.png)
### Shader传入参数
- GroupThreadID(SV_GroupThreadID)
- GroupID(SV_GroupID)
- DispatchThreadID(SV_DispatchThreadID)
- GroupIndex(SV_GroupIndex)
可以在微软的文档中找到具体解释。
### ComputeShader对于PixelShader的优势
PixelShader只能处理当前ShaderComputeShader是任意位置可写。可以用于编写屏幕空间反射等需要将效果写入任意位置的效果。
可以更好地利用显卡的并线单元。使用二分法循环计算:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ComputShaderExposureCalculate.png)
共享内存:举个例子模糊、等需要多次采样各个像素的算法,使用共享内存就可以减少采样次数与消耗。
#### 工作组
工作组会影响共享内存大小,从而影响多次采样像素的效率。但也会影响线程同步,同步速度变慢,也会影响原子操作次数。
### PixelShader对于ComputeShader的优势
- PixelShader可以预加载贴图并且缓存UV使得读取贴图的效率会非常高。而ComputeShader需要计算出UV所以做不到这点。
- PixelShader支持FrameBuffer压缩减少带宽压力。
- PixelShader支持更多的贴图格式。
## ComputeShader优化技巧
- Optimizing Compute Shaders For L2 Locality Using Thread-Group ID Swizzling
- DirectCompute Programming Guild
- DirectCompute Optizations And Best Practices

View File

@@ -0,0 +1,42 @@
## 前言
视频地址https://www.bilibili.com/video/BV1Ge411N7Bm/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290
开启Strata的方式
- ProjectSettings- Enable Strata Materials
为了提高编译效率可以给ConsoleVariables.ini添加`r.D3D.ForceDXC=1`
有关材质函数的帮助可以参考 `Engine/Content/Functions/Strata`
## Strata
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231182809.png)
Fuzz用于模拟天鹅绒这种边缘会泛光的材质。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231212317.png)
这里的MFP指的是Mean Free Path平均自由路径下面这4个引脚控制SSS效果。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231211236.png)
使用下图节点实现透射率转化为MFP这主要是方便美术理解也因为MFP不是线性的关系所致。该节点通过计算BaseColor与TransmittanceColor来取得MFP与Thickness。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231212513.png)
## 混合方式
- Horizontal Mixing水平混合模式。使用Mask贴图混合也可以做到。
- Vertical Layering垂直混合模式。(使用Strata可以保证物理正确)
- ![500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231214110.png)
- Coverage Weight权重混合
- Thin Film
- ![500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231214554.png)
## Strata Tree
可以通过查看Strata材质组成构造。
其中好处就是在跨平台移植时可以方便得关掉一些材质功能,在保证大体效果接近的情况下进行优化。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231214901.png)
## 数据存储方式
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221231215557.png)
## debug与命令
勾选 `Show - Visualize - StrataMaterial`就可以查看占用数据。
r.Strata.Debug.VisualizeMode 1/2/3 可以切换不同的debug视图。

View File

@@ -0,0 +1,32 @@
## 前言
视频地址:
https://www.bilibili.com/video/BV1d841187Pt/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290
## 基础包拆分
- PrimaryAssetLabel
- ChunkIDs
- ObbFilter(Android)
UE默认拆分基础包的缺点
- 只拆分UAsset
- MetaData不会拆分仍在Chunk0
- Chunk中无法配置Non-Asset文件
- UE不支持IOS IPA包内PAK的过滤
## UnrealFileSystem
UE的热更新是基于Pak的热更新。
## Pak
在Cook完之后使用UnrealPak进行打包。
```cmd
UnrealPak.exe SAVE_PAK.pak -create=RESPONSE_FILE.txt -compress
```
### Mount
1. Pak需要挂载到游戏中才能使用。
2. Mount时可以设置Pak的优先级。
自动挂载目录:
1. Engine/Content/Paks
2. GAME_DIR/Content/Paks
3. GAME_DIR/Saved/Paks

View File

@@ -0,0 +1,52 @@
---
title: UE的多线程渲染
date: 2022-11-23 16:55:20
excerpt:
tags: Rendering
rating: ⭐⭐
---
视频https://www.bilibili.com/video/BV1sP4y117Dg/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290
# 并行渲染
因为一些平台的RHI指令需要等待驱动层返回才能继续执行所以会使用分离RHI线程的方式来解决。
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221123173341.png)
渲染线程会每帧的帧头处等待RHI线程执行结束。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221123173030.png)
## RHI
同时渲染线程会将这些RHI命令都打包在一起以链表的方式发送到RHI线程进行处理。其中FRHICommandListImmediate单例只存在一个只存放立即执行的RHI命令比如LockTextre2D子类的。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221123174116.png)
## 多线程翻译RHI指令
- IRHIComputeContext只有一些ComputeShader相关逻辑。
- IRHICommandContext继承自IRHIComputeContext其他更多GPU指令。
Command会以链表的方式被构建成CommandBuffer之后加入驱动层的Queue中。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221124105422.png)
## 线程同步
- 前端使用了TaskGraph System来同步主要通过设置Task与Task之间的条件来实现的。
- 后端使用Barrier、Fence、Semaphore等方法。
## MeshDrawCommand的并行过程
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221124112824.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221124113024.png)
## RDG并行渲染
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221124113241.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221124113657.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221124123846.png)
## 并行渲染Debug
- ByPass
- r.RHICmdBypass
- - forcerhibypass
- GRHISupportsRHIThread
- console variables =>r.metal.IOSRHIThread r.OpenGL.AllowRHIThread r.vulkan.RHIThread
- -rhithread -norhithread
- GRHISupportsParallelRHIExecute
- r.Vulkan.RHIThread >1

View File

@@ -0,0 +1,22 @@
## 时间与天气
首先得确定时间与天气(大气),可以制作一个多维度的表格作为参考,之后就可以在创作时选择接近环境氛围快速出效果了。
这里李文磊介绍了自己做一个渲染参数数轴,对比度、亮度、颜色=>渲染效果。
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%AF%B9%E6%AF%94%E5%BA%A6%E4%BA%AE%E5%BA%A6%E5%9D%90%E6%A0%87%E7%B3%BB.png)
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%AF%B9%E6%AF%94%E5%BA%A6%E4%BA%AE%E5%BA%A6%E9%A2%9C%E8%89%B2.png)
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%AF%B9%E6%AF%94%E5%BA%A6%E4%BA%AE%E5%BA%A6%E9%A2%9C%E8%89%B2%E4%B8%8E%E5%AF%B9%E5%BA%94%E6%B8%B2%E6%9F%93%E7%BB%93%E6%9E%9C.png)
每个突然结果上标注曝光度、主光角度、色温、饱和度以及天气。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221230212221.png)
甚至可以配上其他渲染参考
![800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%AF%B9%E6%AF%94%E5%BA%A6%E4%BA%AE%E5%BA%A6%E9%A2%9C%E8%89%B2%E4%B8%8E%E5%AF%B9%E5%BA%94%E6%B8%B2%E6%9F%93%E5%8F%82%E8%80%83.png)
## 曝光
曝光是确定时间之后第二确定的。但艺术创作需求很可能会是过曝或者欠爆的,所以不能直接依赖与自动曝光。
1、可以通过切换到BaseColorOnly模式下固有色亮度与灯管都会影响曝光测算暗部Ev与亮度Ev值之后再来调整自动曝光的范围。
2、如果真实情况得到想要的效果就可以使用曝光补偿与曝光补偿曲线。

View File

@@ -0,0 +1,46 @@
## 值得看的视频
- [UOD2022]UE5新材质系统-Strata
### 渲染
- [[多线程渲染]]
- [Rendering Dependency Graph解析]( https://www.bilibili.com/video/BV18K411Z7Jg/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- RDG底层原理以及调试工具介绍。
- [Deformer Graph](https://www.bilibili.com/video/BV18g411W7yh/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [[UE5新材质系统-Strata]]
- 移动端
- [UE 遮挡剔除技术改进及Nanite的Culling管线浅析](https://www.bilibili.com/video/BV1rR4y1Z7QW/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- 分析了UE4、UE5Nanite、与移动的剔除实现与改进方法。
- [GAUSSF: 移动端高性能及UE4插件方案](https://www.bilibili.com/video/BV1H14y1H7e5/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- 模仿FSR算法实现的移动端超采样算法。
- [极速光影-探讨《Racing Master》中的光影技术](https://www.bilibili.com/video/BV1sP4y1R7TV/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
### Gameplay
- [物理载具的网络同步](https://www.bilibili.com/video/BV1Ye4y1s7tT/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [不Mass怎么Meta](https://www.bilibili.com/video/BV13D4y1v7xx/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [从行为树到状态树](https://www.bilibili.com/video/BV1ed4y1b7Zk/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- 主要的作用是一个通用的状态机来适配MassAI因为行为树作为一个ActorComponent只能作用于Pawn。它可以在其他各个类中使用。也可以用来代替行为树。
- 不使用其他方案的原因
- 蓝图:复杂
- Ai行为树切换条件不明显
- 动画状态机:不能总览,需要深入每个层级才能了解细节。且该功能转为动画设计。
- ![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221128203900.png)
- [[UE热更新的原理与实现]]
### 关卡
[[气氛和调子-UE影调设计和实战解析]]
[UE5 Geometry Script的探索与应用](https://www.bilibili.com/video/BV1rR4y1o7Db/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
### 动画
[IKRig的应用价值](https://www.bilibili.com/video/BV17P411g7Qp/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
动画重定向、IK动画重定向以及相关应用场景介绍。
### 特效
[Niagara中的物理模拟](https://www.bilibili.com/video/BV1324y1m7nA/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
### 其他
- [主机游戏的跨平台开发与优化-《暗影火炬城》的主机开发经验](https://www.bilibili.com/video/BV1WD4y1e7N6/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- 开发主机游戏的相关经验。
- [低成本虚拟制片 Vol 3](https://www.bilibili.com/video/BV1UG411F79p/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- RealityCapture
- [初探RealityCapture摄影测量常用流程](https://www.bilibili.com/video/BV1k841187dX/?spm_id_from=333.788&vd_source=d47c0bb42f9c72fd7d74562185cee290)
- [从RealityCapture到虚幻引擎5(官方字幕)](https://www.bilibili.com/video/BV1sG4y137qi/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)

View File

@@ -0,0 +1,80 @@
# 蓝图
## 蓝图调试器
位于菜单栏-工具-调试-蓝图调试器,主要有变量信息以及调用堆栈。
可以开启异常时中断蓝图来定位异常位置。位于
- 编辑器偏好设置-通用-试验性功能-蓝图-异常时中断蓝图。
- DefaultEditorPerProjectUserSettings.ini
- `[/Script/UnrealEd.EditorExperimentalSettings]`
- `bBreakOnExceptions=True`
## 在蓝图中触发对应的情况
一些情况与数据很难马上出发,可以使用一下情况直接还原指定状态:
- UFUNCTION中添加**CallInEditor**
- 在蓝图函数中勾选编辑器中调用
- 使用编辑器的蓝图函数库
- Python
- Puerts Editor
## PrintString & AddOnScreenDebugMessage()
李米娜有一个`FName Key`选项可以指定一个Key这样就不会打印出大量数据只会显示当前数据。
# Logs
- Shipping 开启: bUseLoggingInShipping=true (adb log中不显示)启动参数-log:在命令行中显示log
- 修改Log的显示级别
- 控制台命令log LogName NewVerbosity:运行时修改Log的显示级别控制台命令:log global error
- 配置文件参数:DefaultEngine.ini:[Core.Log] global=error
- 启动参数:-ini:Engine:[Core.Log]:global=error
# VisualLogger
# 作弊管理器CheatManager
代码位于CheatManager.h/cpp
# 调试相机DebugCamera
- 快捷键
- 默认快捷键为~
- 可以在 Baselnput.ini中修改默认快捷键/或在Userlnput.ini中重载`[/Script/Engine.Playerlnput]`
- `+DebugExecBindings=(Key=Semicolon,Command="ToggleDebugCamera"`
- `+DebugExecBindings=(Key=Apostrophe,Command="ToggleForceDefaultMaterial")`
- 控制台命令ToggleDebugCamera
- 控制台命令Teleport:将角色移动到debug 相机注视处
- 控制台命令slomo 0.1:所有运行逻辑放缓10倍
# Obj
console命令推荐在打包阶段使用检查UObject的回收情况。具体可以参考UEngine::HandleObjCommand
# VisualStudio
可以通过设置强制不优化部分代码
- 独立c++文件:
- Checkout(使用P4或修改文件(使用git)C++文件会因为adaptive unity build的机制单独编译
- BuildConfiguration.xml:`<BuildConfiguration><bAdaptiveUnityDisablesOptimizations>true<|..>`
- Target.cs:bAdaptiveUnityDisablesOptimizations = true;
- 单个模块:
- 单个模块:
- BuildConfiquration.xml:`<ModuleConfiguration><DisableOptimizeCode><ltem>ModuleName</..>`
- Build.cs:OptimizeCode = CodeOptimization.InShippingBuildsOnly or CodeOptimization.Never
- Target.cs:DisableOptimizeCodeForModules数组
- 代码块:
- UE_DISABLE_OPTIMIZATION / UE_ENABLE_OPTIMIZATION
- UE_ENABLE_OPTIMIZATION_SHIP / UE_DISABLE_OPTIMIZATION_SHIP(shipping)
- Target.cs: UE_CHECK_DISABLE_OPTIMIZATION=1在打包机上设置避免包含未优化代码
## 跳过执行语句
- 拖动当前指示运行代码位置的黄色箭头。
- Ctrl+Shift+F10Set Next StateMent。
- 按住Ctrl+点击黄色箭头
## 查看其他模块的全局变量
在监视窗口中使用ModuleName!VariableName查看全局变量
- UnrealEditor-Core!GConfig
- UnrealEditor-Engine!GPlayInEditorContextString
- UnrealEditor-Core!GFrameCounter
![[VisualStudio_Watcher_Module.png|800]]
## Natvis
- Visual Studio Natvis框架可以自定义数据类型在调试器变量窗口中显示的方式
- Unreal的 Natvis实现:`Engine\Extras\VisualStudioDebugging\Unreal.Natvis`
- 安装方式:将Unreal.Natvis复制到`C:\Users\<user>\Documents|Visual Studio 2022\Visualizers\`路径下
- 修改立即生效
![[VisualStudio_Natvis.png|800]]

View File

@@ -0,0 +1,6 @@
- 《鸣潮》基于虚幻引擎4的多平台效果和性能优化实践 | 王宏波 库洛游戏:https://www.bilibili.com/video/BV1BK411v7FY/?spm_id_from=top_right_bar_window_dynamic.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
- UE4手机端 TAA鬼影解决
- VelocityBuffer渲染次数
- 鸣潮手机端GBuffer结构OnePass)
- 在虚幻引擎5中打造多层体积天空:https://www.bilibili.com/video/BV1d64y1P7Ef/?spm_id_from=top_right_bar_window_dynamic.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
- FSR3技术原理以及在虚幻引擎中的集成:https://www.bilibili.com/video/BV1xe411H71Q/?spm_id_from=top_right_bar_window_dynamic.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290

View File

@@ -0,0 +1,9 @@
# 其他有价值的分享
- 【[UFSH2024]《三角洲行动》,诠释新一代游戏开发方式 | 王理川 天美Y1工作室技术美术组负责人/专家】 https://www.bilibili.com/video/BV18F2oYAEp2/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
- TODO
- [ ] [UFSH2024]用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线 | 晨风 Neverwind 完美世界游戏 https://www.bilibili.com/video/BV1rW2LYvEox/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290
- [x] 【[UFSH2024]虚幻引擎4移动端渲染管线改造总结分享 | 陈勇 成都西山居 引擎组组长】 https://www.bilibili.com/video/BV18F2oYAES8/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
- [ ] 【[UFSH2024]关于程序化生成,我们还能做什么? | 周杰 徐凯鸣 腾讯IEG Global】 https://www.bilibili.com/video/BV1MP2oYhE6h/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
- [ ] 【[UFSH2024]虚幻引擎5移动渲染管线扩展与优化 | 张锦 永航科技 引擎组负责人】 https://www.bilibili.com/video/BV1mP2oY8EBh/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
- [ ] 【[UFSH2024]在虚幻引擎中使用移动端光追实现大世界阴影和环境光遮蔽效果 | 姚巍 陆星宇 腾讯游戏生态发展部】 https://www.bilibili.com/video/BV1mP2oY8Erq/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
- [ ] 【[UFSH2024]0A团队享3A开发 ——像大厂一样玩转LookDev 和CFX | 高颖俊 铃空游戏 首席TA管线TD】 https://www.bilibili.com/video/BV1Vry5Y7EZY/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e

View File

@@ -0,0 +1,16 @@
# 前言
【[中文直播]第56期 | 用Rider点燃你的UE开发之旅 | 孙涛 JetBrains(官方字幕)】 https://www.bilibili.com/video/BV1HAmRYjETW/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
# 好用的功能
- 调试
- **数据断点**:变脸改变时进行断点。
- ***方法断点***:打了断点后,可以输入函数名字,之后在运行到对应的函数后,命中断点。
- 断点数据过滤:可以在堆栈变量显示区域直接输入变量名进行过滤。
- 自定义调试符号服务器。
- 格式
- 自定义代码风格警告:右下角的铅笔可以设置需要提醒的类型。
- 格式识别右键显示更多选项Reformat and cleanup - Detect code settings。
- Rider Livelink安装插件后可以在运行的按钮边上的UE图标处设置运行方式。
- 实用插件
- EzArgs方便输入一些UE项目启动参数。输入过一次后面就可以点了。
- EnhancedUnrealengineDocumentation

View File

@@ -0,0 +1,212 @@
# 移动端SSR
## GBuffer
- SSR计算阶段需要的WorldNormal来自GBufferA,Roughness来自GBufferB
- SSR结果转换成间接高光需要的SpecularColor来自GBufferB和GbufferC
```c++
derived from BaseColor,Metalness,Specular
GBuffer.SpecularColor = ComputeF0(GBuffer.Specular,GBuffer.BaseColor,GBuffer.Metallic);
```
MobileBasePass片段着色器额外增加一张RT--OutColorAux,rgb通道存SpecularColor,a通道存Roughness
```c++
PIXELSHADER_EARLYDEPTHSTENCIL
void Main(
FVertexFactory InterpolantsVSTOPS Interpolants
, FMobileBasePassInterpolantsVSTOPS BasePassInterpolantts
, in float4 SvPosition:SV_Position
OPTIONAL_IsFrontFace
, out half4 OutColor : SV_Target0
, out half4 OutColorAux : SV_Target1
#if DEFERRED_SHADING_PATH
```
```c++
OutColorAux.rgb=ShadingModelContext.Specularcolor;
OutColorAux.a=GBuffer.Roughness;
```
- 其中SpecularColor初始化值为计算得到的间接高光(延迟管线是在混合阶段计算)
```c++
ShadingModelContext.SpecularColor = (DielectricSpecular -)
ShadingModelContext.SpecularColor = GetEnvBRDF(ShadingMode
```
## TemporalAAHistory
- TemporalAA多帧累积的SceneColor,用于射线RayCast交点的屏幕坐标采样获取颜色
- 降噪作用(SSR默认Quality每个像素打一根Ray)
- 移植DesktopTAA替换MobileTAA(详细见图像处)
## HZBFurthest
- 可以理解为场景深度的Mipmap用于RayMarching求交加速。
- 参考UE4.27移植(不是必须)
## SSR计算
- SpecularColor和Roughness从OutColorAux获取,WorldNormal通过SceneDepth重构获得
```c++
#if SHADING_PATH_DEFERRED
FGBufferData GBuffer = GetGBufferDataFromSceneTextures(UV
#else
FGBufferData GBuffer = (FGBufferData)0;
GBuffer.ShadingModelID
= SHADINGMODELID_DEFAULT_LIT;
GBuffer.WorldNormal = normalize(ReconstructNorma1FromDept
GBuffer.SpecularColor = MobileSceneColorAux.SampleLevel(
GBuffer.Roughness = MobileSceneColorAux.SampleLevel(Mobil
GBuffer.Depth = CalcSceneDepthByTexture(UV);
#endif
```
- SSR结果预乘SpecularColor,这样可以与SceneColorMobile做Blend One One混合
```c++
#if FEATURE_LEVEL == FEATURE_LEVEL_ES3_1
Outcolor.rgb*=GBuffer.SpecularColor;
#endif
```
## 结论
- 不足:重建的WorldNormal是面法线,没有延迟管线GBuffer的像素级法线,某些角度反射结果会有差异
- 早期的方案:OutColorAux的rg通道存WorldNormal,a通道存Roughness,b通道存SpecularColor的灰度值(某些光照环境下结果不准确,放弃)
# 实时平面反射
平面反射与DepthPass的兼容问题修改。
## 需求
在包含平面反射(裁剪平面开启)的场景中,开启Full Prepass的情况下,平面反射绘制的内容出现深度遮挡错误
**裁剪平面**
平面反射的裁剪平面(Global Clip Plane)用于保证绘制结果的正确--那些在反射相机的视锥体内,但在反射平面下方的物体不应该参与反射,需要被剔除掉
**硬件实现**
默认实现是通过SV_ClipDistance语义在模型的光栅化阶段剔除,但不是所有的平台都支持,移动平台BasePass走的是PS的clip剔除
## 实现方法
1. 自定义语义
DepthOnlyVS设置ClipDistance值,通过自定义语义插值传递给片段着色器DepthOnlyPS。
```c++
struct FDepthonlyVSTOPS
{
#if USE_PS_CLIP_PLANE
float OutclipDistance : OUTCLIPDIST;
#endif
```
2. PS剔除
在DepthOnlyPS中用clip函数剔除(负值表示在裁剪平面以下,会被剔除)
```c++
#if USE_PS_CLIP_PLANE
clip(Interpolants.OutClipDistance);
#endif
```
3. PS绑定条件
在移动平台,当场景包含平面反射的情况下走DepthOnlyPS
## 结论
- Depth Pass走PS会有额外开销,但仅限场景中包含平面反射的时候,其它情况下Depth Pass仍然可以不需要PS
- 另一种修改方案:当场景中包含平面反射的时候关闭Full Prepass,在某些效果依赖Full Prepass的时候不行(详细见接下来动态阴影分享)
# CSM策略优化
- 移除视锥外物体的阴影渲染
# ModulatedShadow渲染管线改造
## 需求
- 在角色展示时CSM阴影精度不够,阴影效果不稳定(ViewDependentWholeScene的阴影方式)
- 解决方案:Modulated Shadow具有独立的阴影视锥和阴影深度图区域,在阴影精度方面更有优势(Per-Object的阴影方式)
## 问题1:阴影重叠
- Modulated Shadow独立计算各个投影物体的阴影,与SceneColorMobile相乘输出,导致投影重叠区域多次与阴影颜色相乘被压黑
- 解决方案:增加一个中间RenderTarget,做为每个Per-Object阴影视锥的输出目标,最后对RT统一阴影颜色处理后,再与SceneColorMobile相乘输出
## 问题2:透明物体无阴影接收
- 阴影方式处理依赖场景深度,而透明物体不写场景深度
- 解决方案:透明物体的阴影Receive还是走CSM
## 问题3:在MobileBasePass之后计算
- 阴影信息需要早于MobileBassPass构造好,用于着色计算
- 考虑过的方案:MobileBassPass用上一帧的Modulated Shadow信息,但存在阴影拖影和透明物体阴影结合的问题
- 最终方案:参考UE4.27移植并开启Full Prepass,在MobileBassPass之前获得场景深度,构造好Modulated Shadow
## 结论
项目应用两套阴影方案:角色展示场景主要用Modulated Shadow,满足高精度的阴影效果需求,战斗场景用CSM,效率优先
# 特效性能 Separate Translucency透明渲染管线改进
修改方案:参考非移动端做透明渲染RenderPass与MobileBasePass分离。
1. SeparateTranslucencyColor
申请低分辨率的RenderTarget用于透明绘制,然后Upsample到SceneColorMobile上
2. SeparateTranslucencyDepth
SceneDepth需要跟SeparateTranslucencyColor做同比例Downsample
注意:SceneDepth的Resolve跨RenderPass渲染目标改变,基于Memoryless的SceneDepth获取方式需要改为从Texture中获取,比如:DepthFade,LookupDeviceZ
3. 重设SceneTextures
增加EMobileSceneTextureSetupMode去设置降分辨率后的SceneDepthTexture
```c++
(buseSeparateTranslucencyDepth && SceneContext.SeparaateTranslucencyDepth)
SceneTextureParameters.SceneDepthTexture=GetRDG(SceneContext.Separate
```
## 进一步需求
- 角色的透明材质不想受降透明分辨率影响
1.保证角色渲染的精度;2.透明和不透明的衔接部分因为精度不一样出现裂缝问题
## 解决方案
- 通过扩展材质上的SeparateTranslucency的勾选项灵活控制走Normal还是SeparateTranslucencyPass
## 与其他透明的乱序问题
- 通过角色透明写CustomeDepth,其他透明判断CustomDepth解决
## 结论
- 通过透明渲染分辨率可根据机型、画质和场景的不同灵活控制特效的GPU开销
- 透明渲染Pass拆分产生了更多的透明渲染乱序问题,通过设置Sort Priority,配合CustomDepth解决
- SceneCapture下的透明渲染:还是走Normal Translucency (SceneCapture需要使用alpha通道)
# 图像处理 Desktop Temporal AA移动端移植
## 需求
- MSAA对Depth Resolve支持的不好,无法满足渲染管线特性扩展的需求(SSPR、AO和Full Prepass等都需要Depth的Resolve)
- TemporalAA通过每帧使用不同的像素抖动Jitter,把采样结果分散到多帧,累积到History帧与当前帧混合,可以达到很好的抗锯效果
- MobileTAA只做两帧混合,抗锯效果不够,而且没有基于Velocity的像素修正,快速移动会有鬼影(Ghosting)
## 解决方案
**增加Velocity Pass**
- 可移动模型:更新Primitive的LocalToWorld和PreviousLocalToWorld
- 骨骼模型:需要上一帧的骨骼矩阵计算顶点位置,使用SRV Buffer代替Uniform Buffer传递骨骼变换矩阵(UniformBuffer有大小上限)
- VS计算出顶点当前和上一帧的屏幕空间坐标,传递给PS计算差值作为Velocity输出
```c++
// 3d velocity,includes camera an object motion
float3 Velocity = float3(ScreenPos - PrevScreenPos, DeeviceZ - PrevDeviceZ);
```
**Reprojection位置纠正**
- 在后期阶段,通过变换计算当前屏幕坐标在上一帧中的屏幕坐标,再结合Velocity数据对History帧做正确采样
```c++
float2 BackN = PosN.xy - PrevScreen;
BackN = DecodeVelocityFromTexture(EncodedVeloc:ty).xy;
HistoryScreenPosition = InputParams.ScreenPoS -BackN;
```
## 结论
- TAA整体抗锯效果最好,而且基于多帧的时间域实现,可以更好的与新渲染特性兼容,且对SSR起到降噪的作用
- TAA后期位置需放在Bloom之前,避免放在Bloom之后产生的flicker
- 在后期的末端加上FXAA,弥补TAA在后期管线上靠前对有些效果(比如AfterTonemap)抗锯不生效的问题
# 图像处理 Mobile FSR
## 具体方案
从UE5移植Mobile FSR 1.0主要包含针对移动端优化的Upscale和锐化两个Pass。
# 3D UMG
Canvas Panel 3D
- 继承自Canvas Panel,增加透视相关参数,比如Camera的Anchor、FOV和Panel的Rotation、Offset等参数,OnPaint时转换成ViewProjection矩阵传递给Widget
- 优点:1.顶点还是2D,没有增加渲染开销;2.可在编辑器实时预览3D效果
- 不足:只有Canvas的深度信息,无法做顶点级别的深度效果
## 场景交互3DUI
- 新增SeparateTranslucencyAfterPostProcess半透明材质标识,对应新增一个Mesh Pass和Processor收集此类型MeshBatch,放在Tonemap之后绘制
- 解决默认的WidgetComponent受到Tonemap影响带来的色差问题
- 本质上是Mesh的透明渲染,有顶点级别的深度信息,可以访问SceneDepth来实现场景交互
## 需处理的问题
- Tonemap之后绘制,需跳过MobileBasePass的颜色纠正和Android平台的Y轴翻转
- TAA早于TranslucencyAfterPostprocess Pass生效,在该Pass绘制前,View的投影矩阵像素Jitter需去掉(HackRemoveTemporalAAProjectionJitter)
## 结论
- Canvas Panel3D类型满足最常用的界面需求;TranslucencyAfterPostprocess类型满足与场景交互的界面需求
- 抗锯齿:Canvas Panel 3D类型通过带MSAA的中间RenderTarget方式处理;TranslucencyAfterPostprocess类型依靠后期末尾的FXAA
- 其他:使用第三方NiagaraUIRenderer插件来满足UMG上显示特效的需求

View File

@@ -0,0 +1,47 @@
- 【[UFSH2024]虚幻引擎中的DS服务器优化 | 陈杰 铃兰计划UE前端主程序】 https://www.bilibili.com/video/BV1uUy5YkEiN/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
# 拆分DS服务器
**拆分:**
单局游戏会有一个大地图的Server和若干个子地图
的Server。
**通信:**
**每个子地图Server都有TCP协议和大地图Server有实时数据的沟通。**
大地图Server是大场景,以PVP为主;子地图是小
场景,有更复杂的AI和物理行为,以PVE为主。
拆分成大致一下结构:
- LaunchServer
- ServerClient1
- MainMapServer1
- MainMapServer2
- MainMapServer3
- ServerClient1
- MainMapServer1
- MainMapServer2
- MainMapServer3
# 使用Fork部署Server
使用Fork启动Server可以节约性能
- 节约内存占用5% => 1.1%
- 启动速度提升200%
```c++
//DS服务器必须以单线程方式启动 添加命令行参数-nothreading强制单线程启动
FGenericPlatformProcess::EWaitAndForkResult tempResult = FUnixPlatformProcess::WaitAndFork();
switch (tempResult)
{
case FGenericPlatformProcess::EWaitAndForkResuult::Child
//*如果使用sigqueue,有效载荷int将被分成上下uint16值。上面的的值是"cookie"和
//*较低的值为"索引"。这两个值将用于使用模式DS-<cookie>-<index>命名进程。这个名字
//*如果提供-WaitAndForkCmdLinePath=/home/guoyuanhua/Server/ForkChildComd,子进程的命令行参数将用内容填充在
//其中子文件的"索引"是要在目录中读取的文件的名称。
//fork启动文件在/home/guoyuanhua/Server/a.out
//先启动DS服务器./DissidiaServer Main-nothreading Foo引用的目录中找到的文件的
//取命令行参数
//获取进程id FUnixPlatformProcess::GetCurrentProcessld();
}
```
# 整理并且标识同步规则

View File

@@ -0,0 +1,351 @@
地平线的世界设计
1、开方世界——玩家有很大的探索区域
2、昼夜循环
3、动态天气
4、史诗般的场景
5、天空是地形的一部分
•地平线是一个巨大的开放世界,你可以去任何你看到的地方,包括山顶。(我表示呵呵,这个已经被吐槽,真正做到的只有荒野之息了)
•由于这是一个活生生的现实世界,我们模拟地球的自转周期。
•天气是环境的一部分,因此它也会发生变化和演变。
•有很多史诗般的风景:高山,森林,平原和湖泊。
•天空是地平线的一个重要组成部分。他们占了屏幕的一半。天空也是故事和世界建筑的重要组成部分。
云的实现目标
1、美术方面的可操控性
2、真实性需要实现多种云的类型
3、与天气继承
4、可移动的
5、EPIC叫我干什么
早期的模拟方案:
1、使用流体解算器MAYA、houdini效果很好。但是工作室模拟云彩的经验比较少……说到底就是没钱请不来影视特效大佬
2、使用简单的几何体配合体素然后运行流体解算器直到他们像云。
3、然后我们开发了一种照明模型用于预先计算主和次散射。在Houdini完成解算需要10S时间
4、把云转换层多边形烘培了光照等数据。再使用Billboards————缺点无法轻易生成云阴影本人表示严重质疑
5、我们试着把我们所有的voxel云作为一个云来生产它也可以混入大气层。类型的工作。在这一点上我们后退一步来评估什么行不通。没有一个解决方案使云随着时间的推移而进化。没有一种好的方法可以使云层通过头顶。并且对所有方法都有很高的内存使用和透支。所以也许传统的基于资产的方法是不可取的。
6、于是开始考虑体素云了
7、所以我们进入了Houdini并从模拟的云形状中生成了一些3d纹理。使用Houdini的GL扩展我们构建了一个原型GL着色来开发云系统和照明模型。
8、这让我们看到了地平线的云系统。为了更好地解释我把它分成了四个部分:建模、照明、渲染和优化。在了解我们对云的建模之前,我们应该对云是什么以及它们如何演变成不同的形状有一个基本的了解。
9、There are many examples of real-time volume clouds on the internet. The usual approach involves drawing them in a height zone above the camera using something called fBm, Fractal Brownian Motion. This is done by layering Perlinnoises of different frequencies until you get something detailed.
•This noise is then usually combined somehow with a gradient to define a change in cloud density over height.
10、由于光噪声本身不能切割我们开发了自己的分层噪音。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/32C9E5FA800B471DAA04DDFC66AE5542.png)
Worley噪音是由Steven Worley在1996年引入的经常用于因果关系和水的影响。如果它是倒立的就像你在这里看到的:
它的形状非常紧密。
我们把它像标准的perlinfbm方法一样分层
然后我们用它来作为对dilate perlin噪声的偏移量。这使我们能够保持perlin噪声的连接但是增加了一些billowy的形状。
我们把这叫做“佩林-沃利”的噪音
11、在游戏中把声音存储为tiling 3d纹理通常是最好的。
•你想保持纹理读取到最低…
•尽可能小的保持决心。
•在我们的例子中我们已经压缩的声音…
•两个3 d纹理…
•和1 2 d纹理。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/C055723666034AE096C4B945D18D726B.png)
第一个3 d纹理…
•4个通道
•这是128 ^ 3决议…
第一个频道是我刚才描述的perlin- worley噪声。
另外3个是在频率增加的Worley噪声。和标准方法一样这个3d纹理用来定义云的基本形状。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/B89809EC8BAA4624B0F4ADDEDD88DB1D.png)
我们的第二个3 d纹理…
•有3个频道…
•它是32 ^ 3决议…
•使用Worley噪音来增加频率。这个纹理被用来向第一个3d噪音定义的基本云形状添加细节。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/7D9FC6AB9E1B4C679B6C79627DCAFE64.png)
•我们的2 d纹理…
•有3个频道…
•这是128 ^ 2分辨率…
•并使用curl噪音。它不是发散的是用来假装流体运动的。我们使用这种噪音来扭曲我们的云形状并增加气流的感觉。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/C6719605F51941139F16BAD78E97E0C4.png)
回想一下,标准解决方案要求高度梯度来改变高度的噪音信号。相反,我们使用…
•3数学预设,代表了主要的低空…
•当我们在样本位置混合时,云的类型。
•我们也有一个值告诉我们希望在样本位置上有多少云覆盖。这是0和1之间的值。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/1AD13B896C2544DBB2DAC66417C158A8.png)
我们在屏幕右边看到的是一个大约30度的视图。我们将会在相机上方的一个区域中按标准方法绘制云。
首先我们建立一个基本的云形状通过采样我们的第一个3d纹理并将它乘以我们的高度信号。
•下一步是将结果乘上覆盖范围,并减少云层底部的密度。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/229424181EA1428C901DB709DF5DBFE6.png)
使用2D燥波增加基础云的细节
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/B5B4D5E3DD0B4C5496A51E591CABDDBD.png)
•通过在云边缘减去第二个3d纹理来侵蚀基本的云形状。小提示如果你在云的基础上颠倒了Worley的噪音你会得到一些很好的声音形状。
•我们也扭曲第二旋度由2 d纹理噪声假纠结的扭曲的大气湍流可以看到…
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/41DD539373F943D28BBDC56D69B5F70F.png)
在这个图像中你可以看到一个小地图在左下角。这代表了在我们的世界地图上驱动云层的天气设置。您所看到的粉红色白色图案是来自我们的天气系统的输出。红色是覆盖层,绿色是降水,蓝色是云类型。天气系统通过模拟在游戏中进行的模拟来调节这些通道。这里的图像有积雨云直接在头顶(白色)和普通积云在远处。我们有控制偏见的方法来保持事物在一般意义上是直接的。
其实就是通过贴图来控制雨云的分布
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/57857EB5097C4544A78B8A4384CE4E4A.png)
我们还利用我们的天气系统来确保云是地平线,这是很有趣的。
•我们画的cloudscape withina球员…周围半径35000米。
•和从15000米的距离…
•我们开始向积云过渡到50%的覆盖率。
总结
所以总结我们的建模方法…
我们遵循标准的ray - march / sampler框架
但是我们用两个层次的细节来构建云
低频率的云基形状
高频细节和失真
我们的噪音是由Perlin、Worley和Curl杂音制成的
我们为每个云类型使用一组预先设置来控制在高度和云覆盖上的密度
这些都是由我们的天气模拟或定制纹理来驱动的,用于使用剪切场景
它在给定的风向中都是动态的。
云的定向散射或发光质量…
•银衬里当你看向太阳通过云…
当你从太阳中移开时,云上的暗边可以看到。
前两个有标准的解决方案,但第三个是我们必须解决的问题。
在光线进入云再到结束,要么被吸收、要么色散
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/45AD92FC56E94A48A02E4CFF452C11DE.png)
Beer's law说我们可以根据所经过的介质的光学厚度来确定光的到达点。根据比尔斯定律我们有一种基本的方法来描述云中的特定点的光量。
•如果我们用能量来替代云中的透光率,并把它画出来,你就可以看到能量指数的下降。这是我们照明模型的基础。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/CECBE3176ED0405185377E0DA67CB4C0.png)
在云层中,有更高的光散射的可能性。这被称为各向异性散射。
在1941年henyey - greenstein模型被开发用来帮助天文学家在星系尺度上进行光计算但今天它被用于在云照明中可靠地复制各向异性。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/DCCE3D5B67DB4F45AF5098B742371AD1.png)
每次我们采样光能量时我们用henyey - greenstein相函数乘以它。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/CDF9EA6FEA9347CE9D8AD1ADD7ACBE88.png)
在这里你可以看到结果。左边的只是我们照明模型的Beers law部分。在右边我们应用了henyey - greenstein相函数。请注意在右边的太阳周围的云层更明亮。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/671F3D41178E45C5BE7ED6753DA19EE6.png)
当我们在云上越走越深时,我们在散射上的潜力就会增加,更多的信息将会到达我们的眼睛。
•如果你把这两个函数得到描述这样的东西…
•效应以及传统方法。
我还在ACM数字图书馆寻找啤酒粉的近似方法但我还没有发现任何与这个名字有关的东西。
将Power公式与Beer's law相结合得到了Beer's power
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/996CDD462EAE44A0B81AF08E2F1E61BB.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/3C34E5D14A164FB1A2A5EC0EAC226033.png)
但我们必须记住这是一个依赖于视图的效果。我们只看到我们的视图矢量接近光矢量所以Power函数也应该解释这个梯度。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/4BA5F7C597C3427D9B88C1997A362875.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/0F8D74F9099449409DA027F8E8433F64.png)
So, in review our model has 3 components:
•Beers Law
•Henyen-Greenstein
•our powder sugar effect
•And Absorption increasing for rain clouds
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/0D33BE22800A49659E2B3F1846C0CCC3.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/693A2257D0094575A428827A016248A2.png)
低海拔1500~4000用体素云。超过4000就用2D ray march云
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/C33B04DE3E5D4F42ABE11A792C015432.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/EE882FE8787B4926B7FC23B50E3E843E.png)
回想一下,采样器有一种低细节的噪声,它能形成基本的云形状
•高细节的噪音,增加我们所需要的现实细节。
高细节的噪声总是应用于从基云形状边缘的侵蚀。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/5682BFB95ED249878E6C9AD2825B4A67.png)
这意味着我们只需要做高细节的噪声和所有相关的指令,其中低细节的样本返回一个非零的结果。
•这就产生了在我们的云所处的区域周围产生一个等表面的效应。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/E1A53D8D6463443A88F411BB0EBA42F1.png)
因为射线长度增加我们看向地平线,我们开始…
•一个初始potential64样本和结束…
•潜在的128位。我之所以说潜力是因为这些优化可能导致3月份提前退出。我们真的希望他们能做到。
这就是我们如何利用样本来建立我们图像的alpha通道。为了计算光强度我们需要取更多的样品。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/B558F80C8F9A466995AB61AF820A9363.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/4D891FCBA47D4146BE72624E3B2BC16D.png)
In our approach, we sample 6 times in a cone toward the sun. This smooth's the banding we would normally get with 6 simples and weights our lighting function with neighboring density values, which creates a nice ambient effect. The last sample is placed far away from the rest …
•in order to capture shadows cast by distant clouds.
自阴影算法…………

View File

@@ -0,0 +1,494 @@
## 1.1.1 auto笔记
### auto与引用作用则不会抛弃const
```
int x=0;
const auto& g=x; //g->const int&
auto& h =g; //h->const int&
```
### 什么时候用auto
比如在使用迭代器遍历的时候,对类型及获取并不关心的时候。
### auto推断模板函数对应函数返回值
```
struct Foo {
static int get(void) { return 0; }
};
struct Bar {
static const char* get(void) { return "0"; };
};
struct Bar2 {
};
template <class A>
void func(void)
{
auto val = A::get();
}
int main()
{
func<Foo>();
func<Bar>();
func<Bar2>();//Bar2类中不包含get方法此时编译器会报错
system("pause");
return 0;
}
```
### c++14 增加了函数返回值推导
在C++11下如果你想要打印出一个数的平方可能需要这样
```
auto square_int = [](int x) { return x * x; };
auto square_double = [](double x) { return x * x; };
std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;
```
为了保持函数的局部性我们才选择的lambda但C++11的lambda却导致多个类型时代码膨胀且重复此时我们需要回过头来借助全局的模板了。
但C++ 14可以完美的解决上面的问题因为C++14中lambda的参数可以用auto代替具体的类型
```
auto square = [](auto x) { return x * x; };
std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;
```
## 1.1.2 decltype
### decltype推导规则
decltype(exp)
1. exp是标识符、类访问表达式decltype(type)和exp类型一样。
2. exp是函数调用decltype(exp)和返回值的类型一致。
3. 其他情况若exp是一个左值则decltype(exp)是exp类型的左值引用否则和exp类型一致。
```
class Foo{
static const int Number=0;
int x;
};
int n=0;
volatile const int &x=n;
decltype(n) a=n; //a->int
decltype(x) b=n; //b->const volatile int &
decltype(Foo::Number) c=0; //c->const int
Foo foo;
decltype(foo.x) d=0; //d->int
```
```
int& func_int_r(void);
int&& func_int_rr(void);
int func_int(void);
const int& func_cint_r(void);
const int&& func_cint_rr(void);
const int func_cint(void);
const Foo func_cfoo(void);
int x=0;
decltype(func_int_r()) a1=x; //a1->int &
decltype(func_int_rr()) b1=x; //b1->int &&
decltype(func_int()) c1=x; //c1->int
decltype(func_cint_r()) a2=x; //a2->const int &
decltype(func_cint_r()) b2=x; //b2->const int &&
decltype(func_cint()) c2=x; //c2->int & 因为是纯右值所以舍弃掉cv符只有类类型可以携带
decltype(func_cfoo()) ff=foo(); //ff->const foo
```
```
struct Foo{int x;};
const Foo foo=Foo();
decltype(foo.x) a=0; //a->int
decltype((foo.x)) b=a; //b->const int &
int n=0,m=0;
decltype(n+m) c=; c=0; //c->int
decltype(n+=m) d=c; d=c; //d->int &
```
a和b的结果仅仅多加了一对括号他们得到的类型却是不同的。具体解释请见P12
++++
### decltype实际应用
具体解释请见P12
## 1.1.3 返回类型后置语法——auto和decltype结合使用
如何实现类似:
```
template <typename R,typename T,typename U>
R add(T t,U u)
{
return t+u;
}
int a=1;floatb=2.0;
auto c=add<decltype(a+b)>(a+b);
```
错误,定义返回值时参数变量还不存在
```
template <typename T,typename U>
decltype(t+u) add(T t,U u)
{
return t+u;
}
```
正确,但是如果T和U是无参构造函数的类就不行了
```
template <typename T,typename U>
decltype(T()+U()) add(T t,U u)
{
return t+u;
}
```
使用c++返回类型后置语法
```
template <typename T,typename U>
auto add(T t,U u)->decltype(t+u)
{
return t+u;
}
```
另一个例子,根据使用的重载函数推导返回类型
```
int& foo(int& i);
float foo(float& f);
template<typename T>
auto func(T& val)-> decltype(foo(val)
{
return foo(val);
}
```
### 1.2.2 模板的别名
using和typedef都可以定义一个新的类型别名但是using可以直接定义一个模板别名而不像typedef需要一个struct 外敷类来实现。而且using的可读性更高。
```
template <typename T>
struct func_t{
typedef void (*type)(t,t);
};
func_t<int>::type xx_1;
template<typename T>
using func_t=void(*)(T,T);
func_t<int> xx_2;
```
另外using可以定义任意类型的模板表达方式
```
template<typename T>
using type_tt = T;
type_tt<int> a;
```
### 1.3.2初始化列表的使用细节
初始化列表可以用于对应类型函数的返回值中。
例如就等于Foo123,321.0假设有Foointfloat这个构造函数
```
Foo function(void)
{
return {123,321.0};
}
```
### 1.3.3初始化列表
自己定义的类并没有类似Vector、Map容器的任意长度的初始化功能如果想要实现可以添加一下构造函数来实现。
```
class Foo
{
public
Foo(std::initializer_list<int> list){
}
};
```
之后就可以执行Foo foo={1,2,3,4,5,6,7}(需要自己实现list对类内部变量的赋值)
同时std::initializer_list可以用于传值
```
void func(std::initializer_list<int> l){
for(auto it=l.begin();it!=l.end();++it)
{std::cout<<*it<<std::endl;}
}
int main()
{
func({});
func({1,2,3})
}
```
PS.std::initializer_list为了提高初始化效率使用的是引用传值所以当传值结束时这些里面的变量也都被销毁了。所以如果需要传值这需要传递它初始化容器的值。
初始化列表同时可以用于检查类型收窄,即因为类型转换而导致的数据丢失。
### 1.4.1基于范围的for循环
当遍历需要修改容器中的值时,则需要使用引用:
```
for(auto& n: arr)
{
std::cout<<n++<<std::endl;
}
```
如果只是希望遍历而不希望修改可以使用const auto& n
### 1.4.2基于范围的for循环使用细节
在:后面可以写带返回遍历对象的函数,而且这个函数只执行一次。
```
#include <iostream>
#include <vector>
std::vector<int> arr={1,2,3,4,5,6};
std::vector<int>& get_range()
{
std::cout<<"get_range->"<<std::endl;
return arr;
}
int main()
{
for(auto val: get_range())
{
std::cout<<val<<std::endl;
}
return 0;
}
```
在使用基于范围的for循环时如果遍历时修改容器大小很可能出现因为迭代器失效而导致的异常情况。
### 1.4.3 让基于范围的for循环支持自定义类型
```
#include <iostream>
namespace detail_range {
template <typename T>
class iterator {
public:
using value_type = T;
using size_type = size_t;
private:
size_type cursor_;
const value_type step_;
value_type value_;
public:
iterator(size_type cur_start, value_type begin_val, value_type step_val)
:cursor_(cur_start), step_(step_val), value_(begin_val)
{
value_ += (step_*cursor_);
}
value_type operator*() const { return value_; }
bool operator!=(const iterator& rhs) const
{
return (cursor_!=rhs.cursor_);
}
iterator& operator++(void)
{
value_ += step_;
++cursor_;
return (*this);
}
};
template <typename T>
class impl {
public:
using value_type = T;
using reference = const value_type&;
using const_reference = const value_type&;
using iterator = const detail_range::iterator<value_type>;
using const_iterator= const detail_range::iterator<value_type>;
using size_type = typename iterator::size_type;//这里的typename是告诉编译器后面跟着的是类型
private:
const value_type begin_;
const value_type end_;
const value_type step_;
const size_type max_count_;
size_type get_adjusted_count(void) const
{
if (step_ > 0 && begin_ >= end_)
throw std::logic_error("End value must be greater than begin value.");
else if (step_ < 0 && begin_ <= end_)
throw std:: logic_error("End value must be less than begin value.");
size_type x = static_cast<size_type>((end_ - begin_) / step_);
if (begin_ + (step_*x) != end_)
++x;//为了防止出现类似{0,12,0.5}之类的迭代要求,而做的升位处理
return x;
}
public:
impl(value_type begin_val, value_type end_val, value_type step_val)
:begin_(begin_val),
end_(end_val),
step_(step_val),
max_count_(get_adjusted_count())
{}
size_type size(void) const
{
return max_count_;
}
const_iterator begin(void) const
{
return{ 0,begin_,step_ };
}
const_iterator end(void) const
{
return{ max_count_,begin_,step_ };
}
};
template <typename T>
detail_range::impl<T> range(T end)
{
return{ {},end,1 };
}
template <typename T>
detail_range::impl<T> range(T begin, T end)
{
return{ begin,end,1 };
}
template <typename T,typename U>
auto range(T begin, T end, U step)->detail_range::impl<decltype(begin + step)>
{
using r_t = detail_range::impl<decltype(begin + step)>;
return r_t(begin, end, step);
}
}
int main()
{
for (int i : detail_range::range(15))
{
std::cout << " " << i;
}
std::cout << std::endl;
for (auto i : detail_range::range(2,8,0.5))
{
std::cout << " " << i;
}
std::cout << std::endl;
system("pause");
return 0;
}
```
### 1.5.2 可调用对象包装器———std::function
用于解决以下可调用对象的统一操作的问题
1. 函数指针
2. 具有operator() 成员函数的类对象(仿函数)
3. 可被转化为函数指针的类对象
4. 类成员(函数)指针
std::function也可以作为函数参数来使用也就是相当于回调函数的作用
### 1.5.3 std::bind绑定器
std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以用std::function进行保存并延迟调用到任何我们需要调用的时候。
作用:
1. 将可调用对象与其他参数一起绑定成一个仿函数
2. 将多元可调用对象转成一元或者(n-1)元可调用对象,即只绑定部分参数。
关于bind的使用
bind&要调用的函数,&对象, 要调用函数的参数1要调用函数的参数2...,_1(bind函数的参数1),_2(bind函数的参数2)...)
如果bind的是一个非静态成员函数第二个参数一定是一个该成员的一个指针后面才是正常的参数。
数this指针作为一个隐含参数传递给非静态成员函数用以指向该成员函数所属类所定义的对象。当不同对象调用同一个类的成员函数代码时编译器会依据该成员函数的this指针所指向的不同对象来确定应该一用哪个对象的数据成员。下面举一个简单例子。
1bind&f)() 假设f是一个全局函数绑定全局函数并调用
2bind (&A::f, A())() 假设A是一个构造函数为空的类这个形式绑定了类的成员函数故第二个参数需要传入一个成员成员静态函数除外
### 1.6 lambda表达式
```
[capture](params)opt->ret{body;};
auto f=[](int a)->int {return a+1;};
```
c++11允许省略lambada表达式的返回值定义
```
auto x1=[](int i) {return i;};
auto x2=[](){return {1,2};); //error:初始化列表无法推断
```
在没有参数列表时,()是可以省略的
```
auto f1=[]{return 1;};
```
```
class A{
public:
int i_=0;
void func(int x,int y)
{
auto x1=[]{return i_;}; //error,没有捕获外部变量
auto x2=[=]{return i_+x+y;}; //ok
auto x3=[&]{return i_+x+y;}; //ok
auto x4=[this]{return i_;}; //ok
auto x5=[this]{return i_+x+y;}; //error,没有捕获x与y
auto x6=[this,x,y]{return i_+x+y;};//Ok
auto x7=[this]{return i_++} //ok
}
};
int a=0,b=1;
auto f1=[]{return a;}; //error没有捕获外部变量
auto f2=[&]{return a++;}; //ok
auto f3=[=]{return a;}; //ok
auto f4=[=]{return a++;}; //error, a是以复制方式捕获的无法修改
auto f5=[a]{return a+b;}; //error,没有捕获b变量
auto f6=[a,&b]{return a+(b++);};//ok
auto f7=[=,&b]{return a+(b++);};//ok
```
需要注意的是默认状态下lambda表达式无法修改通过复制方式捕获的外部变量如果希望修改需啊需要使用引用方式来捕获。
```
int a=0;
auto f=[=]{return a;};
a+=1;
std::cout<<f()<<std::endl;
```
在以上lambda表示式中修改外部变量并不会真正影响到外部我们却仍然无法修改他们。
那么如果希望去修改按值捕获的外部变量就需要显式致命表达式为mutable:
```
int a=0;
auto f1=[=]{return a++;}; //error
auto f2=[=]()mutable{return a++;}; //ok,mutable
```
需要注意的是被mutable修饰的lambda就算没有参数也需要写参数列表。
我们可以使用std::function与std::bind来操作lambda表达式另外对于没有捕获任何变量的lambda表达式还可以被转化为一个普通的函数指针
```
using func_t=int(*)(int);
func_t f=[](int a){return a;};
f(123);
```
lambda表示式可以基本代替std::function(一些老式库不支持lambda)
### 1.7 tupe元组
```
tuple<const char*,int>tp=make_tuple(sendPack,nSendSize);//构建一个tuple
```
使用tuple相当于构建一个对应的结构体<br>
可以使用std::tie来进行解包来获取对应的数值,也可以用来创建元组。
```
int x=1
int y=2;
string s="aa";
auto tp=std::tie(x,s,y);
//tp是实际类型是std::tuple<int&,string&,int&>
const char* data=tp.get<0>();
int len=tp.get<1>();
int x,y;
string a;
std::tie(x,a,y)=tp;
```
你可以使用std::ignore占位符来表示不解某个位置的值。
```
std::tie(std::ignore,std::ignorey)=tp;//只获取了第三个值
```

View File

@@ -0,0 +1,65 @@
### 7.1 委托构造函数和继承构造函数
#### 7.1.1 委托构造函数
委托构造函数允许在同一个类中一个构造函数可以调用另一个构造函数,从而可以在初始化时简化变量的初始化<br>
```
class class_c
{
public:
int max;
int min;
int middle;
class_c(int my_max) { max = my_max > 0 ? my_max : 10; }
class_c(int my_max, int my_min) :class_c(my_max) {
min = my_min > 0 && my_min < max ? my_min : 1;
}
class_c(int my_max, int my_min, int my_middle) :class_c(my_max, my_min) {
middle = my_middle<max&&my_middle>min ? my_middle : 5;
}
};
```
#### 7.1.2 继承构造函数
让派生类直接使用基类的构造函数
```
struct A:B
{
using B::B;
}
```
这个特性对普通函数也有效
### 7.2 原始字面量
```
string str="D:\A\B\test.test";
string str1="D:\\A\\B\\test.test";
string str2=R"()";
//输出
D:AB est.test
D:\A\B\test.test
D:\A\B\test.test
```
可以通过R直接得到原始意义的字符串只有()中的才有效果
### 7.3 final和override
final用来限制某个类不能被继承或者某个虚函数不能被重写
```
struct A{
virtual void foo() final;
}
struct B final:A{
}
```
override确保在派生类中声明的重写函数与基类的虚函数有相同的签名一样的形参防止写错同时也表明将要重写基类的虚函数还可以防止因疏忽把本来想重写积累的虚函数声明承载
### 7.4 内存对齐
#### 7.4.1 内存对齐
主要讲了一些概念以及msvc与gcc的内存对齐机制区别
http://blog.csdn.net/markl22222/article/details/38051483
我们经常会看到内存对齐的应用是在网络收发包中。一般用于发送的结构体都是1字节对齐的目的是统一收发双方可能处于不同平台之间的数据内存布局以及减少不必要的流量消耗。
#### 7.4.2 堆内存的内存对齐
msvc下使用_aligned_malloc使用默认的对齐大小32位是8字节64位是16字节当然也可以自己实现这里提供一个案例。
#### 7.4.3 利用alignas指定内存对齐大小
#### 7.4.4 利用alignof和std::alignment_of获取内存对齐大小
#### 7.4.5 内存对齐的类型std::aligned_storage
### 7.5 c++11新增便利算法

View File

@@ -0,0 +1,508 @@
## 使用c++11消除重复提高代码质量
### 3.1 type_traits———类型萃取
type_traits提供了丰富的编译期计算、查询、判断、转换和选择的帮助类在很多场合都会使用到。
### 3.1.1 基本的type_traits
##### 1.基本的type_traits<br>
无需自己定义static const int或者enum类型只需要从std::integral_constant派生
```
template<typename Type>
struct GetLeftSize : std::integral_constant<int,1>
```
之后用GetLeftSize::value来取值。
##### 2.类型判断的type_traits
这些从std::integral_constant派生的type_traits,可以通过std::is_xxxx来判断T的类型。也可以通过std::is_xxxx::value是否为true来判断模板类型是否为目标类型。
```
#include <iostream>
#include <type_traits>
int main(){
std::cout<<"is_const:"<<std::endl;
std::cout<<"int:"<<std::is_const<int>::value<<std::endl;
std::cout<<"const int:"<<std::is_const<const int>::value<<std::endl;
std::cout<<"const int&:"<<std::is_const<const int&>::value<<std::endl;
std::cout<<"const int*:"<<std::is_const<const int*>::value<<std::endl;
std::cout<<"int* const:"<<std::is_const<int* const>::value<<std::endl;
}
```
##### 3.判断两类型之间的关系traits
```
struct is_same;//判断两类型是否相同
struct is_base_of;//判断一个是否为另一个的基类
struct is_convertible;//判断模板参数类型能否转化为后面的模板参数类型
```
##### 4.类型转换traits
常用的类型的转换traits包括对const的修改————const的移除与添加引用的修改————引用的移除与添加数组的修改和指针的修改。<br>
创建对象时需要去除引用:
```
template<typename T>
typnename std::remove_reference<t>::type* Create()
{
typedef typename std::remove_reference<t>::type U;
return new U();
}
```
模板参数T可能是引用类型而创建对象时需要原始的类型不能用引用类型所以需要将可能的引用引用。同样const也需要考虑进去。<br>
为了简洁可以使用std::decay,移除const与引用。<br>
另一个用处就是用来保存函数指针。
```
{
using FnType=typename std::decay<F>::type;
SimpFunction(F& f):m_fn(f){
}
void run()
{
m_fn();
}
FnType m_fn;
}
```
### 3.1.2根据条件选择traits
```
template<bool B,class T,class F>
struct conditional;
typedef std::conditional<true,int,float>::type A;//int
```
### 3.1.3获取可调用对象返回类型的traits
一些模板函数的返回值不能直接确定虽然可以通过decltype来进行推断但是可读性较差。<br>
可以通过auto -> decltype进行和简化。
```
template<typename F,template Arg>
auto Func(F f,Arg arg)->decltype(f(arg))
{
return f(arg);
}
```
不过在类没有构造函数的情况下就不行了。此时如果希望编译器进行推导就需要使用std::declval。
```
decltype(std::declval<int>(std::declval<int>())) i=4;
```
declval获得的临时值引用不能用于求值因此需要使用decaltype来推断出最终值。<br>
当然可以用更加简洁的方法:
```
std::result_of<A(int)>::type i=4;
```
### 3.1.4 根据条件禁用或者启用某种或者某些类型traits
```
template<class T>
typnename std::enable_if<std::is_arithmetic<T>::value,T>::type foo(T t)
{
return t;
}
auto r=foo(1);
auto r1=foo(1.2);
auto r2=foo("test");//error
```
不但可以作用于返回值,也可以作用于模板定义、类模板特化和入参类型的限定。
## 3.2 可变参数模板
在c++11中允许模板定义中包含0到任意个数模板参数。声明可变参数模板需要在typename或class后面带上省略号
省略号作用有两个:
1. 声明一个参数包这个参数包中可以包含0到任意个模板参数。
2. 在模板定义的右边,可以将参数包展开成一个一个独立参数。
### 3.2.1 可变参数模板函数
```
template<class... T>
void f(T... args)
{
std::cout<<sizeof...(args)<<std::endl;
}
f(); //0
f(1,2); //2
f(1,2.5,""); //3
```
1. 递归函数方式展开参数包
```
void print()
{
std::cout << "empty" << std::endl;
}
template<class T, class... Args>
void print(T head, Args... rest)
{
std::cout << "parameter" << head << std::endl;
print(rest...);
}
int main()
{
print(1,2,3,4,5);
return 0;
}
```
当然递归终止函数也可以写成,或者使用更多参数
```
template<typename T,typename T1>
void print(T t,T1, t1)
{
std::cout<<t<<""<<t1<<std::endl;
}
```
2. 逗号表达式和初始化列表方式展开参数包
逗号运算符:<br>
运算符有左右两个表达式,先计算左侧的,之后丢弃结果再计算右侧。
```
d=(a=b,c);
```
b会先赋值给a接着括号中的逗号表达式将会返回c的值。
```
template <class T>
void printarg(T t)
{
std::cout<<t<<std::endl;
}
template <class ...Args>
void expand(Args... args)
{
int arr[]={(printarg(args),0)...};
}
expand(1,2,3,4);
```
这里使用了c++11的初始化列表特性{(printarg(args),0)...}将会展开成{(printarg(args1),0),(printarg(args2),0),(printarg(args3),0),(printarg(args4),0),}<br>
之后也可以使用std::initializer_list来优化这样就无需定义一个数组了使用lambda的话printarg函数就无需定义了。
### 3.2.2可变参数模板
std::tuple是一个可变参数模型
```
template<class... Type>
class tuple;
```
1. 模板递归和特化方式展开参数包
```
//前向声明
template<typename... Args>
struct Sum;
//基本定义
template<typename First,typne... Rest>
struct Sum<First.Rest...>
{
enum{value=Sum<First>::value+Sum::<Rest...>::value};
};
//递归终止
template<typename Last>
struct Sum<Last>
{
enum{value=sizeof(Last)};
};
```
2. 继承方式展开参数包
有点复杂不做笔记了<br>
除了用继承还可以使用using来实现。
```
template<int N,int... Indexes>
struct MakeIndexes{
using type=MakeIndexes<N-1N-1Indexes...>::type
};
template<int... Indexes>
struct MakeIndexes<0,Indexes...>
{
using type=IndexSeq<Indexes...>;
};
MakeIndexes<3>=>IndexSeq<0,1,2>
```
### 3.2.3可变参数模板
免去手写大量的模板类代码,而让编译器来自动实现。
```
template<typename... Args>
T* Instance(Args... args)
{
return new T(args);
}
A* pa=Instance<A>(1);
B* pb=Instance<B>(1,2);
```
上面代码中的Args是拷贝值使用右值引用与完美转发来消除损耗。
```
template<typename... Args>
T* Instance(Args&&... args)
{
return new T(std::forward<Args>(args)...);
}
```
### 3.3 可变参数模板和type_taits的综合应有
#### 3.3.1 optional的实现
使用placement new的时候容易出现内存对齐的问题可以使用
```
#include <iostream>
#include <type_traits>
struct A
{
int avg;
A(int a,int b):avg((a+b)/2){}
};
typedef std::aligned_storage<sizeof(A),alignof(A)>::type Aligned_A;
int main()
{
Aligned_A a,b;
new (&a) A(10,20);
b=a;
std::cout<<reinterpret_cast<A&>(b).avg<<std::endl;
return0;
}
```
#### 3.3.2 惰性求值类Lazy的实现
```
#ifndef _LAZY_HPP_
#define _LAZY_HPP_
#include <boost/optional.hpp>
template<typename T>
struct Lazy
{
Lazy(){}
template <typename Func, typename... Args>
Lazy(Func& f, Args && ... args)
{
m_func = [&f, &args...]{return f(args...); };
}
T& Value()
{
if (!m_value.is_initialized())
{
m_value = m_func();
}
return *m_value;
}
bool IsValueCreated() const
{
return m_value.is_initialized();
}
private:
std::function<T()> m_func;
boost::optional<T> m_value;
};
template<class Func, typename... Args>
Lazy<typename std::result_of<Func(Args...)>::type>
lazy(Func && fun, Args && ... args)
{
return Lazy<typename std::result_of<Func(Args...)>::type>(
std::forward<Func>(fun), std::forward<Args>(args)...);
}
#endif
```
```
#include "Lazy.hpp"
#include <iostream>
#include <memory>
struct BigObject
{
BigObject()
{
std::cout << "lazy load big object" << std::endl;
}
};
struct MyStruct
{
MyStruct()
{
m_obj = lazy([]{return std::make_shared<BigObject>(); });
}
void Load()
{
m_obj.Value();
}
Lazy<std::shared_ptr<BigObject>> m_obj;
};
int Foo(int x)
{
return x * 2;
}
void TestLazy()
{
//带参数的普通函数
int y = 4;
auto lazyer1 = lazy(Foo, y);
std::cout << lazyer1.Value() << std::endl;
//不带参数的lamda
Lazy<int> lazyer2 = lazy([]{return 12; });
std::cout << lazyer2.Value() << std::endl;
//带参数的fucntion
std::function < int(int) > f = [](int x){return x + 3; };
auto lazyer3 = lazy(f, 3);
std::cout << lazyer3.Value() << std::endl;
//延迟加载大对象
MyStruct t;
t.Load();
}
int main(void)
{
TestLazy();
system("pause");
return 0;
}
```
#### 3.3.3 dll帮助类实现
首先需要封装GetProcAddress函数需要解决
1. 函数的返回值可能是不同类型,如果以一种通用的返回值来消除这种不同返回值导致的差异
2. 函数的入参数目可能任意个数,且类型也不尽相同,如何来消除入参个数和类型的差异
```
#ifndef _DLLPARSER_HPP_
#define _DLLPARSER_HPP_
#include <Windows.h>
#include <string>
#include <map>
#include <functional>
#include <iostream>
class DllParser
{
public:
DllParser()
{
}
~DllParser()
{
UnLoad();
}
bool Load(const std::string& dllPath)
{
m_hMod = LoadLibrary(dllPath.data());
if (nullptr == m_hMod)
{
std::cout << "LoadLibrary failed\n";
return false;
}
return true;
}
template <typename T, typename... Args>
typename std::result_of<std::function<T>(Args...)>::type
ExcecuteFunc(const std::string& funcName, Args&&... args)
{
auto f = GetFunction<T>(funcName);
if (f == nullptr)
{
std::string s = "can not find this function " + funcName;
throw std::exception(s.c_str());
}
return f(std::forward<Args>(args)...);
}
private:
bool UnLoad()
{
if (m_hMod == nullptr)
return true;
auto b = FreeLibrary(m_hMod);
if (!b)
return false;
m_hMod = nullptr;
return true;
}
template <typename T>
T* GetFunction(const std::string& funcName)
{
auto addr = GetProcAddress(m_hMod, funcName.c_str());
return (T*) (addr);
}
private:
HMODULE m_hMod;
std::map<std::string, FARPROC> m_map;
};
#endif //_DLLPARSER_HPP_
```
### 3.3.4 lambda链式调用
```
#include <iostream>
#include <functional>
#include <type_traits>
template<typename T>
class Task;
template<typename R, typename...Args>
class Task<R(Args...)>
{
public:
Task(std::function<R(Args...)>&& f) : m_fn(std::move(f)){}
Task(std::function<R(Args...)>& f) : m_fn(f){}
template<typename... Args>
R Run(Args&&... args)
{
return m_fn(std::forward<Args>(args)...);
}
template<typename F>
auto Then(F& f)->Task<typename std::result_of<F(R)>::type(Args...)>
{
using return_type = typename std::result_of<F(R)>::type;
auto func = std::move(m_fn);
return Task<return_type(Args...)>([func, f](Args&&... args)
{return f(func(std::forward<Args>(args)...)); });
}
private:
std::function<R(Args...)> m_fn;
};
void TestTask()
{
Task<int(int)> task = [](int i){ return i; };
auto result = task.Then([](int i){return i + 1; }).Then([](int i){
return i + 2; }).Then([](int i){return i + 3; }).Run(1);
std::cout << result << std::endl; // 7
}
int main(void)
{
TestTask();
system("pause");
return 0;
}
```
后面若干章节较难,为了节约时间所以跳过了

View File

@@ -0,0 +1,210 @@
### 使用c++11开发一个半同步半异步线程池
```
#pragma once
#include<list>
#include<mutex>
#include<thread>
#include<condition_variable>
#include <iostream>
using namespace std;
template<typename T>
class SyncQueue
{
public:
SyncQueue(int maxSize) :m_maxSize(maxSize), m_needStop(false)
{
}
void Put(const T&x)
{
Add(x);
}
void Put(T&&x)
{
Add(std::forward<T>(x));
}
void Take(std::list<T>& list)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this] {return m_needStop || NotEmpty(); });
if (m_needStop)
return;
list = std::move(m_queue);
m_notFull.notify_one();
}
void Take(T& t)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this] {return m_needStop || NotEmpty(); });
if (m_needStop)
return;
t = m_queue.front();
m_queue.pop_front();
m_notFull.notify_one();
}
void Stop()
{
{
std::lock_guard<std::mutex> locker(m_mutex);
m_needStop = true;
}
m_notFull.notify_all();
m_notEmpty.notify_all();
}
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
int Count()
{
return m_queue.size();
}
private:
bool NotFull() const
{
bool full = m_queue.size() >= m_maxSize;
if (full)
cout << "full, waitingthread id: " << this_thread::get_id() << endl;
return !full;
}
bool NotEmpty() const
{
bool empty = m_queue.empty();
if (empty)
cout << "empty,waitingthread id: " << this_thread::get_id() << endl;
return !empty;
}
template<typename F>
void Add(F&&x)
{
std::unique_lock< std::mutex> locker(m_mutex);
m_notFull.wait(locker, [this] {return m_needStop || NotFull(); });
if (m_needStop)
return;
m_queue.push_back(std::forward<F>(x));
m_notEmpty.notify_one();
}
private:
std::list<T> m_queue; //缓冲区
std::mutex m_mutex; //互斥量和条件变量结合起来使用
std::condition_variable m_notEmpty;//不为空的条件变量
std::condition_variable m_notFull; //没有满的条件变量
int m_maxSize; //同步队列最大的size
bool m_needStop; //停止的标志
};
```
```
#pragma once
#include<list>
#include<thread>
#include<functional>
#include<memory>
#include <atomic>
#include "SyncQueue.hpp"
const int MaxTaskCount = 100;
class ThreadPool
{
public:
using Task = std::function<void()>;
ThreadPool(int numThreads = std::thread::hardware_concurrency()) : m_queue(MaxTaskCount)
{
Start(numThreads);
}
~ThreadPool(void)
{
//如果没有停止时则主动停止线程池
Stop();
}
void Stop()
{
std::call_once(m_flag, [this]{StopThreadGroup(); }); //保证多线程情况下只调用一次StopThreadGroup
}
void AddTask(Task&&task)
{
m_queue.Put(std::forward<Task>(task));
}
void AddTask(const Task& task)
{
m_queue.Put(task);
}
private:
void Start(int numThreads)
{
m_running = true;
//创建线程组
for (int i = 0; i <numThreads; ++i)
{
m_threadgroup.push_back(std::make_shared<std::thread>(&ThreadPool::RunInThread, this));
}
}
void RunInThread()
{
while (m_running)
{
//取任务分别执行
std::list<Task> list;
m_queue.Take(list);
for (auto& task : list)
{
if (!m_running)
return;
task();
}
}
}
void StopThreadGroup()
{
m_queue.Stop(); //让同步队列中的线程停止
m_running = false; //置为false让内部线程跳出循环并退出
for (auto thread : m_threadgroup) //等待线程结束
{
if (thread)
thread->join();
}
m_threadgroup.clear();
}
std::list<std::shared_ptr<std::thread>> m_threadgroup; //处理任务的线程组
SyncQueue<Task> m_queue; //同步队列
atomic_bool m_running; //是否停止的标志
std::once_flag m_flag;
};
```

View File

@@ -0,0 +1,151 @@
## 使用c++改进出程序性能
### 2.1右值引用
左值:表达式结束后依然存在的持久对象</br>
右值:表达式结束时就不存在的临时对象</br>
一个区分左值与右值的便捷方法:看能不能对表达式取地址,如果能则为左值,否则为右值。所有的具名变量或者对象都未左值,而右值不具名。</br>
右值有两个概念构成,一个是将亡值,另一个是纯右值。
### 2.1.1 &&的特性
右值引用就是对一个右值进行引用的类型。因为右值不具名,所以我们只能通过引用的方式找到它。
```
template<typnename T>
void f(T&& param); //T的类型需要推断,所以是个universal references
template<typename T>
class Test{
Test(Test&& rhs); //已经定义类型,所以&&是个右值
};
void f(Test&& rhs); //同上
template<typnename T>
void f(const T&& params); //&&是个右值因为universal references仅仅在T&&下发生。
```
T&&被左值或者右值初始化被称为引用折叠。<br>
总结:
1. 左值和右值是独立于他们的类型的,右值引用类型可能是左值也可以是右值。
2. auto&&或是函数类型自动推导的T&&是一个未定的引用类型。
3. 所有的右值引用叠加到右值引用上仍然是右值引用其他都是左值引用。当T&&为函数模板时,输入左值,它会变成左值引用,而输入右值时则会变成具名的右值引用。
4. 编译器会将已经命名的右值引用视为左值,而将未命名的右值引用视为右值。
### 2.1.2 右值引用优化性能,避免深拷贝
对于含有堆内存的类,我们需要提供深拷贝的拷贝构造函数,如果使用默认构造函数,会导致堆内存的重复删除:
```
class A{
public:
A():m_ptr(new int(0))
{
}
~A()
{
delete m_ptr;
}
private:
int* m_ptr;
};
A Get(bool flag)
{
A a;
A b;
if(flag)
return a;
else
return b;
}
int main()
{
A a=Get(false); //运行出错
}
```
在上面的代码中默认构造函数是浅拷贝a和b会指向同一个指针m_ptr,在析构时会导致重复删除该指针,正确的方法是提供深拷贝函数。<br>
```
A(const A& a):m_ptr(new int(*a.m_ptr))
{
}
```
但是有时这种拷贝构造函数是没有必要的,使用移动构造函数会更好。<br>
```
A(A&& a):m_ptr(a.m_ptr)
{
a.m_ptr=nullptr;
}
```
这里的A&&用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数。<br>
需要注意的是:一般在提供右值引用的构造函数同时,也会提供左值引用的拷贝构造函数,以保证移动不成还可以用拷贝;提供了移动也同时提供拷贝构造函数。<br>
有关拷贝构造函数相关的一些笔记:<br>
拷贝构造函数是C++独有的,它是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。
当没有重载拷贝构造函数时,通过默认拷贝构造函数来创建一个对象
A a;
A b(a);
A b=a; 都是拷贝构造函数来创建对象b
强调这里b对象是不存在的是用a 对象来构造和初始化b的
浅拷贝:如果复制的对象中引用了一个外部内容(例如分配在堆上的数据),那么在复制这个对象的时候,让新旧两个对象指向同一个外部内容,就是浅拷贝。(指针虽然复制了,但所指向的空间内容并没有复制,而是由两个对象共用,两个对象不独立,删除空间存在)<br>
深拷贝:如果在复制这个对象的时候为新对象制作了外部对象的独立复制,就是深拷贝。
拷贝构造函数重载声明如下:
```
A (const A&other)
```
下面为拷贝构造函数的实现:
```
class A
{
int m_i
A(const A& other):m_i(other.m_i)
{
Cout<<”拷贝构造函数”<<endl;
}
}
```
赋值函数<br>
当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数。
当没有重载赋值函数(赋值运算符)时,通过默认赋值函数来进行赋值操作
A a;
A b;
b=a; <br>
强调这里a,b对象是已经存在的是用a 对象来赋值给b的
赋值运算的重载声明如下:
```
A& operator = (const A& other)
```
1. 拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。
```
class A;
A a;
A b=a; //调用拷贝构造函数b不存在
A c(a) ; //调用拷贝构造函数
/****/
class A;
A a;
A b;
b = a ; //调用赋值函数(b存在)</span>
```
2. 一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象
3. 实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。
### 2.2 move语义
普通左值也可以借助移动语义进行优化<br>
c++11提供std::move方法将左值转化为右值从而方便使用移动语义。即强制将左值转化为右值引用。
```
{
std::list<std::string> tokens;
std::list<std::string> t=tokens;
}
std::list<std::string> tokens;
std::list<std::string> t=std::move(tokens);
```
事实上标准库容器都实现了move语义而一些int之类的基础类型计算使用move但仍然会发生拷贝。
### 2.3 forward和完美转发
右值引用类型独立于值,一个右值引用参数作为函数的形参,在函数内部再转发该参数时它已经变成一个左值了。<br>
所以我们需要“完美转发”是指在函数模板中完全依照模板的参数的类型即保持参数的左值、右值特征讲参数传递给函数模板中调用另一个函数。c++11中提供了std::forward
### 2.4emplace_back减少内存拷贝与移动
emplace_back能通过参数构造对象不需要拷贝或者移动内存相比push_back能够更好得避免内存的拷贝与移动使得容器性能得到进一步提升。大多数情况下应该优先使用emplace_back。<br>
其原理是直接利用构造函数直接构造对象<br>
当然在push_back还不能完全被代替比如在没有提供构造函数的的struct。

View File

@@ -0,0 +1,647 @@
## 使用c++11让线程线程开发变得简单
### 5.1 线程创建
1. jon()
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
t.join();//join将会阻塞当前线程直到线程执行结束
return 0;
}
```
2. detach
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
t.detach();//线程与对象分离,让线程作为后台线程去运行,但同时也无法与线程产生联系(控制)了
return 0;
}
```
线程不能被复制,但是可以被移动
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
std::thread t2(std::move(t));
t2.join();
return 0;
}
```
需要注意线程的生命周期
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
return 0;
}
```
以上代码会报错因为线程对象先于线程结束了可以通过join()或者detach()来解决,当然也可以放到一个容器中来保存,以保证线程生命周期
```
#include <thread>
std::vector<std::thread> g_list;
std::vector<std::shared_ptr<std::thread>> g_list2;
void func(){}
void CreateThread()
{
std::thread t(func);
g_list.push_back(std::move(t));
g_list2.push_back(std::make_shared<std::thread>(func));
}
int main()
{
void CreateThread();
for(auto& thread : g_list)
{
thread.join();
}
for(auto& thread : g_list2)
{
thread->join();
}
return 0;
}
```
#### 线程的基本用法
1. 获取当前信息
```
void func()
{
}
int main()
{
std::thread t(func);
std::cout << t.get_id() << std::endl;//当前线程id
//获得cpu核心数
std::cout << std::thread::hardware_concurrency() << std::endl;
}
```
2. 线程休眠
```
void f() {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "time out" << std::endl;
}
int main()
{
std::thread t(f);
t.join();
}
```
### 互斥量
#### 独占互斥锁std::mutex
```
std::mutex g_lock;
void func() {
g_lock.lock();
std::cout << "entered thread" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "leaving thread" << std::this_thread::get_id() << std::endl;
g_lock.unlock();
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
t1.join();
t2.join();
t3.join();
}
```
使用lock_guard可以简化lock/unlock同时也更加安全。因为是在构造时自动锁定互斥量析构时自动解锁。
```
void func() {
std::lock_guard<std::mutex> locker(g_lock);
std::cout << "entered thread" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "leaving thread" << std::this_thread::get_id() << std::endl;
}
```
#### 5.2.2 递归的独占互斥量std::recursive_mutex
递归锁允许同一个线程多次获得该锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
```
struct Complex
{
std::mutex mutex;
int i;
Complex() :i(0) {}
void mul(int x) {
std::lock_guard<std::mutex> lock(mutex);
i *= x;
}
void div(int x) {
std::lock_guard<std::mutex> lock(mutex);
i *= x;
}
void both(int x,int y) {
std::lock_guard<std::mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(32, 23);
}
```
简单的方法就是使用std::recursive_mutex
```
struct Complex
{
std::recursive_mutex mutex;
int i;
Complex() :i(0) {}
void mul(int x) {
std::lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
}
void div(int x) {
std::lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
}
void both(int x,int y) {
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(32, 23);
}
```
需要注意的是:
1. 需要用到递归锁定的多线程互斥处理往往本身就可以简化,允许递归互斥很容易放纵复杂逻辑的产生
2. 递归锁比起非递归锁效率会低一些
3. 递归锁虽然允许同一个线程多次获得同一个互斥锁可重复获得的最大次数并未具体说明一旦超过一定次数再对lock进行调用就会抛出std::system错误
#### 5.2.3带超时的互斥量std::timed_mutex和std::recursive_timed_mutex
主要用于在获取锁时增加超时等待功能。不至于一直等待
```
std::timed_mutex mutex;
void work()
{
std::chrono::milliseconds timeout(100);
while (true)
{
if (mutex.try_lock_for(timeout))
{
std::cout << std::this_thread::get_id() << ": do work with the mutex" << std::endl;
std::chrono::milliseconds sleepDuration(250);
std::this_thread::sleep_for(sleepDuration);
mutex.unlock();
std::this_thread::sleep_for(sleepDuration);
}
else
{
std::cout << std::this_thread::get_id() << ": do work without the mutex" << std::endl;
std::chrono::milliseconds sleepDuration(100);
std::this_thread::sleep_for(sleepDuration);
}
}
}
int main(void)
{
std::thread t1(work);
std::thread t2(work);
t1.join();
t2.join();
return 0;
}
```
#### 5.3 条件变量
条件变量时c++11提供的另外一种用于等待的同步机制它能柱塞一个或者多个线程直到收到另一个线程发出的通知或者超时才会唤醒当前阻塞的线程。<br>
condition_variable
condition_variable_any
```
#include<list>
#include<mutex>
#include<thread>
#include<condition_variable>
#include <iostream>
template<typename T>
class SyncQueue
{
private:
bool IsFull() const
{
return m_queue.size() == m_maxSize;
}
bool IsEmpty() const
{
return m_queue.empty();
}
public:
SyncQueue(int maxSize) : m_maxSize(maxSize)
{
}
void Put(const T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsFull())
{
std::cout << "缓冲区满了,需要等待..." << std::endl;
m_notFull.wait(m_mutex);
}
m_queue.push_back(x);
m_notFull.notify_one();
}
void Take(T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsEmpty())
{
std::cout << "缓冲区空了,需要等待..." << std::endl;
m_notEmpty.wait(m_mutex);
}
x = m_queue.front();
m_queue.pop_front();
m_notFull.notify_one();
}
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
int Count()
{
return m_queue.size();
}
private:
std::list<T> m_queue; //缓冲区
std::mutex m_mutex; //互斥量和条件变量结合起来使用
std::condition_variable_any m_notEmpty;//不为空的条件变量
std::condition_variable_any m_notFull; //没有满的条件变量
int m_maxSize; //同步队列最大的size
};
```
更好的方法是使用unique_lockunique可以随时释放锁
```
#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#include <iostream>
template<typename T>
class SimpleSyncQueue
{
public:
SimpleSyncQueue(){}
void Put(const T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Take(T& x)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this]{return !m_queue.empty(); });
x = m_queue.front();
m_queue.pop_front();
}
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
private:
std::list<T> m_queue;
std::mutex m_mutex;
std::condition_variable m_notEmpty;
};
```
#### 5.4 原子变量
使用原子变量就不需要来使用互斥量来保护该变量了<br>
使用std::mutex
```
#include <iostream>
#include <thread>
#include <mutex>
struct Counter
{
int value = 0;
std::mutex mutex;
void increment()
{
std::lock_guard<std::mutex> lock(mutex);
++value;
std::cout << value << std::endl;
}
void decrement()
{
std::lock_guard<std::mutex> lock(mutex);
--value;
std::cout << value << std::endl;
}
int get()
{
return value;
}
};
Counter g_counter;
void Increments()
{
for (int i = 0; i < 10; ++i)
{
g_counter.increment();
}
}
void Decrements()
{
for (int i = 0; i < 5; ++i)
{
g_counter.decrement();
}
}
int main(void)
{
std::thread t1(Increments);
std::thread t2(Decrements);
t1.join();
t2.join();
system("pause");
return 0;
}
```
使用std::atomic<T>
```
#include <iostream>
#include <thread>
#include <atomic>
struct Counter
{
std::atomic<int> value = 0;
void increment()
{
++value;
}
void decrement()
{
--value;
}
int get()
{
return value;
}
};
Counter g_counter;
void Increments()
{
for (int i = 0; i < 10; ++i)
{
g_counter.increment();
std::cout << g_counter.get() << std::endl;
}
}
void Decrements()
{
for (int i = 0; i < 5; ++i)
{
g_counter.decrement();
std::cout << g_counter.get() << std::endl;
}
}
int main(void)
{
std::thread t1(Increments);
std::thread t2(Decrements);
t1.join();
t2.join();
system("pause");
return 0;
}
```
#### 5.5 call_once/once_flag的使用
为了保证多线程环境中某个函数仅被调用一次,比如需要初始化某个对象,而这个对象只能初始化一次。
```
#include <thread>
#include <iostream>
#include <mutex>
std::once_flag flag;
void do_once() {
std::call_once(flag, [] {std::cout << "Called once" << std::endl; });
}
int main(void)
{
std::thread t1(do_once);
std::thread t2(do_once);
std::thread t3(do_once);
t1.join();
t2.join();
t3.join();
system("pause");
return 0;
}
```
#### 5.6 异步操作类
#### 5.6.1 std::future
thread库提供了future用来访问异步操作结果因为异步操作结果是一个未来的期待值所以被称为future。future提供了异步获取操作结果的通道我们可以以同步等待的方式来获取结果可以通过查询future的状态(future_status)来获取异步操作结果。
```
std::future_status status;
do
{
status = future.wait_for(std::chromno::seconds(1));
if (status == std::future_status::deferred) {
}
else if (status == std::future_status::timeout) {
}
else if (status==std::future_status::ready) {
}
} while (status!= std::future_status::ready);
```
获取future有三种方式:
1. get等待异步操作结束并返回结果
2. wait等待异步操作完成
3. wait_for超时等待返回结果
#### 5.6.2 std::promise
std::promise将数据和future绑定起来为获取线程函数中的某个值提供便利在线程函数中为外面传进来的promise赋值在线程函数执行完成之后就可以通过promise的future获取该值。
```
std::promise<int> pr;
std::thread t([](std::promise<int>&p) {
p.set_value_at_thread_exit(9);
}, std::ref(pr));
std::future<int> f = pr.get_future();
auto r = f.get();
```
#### 5.6.3 std::package_task
```
std::packaged_task<int()> task([]() {return 7; });
std::thread t1(std::ref(task));
std::future<int> f1 = task.get_future();
auto r1 = f1.get();
```
#### 5.6.4 以上三者关系
std::future提供了一个访问异步操作结果的机制它和线程是一个级别的属于低层次对象。之上是std::packaged_task和std::promise他们内部都有future以便访问异步操作结果std::packaged_task包装的是一个异步操作std::promise包装的是一个值。那这两者又是什么关系呢可以将一个异步操作的结果放到std::promise中。<br>
future被promise和package_task用来作为异步操作或者异步的结果的连接通道用std::future和std::shared_future来获取异步的调用结果。future是不可拷贝的shared_future是可以拷贝的当需要将future放到容器中则需要shared_future。
```
#include <iostream>
#include <thread>
#include <utility>
#include <future>
#include <vector>
int func(int x) { return x + 2; }
int main(void)
{
std::packaged_task<int(int)> tsk(func);
std::future<int> fut = tsk.get_future(); //获取future
std::thread(std::move(tsk), 2).detach();
int value = fut.get(); //等待task完成并获取返回值
std::cout << "The result is " << value << ".\n";
std::vector<std::shared_future<int>> v;
std::shared_future<int> f = std::async(std::launch::async, [](int a, int b){return a + b; }, 2, 3);
v.push_back(f);
std::cout << "The shared_future result is " << v[0].get() << std::endl;
return 0;
}
```
#### 5.7 线程异步操作函数
std::async可以直接创建异步的task异步任务结果也保存在future中调用furturn.get()获取即可如果不关心结果可以使用furturn.wait()<br>
两种线程创建策略:<br>
1. std::launch::async:在调用async时就开始创建线程。
2. std::launch::deferred:延迟加载方式创建线程。调用async时不创建线程直到调用future的get或者wait时才创建。
```
#include <iostream>
#include <future>
void TestAsync()
{
std::future<int> f1 = std::async(std::launch::async, [](){
return 8;
});
std::cout << f1.get() << std::endl; //output: 8
std::future<void> f2 = std::async(std::launch::async, [](){
std::cout << 8 << std::endl; return;
});
f2.wait(); //output: 8
std::future<int> future = std::async(std::launch::async, [](){
std::this_thread::sleep_for(std::chrono::seconds(3));
return 8;
});
std::cout << "waiting...\n";
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred)
{
std::cout << "deferred\n";
}
else if (status == std::future_status::timeout)
{
std::cout << "timeout\n";
}
else if (status == std::future_status::ready)
{
std::cout << "ready!\n";
}
} while (status != std::future_status::ready);
std::cout << "result is " << future.get() << '\n';
}
int main(void)
{
TestAsync();
return 0;
}
```

View File

@@ -0,0 +1,249 @@
### 使用c++11改进我们的模式
### 8.1 改进单例模式
单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。
```
#pragma once
template <typename T>
class Singleton
{
public:
template<typename... Args>
  static T* Instance(Args&&... args)
  {
if(m_pInstance==nullptr)
m_pInstance = new T(std::forward<Args>(args)...);
return m_pInstance;
}
  static T* GetInstance()
  {
    if (m_pInstance == nullptr)
      throw std::logic_error("the instance is not init, please initialize the instance first");
    return m_pInstance;
  }
static void DestroyInstance()
{
delete m_pInstance;
m_pInstance = nullptr;
}
private:
Singleton(void);
virtual ~Singleton(void);
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static T* m_pInstance;
};
template <class T> T* Singleton<T>::m_pInstance = nullptr;
```
使用了可变参数与完美转发避免了写N个模板函数
单例模式应用的场景一般发现在以下条件下:
1. 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
2. 控制资源的情况下,方便资源之间的互相通信。如线程池等。
3. 只需一个存在就可以的情况
### 8.2 改进观察者模式
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都能得到通知并被自动更新。
```
#pragma once
class NonCopyable
{
public:
NonCopyable(const NonCopyable&) = delete; // deleted
NonCopyable& operator = (const NonCopyable&) = delete; // deleted
NonCopyable() = default; // available
};
```
```
#include <iostream>
#include <string>
#include <functional>
#include <map>
using namespace std;
#include "NonCopyable.hpp"
template<typename Func>
class Events : NonCopyable
{
public:
//注册观察者,支持右值引用
int Connect(Func&& f)
{
return Assgin(f);
}
//注册观察者
int Connect(const Func& f)
{
return Assgin(f);
}
//移除观察者
void Disconnect(int key)
{
m_connections.erase(key);
}
//通知所有的观察者
template<typename... Args>
void Notify(Args&&... args)
{
for (auto& it: m_connections)
{
it.second(std::forward<Args>(args)...);
}
}
private:
//保存观察者并分配观察者的编号
template<typename F>
int Assgin(F&& f)
{
int k=m_observerId++;
m_connections.emplace(k, std::forward<F>(f));
return k;
}
int m_observerId=0;//观察者对应的编号
std::map<int, Func> m_connections;//观察者列表
};
```
### 8.3 改进访问者模式
访问者模式需要注意的问题:定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重新定义对所有访问者的接口。<br>
通过可变参数模板就可以实现一个稳定的接口层,可以让访问者的接口层访问任意个数的访问者,这样就不需要每增加一个新的被访问者就修改接口层,从而使接口层保证稳定。
```
template<typename... Types>
struct Visitor;
template<typename T, typename... Types>
struct Visitor<T, Types...> : Visitor<Types...>
{
using Visitor<Types...>::Visit;//通过using避免隐藏基类的visit同名方法
virtual void Visit(const T&) = 0;
};
template<typename T>
struct Visitor<T>
{
virtual void Visit(const T&) = 0;
};
```
### 8.4 改进命令模式
命令模式的作用是将请求封装成一个对象,将请求的发起者和执行者解耦,支持对请求排队、撤销和重做。由于将请求都封装成一个一个命令对象了,使得我们可以集中处理或者延迟处理这些命令请求,而且不同客户对象可以共享这些命令,还可以控制请求的优先权、排队、支持请求的撤销和重做。<br>
缺点:越来越多的命令会导致类爆炸,难以管理<br>
接收function、函数对象、lambda和普通函数的包装器
```
template<class F,class... Args,class=typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void Wrap(F&& f, Args && ...args)
{
return f(std::forward<Args>(args)...);
}
```
接收成员函数的包装器:
```
template<class R,class C,class... DArgs,class P,class... Args>
void Wrap(R(C::*f)(DArgs...),P&& p,Args&& ... args)
{
return; (*p.*f)(std::forward<Args>(args)...);
}
```
```
#include <functional>
#include <type_traits>
template<typename Ret = void>
struct CommCommand
{
private:
std::function < Ret()> m_f;
public:
//接受可调用对象的函数包装器
template< class F, class... Args, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void Wrap(F && f, Args && ... args)
{
m_f = [&]{return f(args...); };
}
//接受常量成员函数的函数包装器
template<class R, class C, class... DArgs, class P, class... Args>
void Wrap(R(C::*f)(DArgs...) const, P && p, Args && ... args)
{
m_f = [&, f]{return (*p.*f)(args...); };
}
//接受非常量成员函数的函数包装器
template<class R, class C, class... DArgs, class P, class... Args>
void Wrap(R(C::*f)(DArgs...), P && p, Args && ... args)
{
m_f = [&, f]{return (*p.*f)(args...); };
}
Ret Excecute()
{
return m_f();
}
};
```
### 8.5 改进对象池
```
#include <string>
#include <functional>
#include <memory>
#include <map>
#include "NonCopyable.hpp"
using namespace std;
const int MaxObjectNum = 10;
template<typename T>
class ObjectPool : NonCopyable
{
template<typename... Args>
using Constructor = std::function<std::shared_ptr<T>(Args...)>;
public:
//默认创建多少个对象
template<typename... Args>
void Init(size_t num, Args&&... args)
{
if (num<= 0 || num> MaxObjectNum)
throw std::logic_error("object num out of range.");
auto constructName = typeid(Constructor<Args...>).name(); //不区分引用
for (size_t i = 0; i <num; i++)
{
m_object_map.emplace(constructName, shared_ptr<T>(new T(std::forward<Args>(args)...), [this, constructName](T* p) //删除器中不直接删除对象,而是回收到对象池中,以供下次使用
{
m_object_map.emplace(std::move(constructName), std::shared_ptr<T>(p));
}));
}
}
//从对象池中获取一个对象
template<typename... Args>
std::shared_ptr<T> Get()
{
string constructName = typeid(Constructor<Args...>).name();
auto range = m_object_map.equal_range(constructName);
for (auto it = range.first; it != range.second; ++it)
{
auto ptr = it->second;
m_object_map.erase(it);
return ptr;
}
return nullptr;
}
private:
multimap<string, std::shared_ptr<T>> m_object_map;
};
```

View File

@@ -0,0 +1,120 @@
### 使用c++11中便利的工具
#### 6.1 处理日期和时间的chrono库
#### 6.1.1 记录时长的duration
```
std::chrono::duration <rep,std::ratio<1,1>> seconds;//表示秒
```
chrono的count(),可以获取时钟周期数<br>
需要注意的是当两个duration时钟周期不同的时候会先统一周期再进行计算。<br>
还可以通过duration_cast<>()进行时钟周期的转换
#### 6.1.2 表示时间点的time point
#### 6.1.3 获得系统时钟的clocks
#### 6.1.4 计时器timer
```
#include<chrono>
class Timer
{
public:
Timer() : m_begin(std::chrono::high_resolution_clock::now()) {}
void reset()
{
m_begin = std::chrono::high_resolution_clock::now();
}
//默认输出毫秒
int64_t elapsed() const
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//微秒
int64_t elapsed_micro() const
{
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//纳秒
int64_t elapsed_nano() const
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//秒
int64_t elapsed_seconds() const
{
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//分
int64_t elapsed_minutes() const
{
return std::chrono::duration_cast<std::chrono::minutes>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//时
int64_t elapsed_hours() const
{
return std::chrono::duration_cast<std::chrono::hours>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
private:
std::chrono::time_point<std::chrono::high_resolution_clock> m_begin;
};
void fun()
{
std::cout << "hello world" << std::endl;
}
void Test()
{
std::cout << "\nTest()\n";
Timer t; //开始计时
fun();
std::cout << t.elapsed_seconds() << std::endl; //打印fun函数耗时多少秒
std::cout << t.elapsed_nano() << std::endl; //打印纳秒
std::cout << t.elapsed_micro() << std::endl; //打印微秒
std::cout << t.elapsed() << std::endl; //打印毫秒
std::cout << t.elapsed_seconds() << std::endl; //打印秒
std::cout << t.elapsed_minutes() << std::endl; //打印分钟
std::cout << t.elapsed_hours() << std::endl; //打印小时
}
int main(void)
{
Test();
system("pause");
return 0;
}
```
#### 6.2 数值类型和字符类型的互相转换
std::string to_string(type value)
std::string to_wstring(type value)
1. atoi字符串转int
2. atol字符串转long
3. atoll字符串转long long
4. atof字符串转float
#### 6.3 宽窄字符转换
```
std::wstring str=L"中国人";
//宽窄字符转换器
std::codecvt_utf8
std::codecvt_utf16
std::codecvt_utf8_utf16
std::wstring_convert
```

View File

@@ -0,0 +1,121 @@
### 使用c++11开发一个轻量级的Ioc容器
Ioc容器具备两种能力一种是对象工厂不仅可以创建所有的对象还可以根据配置去创建对象另一种能力是可以去创建依赖对象应有不需要直接创建对象由Ioc容器去创建实现控制反转。<br>
实现Ioc容器需要解决三个问题第一个问题是创建所有类型的对象第二个问题是类型擦除第三个问题是如何创建依赖对象。
#### 类型擦除的常用方法
类型擦除就是讲原有类型消除或者隐藏。常用的方法有:
1. 通过多态来擦除类型
2. 通过模板来擦除类型
3. 通过某种类型的模板容器擦除类型(Variant)
4. 通过某种通用类型来擦除类型(any)
5. 通过闭包来擦除类型通过模板函数将数值包入函数中再包入std::function
```
#pragma once
#include<string>
#include<unordered_map>
#include<memory>
#include<functional>
using namespace std;
#include<Any.hpp>
#include <NonCopyable.hpp>
class IocContainer : NonCopyable
{
public:
IocContainer(void){}
~IocContainer(void){}
template<class T, typename Depend, typename... Args>
void RegisterType(const string& strKey)
{
std::function<T* (Args...)> function = [](Args... args){ return new T(new Depend(args...)); };//通过闭包擦除了参数类型
RegisterType(strKey, function);
}
template<class T, typename... Args>
T* Resolve(const string& strKey, Args... args)
{
if (m_creatorMap.find(strKey) == m_creatorMap.end())
returnnullptr;
Any resolver = m_creatorMap[strKey];
std::function<T* (Args...)> function = resolver.AnyCast<std::function<T* (Args...)>>();
return function(args...);
}
template<class T, typename... Args>
std::shared_ptr<T> ResolveShared(const string& strKey, Args... args)
{
T* t = Resolve<T>(strKey, args...);
return std::shared_ptr<T>(t);
}
private:
void RegisterType(const string& strKey, Any constructor)
{
if (m_creatorMap.find(strKey) != m_creatorMap.end())
throw std::invalid_argument("this key has already exist!");
//通过Any擦除了不同类型的构造器
m_creatorMap.emplace(strKey, constructor);
}
private:
unordered_map<string, Any> m_creatorMap;
};
/*test code
struct Base
{
virtual void Func(){}
virtual ~Base(){}
};
struct DerivedB : public Base
{
DerivedB(int a, double b):m_a(a),m_b(b)
{
}
void Func()override
{
cout<<m_a+m_b<<endl;
}
private:
int m_a;
double m_b;
};
struct DerivedC : public Base
{
};
struct A
{
A(Base * ptr) :m_ptr(ptr)
{
}
void Func()
{
m_ptr->Func();
}
~A()
{
if(m_ptr!=nullptr)
{
delete m_ptr;
m_ptr = nullptr;
}
}
private:
Base * m_ptr;
};
void TestIoc()
{
IocContainer ioc;
ioc.RegisterType<A, DerivedC>(“C”); //配置依赖关系
auto c = ioc.ResolveShared<A>(“C”);
ioc.RegisterType<A, DerivedB, int, double>(“C”); //注册时要注意DerivedB的参数int和double
auto b = ioc.ResolveShared<A>(“C”, 1, 2.0); //还要传入参数
b->Func();
}
*/
```

View File

@@ -0,0 +1,235 @@
### 使用c++11封装sqlite库
#### 13.1 sqlite基本用法
对于带参数的sql语句<br>
其中sqlite3_perpare_v2用于解析sql文本并且保存到sqlite_stmt对象中sqlite3_stmt将作为后面一些函数的入参sqlite3_bind_XXX用于绑定sql文本中的参数
```
#include <sqlite3.h>
#include <string>
bool test()
{
sqlite3* dbHandle = nullptr;
int result = sqlite3_open("test.db", &dbHandle);
if (result != SQLITE_OK)
{
sqlite3_close(dbHandle);
return false;
}
const char* sqlcreat = "CREATE TABLE if not exists PersonTable(ID INTEGER NOT NULL, Name Text,Address BLOB);";
result = sqlite3_exec(dbHandle, sqlcreat, nullptr, nullptr, nullptr);
//插入数据
sqlite3_stmt* stmt = NULL;
const char* sqlinsert = "INSERT INTO PersonTable(ID,Name,Adress) VALUE(?,?,?);";
//解析并且保存sql脚本
sqlite3_prepare_v2(dbHandle, sqlinsert, strlen(sqlinsert), &stmt, nullptr);
int id = 2;
const char* name = "peter";
for (int i=0;i<10;++i)
{
sqlite3_bind_int(stmt, 1, id);
sqlite3_bind_text(stmt, 2, name, strlen(name), SQLITE_TRANSIENT);
sqlite3_bind_null(stmt, 3);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
sqlite3_close(dbHandle);
}
//重新初始化stmt对象下次再用
sqlite3_reset(stmt);
}
//使用完需要释放,不然会内存泄露
sqlite3_finalize(stmt);
sqlite3_close(dbHandle);
return result = SQLITE_OK;
}
```
最后通过
sqlite3_colume_xxx(sqlite3_stmt*,int iCol)取得结果
```
int colCount=sqlite3_column_count(stmt);
while(true)
{
int r=sqlite3_step(stmt);
if(r==SQLITE_DONE)
{
break;//数据行都已经获取,跳出循环
}
if(r==SQLITE_ROW)
{
break;//获得某一行数据失败,跳出循环
}
//获得每一列数据
for(int i=0;i<colCount;++i)
{
int coltype=sqlite3_column_type(stmt,i);
if(coltype==SQLITE_INTEGER)
{
int val=sqlite3_column_int(stmt,i);
}else if(coltype==SQLITE_FLOAT)
{
double val=sqlite3_column_double(stmt,i);
}else if(coltype==SQLITE_TEXT)
{
const char* val=(const char*)sqlite3_column_text(stmt,i);
}else if(coltype==SQLITE_NULL)
{
}
}
}
sqlite3_finalize(stmt);
```
事务
```
sqlite3_exec(dbHandle,"BEGIN");
sqlite3_exec(dbHandle,"ROLLBACK");
sqlite3_exec(dbHandle,"COMMIT");
sqlite3_exec(dbHandle,"END");
```
```
/**
* 不带占位符。执行sql不带返回结果, 如insert,update,delete等
* @param[in] query: sql语句, 不带占位符
* @return bool, 成功返回true否则返回false
*/
bool Excecute(const string& sqlStr)
{
m_code = sqlite3_exec(m_dbHandle, sqlStr.data(), nullptr, nullptr, nullptr);
return SQLITE_OK == m_code;
}
/**
* 带占位符。执行sql不带返回结果, 如insert,update,delete等
* @param[in] query: sql语句, 可能带占位符"?"
* @param[in] args: 参数列表,用来填充占位符
* @return bool, 成功返回true否则返回false
*/
template <typename... Args>
bool Excecute(const string& sqlStr, Args && ... args)
{
if (!Prepare(sqlStr))
{
return false;
}
return ExcecuteArgs(std::forward<Args>(args)...);
}
/**
* 批量操作之前准备sql接口必须和ExcecuteBulk一起调用准备批量操作的sql可能带占位符
* @param[in] query: sql语句, 带占位符"?"
* @return bool, 成功返回true否则返回false
*/
bool Prepare(const string& sqlStr)
{
m_code = sqlite3_prepare_v2(m_dbHandle, sqlStr.data(), -1, &m_statement, nullptr);
if (m_code != SQLITE_OK)
{
return false;
}
return true;
}
/**
* 批量操作接口必须先调用Prepare接口
* @param[in] args: 参数列表
* @return bool, 成功返回true否则返回false
*/
template <typename... Args>
bool ExcecuteArgs(Args && ... args)
{
if (SQLITE_OK != detail::BindParams(m_statement, 1, std::forward<Args>(args)...))
{
return false;
}
m_code = sqlite3_step(m_statement);
sqlite3_reset(m_statement);
return m_code == SQLITE_DONE;
}
template<typename Tuple>
bool ExcecuteTuple(const string& sqlStr, Tuple&& t)
{
if (!Prepare(sqlStr))
{
return false;
}
m_code = detail::ExcecuteTuple(m_statement, detail::MakeIndexes<std::tuple_size<Tuple>::value>::type(), std::forward<Tuple>(t));
return m_code == SQLITE_DONE;
}
bool ExcecuteJson(const string& sqlStr, const char* json)
{
rapidjson::Document doc;
doc.Parse<0>(json);
if (doc.HasParseError())
{
cout << doc.GetParseError() << endl;
return false;
}
if (!Prepare(sqlStr))
{
return false;
}
return JsonTransaction(doc);
}
/**
* 执行sql返回函数执行的一个值, 执行简单的汇聚函数如select count(*), select max(*)等
* 返回结果可能有多种类型返回Value类型在外面通过get函数去取
* @param[in] query: sql语句, 可能带占位符"?"
* @param[in] args: 参数列表,用来填充占位符
* @return int: 返回结果值,失败则返回-1
*/
template < typename R = sqlite_int64, typename... Args>
R ExecuteScalar(const string& sqlStr, Args&&... args)
{
if (!Prepare(sqlStr))
return GetErrorVal<R>();
if (SQLITE_OK != detail::BindParams(m_statement, 1, std::forward<Args>(args)...))
{
return GetErrorVal<R>();
}
m_code = sqlite3_step(m_statement);
if (m_code != SQLITE_ROW)
return GetErrorVal<R>();
SqliteValue val = GetValue(m_statement, 0);
R result = val.Get<R>();// get<R>(val);
sqlite3_reset(m_statement);
return result;
}
template <typename... Args>
std::shared_ptr<rapidjson::Document> Query(const string& query, Args&&... args)
{
if (!PrepareStatement(query, std::forward<Args>(args)...))
nullptr;
auto doc = std::make_shared<rapidjson::Document>();
m_buf.Clear();
m_jsonHelper.BuildJsonObject(m_statement);
doc->Parse<0>(m_buf.GetString());
return doc;
}
```
详细代码见
https://github.com/qicosmos/SmartDB1.03/blob/master/SmartDB.hpp

View File

@@ -0,0 +1,2 @@
### 使用c++11开发一个对象的消息总线库
大致了解消息总线的设计思路,但是没有一定的模板基础,代码看了也白看

View File

@@ -0,0 +1,132 @@
### 使用c++11开发一个轻量级的并行task库
#### 15.1 TBB的基本用法
#### 15.1.1 TBB概述
TBB 是inter用标准c++写的一个开源的并行计算库,它的目的是提升数据并行计算的能力。主要功能如下:
1. 并行计算
2. 任务调度
3. 并行容器
4. 同步原语
5. 内存分配器
#### 15.1.2 TBB并行算法
1. parallel_for以并行的方式遍历一个区间
2. parallel_do和parallel_for_each将算法用于一个区间
3. parallel_reduce并行汇聚
4. parallel_pipeline并行的管道过滤器
5. parallel_sort和parallel_invoke并行排序和调和
#### 15.1.3 TBB的任务组
```
tbb::task_group g;
g.run([]{task();});
g.run([]{task();});
g.run([]{task();});
g.wait();
```
#### 15.2 PPL的基本用法
两者差异:
1. parallel_reduce的原型有些不同。
2. PPL中没有parallel_pipeline接口
3. TBB的task没有PPL的task强大PPL的task可以链式连续执行还可以组合任务而TBB的task不行。
#### 15.5 TaskCpp的任务
#### 15.5.1 task的实现
基于task的并行编程模型最基本的执行单元是task一个task就代表了一个要执行的任务。外部只需要简单调用接口就可以创建task并且执行另一个细节就是异步执行。
```
template<typename T>
class Task;
template<typename R, typename...Args>
class Task<R(Args...)>
{
std::function<R(Args...)> m_fn;
public:
typedef R return_type;
template<typename F>
auto Then(F&& f)//->Task<typename std::result_of<F(R)>::type(Args...)>
{
typedef typename std::result_of<F(R)>::type ReturnType;
auto func = std::move(m_fn);
return Task<ReturnType(Args...)>([func, &f](Args&&... args)
{
std::future<R> lastf = std::async(func, std::forward<Args>(args)...);
return std::async(f, lastf.get()).get();
});
}
Task(std::function<R(Args...)>&& f) :m_fn(std::move(f)){}
Task(std::function<R(Args...)>& f) :m_fn(f){}
~Task()
{
}
void Wait()
{
std::async(m_fn).wait();
}
template<typename... Args>
R Get(Args&&... args)
{
return std::async(m_fn, std::forward<Args>(args)...).get();
}
std::shared_future<R> Run()
{
return std::async(m_fn);
}
};
```
#### 15.5.2 task的延续
```
#include <functional>
namespace Cosmos
{
template<typename T>
class Task;
template<typename R, typename...Args>
class Task<R(Args...)>
{
std::function<R(Args...)> m_fn;
public:
typedef R return_type;
template<typename F>
auto Then(F&& f)//->Task<typename std::result_of<F(R)>::type(Args...)>
{
typedef typename std::result_of<F(R)>::type ReturnType;
auto func = std::move(m_fn);
return Task<ReturnType(Args...)>([func, &f](Args&&... args)
{
std::future<R> lastf = std::async(func, std::forward<Args>(args)...);
return std::async(f, lastf.get()).get();
});
}
Task(std::function<R(Args...)>&& f) :m_fn(std::move(f)){}
Task(std::function<R(Args...)>& f) :m_fn(f){}
~Task()
{
}
void Wait()
{
std::async(m_fn).wait();
}
template<typename... Args>
R Get(Args&&... args)
{
return std::async(m_fn, std::forward<Args>(args)...).get();
}
std::shared_future<R> Run()
{
return std::async(m_fn);
}
};
}
```

View File

@@ -0,0 +1 @@
所有类型的容器和数组都抽象为一个Range这个Range由一族迭代器组成然后就可以基于这个抽象的Range实现更为抽象、规范、统一的算法了。Boost库已经实现了Range。

Some files were not shown because too many files have changed in this diff Show More