Init
This commit is contained in:
1
02-Note/.keep
Normal file
1
02-Note/.keep
Normal file
@@ -0,0 +1 @@
|
||||
this file is created for keeping the folder after git.
|
11
02-Note/02-Note.md
Normal file
11
02-Note/02-Note.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 02-Note Overview
|
||||
```ccard
|
||||
type: folder_brief_live
|
||||
noteOnly: true
|
||||
style: card
|
||||
```
|
||||
|
||||
### 网址收藏
|
||||
- [[模型资源]]
|
||||
- [[资源笔记]]
|
||||
- [[UE知识库]]
|
75
02-Note/ASoul/ASoul NAS部署服务.md
Normal file
75
02-Note/ASoul/ASoul NAS部署服务.md
Normal 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
92
02-Note/ASoul/ASoul.md
Normal 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. 人员组织架构。需要哪些技术栈。方便后续复现。
|
6
02-Note/ASoul/LiveDirerctor项目结构笔记.md
Normal file
6
02-Note/ASoul/LiveDirerctor项目结构笔记.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- [ ] Config
|
||||
-
|
||||
- [ ] Content
|
||||
- [ ] Source
|
||||
- [ ] Plugins
|
||||
- [ ] EditorTool
|
16
02-Note/ASoul/优化笔记/2024乃琳生日会优化笔记.md
Normal file
16
02-Note/ASoul/优化笔记/2024乃琳生日会优化笔记.md
Normal 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掉。
|
||||
- 场景变换卡顿。
|
90
02-Note/ASoul/优化笔记/2024贝拉生日会优化笔记.md
Normal file
90
02-Note/ASoul/优化笔记/2024贝拉生日会优化笔记.md
Normal 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
|
45
02-Note/ASoul/动画相关/动捕&面捕.md
Normal file
45
02-Note/ASoul/动画相关/动捕&面捕.md
Normal 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. (AMediaPipeMocapReceiverActor)Tick => OnGetMediaPipeData() => **(TsMediaPipeSkeleton)Skeleton.OnGetMediaPipeData(Data)** ,这个函数逻辑在TsMediaPipeMocapReceiverActor。
|
||||
2. (TsMediaPipeMocapReceiverActor)ReceiveTick() => UpdateAnimation() 对数据进行过滤调整之后,将**面捕数据塞入AnimNode_FacialExpression**。
|
513
02-Note/ASoul/动画相关/动捕逻辑.md
Normal file
513
02-Note/ASoul/动画相关/动捕逻辑.md
Normal 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] 添加时间轴判断,避免加入重复的帧。
|
||||
- AChingmuMocapReceiverActor(Tick)=>
|
||||
- 从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()。
|
17
02-Note/ASoul/动画相关/动捕逻辑思维导图.canvas
Normal file
17
02-Note/ASoul/动画相关/动捕逻辑思维导图.canvas
Normal 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"}
|
||||
]
|
||||
}
|
29
02-Note/ASoul/动画相关/动画蓝图逻辑.md
Normal file
29
02-Note/ASoul/动画相关/动画蓝图逻辑.md
Normal 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
|
163
02-Note/ASoul/实现效果/传送效果.md
Normal file
163
02-Note/ASoul/实现效果/传送效果.md
Normal file
@@ -0,0 +1,163 @@
|
||||
"PropName": "梦境楼梯",
|
||||
"AssetPath": "/Game/Props/SceneProps/Items/BP_Steps.BP_Steps",
|
||||
|
||||
定义场景A与场景B,A=>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);
|
||||
}
|
||||
}
|
||||
```
|
2
02-Note/ASoul/实现效果/绳子拉取光捕相机.md
Normal file
2
02-Note/ASoul/实现效果/绳子拉取光捕相机.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# 光捕相机类型
|
||||
BP_HandHeldCam => HandHeldCamera,同过BP_HandHeldCamera_Proxy Actor实时发送LiveLink数据。
|
40
02-Note/ASoul/导播台架构图.canvas
Normal file
40
02-Note/ASoul/导播台架构图.canvas
Normal 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":"MultiView(24个镜头预览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"}
|
||||
]
|
||||
}
|
114
02-Note/ASoul/导播台笔记/RuntimeEditor.md
Normal file
114
02-Note/ASoul/导播台笔记/RuntimeEditor.md
Normal 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
|
219
02-Note/ASoul/导播台笔记/Sequoia.md
Normal file
219
02-Note/ASoul/导播台笔记/Sequoia.md
Normal 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
|
||||
## ISequoiaEvalTemplate(SequoiaCamShotEvalTemplate)
|
||||
- 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.
|
9
02-Note/ASoul/导播台笔记/启动逻辑.md
Normal file
9
02-Note/ASoul/导播台笔记/启动逻辑.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 启动逻辑
|
||||
1. ULiveDirectorGameInstance::ParseCommandLine():解析DirectorMode、PGMMode字符串并设置。
|
||||
2. SetDirectorModeStr()
|
||||
3. SetDirectorMode()
|
||||
4. 调用蓝图事件OnDirectorModeChanged()
|
||||
|
||||
TsLiveDirectorGameInstance extends UE.LiveDirectorGameInstance
|
||||
|
||||
TsDirectorCamManagerActor.ts
|
20
02-Note/ASoul/导播台笔记/场景功能实现.md
Normal file
20
02-Note/ASoul/导播台笔记/场景功能实现.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 相关类
|
||||
- WBP_LevelFunctionView
|
||||
- TsMapEnvironmentFunctionManager
|
||||
- TsCommonVisibleLevelFunction
|
||||
- TsCommonGravityLevelFunction
|
||||
- TsCommonTimeLevelFunction
|
||||
- TsCommonVariantComponent
|
||||
- TsCommonWindLevelFunction
|
||||
- TsBaseLevelFunctionActor
|
||||
|
||||
# 糖果工厂的移动LiveArea实现
|
||||
- UI:TsCandyFactoryDetails.tsx
|
||||
- 逻辑实现:/ResArt/Scene/Map_Stylized_Vilage/BP/BP_CandyFactoryLift
|
||||
- 基类TsBaseLevelFunctionActor
|
||||
|
||||
|
||||
2个圈转到相对原点。停下。
|
||||
|
||||
# Area动画
|
||||
/Props/AreaSequence/天空之城_降落
|
6
02-Note/ASoul/导播台笔记/大世界换地图功能.md
Normal file
6
02-Note/ASoul/导播台笔记/大世界换地图功能.md
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
ALiveDirectorGameMode::GetSeamlessTravelActorList
|
||||
|
||||
https://www.uejoy.com/archives/1130
|
||||
|
||||
使用了UE5的RelicationGraph,UDirectorReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConnection()
|
76
02-Note/ASoul/导播台笔记/搜索功能.md
Normal file
76
02-Note/ASoul/导播台笔记/搜索功能.md
Normal 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();
|
||||
}
|
||||
```
|
4
02-Note/ASoul/导播台笔记/身份判断.md
Normal file
4
02-Note/ASoul/导播台笔记/身份判断.md
Normal file
@@ -0,0 +1,4 @@
|
||||
身份判断逻辑:
|
||||
```c++
|
||||
if (Utils.IsCurDirectorModeEqual(this, DirectorMode.IdolControllerMaster))
|
||||
```
|
532
02-Note/ASoul/导播操作笔记.md
Normal file
532
02-Note/ASoul/导播操作笔记.md
Normal 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_Win:PVW(预览屏)、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. Designer:StreamDock插件相关,包括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-studio:OBS源码,添加了若干插件,但用不了,因为技术服务属于字节。
|
||||
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. RuntimeEditor:ASoul导播台的实时编辑器模块。
|
||||
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. Editor:RuntimeEditor相关。
|
||||
3. LiveDirector:
|
||||
1. [ ] Character/TsIdolActor.ts:[[角色流程#TsIdolActor.ts]]
|
||||
4. Camera
|
||||
5. Characrer:
|
||||
6. Danma:弹幕相关,View以及控制。
|
||||
7. DeckLinkViewProcess:视频处理,叠加UI之类的操作。
|
||||
8. Decoration:UI UMG类定义。
|
||||
9. DeviceINputActor:Media以及串口控制器(Actor)
|
||||
10. DirectorFrameWork:GameMode、Contorl、 UIManage之类的通用框架。
|
||||
11. DirectorToolMenu:编辑器UI相关。
|
||||
12. Level:场景切换控制器。
|
||||
13. LiveArea:直播区域。
|
||||
14. MapEnvironment:Level里的效果以及相关逻辑。天气控制。
|
||||
15. Pico相关。
|
||||
16. Prop:道具相关道具。
|
||||
17. QuickControl:简单UI控制器。
|
||||
18. ScreenPlayerTextureRenderer:将视频渲染成贴图之后再场景中渲染。
|
||||
19. SeiSender:OBS 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. BlackMagicInput:UE官方移植,视频采集卡。
|
||||
2. BlackMagicOutput:UE官方移植,视频采集卡。
|
||||
3. DeckLinkOuput:UE官方移植,视频采集卡。
|
||||
4. GameCluster:未完成
|
||||
6. MultiViewRenderer:20个View的UI相关。
|
||||
7. UIModule:UI样式定义,功能。
|
||||
|
||||
### 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-Material,M_ToonHair_v01。v02为新版。
|
||||
- CommonMaterial-Functions:ShadingModel的骚操作。
|
||||
## 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_Live:Live场景。
|
63
02-Note/ASoul/引擎相关.md
Normal file
63
02-Note/ASoul/引擎相关.md
Normal 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
|
||||
}
|
||||
```
|
158
02-Note/ASoul/流程笔记/MultiView逻辑.md
Normal file
158
02-Note/ASoul/流程笔记/MultiView逻辑.md
Normal 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.Preview:bBlackMagicCard = false
|
||||
- PVW&PGM:bBlackMagicCard = 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);***
|
266
02-Note/ASoul/流程笔记/VJ播放.md
Normal file
266
02-Note/ASoul/流程笔记/VJ播放.md
Normal 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
|
||||
## UYVY(YUV422)
|
||||
- https://zhuanlan.zhihu.com/p/695302926
|
||||
- https://blog.csdn.net/gsp1004/article/details/103037312
|
||||

|
50
02-Note/ASoul/流程笔记/场景流程&镜头流程.md
Normal file
50
02-Note/ASoul/流程笔记/场景流程&镜头流程.md
Normal 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面部按钮以及图标。
|
||||
|
11
02-Note/ASoul/流程笔记/大世界添加笔记.md
Normal file
11
02-Note/ASoul/流程笔记/大世界添加笔记.md
Normal 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或者大世界的预览图标
|
18
02-Note/ASoul/流程笔记/环境&四季流程.md
Normal file
18
02-Note/ASoul/流程笔记/环境&四季流程.md
Normal 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;
|
||||
}
|
||||
```
|
149
02-Note/ASoul/流程笔记/角色流程.md
Normal file
149
02-Note/ASoul/流程笔记/角色流程.md
Normal 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到对应Idol(UI逻辑)
|
||||
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=true,client需要同步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()
|
18
02-Note/ASoul/流程笔记/调试启动流程.md
Normal file
18
02-Note/ASoul/流程笔记/调试启动流程.md
Normal 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级添加角色
|
106
02-Note/ASoul/流程笔记/道具流程.md
Normal file
106
02-Note/ASoul/流程笔记/道具流程.md
Normal 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()
|
||||
}
|
||||
```
|
264
02-Note/ASoul/渲染方案/PVW相关.md
Normal file
264
02-Note/ASoul/渲染方案/PVW相关.md
Normal 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);
|
||||
}
|
||||
}
|
||||
```
|
737
02-Note/ASoul/渲染方案/Shader部分.md
Normal file
737
02-Note/ASoul/渲染方案/Shader部分.md
Normal 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
|
||||
```
|
314
02-Note/ASoul/渲染方案/材质部分.md
Normal file
314
02-Note/ASoul/渲染方案/材质部分.md
Normal 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_Matcap:Matcap效果,输出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_NormalMapIntensity:Normal强度调整,被大量材质引用。
|
||||
- ***MF_SceneEffects***:调用了MF_Dissolve实现了溶解效果。但备用他的材质并不多。
|
||||
- MF_ShiftTangent:Kajiya-Kay中的ShiftTangent,被M_ToonHair_V01调用。
|
||||
- MF_StrandSpec:Kajiya-Kay中的高光计算逻辑,被M_ToonHair_V01调用。
|
||||
- MF_Surface:Surface材质相关属性逻辑,被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的采样UV(V轴)** 进行偏移,以此实现高光偏移效果。
|
||||
- **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
|
||||
存储一些默认贴图与一些测试用(无用)贴图。
|
12
02-Note/ASoul/问题解决.md
Normal file
12
02-Note/ASoul/问题解决.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 面捕问题解决
|
||||
|
||||
## c++
|
||||
~~IdolAnimInstance~~:没相关代码。
|
||||
~~MotionSyncComponent~~
|
||||
~~UOVRLipSyncActorComponentBase~~
|
||||
~~MotionReceiverActor.cpp~~
|
||||
|
||||
## ts
|
||||
TsArkitDataReceiver
|
||||
TsMotionRetargetComponent
|
||||
TsMotionSyncComponent
|
16
02-Note/ASoul/面部软件FaceMask笔记.md
Normal file
16
02-Note/ASoul/面部软件FaceMask笔记.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 环境
|
||||
- Unity:2020.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证书。
|
||||
|
||||
# 打包
|
132
02-Note/DAWA/2024.12.31新项目笔记.md
Normal file
132
02-Note/DAWA/2024.12.31新项目笔记.md
Normal 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),
|
119
02-Note/DAWA/AI/AI资产生成管线思路.md
Normal file
119
02-Note/DAWA/AI/AI资产生成管线思路.md
Normal 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滤镜) 
|
||||
- [ ] 使用材质修改颜色的换皮怪是否会影响最后的训练结果?
|
||||
- [ ] 最终输出哪些东西? 与游戏目录结构相同的文件目录以及对应贴图模型文件、附带一个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,
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
36
02-Note/DAWA/AI/动画重定向功能调研.md
Normal file
36
02-Note/DAWA/AI/动画重定向功能调研.md
Normal 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文件。
|
||||
|
||||
# 怎么对接
|
107
02-Note/DAWA/AI/协助构建ue-runtime镜像请求.md
Normal file
107
02-Note/DAWA/AI/协助构建ue-runtime镜像请求.md
Normal 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"
|
||||
|
||||
启动过程没有报错就可以了。
|
408
02-Note/DAWA/AI偶像陪伴项目/AIVirtualIdel动画方案.md
Normal file
408
02-Note/DAWA/AI偶像陪伴项目/AIVirtualIdel动画方案.md
Normal 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 字节以内)进行捆绑,并且应由接收方进行适当的处理。
|
||||
- 传输周期以发送方的任意间隔执行。并非所有消息都会在每个周期发送。
|
||||
另外,发送方应该能够调整发送周期的间隔,或者以足够低的频率发送。
|
||||
- 接收方应丢弃不必要的消息。您不必处理所有消息。
|
||||
- 发送或接收哪些消息取决于两者的实现。
|
||||
- 未知地址,应忽略太多参数。
|
||||
- 如果您发现参数太少或类型与扩展规范中定义的参数不同,请将它们视为错误或忽略它们。
|
||||
|
||||

|
||||

|
||||
|
||||
### 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, "");
|
||||
|
||||
}
|
||||
|
||||
```
|
94
02-Note/DAWA/AI偶像陪伴项目/AI偶像陪伴项目笔记.md
Normal file
94
02-Note/DAWA/AI偶像陪伴项目/AI偶像陪伴项目笔记.md
Normal 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 Server;AIVirtualIdol接受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中一个重要的概念就是消息。
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
**网络中实际发送的内容**。
|
||||
|
||||

|
||||
|
||||
Chunk Format
|
||||
|
||||
|
||||
Message被切割成一个或多个Chunk,然后在网络上进行发送。
|
||||
当发送时,一个chunk发送完毕后才可以发送下一个chunk。
|
||||
|
||||

|
||||
|
||||
Message被拆分成一个或多个Chunk,然后在网络上发送
|
||||
|
||||
拆分的时候,**默认的Chunk Size是128字节**,以Message大小为300字节举例,进行拆分。
|
||||
|
||||
```undefined
|
||||
300 = 128 + 128 + 44
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
作者: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读取曲线并且播放动画。
|
122
02-Note/DAWA/AI偶像陪伴项目/AI虚拟偶像陪伴 & AI虚拟直播间 开发计划与阶段目标.md
Normal file
122
02-Note/DAWA/AI偶像陪伴项目/AI虚拟偶像陪伴 & AI虚拟直播间 开发计划与阶段目标.md
Normal 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
18
02-Note/DAWA/DAWA.md
Normal 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系统
|
8
02-Note/DAWA/InHouse插件/场景检查与设置预设插件.md
Normal file
8
02-Note/DAWA/InHouse插件/场景检查与设置预设插件.md
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
## 参数检查
|
||||
### 灯光
|
||||
- [ ] 开启RayTracing之后检查所有Map的SpotLight、RectLight、SkyLight、DirectionalLight(Angle较大时)的采样值。
|
||||
|
||||
## 参数预设与参数文档
|
||||
- [ ] MovieRenderQueue的预设以及相关参数文档
|
||||
- [ ] TAA相关的参数文档
|
92
02-Note/DAWA/MUTools/MUTools笔记.md
Normal file
92
02-Note/DAWA/MUTools/MUTools笔记.md
Normal 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)节点
|
644
02-Note/DAWA/Old/BH3音乐会场景优化.md
Normal file
644
02-Note/DAWA/Old/BH3音乐会场景优化.md
Normal 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材质
|
||||
变量:
|
||||
- NSlice:NearPlane 交点世界坐标
|
||||
- NDepth:
|
||||
- FSlice:FarPlane 交点世界坐标
|
||||
- FDepth:
|
||||
|
||||
MF_WSIntersection:如果没有相交,RelIntersectPos为,IntersectDistance为。
|
||||
|
||||
|
||||
### 优化方案
|
||||
1. DMX模型改成StaticMeshInstance,Gun的模型是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 Detail:4=>1
|
||||
- Lumen Scene View Distance:20000=>1000~3000
|
||||
- Max Trace Distance:20000=>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。
|
||||
MininumResidency:2MB
|
||||
Fallback Relative Error:1.0=> 0.1~0.0
|
||||
|
||||
#### 关闭FeiChuang蓝图中StaticMesh的Shadow
|
||||
|
||||
### Light&Shadow
|
||||
- [ ] 远处的几个小飞船 去掉CastShadow选项。
|
||||
|
||||
- DirectionalLight
|
||||
- Num Dynamic Shadow Cascades:5=>3
|
||||
- Dynamic Shadow Distance MovableLight:20000=>5000
|
||||
- DistanceField Shadow Distance:51200=>5000
|
||||
- Shadow Resolution Scale:1=>0.1~0.5
|
||||
- SpotLight3
|
||||
- 关闭CastShadow(3与4至少关一个)
|
||||
- Attenuation Radius:11052.831055=> 2040
|
||||
- Shadow Resolution Scale:1=>0.5
|
||||
- 第二次修改
|
||||
- Outer Cone Angle 44=>40
|
||||
- Shadow Resolution Scale:0.5=>0.3
|
||||
- SpotLight4
|
||||
- 关闭CastShadow(3与4至少关一个)
|
||||
- Attenuation Radius:12052.831055=>4000.0
|
||||
- 点光源
|
||||
- 关闭阴影
|
||||
- 降低Attenuation Radius到合适范围。
|
||||
- 删除机翼处重新的点光源,pointLight81
|
||||
|
||||
### Lumen
|
||||
调整Lumen渲染参数或者后处理盒子参数:
|
||||
Lumen Scene Lighting Quality: 2 =>1
|
||||
Lumen Scene Detail:4=>1
|
||||
Lumen Scene View Distance:20000=>5000
|
||||
Max Trace Distance:20000=>5000
|
||||
|
||||
### Translucent
|
||||
#### Nebula
|
||||
以下几个建议删除:
|
||||
BP_NebulaProceduralGenerator
|
||||
BP_NebulaProceduralGenerator2
|
||||
BP_NebulaProceduralGenerator3N
|
||||
BP_NebulaProceduralGeneratorMove
|
||||
BP_NebulaProceduralGeneratorMove2
|
||||
|
||||
- BP_NebulaProceduralGenerator5
|
||||
- Volumetric Sphere Amount:10=>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 Amount:10=>1~3(推荐1)
|
||||
- Quality:Hight => 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
|
||||
Sequence:Seq_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 Distance:20000=>1000
|
||||
- Max Trace Distance:20000=>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 Detail:2 => 1
|
||||
- Final Gather Quality:3 => 1
|
||||
- Lumen Scene View Distance:20000 => 5000
|
||||
- Max Trace Distance:20000 => 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 Distance:20000 => 1000
|
||||
- Max Trace Distance:20000 => 1000
|
||||
|
||||
### 粒子(严重影响性能!考虑删除或者优化)
|
||||
- EnvEmitter_System02
|
||||
- BP_EasyFog_2
|
||||
|
||||
### 合并模型
|
||||
将场景的中的圆环状模型、Box模型合并一下。
|
||||
|
||||
## L_JianZhong
|
||||
### Shadow
|
||||
- Ultra_Dynamic_Sky中的方向光
|
||||
- Dynamic Shadow Distance MovableLight:20000 => 10000
|
||||
- Num Dynamic Shadow Cascades:5 => 1
|
||||
- Distance Field Shadows:True => False
|
||||
- 关闭阴影
|
||||
- SpotLight17
|
||||
- SpotLight18
|
||||
- SpotLight
|
||||
|
||||
### 模型优化
|
||||
- 将场景中的石头、后景石头、剑以及剑下面的石头都转成Instance
|
||||
- 场景中的模型转成Nanite。
|
||||
- 相机背后看不到的模型都删除了。
|
||||
- 删除场景中的CrystalFlw模型
|
||||
- 将场景中的重复模型使用单个模型重新摆放,最后设置成Instance。
|
||||
|
||||
### Shadow
|
||||
- 关闭阴影
|
||||
- PointLight
|
||||
- PointLight4
|
||||
- PointLight5
|
||||
- PointLight6
|
||||
- SpotLight2
|
||||
- SkyLight
|
||||
- VolumetricSpotlightEngine4的SpotLight
|
||||
- 删除灯光
|
||||
- PointLight2
|
||||
- PointLight3
|
||||
- DirectionalLight
|
||||
- Dynamic Shadow Distance MovableLight:20000 =>4000
|
||||
- Num Dynamic Shadow Cascades:5 => 1
|
||||
- DistanceField Shadow Distance:4000
|
||||
- DistanceField Trace Distance:25000 =>2000~4000
|
||||
- Shadow Resolution Scale:1 => 0.1
|
||||
### Lumen
|
||||
PostProcessVolumn => Lumen
|
||||
- Lumen Scene Lighting Quality: 2 => 0.25
|
||||
- Lumen Scene Detail:2 => 1
|
||||
- Final Gather Quality:3 => 1
|
||||
- Lumen Scene View Distance:20000 => 1000
|
||||
- Max Trace Distance:20000 => 1000
|
||||
|
||||
ProjectSettings - Lumen (以下设置仅提高1~2帧)
|
||||
- Use Hardware Ray Tracing when available:
|
||||
- High Quality Translucency Reflections:
|
||||
- Software Ray Tracing Mode:DetailTracing => 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 ScreenPercentage:66 GPU帧数:27~29
|
||||
- TAA ScreenPercentage:100 GPU帧数:36~37
|
||||
- FSR+TAA ScreenPercentage:66 GPU帧数:26~28
|
||||
- DLSS+TAA ScreenPercentage:66 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
|
17
02-Note/DAWA/Old/FightingGameStemp笔记.md
Normal file
17
02-Note/DAWA/Old/FightingGameStemp笔记.md
Normal 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蓝图类中。
|
200
02-Note/DAWA/Old/UE5制片流程相关想法.md
Normal file
200
02-Note/DAWA/Old/UE5制片流程相关想法.md
Normal 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. 虚拟光照贴图(需要固态硬盘)
|
||||
|
||||
#### Lumen(GI与反射)
|
||||
- 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 Tip:https://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的切片渲染功能、或者联机部署)。
|
17
02-Note/DAWA/Old/原神&绝区零&星穹铁道 资产提取.md
Normal file
17
02-Note/DAWA/Old/原神&绝区零&星穹铁道 资产提取.md
Normal 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
|
135
02-Note/DAWA/Old/武庚纪优化.md
Normal file
135
02-Note/DAWA/Old/武庚纪优化.md
Normal file
@@ -0,0 +1,135 @@
|
||||
## 优化记录
|
||||
1. 灯光优化
|
||||
1. 主场景(城楼、栈桥与城门部分)
|
||||
1. 主光源
|
||||
1. 将DynamicShadowDistanceMoveableLight调整到合适范围(近景细节阴影):40000 => 4000~15000范围
|
||||
2. 调整DistanceFieldShadowDistance(远景的软阴影效果):20000 => 4000~15000,如果远景阴影效果有问题再调整DistanceFieldTraceDistance:100000.0 => 100000.0~40000000.0
|
||||
3. 调整Num Dynamic Shadow Cascades:4=> 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 Time,Spawn 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. 设置InstancedStaticMeshComponent,StaticMesh与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
|
||||
- DistanceFieldShadowDistance:40000=》10000
|
||||
- DirectionalLight2
|
||||
- 级联4=》2
|
||||
- DynamicShadowDistanceMoveableLight 40000=》25000
|
||||
- DistanceFieldShadowDistance:10000=》1000000.0
|
||||
- DistanceFieldTraceDistance: =>2500000.0
|
||||
- DirectionalLight4
|
||||
- 级联5=》0
|
||||
- DynamicShadowDistanceMoveableLight 40000000.0=》4000.
|
||||
- DistanceFieldShadowDistance:1000000.0 =》1000000.0
|
||||
- DistanceFieldTraceDistance: 100000.0=》40000000.0
|
||||
|
||||
##### 2022.9.21
|
||||
- [x] DirectionalLight2
|
||||
- 删除
|
||||
- [ ] PointLight3
|
||||
- 亮度150=》50
|
||||
- 关闭CastShadow
|
||||
- [ ] SpotLight11
|
||||
- ShadowResolutionScale:1=》0.1
|
||||
- 或者考虑降低角度,只对着城楼。
|
||||
- [ ] RectLight7
|
||||
- 建议调整范围 =》降低至能刚好包裹城门的区域。
|
||||
- 亮度 500=>100
|
||||
- 关闭阴影
|
||||
- 或者删了
|
||||
- [ ] SpotLight25
|
||||
- 降低半径2000 角度 60
|
||||
- 阴影分辨率1=>0.5
|
||||
- [ ] SpotLight26
|
||||
- 降低半径到2500,角度降低到45,亮度提高=》10000 Shadow分辨率 1=》0.5
|
||||
- [ ] SpotLight27
|
||||
- 降低半径到2300,角度降低到45,亮度提高=》12000,Shadow分辨率 8=》0.5
|
||||
- [ ] PointLight56
|
||||
- 关闭CastShadow
|
||||
- 降低半径到1600
|
||||
- [ ] PointLight57
|
||||
- 关闭CastShadow 或者降低阴影分辨率 0.1=》0.01
|
||||
- 降低半径到700
|
||||
- [ ] PointLight74
|
||||
- 关闭CastShadow
|
||||
- 或者换成SpotLight
|
||||
- [ ] RectLight14
|
||||
- 降低半径到2860
|
||||
- 关闭CastShadow
|
||||
- 或者换成SpotLight
|
||||
|
||||
### 后处理
|
||||
- AmbientCubeMap:Intensity 1=》0
|
||||
- Ambient Occlusion:Intensity 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优化方法与实践笔记]]
|
25
02-Note/DAWA/柔体模拟/柔体模拟开发计划.md
Normal file
25
02-Note/DAWA/柔体模拟/柔体模拟开发计划.md
Normal file
@@ -0,0 +1,25 @@
|
||||
## 使用的模拟算法
|
||||
FEM + 肌肉(变量):
|
||||
PBD:Position 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中复现出现。
|
13
02-Note/DAWA/福瑞格斗项目/FighterGameTemplateProject笔记.md
Normal file
13
02-Note/DAWA/福瑞格斗项目/FighterGameTemplateProject笔记.md
Normal 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中。
|
94
02-Note/DAWA/福瑞格斗项目/HitBoxBlueprint笔记.md
Normal file
94
02-Note/DAWA/福瑞格斗项目/HitBoxBlueprint笔记.md
Normal 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. true,ActionComponent->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。
|
103
02-Note/DAWA/福瑞格斗项目/开发计划.md
Normal file
103
02-Note/DAWA/福瑞格斗项目/开发计划.md
Normal 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
|
134
02-Note/Obsidian/Obsidian插件推荐.md
Normal file
134
02-Note/Obsidian/Obsidian插件推荐.md
Normal 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://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甘特图笔记]]
|
25
02-Note/Obsidian/Obsidian甘特图笔记.md
Normal file
25
02-Note/Obsidian/Obsidian甘特图笔记.md
Normal 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
|
||||
```
|
||||
|
1
02-Note/WY/项目相关.md
Normal file
1
02-Note/WY/项目相关.md
Normal file
@@ -0,0 +1 @@
|
||||
# 前言
|
72
02-Note/演讲与教程笔记/UE5你可能不知道的35个新功能.md
Normal file
72
02-Note/演讲与教程笔记/UE5你可能不知道的35个新功能.md
Normal 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`。
|
||||

|
||||
|
||||
之后就可以在Window-ConsoleVariables打开这个工具了。
|
||||

|
||||
|
||||
主要的操作方法就是点击左上角的按钮添加ConsoleVariables。
|
||||

|
||||

|
||||
|
||||
之后还可以将其保存成预设,方便进行切换。
|
||||

|
||||
|
||||
### 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就可以保留物理模拟后的结果,适场景美术。
|
74
02-Note/演讲与教程笔记/杭州EPIC2019传媒学院线下技术交流沙龙.md
Normal file
74
02-Note/演讲与教程笔记/杭州EPIC2019传媒学院线下技术交流沙龙.md
Normal 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相关
|
||||

|
||||
|
||||

|
||||
|
||||
使用这个方法可以解决物体与地形的边界过于明显的问题。另一种解决方法是:
|
||||

|
||||
|
||||
Lerp的Alpha还可以使用HeightLerp、相对高度甚至是距离场来设置。
|
||||

|
||||
|
||||
地形的融合注意使用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。
|
100
02-Note/演讲与教程笔记/杭州UnrealCircle2022笔记.md
Normal file
100
02-Note/演讲与教程笔记/杭州UnrealCircle2022笔记.md
Normal 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并修改引擎的碰撞规则处理
|
118
02-Note/演讲与教程笔记/究极风暴渲染技术.md
Normal file
118
02-Note/演讲与教程笔记/究极风暴渲染技术.md
Normal 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。】
|
||||

|
||||
|
||||
### 火焰的特效
|
||||
图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噪音的简单角色表现
|
||||
动画作画的关键就是对信息量的控制。用少量的信息表现出强烈的个性,怎么才能做出活灵活现的角色呢?
|
||||
|
||||

|
||||
|
||||
### 角色模型的LOD
|
||||
角色的建模使用3dsMax。图里是主人公漩涡鸣人的身体和头的建模(从左开始是高、中、低模)。多边形数量各自为高模16000(头部2400)、中模8000(头部1400)、低模4000(头部700),基本上由从镜头的距离算起:5米以内、5到7米、8米以上来区分,以此进行LOD处理。梅田公一先生表示,这是为了在减轻描画处理负荷的同时,表现出动画特有的[远处的东西被画出线条是经过整理的形态]。还有,有时有必要让人清楚的看到表演性质的脸,还会发生由于大量的角色登场引起的负荷上升那样局面的情况,要根据情况强制性的切换高模或是低模。还有,脸上使用到了很多的多边形,本作中使用法术时结印的手指也分配到了相当精细的多边形。
|
||||
|
||||
【千里马肝注:临、兵、斗、者、皆、阵、烈、在、前。这九个字源于东晋葛洪的《抱朴子*登涉篇》,(葛洪者,乃东晋时结合儒家思想改造道教,宣扬采药炼丹、长生不老的人,至此,道教变成为封建统治服务的宗教)“临兵斗者,皆数组前行,常当视之,无所不辟。” 意思是说,常念这九个字,就可以辟除一些邪恶。在抄录这九个字时,把“数、组、前、行”误抄成“阵、列、在、前”而沿用至今。这九个字分别的意思是:临,代表身心稳定。兵,代表能量。斗,代表宇宙共鸣。者,代表复原。皆,代表危机感应。阵,代表心电感应或隐身。列,代表时空控制。在,代表对五元素的控制。(五元素:就是我们熟知的金、木、水、火、土。)前,代表光明。】
|
||||

|
||||
|
||||
### 重视画面效果的生动配色阴影
|
||||
公司内部以前使用过的乘法CelShader,绘制出皮肤的阴影部分的褪色效果是个难点,见上图A。在本作中,为了解决那个问题,模仿了Photoshop的softlight图层效果开发出了CelShader,见上图B。用这个方法虽然确实在皮肤的影子上能得到良好的结果,但同时衣服等东西原本的色彩很高的部分影子也变的明亮,结果作为影子就会出现破绽。于是开发出了新的彩度高的像素上使用乘算,彩度低的像素上用softlight的,把影子颜色在直线上混合描绘的shader,实现了取得平衡的阴影,见上图C。另外,项目主管的西川裕贵先生表示,因为这个shader保持相当的的彩度,根据动画上经常用的色调也可以适用在阴影的表现上,例如说把夜里的阴影做成青色时也很有用。
|
||||
|
||||
【千里马肝注:动画在绘制之前,会由一位经验丰富的美术绘制一套被称作“色彩指定”,简称“色指”的图片,其中对于角色的肤色、头发色、眼睛、服饰等的颜色进行规定,包括该部分在阴影下的颜色、高光的颜色等等。这样严格要求的结果是,当动画关键帧被绘制完成后,流水线上的美术必须严格按照色指进行上色,于是出来的动画效果就如同一个人画出来的一样。】
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 整理出动画风格的阴影
|
||||
虽然图A把高模做出了GlowShading,但是如果使用这个光源设定进行CelShading,光照会像图B那样忠实的表现出阴影来,导致表情变的非常僵硬。这样就达不到动画片的效果,所以需要像图C和D那样让模型膨胀再整理法线,作成调整过的模型,取得了法线信息后,把那些适用在原模型上,就可以再现出图E那样动画风格的柔软的表情。还有,在实际主机上的再现是开发了专用的插件对应完成的。这个Shading技术可以说是初期研究开发阶段很大的一个成果。
|
||||
|
||||
【千里马肝注:这种手法之前在《偶像大师1、2》中也出现过,具体实现还是第一次见到。需要试验一下,这种膨胀的方式,当光源位置变化时会是什么效果?】
|
||||

|
||||
|
||||
### 必要的轮廓线(Outline)抽出法
|
||||
作为轮廓线的生成法,从PS2的时候就是被经常使用的方法,把模型按法线的方向推出很大的一圈,再把多边形的法线反向作为轮廓的绘制手法,图A。用这个方法,因为对于全部的多边形都适用一样的处理,头发等的轮廓也忠实的绘制出来,表现出期望中的动画风格的质感。还有一种生成法,一次性把模型渲染到2Dbuffer中进行post处理,是一种抽出模型的轮廓(silhouette)的绘制手法,图B。用这个手法,虽然使用silhouette生成了简单的的轮廓线(Outline),但不能在silhouette的内侧生成。所以本作的Celshading上附加了轮廓线(Outline)选择的选项,由于设计师会对应状况选择绘制方法,实现了这两个手法的并用。
|
||||
|
||||
【千里马肝注:图A的方法不用多说;图B的方法可以将normal或depth渲染到rendertarget中,然后进行Sobelfilter处理。作为卡通渲染,有时的确不希望过多的细节被表现出来,这里本作使用2种可选项是个好办法。更多的信息请见RenderMonkey中的NPR。】
|
||||
|
||||
|
||||
|
||||
### 动画中加入了其所特有的夸张
|
||||
动画的深奥妙趣,就是充分利用省略和强调张驰。
|
||||
那么如何通过CG来表现这些并灵活的运用呢?
|
||||
这里来披露一些吧。
|
||||

|
||||
|
||||
### 索具(Rigging)的准备配置(Setup)
|
||||
角色的索具装配使用了Biped。骨骼的构成,被称为全角色共通的基础的躯干部分有52个,各种调整用的自由骨骼是22个,面部是67个,服装和头发等摇动的物体用的延伸骨骼是30到80个,全部大概每个角色可以算出是170到220个。还有,做成的动画数,漩涡鸣人的情况,在自由战斗时是125个,极限任务模式里是90个。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
### 动画拖影(AnimeBlur)的实现
|
||||
作为动画片固有的速度表现,速度过快的会有轮廓被拖长的情况。在本作中把三角锤的模型重叠(图A),提前加入角色的末端部分(图B),把骨骼拉出来(图C),实现了这个效果(图D)。在骨架上加入了弹簧控制,根据动作的速度把拉出的长度自动的变化,虽然做出的动作也有些和表演意图不同,但为了取得平衡,全部的人都加入了。
|
||||
【千里马肝注: 关于弹簧控制,请搜索:虎克定律。】
|
||||
|
||||

|
||||
|
||||
### 面部配置(Setup)的基础
|
||||
面部正如前述那样由67个骨骼控制(图A)。作为本作特有的设定,下唇的下骨骼占到脸的三分只一。这是为了可以表现出即使是动画独特的张嘴,下巴的位置也不会改变,或是表现出图B那样,脸有很大的扭曲。还有一看就明白的,真实系的面部配置中,大多被设定的脸颊或鼻根的骨骼极少。这样着色在平面上难以反映变化,所以并不是要设定最低限度的骨骼,而代替那些皱纹表现的,要用专用的3D模型(图C)来实现,图D。顺便说一句,图C的两个白色圆板是眼睛的高光,这样可以把皱纹以及高光从shader上分离,容易按意图做出表情
|
||||

|
||||
|
||||
### 动画独特的不合常态的关节
|
||||
图A作为人的动作当然相当的正确,但是按动画来看就太过死板缺乏趣感。正是像图B那样柔软有弹性的形状变化才符合动画片。为了实现那些,要用基础的骨骼(图A)制作动作后,再把子阶层模型顶点网格通过自由骨骼对权重进行控制(图C的红色部分),这样就可以制作动画独特的柔软性。
|
||||

|
||||
|
||||
### 大胆夸张的动画风格构图
|
||||
在实际的镜头中,一旦让被摄影对象的人完全的进入画面,怎样努力也如图A那样有透视的限制。所以为了得到如图B那样动画型的夸张的构图,要通过Biped的骨架的辅助动画中加算上Scale,让其在物理性上巨大化。
|
||||

|
||||
|
||||
### 面部的作业环境
|
||||
使用了以前的目标变形(MorphTarget)是因为要在本作中变更骨骼,如图那样,准备了选择器,工作中可以迅速的对应新的手法(图A)。还有,由于这个变更,表情的数据库管理化就成了问题,但是可以把表情的骨骼和标准的姿态的骨骼的差值做成文本档案化,解决了输出脚本的制作问题。由于这样,通过目标变形能够混合两个表情的同时,还可以转移到拥有同一个骨架构造的其他角色上做出类似的表情,对工作的效率化,还有风格的统一等有很大的作用。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
### 背景的概要
|
||||
上图是战斗舞台的其中一个,角色的移动可能范围按实际尺寸是直径50米,最远景是500米左右,大约用了5万多边形做成。担当背景制作的四所铁矢先生表示,开发当初好像有40万到50万多边形,一边要维持品质一边要把多边形消减到能够实时绘制处理的水平,很辛苦呢
|
||||

|
||||
|
||||
### 丰富的河流表现
|
||||
让水面上反射出美丽阳光的河流(图A),如何能用一张平面的多边形作出来(图B)。使用的贴图有,color(图C),height(图D),normal(图E),shadow(图F),环境(图G)5个种类,水的颜色在shader参数里设定,在颜色上也可以增加高度的信息。环境贴图拥有alpha通道,加入了模拟的HDR信息(图H)绘制太阳一类的效果。像素描绘的结果,会显出很强的GlareFilter效果,花了很多工夫。
|
||||

|
||||
|
||||
### 背景的贴图
|
||||
在贴图上使用了Albedo贴图(图A)和阴影贴图(图B)。根据适用的地方,在2048X2048到256X256像素的范围内选择尺寸,使用DXTC格式总计30MB的程度。还有,用Photoshop的插件做成Mipmap来使用(图C)。虽然由于这些贴图的容量会增加33%,但和没使用时比起来,GPU的处理负荷减轻了接近一半,那个效果是非常大的。
|
||||
|
||||

|
||||
|
||||
### 背景的作业环境
|
||||
公司内部在构筑开发环境上也倾注了全力,在背景制作中使用了30台以上的wacom的21寸液晶绘图板。更有应该注意的是在负责人的头上,有个防止面对监视器时映入灯光的特别定作的圆盘形的遮光罩。这是日产汽车和slik共同开发的产品,由于这个抑制了照明,实现了没有那么明亮的作业环境。
|
||||

|
27
02-Note/演讲与教程笔记/虚幻开放日2020/LiveLink的应用及扩展.md
Normal file
27
02-Note/演讲与教程笔记/虚幻开放日2020/LiveLink的应用及扩展.md
Normal 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组件
|
26
02-Note/演讲与教程笔记/虚幻开放日2020/有用演讲地址.md
Normal file
26
02-Note/演讲与教程笔记/虚幻开放日2020/有用演讲地址.md
Normal 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,之后再制作粒子效果。
|
||||
|
||||
### 用虚幻示意图形(UMG)UI设计器优化并构建3A级UI
|
||||
https://www.bilibili.com/video/BV1EK4y1j7VB
|
||||
UMG的优化
|
||||
|
||||
### 用Unreal Insights收集、分析及可视化你的数据
|
||||
https://www.bilibili.com/video/BV1Ay4y1q7Kj
|
||||
|
||||
### 利用虚幻引擎自动化框架进行性能测试
|
||||
https://www.bilibili.com/video/BV1Ca4y1p7Bq
|
18
02-Note/演讲与教程笔记/虚幻开放日2020/用CI与CD基础架构实现高效的移动游戏开发.md
Normal file
18
02-Note/演讲与教程笔记/虚幻开放日2020/用CI与CD基础架构实现高效的移动游戏开发.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## 视频地址
|
||||
https://www.bilibili.com/video/BV19p4y167gh
|
||||
|
||||
## 为什么需要CL
|
||||
- 多操作系统开发需要
|
||||
- 构建与发布产品(不同环境)
|
||||
- 构建DLL并且上传到哨兵程序中
|
||||
- 构建自定义引擎
|
||||
- 运行自动测试程序
|
||||
- 检测代码与分支的有效性
|
||||
- 为每个设置构建对应的版本
|
||||
|
||||
## CL工具
|
||||
- TeamCity
|
||||
- Jenkins
|
||||
- Bamboo
|
||||
|
||||
## CD
|
40
02-Note/演讲与教程笔记/虚幻开放日2020/虚幻引擎移动端ComputeShader的应用.md
Normal file
40
02-Note/演讲与教程笔记/虚幻开放日2020/虚幻引擎移动端ComputeShader的应用.md
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
## 视频地址
|
||||
https://www.bilibili.com/video/BV1ey4y1q7s4
|
||||
|
||||
## ComputerShadder
|
||||
### 支持原子操作
|
||||
InterLockedAdd(),Min()/Max(),Exchange()
|
||||

|
||||
|
||||
### 线程同步
|
||||
GroupMemoryBarrierWithGroupSync()
|
||||
|
||||
### Shader传入参数
|
||||
- GroupThreadID(SV_GroupThreadID)
|
||||
- GroupID(SV_GroupID)
|
||||
- DispatchThreadID(SV_DispatchThreadID)
|
||||
- GroupIndex(SV_GroupIndex)
|
||||
|
||||
可以在微软的文档中找到具体解释。
|
||||
|
||||
### ComputeShader对于PixelShader的优势
|
||||
PixelShader只能处理当前Shader,ComputeShader是任意位置可写。可以用于编写屏幕空间反射等需要将效果写入任意位置的效果。
|
||||
可以更好地利用显卡的并线单元。使用二分法循环计算:
|
||||
|
||||

|
||||
|
||||
共享内存:举个例子模糊、等需要多次采样各个像素的算法,使用共享内存就可以减少采样次数与消耗。
|
||||
|
||||
#### 工作组
|
||||
工作组会影响共享内存大小,从而影响多次采样像素的效率。但也会影响线程同步,同步速度变慢,也会影响原子操作次数。
|
||||
|
||||
### 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
|
42
02-Note/演讲与教程笔记/虚幻开放日2022/UE5新材质系统-Strata.md
Normal file
42
02-Note/演讲与教程笔记/虚幻开放日2022/UE5新材质系统-Strata.md
Normal 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
|
||||
|
||||

|
||||
|
||||
Fuzz用于模拟天鹅绒这种边缘会泛光的材质。
|
||||

|
||||
|
||||
这里的MFP指的是Mean Free Path(平均自由路径),下面这4个引脚控制SSS效果。
|
||||

|
||||
|
||||
使用下图节点实现透射率转化为MFP,这主要是方便美术理解,也因为MFP不是线性的关系所致。该节点通过计算BaseColor与TransmittanceColor来取得MFP与Thickness。
|
||||

|
||||
|
||||
## 混合方式
|
||||
- Horizontal Mixing:水平混合模式。使用Mask贴图混合也可以做到。
|
||||
- Vertical Layering:垂直混合模式。(使用Strata可以保证物理正确)
|
||||
- 
|
||||
- Coverage Weight:权重混合
|
||||
- Thin Film
|
||||
- 
|
||||
## Strata Tree
|
||||
可以通过查看Strata材质组成构造。
|
||||
|
||||
其中好处就是在跨平台移植时可以方便得关掉一些材质功能,在保证大体效果接近的情况下进行优化。
|
||||

|
||||
|
||||
## 数据存储方式
|
||||

|
||||
|
||||
## debug与命令
|
||||
勾选 `Show - Visualize - StrataMaterial`就可以查看占用数据。
|
||||
|
||||
r.Strata.Debug.VisualizeMode 1/2/3 可以切换不同的debug视图。
|
32
02-Note/演讲与教程笔记/虚幻开放日2022/UE热更新的原理与实现.md
Normal file
32
02-Note/演讲与教程笔记/虚幻开放日2022/UE热更新的原理与实现.md
Normal 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
|
52
02-Note/演讲与教程笔记/虚幻开放日2022/多线程渲染.md
Normal file
52
02-Note/演讲与教程笔记/虚幻开放日2022/多线程渲染.md
Normal 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线程的方式来解决。
|
||||

|
||||
|
||||
渲染线程会每帧的帧头处等待RHI线程执行结束。
|
||||

|
||||
|
||||
## RHI
|
||||
同时渲染线程会将这些RHI命令都打包在一起,以链表的方式发送到RHI线程进行处理。其中FRHICommandListImmediate(单例,只存在一个)只存放立即执行的RHI命令,比如LockTextre2D子类的。
|
||||

|
||||
|
||||
## 多线程翻译RHI指令
|
||||
- IRHIComputeContext:只有一些ComputeShader相关逻辑。
|
||||
- IRHICommandContext(继承自IRHIComputeContext):其他更多GPU指令。
|
||||
|
||||
Command会以链表的方式被构建成CommandBuffer之后加入驱动层的Queue中。
|
||||

|
||||
|
||||
## 线程同步
|
||||
- 前端使用了TaskGraph System来同步,主要通过设置Task与Task之间的条件来实现的。
|
||||
- 后端使用Barrier、Fence、Semaphore等方法。
|
||||
|
||||
## MeshDrawCommand的并行过程
|
||||

|
||||
|
||||

|
||||
|
||||
## RDG并行渲染
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 并行渲染Debug
|
||||
- ByPass
|
||||
- r.RHICmdBypass
|
||||
- - forcerhibypass
|
||||
- GRHISupportsRHIThread
|
||||
- console variables =>r.metal.IOSRHIThread r.OpenGL.AllowRHIThread r.vulkan.RHIThread
|
||||
- -rhithread -norhithread
|
||||
- GRHISupportsParallelRHIExecute
|
||||
- r.Vulkan.RHIThread >1
|
22
02-Note/演讲与教程笔记/虚幻开放日2022/气氛和调子-UE影调设计和实战解析.md
Normal file
22
02-Note/演讲与教程笔记/虚幻开放日2022/气氛和调子-UE影调设计和实战解析.md
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
## 时间与天气
|
||||
首先得确定时间与天气(大气),可以制作一个多维度的表格作为参考,之后就可以在创作时选择接近环境氛围快速出效果了。
|
||||
|
||||
这里李文磊介绍了自己做一个渲染参数数轴,对比度、亮度、颜色=>渲染效果。
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
每个突然结果上标注曝光度、主光角度、色温、饱和度以及天气。
|
||||

|
||||
|
||||
|
||||
甚至可以配上其他渲染参考
|
||||

|
||||
|
||||
## 曝光
|
||||
曝光是确定时间之后第二确定的。但艺术创作需求很可能会是过曝或者欠爆的,所以不能直接依赖与自动曝光。
|
||||
|
||||
1、可以通过切换到BaseColorOnly模式下(固有色亮度与灯管都会影响曝光),测算暗部Ev与亮度Ev值之后,再来调整自动曝光的范围。
|
||||
2、如果真实情况得到想要的效果就可以使用曝光补偿与曝光补偿曲线。
|
46
02-Note/演讲与教程笔记/虚幻开放日2022/虚幻开放日2022.md
Normal file
46
02-Note/演讲与教程笔记/虚幻开放日2022/虚幻开放日2022.md
Normal 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行为树:切换条件不明显
|
||||
- 动画状态机:不能总览,需要深入每个层级才能了解细节。且该功能转为动画设计。
|
||||
- 
|
||||
- [[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)
|
80
02-Note/演讲与教程笔记/虚幻开放日2023/总有一个你不知道的虚幻引擎调试技巧.md
Normal file
80
02-Note/演讲与教程笔记/虚幻开放日2023/总有一个你不知道的虚幻引擎调试技巧.md
Normal 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+F10,Set 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]]
|
6
02-Note/演讲与教程笔记/虚幻开放日2023/渲染相关的分享.md
Normal file
6
02-Note/演讲与教程笔记/虚幻开放日2023/渲染相关的分享.md
Normal 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
|
9
02-Note/演讲与教程笔记/虚幻开放日2024/其他有价值的分享.md
Normal file
9
02-Note/演讲与教程笔记/虚幻开放日2024/其他有价值的分享.md
Normal 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
|
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/Lumen_SHData.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/Lumen_SHData.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/Lumen_能量补偿1.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/Lumen_能量补偿1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/Lumen_能量补偿2.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/Lumen_能量补偿2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/SDF阴影与额发阴影.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/SDF阴影与额发阴影.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/场景半程阴影屏蔽后.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/场景半程阴影屏蔽后.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/角色阴影最终合成.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/角色阴影最终合成.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/角色非半程阴影屏蔽前.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/角色非半程阴影屏蔽前.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/角色非半程阴影屏蔽后.png
(Stored with Git LFS)
Normal file
BIN
02-Note/演讲与教程笔记/虚幻开放日2024/用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线/角色非半程阴影屏蔽后.png
(Stored with Git LFS)
Normal file
Binary file not shown.
16
02-Note/演讲与教程笔记/虚幻开放日2024/第56期用Rider点燃你的UE开发之旅.md
Normal file
16
02-Note/演讲与教程笔记/虚幻开放日2024/第56期用Rider点燃你的UE开发之旅.md
Normal 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
|
212
02-Note/演讲与教程笔记/虚幻开放日2024/虚幻引擎4移动端渲染管线改造总结分享.md
Normal file
212
02-Note/演讲与教程笔记/虚幻开放日2024/虚幻引擎4移动端渲染管线改造总结分享.md
Normal 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上显示特效的需求
|
47
02-Note/演讲与教程笔记/虚幻开放日2024/虚幻引擎中的DS服务器优化.md
Normal file
47
02-Note/演讲与教程笔记/虚幻开放日2024/虚幻引擎中的DS服务器优化.md
Normal 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();
|
||||
}
|
||||
```
|
||||
|
||||
# 整理并且标识同步规则
|
351
02-Note/读书笔记/地平线的世界设计.md
Normal file
351
02-Note/读书笔记/地平线的世界设计.md
Normal 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、由于光噪声本身不能切割,我们开发了自己的分层噪音。
|
||||
|
||||

|
||||
|
||||
Worley噪音是由Steven Worley在1996年引入的,经常用于因果关系和水的影响。如果它是倒立的,就像你在这里看到的:
|
||||
|
||||
它的形状非常紧密。
|
||||
|
||||
我们把它像标准的perlinfbm方法一样分层
|
||||
|
||||
然后,我们用它来作为对dilate perlin噪声的偏移量。这使我们能够保持perlin噪声的连接,但是增加了一些billowy的形状。
|
||||
|
||||
我们把这叫做“佩林-沃利”的噪音
|
||||
|
||||
11、在游戏中,把声音存储为tiling 3d纹理通常是最好的。
|
||||
|
||||
•你想保持纹理读取到最低…
|
||||
|
||||
•尽可能小的保持决心。
|
||||
|
||||
•在我们的例子中我们已经压缩的声音…
|
||||
|
||||
•两个3 d纹理…
|
||||
|
||||
•和1 2 d纹理。
|
||||
|
||||

|
||||
|
||||
第一个3 d纹理…
|
||||
|
||||
•4个通道
|
||||
|
||||
•这是128 ^ 3决议…
|
||||
|
||||
第一个频道是我刚才描述的perlin- worley噪声。
|
||||
|
||||
另外3个是在频率增加的Worley噪声。和标准方法一样,这个3d纹理用来定义云的基本形状。
|
||||
|
||||

|
||||
|
||||
我们的第二个3 d纹理…
|
||||
|
||||
•有3个频道…
|
||||
|
||||
•它是32 ^ 3决议…
|
||||
|
||||
•使用Worley噪音来增加频率。这个纹理被用来向第一个3d噪音定义的基本云形状添加细节。
|
||||
|
||||

|
||||
|
||||
•我们的2 d纹理…
|
||||
|
||||
•有3个频道…
|
||||
|
||||
•这是128 ^ 2分辨率…
|
||||
|
||||
•并使用curl噪音。它不是发散的,是用来假装流体运动的。我们使用这种噪音来扭曲我们的云形状并增加气流的感觉。
|
||||
|
||||

|
||||
|
||||
回想一下,标准解决方案要求高度梯度来改变高度的噪音信号。相反,我们使用…
|
||||
|
||||
•3数学预设,代表了主要的低空…
|
||||
|
||||
•当我们在样本位置混合时,云的类型。
|
||||
|
||||
•我们也有一个值,告诉我们希望在样本位置上有多少云覆盖。这是0和1之间的值。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
我们在屏幕右边看到的是一个大约30度的视图。我们将会在相机上方的一个区域中按标准方法绘制云。
|
||||
|
||||
首先,我们建立一个基本的云形状,通过采样我们的第一个3d纹理并将它乘以我们的高度信号。
|
||||
|
||||
•下一步是将结果乘上覆盖范围,并减少云层底部的密度。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
使用2D燥波增加基础云的细节
|
||||
|
||||

|
||||
|
||||
•通过在云边缘减去第二个3d纹理来侵蚀基本的云形状。小提示,如果你在云的基础上颠倒了Worley的噪音,你会得到一些很好的声音形状。
|
||||
|
||||
•我们也扭曲第二旋度由2 d纹理噪声假纠结的扭曲的大气湍流可以看到…
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
在这个图像中你可以看到一个小地图在左下角。这代表了在我们的世界地图上驱动云层的天气设置。您所看到的粉红色白色图案是来自我们的天气系统的输出。红色是覆盖层,绿色是降水,蓝色是云类型。天气系统通过模拟在游戏中进行的模拟来调节这些通道。这里的图像有积雨云直接在头顶(白色)和普通积云在远处。我们有控制偏见的方法来保持事物在一般意义上是直接的。
|
||||
|
||||
|
||||
|
||||
其实就是通过贴图来控制雨云的分布
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
我们还利用我们的天气系统来确保云是地平线,这是很有趣的。
|
||||
|
||||
•我们画的cloudscape withina球员…周围半径35000米。
|
||||
|
||||
•和从15000米的距离…
|
||||
|
||||
•我们开始向积云过渡到50%的覆盖率。
|
||||
|
||||
|
||||
|
||||
总结
|
||||
|
||||
所以总结我们的建模方法…
|
||||
|
||||
我们遵循标准的ray - march / sampler框架
|
||||
|
||||
但是我们用两个层次的细节来构建云
|
||||
|
||||
低频率的云基形状
|
||||
|
||||
高频细节和失真
|
||||
|
||||
我们的噪音是由Perlin、Worley和Curl杂音制成的
|
||||
|
||||
我们为每个云类型使用一组预先设置来控制在高度和云覆盖上的密度
|
||||
|
||||
这些都是由我们的天气模拟或定制纹理来驱动的,用于使用剪切场景
|
||||
|
||||
它在给定的风向中都是动态的。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
云的定向散射或发光质量…
|
||||
|
||||
•银衬里当你看向太阳通过云…
|
||||
|
||||
当你从太阳中移开时,云上的暗边可以看到。
|
||||
|
||||
前两个有标准的解决方案,但第三个是我们必须解决的问题。
|
||||
|
||||
|
||||
|
||||
在光线进入云再到结束,要么被吸收、要么色散
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
Beer's law说,我们可以根据所经过的介质的光学厚度来确定光的到达点。根据比尔斯定律,我们有一种基本的方法来描述云中的特定点的光量。
|
||||
|
||||
•如果我们用能量来替代云中的透光率,并把它画出来,你就可以看到能量指数的下降。这是我们照明模型的基础。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
在云层中,有更高的光散射的可能性。这被称为各向异性散射。
|
||||
|
||||
在1941年,henyey - greenstein模型被开发用来帮助天文学家在星系尺度上进行光计算,但今天它被用于在云照明中可靠地复制各向异性。
|
||||
|
||||

|
||||
|
||||
每次我们采样光能量时,我们用henyey - greenstein相函数乘以它。
|
||||
|
||||

|
||||
|
||||
在这里你可以看到结果。左边的只是我们照明模型的Beer‘s law部分。在右边,我们应用了henyey - greenstein相函数。请注意,在右边的太阳周围的云层更明亮。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
当我们在云上越走越深时,我们在散射上的潜力就会增加,更多的信息将会到达我们的眼睛。
|
||||
|
||||
•如果你把这两个函数得到描述这样的东西…
|
||||
|
||||
•效应以及传统方法。
|
||||
|
||||
我还在ACM数字图书馆寻找啤酒粉的近似方法,但我还没有发现任何与这个名字有关的东西。
|
||||
|
||||
|
||||
|
||||
将Power公式与Beer's law相结合,得到了Beer's power
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
但我们必须记住,这是一个依赖于视图的效果。我们只看到,我们的视图矢量接近光矢量,所以Power函数也应该解释这个梯度。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
So, in review our model has 3 components:
|
||||
|
||||
•Beer’s Law
|
||||
|
||||
•Henyen-Greenstein
|
||||
|
||||
•our powder sugar effect
|
||||
|
||||
•And Absorption increasing for rain clouds
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
低海拔1500~4000用体素云。超过4000就用2D ray march云
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
回想一下,采样器有一种低细节的噪声,它能形成基本的云形状
|
||||
|
||||
•高细节的噪音,增加我们所需要的现实细节。
|
||||
|
||||
高细节的噪声总是应用于从基云形状边缘的侵蚀。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
这意味着我们只需要做高细节的噪声和所有相关的指令,其中低细节的样本返回一个非零的结果。
|
||||
|
||||
•这就产生了在我们的云所处的区域周围产生一个等表面的效应。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
因为射线长度增加我们看向地平线,我们开始…
|
||||
|
||||
•一个初始potential64样本和结束…
|
||||
|
||||
•潜在的128位。我之所以说潜力,是因为这些优化可能导致3月份提前退出。我们真的希望他们能做到。
|
||||
|
||||
这就是我们如何利用样本来建立我们图像的alpha通道。为了计算光强度,我们需要取更多的样品。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
自阴影算法…………
|
2053
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/c++17带来的代码变化.md
Normal file
2053
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/c++17带来的代码变化.md
Normal file
File diff suppressed because it is too large
Load Diff
494
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第一章.md
Normal file
494
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第一章.md
Normal 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初始化列表的使用细节
|
||||
初始化列表可以用于对应类型函数的返回值中。
|
||||
例如:就等于Foo(123,321.0),(假设有Foo(int,float)这个构造函数)
|
||||
```
|
||||
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指针所指向的不同对象来确定应该一用哪个对象的数据成员。下面举一个简单例子。
|
||||
|
||||
(1)bind(&f)() 假设f是一个全局函数,绑定全局函数并调用;
|
||||
|
||||
(2)bind (&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::ignore,y)=tp;//只获取了第三个值
|
||||
```
|
65
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第七章.md
Normal file
65
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第七章.md
Normal 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新增便利算法
|
508
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第三章.md
Normal file
508
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第三章.md
Normal 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-1,N-1,Indexes...>::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;
|
||||
}
|
||||
```
|
||||
后面若干章节较难,为了节约时间所以跳过了
|
210
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第九章.md
Normal file
210
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第九章.md
Normal 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, waiting,thread id: " << this_thread::get_id() << endl;
|
||||
return !full;
|
||||
}
|
||||
|
||||
bool NotEmpty() const
|
||||
{
|
||||
bool empty = m_queue.empty();
|
||||
if (empty)
|
||||
cout << "empty,waiting,thread 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;
|
||||
};
|
||||
```
|
151
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第二章.md
Normal file
151
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第二章.md
Normal 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。
|
647
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第五章.md
Normal file
647
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第五章.md
Normal 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_lock,unique可以随时释放锁
|
||||
```
|
||||
#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;
|
||||
}
|
||||
```
|
249
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第八章.md
Normal file
249
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第八章.md
Normal 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;
|
||||
};
|
||||
```
|
120
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第六章.md
Normal file
120
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第六章.md
Normal 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
|
||||
```
|
121
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第十一章.md
Normal file
121
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第十一章.md
Normal 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();
|
||||
}
|
||||
*/
|
||||
```
|
235
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第十三章.md
Normal file
235
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第十三章.md
Normal 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
|
@@ -0,0 +1,2 @@
|
||||
### 使用c++11开发一个对象的消息总线库
|
||||
大致了解消息总线的设计思路,但是没有一定的模板基础,代码看了也白看
|
132
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第十五章.md
Normal file
132
02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第十五章.md
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
@@ -0,0 +1 @@
|
||||
所有类型的容器和数组都抽象为一个Range,这个Range由一族迭代器组成,然后就可以基于这个抽象的Range实现更为抽象、规范、统一的算法了。Boost库已经实现了Range。
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user