Init
This commit is contained in:
65
03-UnrealEngine/卡通渲染相关资料/演讲笔记/CEDEC.md
Normal file
65
03-UnrealEngine/卡通渲染相关资料/演讲笔记/CEDEC.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Untitled
|
||||
date: 2024-09-18 18:18:29
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# CEDEC
|
||||
ppt 下载 https://cedil.cesa.or.jp/cedil_sessions/view/3000
|
||||
|
||||
# CEDEC 2024
|
||||
## **神明寄宿于细节之中!「学园偶像大师」精益求精的3D角色·背景制作(个人翻译记录)**
|
||||
https://zhuanlan.zhihu.com/p/716241556
|
||||
https://www.famitsu.com/article/202408/15047
|
||||
https://www.4gamer.net/games/778/G077853/20240822037/
|
||||
https://cedec.cesa.or.jp/2024/session/detail/s660138bbdf4c1/
|
||||
- ppt https://cedil.cesa.or.jp/cedil_sessions/view/3000
|
||||
|
||||
[[神は細部に宿る!「学園アイドルマスター」のこだわり抜いた3Dキャラクター・背景制作.pptx]]
|
||||
## **次世代 NPR の輪郭線表現に迫る!リアルタイムベクトルストローク生成手法 StrokeGen を徹底解剖**
|
||||
https://www.siliconstudio.co.jp/rd/presentations/files/CEDEC2024/CEDEC2024_StrokeGen_submit.pdf
|
||||
[[CEDEC2024_StrokeGen_submit.pdf]]
|
||||
|
||||
## **統合スタイライズドレンダリングシステムの開発について紹介 〜共通利用ができる基盤を目指して〜**
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2961
|
||||
[[統合スタイライズドレンダリングシステムの開発について紹介 〜共通利用ができる基盤を目指して〜.pdf]]
|
||||
|
||||
## God of War, God of War Ragnarökにおける物理シミュレーション活用テクニック
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2970
|
||||
[[CEDEC2024_matsuike_jpn.pptx]]
|
||||
|
||||
## 3Dでキレイな線を引くために。ギルティギアシリーズのトゥーンライン制御テクニック
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2952
|
||||
[[講演CEDEC2024ギルティギアライン制御テクニック.pptx.pdf]]
|
||||
|
||||
## 『GRANBLUE FANTASY: Relink』最高の「没入感」を実現するカットシーン制作手法とそれを支える技術
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2978
|
||||
[[亀GRANBLUE FANTASY Relink亁嵟崅偺乽杤擖姶乿傪幚尰偡傞僇僢僩僔乕儞惂嶌庤朄偲偦傟傪巟偊傞媄弍.pdf]]
|
||||
|
||||
## 『GRANBLUE FANTASY: Relink』ソフトウェアラスタライザによる実践的なオクルージョンカリング
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2940
|
||||
[[亀GRANBLUE FANTASY Relink亁僜僼僩僂僃傾儔僗僞儔僀僓偵傛傞幚慔揑側僆僋儖乕僕儑儞僇儕儞僌.pdf]]
|
||||
|
||||
## 『GRANBLUE FANTASY: Relink』キャラクターの魅力を支えるリグ・シミュレーション制作事例
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2939
|
||||
[[亀GRANBLUE FANTASY Relink亁僉儍儔僋僞乕偺枺椡傪巟偊傞儕僌丒僔儈儏儗乕僔儑儞惂嶌帠椺.ppsx]]
|
||||
## **「学園アイドルマスター」におけるモバイルの性能を限界まで引き出すレンダリングパイプライン**
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2936
|
||||
[[CEDEC2024_gkmas_pipeline.pdf]]
|
||||
|
||||
# CEDEC 2023
|
||||
## 异度之刃3 CEDEC渲染分享
|
||||
https://zhuanlan.zhihu.com/p/588630949
|
||||
|
||||
## 自動生成マップの描画を高速化!~ComputeShaderを使ったオクルージョンカリングの理論と実装~
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2787
|
||||
[[自動生成マップの描画を高速化!ComputeShaderを使ったオクルージョンカリングの理論と実装.pptx]]
|
||||
|
||||
## **“BanG梦想! 女子乐队派对! ” 支持实时 3D 现场表演的图形和管道技术**
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2744
|
||||
[[『バンドリ! ガールズバンドパーティ!』リアルタイム3Dライブを支えるグラフィックとパイプラインの技術.pdf]]
|
||||
|
||||
## 『FINAL FANTASY VII EVER CRISIS』モバイル開発におけるハイクオリティ3D演出
|
||||
https://cedil.cesa.or.jp/cedil_sessions/view/2845
|
||||
[[CEDEC2023_applibot0909.ppsx]]
|
500
03-UnrealEngine/卡通渲染相关资料/演讲笔记/JasonMa/JasonMa渲染方案分析.md
Normal file
500
03-UnrealEngine/卡通渲染相关资料/演讲笔记/JasonMa/JasonMa渲染方案分析.md
Normal file
@@ -0,0 +1,500 @@
|
||||
---
|
||||
title: JasonMa渲染方案分析
|
||||
date: 2024-01-25 16:02:26
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
截止版本UE5.3.2。添加了一种新的GBuffer精度格式GBT_Uint_32_32_32_32,以及RT7。
|
||||
|
||||
修改内容:
|
||||
- GBufferPostDecode.ush:在GBufferPostDecode中给FGBufferData添加InitMooaToonContext()逻辑
|
||||
- SceneRendering.cpp:Debug用的渲染变量调节,并且在FViewInfo::SetupUniformBufferParameters()添加设置相关变量的逻辑。
|
||||
- 其他:
|
||||
- MaterialBaking:ExportMaterialProxy.h
|
||||
- MaterialEditor:MaterialEditor.cpp
|
||||
- PixelInspector:PixelInspectorResult.cpp、PixelInspectorResult.h
|
||||
|
||||
# ToonShadingCommon.ush
|
||||
除了一下工具函数,主要实现:
|
||||
1. GBuffer的一些Encode / Decode Helper函数。
|
||||
2. 用于存储数据的struct FMooaToonData、FMooaToonContext。
|
||||
3. 针对上面2个结构体编码与解码的函数。FMooaCustomPayload用于光追Shader。
|
||||
|
||||
## 数据结构体
|
||||
```c++
|
||||
struct FMooaToonContext
|
||||
{
|
||||
FMooaToonData MooaToonData;
|
||||
bool IsMainLight;
|
||||
float3 LightColor;
|
||||
bool IsEditorPreviewWorldType;
|
||||
float Exposure;
|
||||
uint2 PixelPos; // SVPos.xy
|
||||
};
|
||||
|
||||
struct FMooaToonData
|
||||
{
|
||||
// Diffuse
|
||||
float3 MainLightShadowColor;
|
||||
float MainLightShadowValueOffset;
|
||||
float MainLightShadowApplyLightColor;
|
||||
float OtherLightDiffuseThreshold;
|
||||
float OtherLightDiffuseFeather;
|
||||
float GlobalIlluminationIntensity;
|
||||
float GlobalIlluminationDirectionality;
|
||||
|
||||
// Specular
|
||||
float3 SpecularColor;
|
||||
float SpecularThreshold;
|
||||
float SpecularFeather;
|
||||
float ReflectionIntensity;
|
||||
bool IsPBRSpecular;
|
||||
float RimLightIntensity;
|
||||
float RimLightWidth;
|
||||
float RimLightAngle;
|
||||
float RimLightDepthThreshold;
|
||||
// Ray Tracing Shadow
|
||||
uint Stencil;
|
||||
uint RayTracingShadowFlag;
|
||||
float HairShadowWidth;
|
||||
float HairShadowIntensity;
|
||||
|
||||
// Unused
|
||||
float CustomData3;
|
||||
};
|
||||
```
|
||||
## Encode/Decode
|
||||
这里推荐使用计算器进行计算更加好理解。
|
||||
```c++
|
||||
//取得指定Bits位数的最大数值,8位 => 255
|
||||
uint GetBitsMaxValue(int Bits = 8) {return (1L << Bits) - 1;}
|
||||
|
||||
//将2个uint合并1个uint,将Src1移动Scr2的位数后,将2者进行或运算合并到一起。
|
||||
uint EncodeUint2ToUint(uint Src1, uint Src2, int BitsSrc1 = 8, int BitsSrc2 = 8)
|
||||
{
|
||||
return ((Src1 & GetBitsMaxValue(BitsSrc1)) << BitsSrc2) |
|
||||
(Src2 & GetBitsMaxValue(BitsSrc2));
|
||||
}
|
||||
|
||||
//将2个uint合并1个uint后转换成浮点数(0~1)
|
||||
float EncodeUint2ToFloat(uint Src1, uint Src2, int BitsSrc1 = 8, int BitsSrc2 = 8)
|
||||
{
|
||||
return float(EncodeUint2ToUint(Src1, Src2, BitsSrc1, BitsSrc2)) / GetBitsMaxValue(BitsSrc1 + BitsSrc2);
|
||||
}
|
||||
|
||||
//将合并后Uint还原成2个uint
|
||||
void DecodeUint2FromUint(uint Src, out uint Dst1, out uint Dst2, int BitsDst1 = 8, int BitsDst2 = 8)
|
||||
{
|
||||
Dst1 = (Src >> BitsDst2) & GetBitsMaxValue(BitsDst1);
|
||||
Dst2 = Src & GetBitsMaxValue(BitsDst2);
|
||||
}
|
||||
|
||||
//将合并后float(0~1)还原成2个uint
|
||||
void DecodeUint2FromFloat(float Src, out uint Dst1, out uint Dst2, int BitsDst1 = 8, int BitsDst2 = 8)
|
||||
{
|
||||
uint FloatToUint = uint(Src * GetBitsMaxValue(BitsDst1 + BitsDst2));
|
||||
DecodeUint2FromUint(FloatToUint, Dst1, Dst2, BitsDst1, BitsDst2);
|
||||
}
|
||||
|
||||
//将float(0~1)编码成uint
|
||||
uint EncodeFloatToUint(float Src1, int BitsSrc1 = 8)
|
||||
{
|
||||
return saturate(Src1) * GetBitsMaxValue(BitsSrc1);
|
||||
}
|
||||
|
||||
uint EncodeFloat2ToUint(float Src1, float Src2, int BitsSrc1 = 8, int BitsSrc2 = 8)
|
||||
{
|
||||
return EncodeUint2ToUint(EncodeFloatToUint(Src1, BitsSrc1), EncodeFloatToUint(Src2, BitsSrc2), BitsSrc1, BitsSrc2);
|
||||
}
|
||||
|
||||
float EncodeFloat2ToFloat(float Src1, float Src2, int BitsSrc1 = 8, int BitsSrc2 = 8)
|
||||
{
|
||||
return EncodeUint2ToFloat(EncodeFloatToUint(Src1, BitsSrc1), EncodeFloatToUint(Src2, BitsSrc2), BitsSrc1, BitsSrc2);
|
||||
}
|
||||
|
||||
//将uint还原成float(0~1)
|
||||
float DecodeFloatFromUint(uint Src, int BitsDst1 = 8)
|
||||
{
|
||||
return float(Src & GetBitsMaxValue(BitsDst1)) / GetBitsMaxValue(BitsDst1);
|
||||
}
|
||||
|
||||
void DecodeFloat2FromUint(uint Src, out float Dst1, out float Dst2, int BitsDst1 = 8, int BitsDst2 = 8)
|
||||
{
|
||||
uint OutUint1, OutUint2;
|
||||
DecodeUint2FromUint(Src, OutUint1, OutUint2, BitsDst1, BitsDst2);
|
||||
Dst1 = DecodeFloatFromUint(OutUint1, BitsDst1);
|
||||
Dst2 = DecodeFloatFromUint(OutUint2, BitsDst2);
|
||||
}
|
||||
|
||||
void DecodeFloat2FromFloat(float Src, out float Dst1, out float Dst2, int BitsDst1 = 8, int BitsDst2 = 8)
|
||||
{
|
||||
uint OutUint1, OutUint2;
|
||||
DecodeUint2FromFloat(Src, OutUint1, OutUint2, BitsDst1, BitsDst2);
|
||||
Dst1 = DecodeFloatFromUint(OutUint1, BitsDst1);
|
||||
Dst2 = DecodeFloatFromUint(OutUint2, BitsDst2);
|
||||
```
|
||||
|
||||
## 结构体与GBuffer Encode/Decode
|
||||
```c++
|
||||
void EncodeMooaToonData(FMooaToonData MooaToonData, out float4 MooaToonDataA, out float4 MooaToonDataB, out float4 MooaToonDataC)
|
||||
{
|
||||
MooaToonDataA = (float4)0;
|
||||
MooaToonDataB = (float4)0;
|
||||
MooaToonDataC = (float4)0;
|
||||
|
||||
MooaToonDataA.x = EncodeFloat2ToUint(MooaToonData.OtherLightDiffuseThreshold, MooaToonData.OtherLightDiffuseFeather);
|
||||
MooaToonDataA.y = EncodeFloat2ToUint(MooaToonData.GlobalIlluminationIntensity, MooaToonData.GlobalIlluminationDirectionality, 6, 6);
|
||||
MooaToonDataA.z = EncodeFloat2ToUint(MooaToonData.MainLightShadowApplyLightColor, MooaToonData.CustomData3, 6, 4);
|
||||
MooaToonDataA.w = EncodeUint2ToUint(
|
||||
EncodeFloatToUint(MooaToonData.HairShadowWidth, 6),
|
||||
EncodeUint2ToUint(
|
||||
MooaToonData.Stencil,
|
||||
EncodeUint2ToUint(MooaToonData.RayTracingShadowFlag, MooaToonData.IsPBRSpecular ? 1 : 0, 2, 1),
|
||||
5, 3),
|
||||
6, 8);
|
||||
|
||||
MooaToonDataB.x = EncodeFloat2ToUint(MooaToonData.SpecularColor.r, MooaToonData.SpecularColor.g);
|
||||
MooaToonDataB.y = EncodeUint2ToUint(
|
||||
EncodeFloatToUint(MooaToonData.SpecularColor.b),
|
||||
EncodeFloat2ToUint(MooaToonData.ReflectionIntensity, MooaToonData.HairShadowIntensity, 6, 6),
|
||||
8, 12);
|
||||
MooaToonDataB.z = EncodeUint2ToUint(
|
||||
EncodeFloat2ToUint(MooaToonData.SpecularThreshold, MooaToonData.SpecularFeather),
|
||||
EncodeFloatToUint(MooaToonData.RimLightIntensity, 6),
|
||||
16, 6);
|
||||
MooaToonDataB.w = EncodeUint2ToUint(
|
||||
EncodeFloat2ToUint(MooaToonData.RimLightWidth, MooaToonData.RimLightAngle, 6, 6),
|
||||
EncodeFloatToUint(MooaToonData.RimLightDepthThreshold, 6),
|
||||
12, 6);
|
||||
|
||||
MooaToonDataC.rgb = saturate(MooaToonData.MainLightShadowColor);
|
||||
MooaToonDataC.a = saturate(MooaToonData.MainLightShadowValueOffset);
|
||||
}
|
||||
|
||||
FMooaToonData DecodeMooaToonData(float4 MooaToonDataA, float4 MooaToonDataB, float4 MooaToonDataC)
|
||||
{
|
||||
FMooaToonData MooaToonData = (FMooaToonData)0;
|
||||
uint Out0, Out1, Out2, Out3;
|
||||
DecodeFloat2FromUint(MooaToonDataA.x, MooaToonData.OtherLightDiffuseThreshold, MooaToonData.OtherLightDiffuseFeather);
|
||||
DecodeFloat2FromUint(MooaToonDataA.y, MooaToonData.GlobalIlluminationIntensity, MooaToonData.GlobalIlluminationDirectionality, 6, 6);
|
||||
DecodeFloat2FromUint(MooaToonDataA.z, MooaToonData.MainLightShadowApplyLightColor, MooaToonData.CustomData3, 6, 4);
|
||||
DecodeUint2FromUint(MooaToonDataA.w, Out0, Out1, 6, 8);
|
||||
MooaToonData.HairShadowWidth = DecodeFloatFromUint(Out0, 6);
|
||||
DecodeUint2FromUint(Out1, MooaToonData.Stencil, Out2, 5, 3);
|
||||
DecodeUint2FromUint(Out2, MooaToonData.RayTracingShadowFlag, MooaToonData.IsPBRSpecular, 2, 1);
|
||||
|
||||
DecodeFloat2FromUint(MooaToonDataB.x, MooaToonData.SpecularColor.r, MooaToonData.SpecularColor.g);
|
||||
DecodeUint2FromUint(MooaToonDataB.y, Out0, Out1, 8, 12);
|
||||
MooaToonData.SpecularColor.b = DecodeFloatFromUint(Out0);
|
||||
DecodeFloat2FromUint(Out1, MooaToonData.ReflectionIntensity, MooaToonData.HairShadowIntensity, 6, 6);
|
||||
|
||||
DecodeUint2FromUint(MooaToonDataB.z, Out0, Out1, 16, 6);
|
||||
DecodeFloat2FromUint(Out0, MooaToonData.SpecularThreshold, MooaToonData.SpecularFeather);
|
||||
MooaToonData.RimLightIntensity = DecodeFloatFromUint(Out1, 6);
|
||||
DecodeUint2FromUint(MooaToonDataB.w, Out0, Out1, 12, 6);
|
||||
DecodeFloat2FromUint(Out0, MooaToonData.RimLightWidth, MooaToonData.RimLightAngle, 6, 6);
|
||||
MooaToonData.RimLightDepthThreshold = DecodeFloatFromUint(Out1, 6);
|
||||
|
||||
MooaToonData.MainLightShadowColor = MooaToonDataC.rgb;
|
||||
MooaToonData.MainLightShadowValueOffset = MooaToonDataC.a;
|
||||
|
||||
return MooaToonData;
|
||||
}
|
||||
|
||||
uint4 EncodeMooaToonDataToBuffer(FMooaToonData MooaToonData)
|
||||
{
|
||||
MooaToonData.SpecularColor = LinearToSrgb(saturate(MooaToonData.SpecularColor));
|
||||
|
||||
float4 MooaToonDataA, MooaToonDataB, MooaToonDataC;
|
||||
uint4 Out;
|
||||
EncodeMooaToonData(MooaToonData, MooaToonDataA, MooaToonDataB, MooaToonDataC);
|
||||
Out.x = EncodeUint2ToUint(MooaToonDataA.x, MooaToonDataB.x, 16, 16);
|
||||
Out.y = EncodeUint2ToUint(MooaToonDataA.y, MooaToonDataB.y, 12, 20);
|
||||
Out.z = EncodeUint2ToUint(MooaToonDataA.z, MooaToonDataB.z, 10, 22);
|
||||
Out.w = EncodeUint2ToUint(MooaToonDataA.w, MooaToonDataB.w, 14, 18);
|
||||
|
||||
return Out;
|
||||
}
|
||||
|
||||
FMooaToonData DecodeMooaToonDataFromBuffer(uint4 ToonBufferA, float4 CustomData)
|
||||
{
|
||||
uint4 Out0, Out1;
|
||||
DecodeUint2FromUint(ToonBufferA.x, Out0.x, Out1.x, 16, 16);
|
||||
DecodeUint2FromUint(ToonBufferA.y, Out0.y, Out1.y, 12, 20);
|
||||
DecodeUint2FromUint(ToonBufferA.z, Out0.z, Out1.z, 10, 22);
|
||||
DecodeUint2FromUint(ToonBufferA.w, Out0.w, Out1.w, 14, 18);
|
||||
float4 MooaToonDataA = Out0;
|
||||
float4 MooaToonDataB = Out1;
|
||||
FMooaToonData MooaToonData = DecodeMooaToonData(MooaToonDataA, MooaToonDataB, CustomData);
|
||||
MooaToonData.MainLightShadowColor = sRGBToLinear(MooaToonData.MainLightShadowColor);
|
||||
MooaToonData.SpecularColor = sRGBToLinear(MooaToonData.SpecularColor);
|
||||
return MooaToonData;
|
||||
}
|
||||
```
|
||||
# BasePassPixelShader.usf
|
||||
|
||||
## GBuffer
|
||||
```c++
|
||||
/*
|
||||
* CustomData (GBufferD) (MP_MooaToonDataC) (0~1) = MainLightShadowColor(8 8 8) MainLightShadowValueOffset(8)
|
||||
*
|
||||
* ToonBufferA (RGBA Uint 32)
|
||||
* xyzw: MooaToonDataA.xyzw | MooaToonDataB.xyzw (32 bits total)
|
||||
*
|
||||
* MP_MooaToonDataA (float4, max storage 23 bits uint)
|
||||
* x: OtherLightDiffuseThreshold(8) OtherLightDiffuseFeather(8)
|
||||
* y: GlobalIlluminationIntensity(6) GlobalIlluminationDirectionality(6)
|
||||
* z: MainLightShadowApplyLightColor(6) CustomData3(4)
|
||||
* w: HairShadowWidth(6) Stencil(5) RayTracingShadowFlag(2) IsPBRSpecular(1)
|
||||
*
|
||||
* MP_MooaToonDataB (float4, max storage 23 bits uint)
|
||||
* x: SpecularColor.x(8) SpecularColor.y(8)
|
||||
* y: SpecularColor.z(8) ReflectionIntensity(6) HairShadowIntensity(6)
|
||||
* z: SpecularThreshold(8) SpecularFeather(8) RimLightIntensity(6)
|
||||
* w: RimLightWidth(6) RimLightAngle(6) RimLightDepthThreshold(6)
|
||||
*
|
||||
*
|
||||
* Tips: store sRGB Color to maximize accuracy
|
||||
*/
|
||||
```
|
||||
|
||||
>PS.float4, max storage 23 bits uint
|
||||
>Jason自己添加了GBT_Uint_32_32_32_32的GBuffer格式。
|
||||
- MooaToonDataA (RGBA Uint 32 **ToonBufferA**)
|
||||
- R(4+4):OtherLightDiffuseThreshold + OtherLightDiffuseFeather
|
||||
- G(4+4):GlobalIlluminationIntensity + GlobalIlluminationDirectionality
|
||||
- B(4+4):MainLightShadowApplyLightColor + CustomData3
|
||||
- A (6+5+2+1):HairShadowWidth + Stencil + RayTracingShadowFlag + IsPBRSpecular
|
||||
- MooaToonDataB (RGBA Uint 32,从材质直接传递到FGBuffer结构体中,位置位于BasePassPixelShader.usf的InitMooaToonData与InitMooaToonContext,其他可能位置位于GBufferHelpers.ush的GBufferPostDecode()、DeferredShadingCommon.ush的DecodeGBufferData())
|
||||
- R(8+8):SpecularColor.x + SpecularColor.y
|
||||
- G(8+6+6):SpecularColor.z + ReflectionIntensity + HairShadowIntensity
|
||||
- B(8+8+6):SpecularThreshold + SpecularFeather + RimLightIntensity
|
||||
- A(6+6+6):RimLightWidth + RimLightAngle + RimLightDepthThreshold(6)
|
||||
- MooaToonDataC(**GBufferD CustomData**) = MainLightShadowColor(8 8 8) + MainLightShadowValueOffset(8)
|
||||
|
||||
|
||||
# ShaderMoodel
|
||||
```c++
|
||||
/* ================================== Mooa Toon Deferred Lighting Model ======================================================================================================
|
||||
* IndirectColor (DiffuseIndirectComposite.usf) = IndirectDiffuse + IndirectSpecular
|
||||
* DirectionalLightShadingColor (DeferredLightPixelShaders.usf) = (lerp(ShadowColor, Diffuse * LightColor, min(ShadowValue, LightAttenuation)) + Specular * LightColor) * MooaExposure
|
||||
* OtherLightShadingColor (DeferredLightPixelShaders.usf) = (Diffuse + Specular) * LightColor * min(ShadowValue, LightAttenuation) * OtherLightCount
|
||||
* FinalColor = IndirectColor + DirectionalLightShadingColor + OtherLightShadingColor
|
||||
* ==================================================================================================================================================================
|
||||
*/
|
||||
// Mooa Indirect Lighting
|
||||
if (Material.GBufferData.ShadingModelID == SHADINGMODELID_TOON)
|
||||
{
|
||||
float3 SHDiffuseAverage = GetSkySHDiffuse(0.0f) * View.SkyLightColor.rgb * View.PreExposure;
|
||||
float3 LumenGiDiffuse = IndirectLighting.Diffuse;
|
||||
FMooaToonData MooaToonData = Material.GBufferData.MooaToonContext.MooaToonData;
|
||||
OutAddColor.rgb = //MooaToonData.MainLightShadowColor +
|
||||
lerp(SHDiffuseAverage * Material.GBufferData.BaseColor, LumenGiDiffuse, MooaToonData.GlobalIlluminationDirectionality) * MooaToonData.GlobalIlluminationIntensity;
|
||||
OutAddColor.rgb += IndirectLighting.Specular * Pow2(MooaToonData.ReflectionIntensity);
|
||||
OutAddColor.rgb *= View.MooaExposureScale;
|
||||
OutAddColor.a = Luminance(OutAddColor.rgb);
|
||||
}
|
||||
else
|
||||
{
|
||||
IndirectLighting.Specular *= GetSSSCheckerboadSpecularScale(PixelPos, Material.bNeedsSeparateLightAccumulation);
|
||||
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
|
||||
LightAccumulator_Add(
|
||||
LightAccumulator,
|
||||
IndirectLighting.Diffuse + IndirectLighting.Specular,
|
||||
IndirectLighting.Diffuse,
|
||||
1.0f,
|
||||
Material.bNeedsSeparateLightAccumulation);
|
||||
OutAddColor = LightAccumulator_GetResult(LightAccumulator);
|
||||
}
|
||||
```
|
||||
|
||||
```c++
|
||||
|
||||
FDirectLighting ToonBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow)
|
||||
{
|
||||
FMooaToonContext MooaToonContext = GBuffer.MooaToonContext;
|
||||
FToonGBufferData ToonGBuffer = GBuffer.MooaToonContext.ToonGBuffer;
|
||||
FDirectLighting Lighting = (FDirectLighting)0;
|
||||
float3 LightColorAndAttenuation = AreaLight.FalloffColor * MooaToonContext.LightColor * Falloff;
|
||||
ColorSaturationPowerAndScale(LightColorAndAttenuation, View.MooaLightSaturationScale);
|
||||
|
||||
/* Tips:
|
||||
* SvPosition == PixelPos (0 ~ View.BufferSizeAndInvSize.xy)
|
||||
* SceneTextureUV == BufferUV == ScreenUV (0 ~ 1), used to GetScreenSpaceData() / GetGBufferData()
|
||||
*
|
||||
* ScreenPosition = ClipPosition.xy / ClipPosition.w
|
||||
* ViewportUV = ScreenPosition * float2(0.5, -0.5) + 0.5
|
||||
* ViewportUV is the visible part of Buffer's UV (0 ~ 1)
|
||||
*/
|
||||
const float NoL_Full = dot(N, L);
|
||||
const float NoL_Half = NoL_Full * 0.5f + 0.5f;
|
||||
const float3 H = normalize(V + L);
|
||||
const float2 BufferUV = SvPositionToBufferUV(float4(MooaToonContext.PixelPos, 0, 0));
|
||||
const float2 ViewportUV = BufferUVToViewportUV(BufferUV);
|
||||
const float3 L_ClipSpace = mul(L, (float3x3)View.TranslatedWorldToClip).xyz;
|
||||
const float2 L_ViewportSpace = (L_ClipSpace.xy * float2(0.5, -0.5));
|
||||
const float3 N_ClipSpace = mul(N, (float3x3)View.TranslatedWorldToClip).xyz;
|
||||
const float2 N_ViewportSpace = (N_ClipSpace.xy * float2(0.5, -0.5));
|
||||
const float ViewportSpaceToWorldSpaceDir = rcp(GBuffer.Depth);
|
||||
|
||||
|
||||
// Diffuse
|
||||
{
|
||||
float HairShadowOffset = 0;
|
||||
float DiffuseColorRampUVOffset = (ToonGBuffer.DiffuseColorRampUVOffset * 2.0f - 1.0f) * View.MooaDiffuseColorRampUVOffsetMaxRange;
|
||||
float ShadowGradient = saturate(NoL_Half + DiffuseColorRampUVOffset);
|
||||
|
||||
#if SHADING_PATH_DEFERRED && defined(MOOA_TOON_DEFERRED_LIGHTING) && !SUBSTRATE_ENABLED
|
||||
{
|
||||
// Screen Space Depth Test Hair Shadow
|
||||
const float HairShadowWidth = 2.0f * View.MooaHairShadowWidth;
|
||||
const float HairShadowIntensity = View.MooaHairShadowIntensity;
|
||||
BRANCH if(ToonGBuffer.RayTracingShadowFlag == MOOA_RAY_TRACING_SHADOW_FLAG_FACE_SCREEN_SPACE_HAIR_SHADOW &&
|
||||
HairShadowWidth > 0 && HairShadowIntensity > 0)
|
||||
{
|
||||
float2 ViewportUVOffset = L_ViewportSpace * ViewportSpaceToWorldSpaceDir * HairShadowWidth;
|
||||
float2 TargetBufferUV = ViewportUVToBufferUV(saturate(ViewportUV + ViewportUVOffset));
|
||||
FGBufferData HairGbuffer = GetGBufferData(TargetBufferUV);
|
||||
float DepthFade = saturate(max(0, GBuffer.Depth - HairGbuffer.Depth - View.MooaHairShadowDepthTestThreshold) / max(1e-5, View.MooaHairShadowDepthTestFadeDistance));
|
||||
|
||||
if (HairGbuffer.ShadingModelID == SHADINGMODELID_TOON &&
|
||||
HairGbuffer.MooaToonContext.ToonGBuffer.RayTracingShadowFlag == MOOA_RAY_TRACING_SHADOW_FLAG_HAIR)
|
||||
{
|
||||
HairShadowOffset = -1 * HairShadowIntensity * DepthFade;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
GetDistanceFieldFacialShadow(GBuffer, ToonGBuffer, L, DiffuseColorRampUVOffset,
|
||||
ShadowGradient);
|
||||
|
||||
float DiffuseColorRampU = min3(saturate(Shadow.SurfaceShadow + HairShadowOffset), GBuffer.GBufferAO, ShadowGradient);
|
||||
half4 DiffuseColorRamp = SampleGlobalRamp(View.MooaGlobalDiffuseColorRampAtlas, DiffuseColorRampU, ToonGBuffer.DiffuseColorRampIndex, View.MooaGlobalDiffuseColorRampAtlasHeight);
|
||||
Shadow.SurfaceShadow = DiffuseColorRampU;
|
||||
|
||||
half3 ToonDiffuseColor = GBuffer.DiffuseColor;
|
||||
half3 ToonShadowColor = ToonGBuffer.MainLightShadowColor * (1 - GBuffer.Metallic) * GetShadowColorIntensity(MooaToonContext);
|
||||
#if USE_DEVELOPMENT_SHADERS
|
||||
ToonShadowColor *= View.DiffuseOverrideParameter.w + View.DiffuseOverrideParameter.xyz;
|
||||
#endif
|
||||
ToonDiffuseColor = lerp(ToonShadowColor, ToonDiffuseColor, DiffuseColorRamp.a);
|
||||
|
||||
Lighting.Diffuse = Diffuse_Lambert(ToonDiffuseColor) * DiffuseColorRamp.rgb * LightColorAndAttenuation;
|
||||
}
|
||||
|
||||
|
||||
// Anisotropy Specular BxDF Context
|
||||
bool bHasAnisotropy;
|
||||
BxDFContext Context = (BxDFContext)0;
|
||||
{
|
||||
#if SUPPORTS_ANISOTROPIC_MATERIALS
|
||||
bHasAnisotropy = true;// HasAnisotropy(GBuffer.SelectiveOutputMask);
|
||||
#else
|
||||
bHasAnisotropy = false;
|
||||
#endif
|
||||
if (ToonGBuffer.ShadingFeatureID == MOOA_SHADING_FEATURE_ID_DISTANCE_FIELD_FACIAL_SHADOW)
|
||||
bHasAnisotropy = false;
|
||||
|
||||
BRANCH if (bHasAnisotropy)
|
||||
{
|
||||
half3 X = GBuffer.WorldTangent;
|
||||
half3 Y = normalize(cross(N, X));
|
||||
Init(Context, N, X, Y, V, L);
|
||||
}
|
||||
else
|
||||
{
|
||||
Init(Context, N, V, L);
|
||||
GBuffer.Anisotropy = 0;
|
||||
}
|
||||
Context.NoV = saturate(abs( Context.NoV ) + 1e-5);
|
||||
}
|
||||
|
||||
|
||||
// Specular
|
||||
BRANCH if (ToonGBuffer.ShadingFeatureID == MOOA_SHADING_FEATURE_ID_PBR_SPECULAR)
|
||||
{
|
||||
#if SUPPORTS_ANISOTROPIC_MATERIALS
|
||||
BRANCH if (bHasAnisotropy)
|
||||
{
|
||||
Lighting.Specular = LightColorAndAttenuation * Shadow.SurfaceShadow * NoL * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
BRANCH if( IsRectLight(AreaLight) )
|
||||
{
|
||||
Lighting.Specular = MooaToonContext.LightColor * Shadow.SurfaceShadow * RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lighting.Specular = LightColorAndAttenuation * Shadow.SurfaceShadow * NoL * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float MaxSpecularValue;
|
||||
float SpecularColorRampU = GetSpecularColorRampUAndMaxSpecularValue(GBuffer, Context, N, H,
|
||||
MaxSpecularValue);
|
||||
float SpecularColorRampUVOffset = (ToonGBuffer.SpecularColorRampUVOffset * 2.0f - 1.0f) * View.MooaSpecularColorRampUVOffsetMaxRange;
|
||||
half3 SpecularColor = SampleGlobalRamp(View.MooaGlobalSpecularColorRampAtlas, SpecularColorRampU + SpecularColorRampUVOffset, ToonGBuffer.SpecularColorRampIndex, View.MooaGlobalSpecularColorRampAtlasHeight).rgb;
|
||||
|
||||
Lighting.Specular = GBuffer.SpecularColor * ToonGBuffer.SpecularColor * MaxSpecularValue * SpecularColor * LightColorAndAttenuation * Shadow.SurfaceShadow;
|
||||
}
|
||||
|
||||
// Rimlight
|
||||
float3 RimLight = GetScreenSpaceDepthTestRimlightColor(GBuffer, ToonGBuffer, ViewportUV, L_ViewportSpace, ViewportSpaceToWorldSpaceDir) * LightColorAndAttenuation * Shadow.SurfaceShadow;
|
||||
ColorSaturationPowerAndScale(RimLight, View.MooaRimLightSaturationScale, View.MooaRimLightIntensity);
|
||||
Lighting.Specular += RimLight;
|
||||
|
||||
return Lighting;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Diffuse
|
||||
```c++
|
||||
...
|
||||
float3 LightColorAndAttenuation = AreaLight.FalloffColor * MooaToonContext.LightColor * Falloff;
|
||||
...
|
||||
...
|
||||
// Diffuse
|
||||
BRANCH if (Context.IsMainLight)
|
||||
{
|
||||
Shadow.SurfaceShadow = saturate(Shadow.SurfaceShadow + HairShadowValueOffset + MooaToonData.MainLightShadowValueOffset * 2.0f - 1.0f);
|
||||
LightColorAndAttenuation *= Shadow.SurfaceShadow;
|
||||
float3 MainLightShadowColor = MooaToonData.MainLightShadowColor * lerp(1, Context.LightColor, MooaToonData.MainLightShadowApplyLightColor) * View.MooaIsGlobalIlluminationEnabled;
|
||||
Lighting.Diffuse = lerp(MainLightShadowColor, GBuffer.BaseColor * Context.LightColor, Shadow.SurfaceShadow) * PI_INV;
|
||||
}
|
||||
else
|
||||
{
|
||||
float OtherLightShadowValue = ToonStep(halfNoL, MooaToonData.OtherLightDiffuseFeather, MooaToonData.OtherLightDiffuseThreshold);
|
||||
// Non-Directional light's SurfaceShadow contains distance attenuation, not just shadowmap
|
||||
Shadow.SurfaceShadow *= saturate(OtherLightShadowValue + HairShadowValueOffset);
|
||||
LightColorAndAttenuation *= Shadow.SurfaceShadow;
|
||||
Lighting.Diffuse = GBuffer.BaseColor * LightColorAndAttenuation * PI_INV;
|
||||
}
|
||||
```
|
||||
|
||||
# 实用函数
|
||||
如果需要脸部SDF阴影,就覆盖ShadowGradient。
|
||||
```c++
|
||||
void GetDistanceFieldFacialShadow(FGBufferData GBuffer, FToonGBufferData ToonGBuffer, float3 L, float DiffuseColorRampUVOffset,
|
||||
inout float ShadowGradient)
|
||||
{
|
||||
BRANCH if (ToonGBuffer.ShadingFeatureID == MOOA_SHADING_FEATURE_ID_DISTANCE_FIELD_FACIAL_SHADOW)
|
||||
{
|
||||
float3 FaceForwardDir = GBuffer.WorldTangent;
|
||||
float LightAngle = RadianToDegree(FastACos(dot(FaceForwardDir, L)));
|
||||
float RampUVOffsetByLightAngle = (1.0 - clamp(LightAngle / 180.0f, 1e-4, 1 - 1e-4)) - 0.5f;
|
||||
bool bLightAtRight = cross(FaceForwardDir, L).z >= 0; // TODO: Fix the numerical accuracy issue of extreme angles
|
||||
float shadowSdf = bLightAtRight ? ToonGBuffer.FacialShadowSdfRight : ToonGBuffer.FacialShadowSdfLeft;
|
||||
|
||||
ShadowGradient = saturate(RampUVOffsetByLightAngle + shadowSdf + DiffuseColorRampUVOffset);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
35
03-UnrealEngine/卡通渲染相关资料/演讲笔记/LoveLive All Star笔记.md
Normal file
35
03-UnrealEngine/卡通渲染相关资料/演讲笔记/LoveLive All Star笔记.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: LoveLive All Star笔记
|
||||
date: 2022-10-13 17:36:10
|
||||
excerpt:
|
||||
tags: 卡通渲染
|
||||
rating: ⭐
|
||||
---
|
||||
原文:CEDEC 2020 - 高品質かつ低負荷な3Dライブを実現するシェーダー開発 ~『ラブライブ!スクールアイドルフェスティバル ALL STARS』(スクスタ)の開発事例~
|
||||
|
||||
## 描边
|
||||
采用BackFace挤出的方法。
|
||||
- 考虑相机距离与Fov宽度修正
|
||||
- 支持模型内部轮廓线
|
||||
- 可通过定点色微调
|
||||
|
||||
### 模型内部轮廓线
|
||||

|
||||
|
||||
使用PixelShader对顶点色的B通道进行二值化所得。
|
||||
|
||||
### 顶点色
|
||||
R:轮廓粗细
|
||||
G:把不需要轮廓压入模型
|
||||
B:控制轮廓粗细
|
||||
|
||||
## 灯的轮廓光效果
|
||||

|
||||
View空间的方向光强度Ramp。FlashYiYi有说过(**存疑**)。
|
||||
- 方向をZ軸(デフォルト)にすれ䜀、逆光表現䛾リムライト
|
||||
- 方向を設定すれ䜀、平行光源䛾ライティング
|
||||
|
||||
根据方向光的DirectionVector来Fake一个方向光Ramp;如果方向是完全背对角色则使用RimLighting效果。
|
||||
|
||||
## 边缘光与轮廓线的混合
|
||||

|
408
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U 功能分析与功能列表翻译.md
Normal file
408
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U 功能分析与功能列表翻译.md
Normal file
@@ -0,0 +1,408 @@
|
||||
# VRM4U角色
|
||||
https://hub.vroid.com/en/users/36144806
|
||||
|
||||
B站整理 https://www.bilibili.com/video/BV1gR4y1j7sK/?spm_id_from=333.788.recommend_more_video.2&vd_source=d47c0bb42f9c72fd7d74562185cee290
|
||||
|
||||
- Alicia Solid:https://hub.vroid.com/en/characters/515144657245174640/models/6438391937465666012
|
||||
- AvatarSample_F:https://hub.vroid.com/en/characters/6193066630030526355/models/537531113514541613
|
||||
|
||||
# VRM4U学习笔记
|
||||
VRM4U是一个不错的插件,在此记录一下功能与大致实现方式。
|
||||
快速入门文档:https://ruyo.github.io/VRM4U/01_quick-start/
|
||||
功能介绍视频:https://youtu.be/epcQ-uU6tfU
|
||||
|
||||
视频中的右上角的自定义编辑器可以通过搜索"WBP_"来找到对应的EditorUtilityWidget,之后在Asset上有右键——点击运行EditorUtilityWidget。
|
||||
|
||||
- VRm4U:定义了主要的VrmAssetListObject、VrmMetaObject、VrmLicenseObject、VrmRuntimeSettings以及若干组件与动画节点。
|
||||
- VRM4UImporter:主要的导入相关逻辑实现:导入流程、导入设定窗口、若干动画节点。
|
||||
- VRM4ULoader:导入数据转换以及生成逻辑:数据转换与生成Asset相关的类、UVrmLoaderComponent与若干组件实现。
|
||||
- VRM4UEditor:Sequence编辑器函数EvaluateCurvesFromSequence()实现。
|
||||
- VRM4UMisc:Log日志类型定义。
|
||||
|
||||
PS.
|
||||
- FVRM4UModule模块启动与结束时,会分别注册/卸载
|
||||
- FVRM4UImporterModule模块启动与结束时,会分别注册/卸载 UVrmAssetListObject、UVrmLicenseObject、UVrmMetaObject自定义Asset类型与FVRMRetargetSrcAnimSequenceCustomization自定义属性编辑器。
|
||||
- FVRM4ULoaderModule模块启动与结束时,加载/卸载assimp的动态链接库;如果勾选**Drop VRMFile Enable**,则会
|
||||
|
||||
## VRM4U的格式导入功能
|
||||
值得学习:
|
||||
- Runtime Import功能:在游戏打包后,依然可以通过拖拽文件到游戏窗口的方式来导入VRM文件。
|
||||
- 导入窗口实现:实现一个导入窗口(SVrmOptionWindow),这样Runtime与Editor导入都可以输入自定义的参数。
|
||||
|
||||
待改进:
|
||||
- 没有实现VMD的导入逻辑(虽然实现了BVH格式的导入逻辑)
|
||||
|
||||
### 导入功能
|
||||
VRM4U使用Assimp库来进行格式解析,并且通过修改GLTF来实现VRM格式导入(VRM是基于GLTF开发的)。插件作者也公开了魔改的Assimp代码,当然最好的方式就是下载编译好的库文件与h文件直接引用了。
|
||||
|
||||
得益于Assimp,所以也可以导入PMX格式,但需要勾选 Project Setting>Plugin>VRM4U>Allow All AassImp Format的选项,之后就可以导入PMX格式的模型。(开放格式:pmx、obj、fbx、dae、gltf)
|
||||
|
||||

|
||||
|
||||
同时可以通过下面的Ext List来添加其他格式的后缀名,来使用VRM4U导入。
|
||||
|
||||
主要逻辑位于VRM4UImporter模块的UVRM4UImporterFactory中。
|
||||
|
||||
#### FactoryCreateBinary
|
||||
UVrmImportUI用于存储用户设置的导入选项数据。ULoaderBPFunctionLibrary::GetVRMMeta()用来导入VRM的Meta数据。ULoaderBPFunctionLibrary::LoadVRMFileFromMemory()为核心导入逻辑。
|
||||
|
||||
导入流程为:
|
||||
1. 在初始化UVrmImportUI(导入选项对象)后,会调用ULoaderBPFunctionLibrary::GetVRMMeta(),通过Assimp读取VRM文件的Meta数据(缩略图与版权信息)来填充UVrmImportUI。对于PMX格式会有额外的设置。(模型缩放1=>0.1、不合并材质、不合并图元、强制材质双面显示)
|
||||
2. 取得父窗口,之后创建并添加自定义的SWindow2包裹SVrmOptionWindow来进行导入参数显示,会在用户选择好数据后,进行之后的步骤。
|
||||
3. 创建`TAssetPtr<UVrmAssetListObject>`、`TArray< TAssetPtr<UObject> >`、`TAssetPtr<UClass>`对象。
|
||||
4. 取得默认加载设置对象UVrmRuntimeSettings,之后会依次尝试载入VrmObjectListBP、VrmAssetListObjectBP、UVrmAssetListObject来初始化上一步说的`TAssetPtr<UVrmAssetListObject>`。
|
||||
5. 创建static VRMConverter::Options并使用之前的用户修改过的设置选项进行初始化。
|
||||
6. 使用上一步取得选项对象,并调用ULoaderBPFunctionLibrary::LoadVRMFileLocal()来导入文件。导入的存放在UVrmAssetListObject mret中。
|
||||
7. 返回mret->GetOuter();
|
||||
|
||||
#### LoadVRMFileFromMemory
|
||||
1. 根据后缀名设置对应参数
|
||||
2. 调用Assimp::Importer的ReadFileFromMemory()或者ReadFile()读取文件来获取场景节点aiScene。
|
||||
3. 创建UVrmAssetListObject* OutVrmAsset(复制InVrmAsset或NewObject)
|
||||
4. 使用VRMConverter对象,取出VRM的Json与aiScene数据来以此初始化。之后更新OutVrmAsset里的数据(调整材质的AlphaCutoff选项、替换骨骼名称、转换并且生成贴图与材质、转换VRM的Meta数据、转换并且生成模型数据、重命名Meta数据、设置骨骼模型的Rig Pose MorphTarget、转换重定向用的Humanoid、将之前所有数据保存成Asset)
|
||||
|
||||
#### 运行时导入
|
||||
主要的逻辑为调用LoadVRMFileAsync()加载文件并且初始化UVRMAssetList,之后再设置SkeletalMesh或者SpawnActor等一系列设置。
|
||||
|
||||
使用这个功能前首先得确定 Project Settings->Plugin->VRM4U->Drop VRMFile Enable处于勾选状态才能使用这个功能。
|
||||
功能演示可以查看参考插件Content的Maps/VRM4U_runtimeload,其功能位于DropActor。其挂载了UVrmDropFilesComponent组件,并在Beginplay绑定UVrmDropFilesComponent组件的OnDrogFile委托。其绑定的LoadVrmFronFilePath事件主要逻辑为:创建VRMAssetList对象,之后调用LoadVRMFileAsync()加载文件。之后给SpawnMToonActor,并给骨骼物体设置动画。(还有一些逻辑在关卡蓝图中)
|
||||
|
||||
UVrmDropFilesComponent的主要功能
|
||||
|
||||
它定义了2个委托,并会在组件注册/卸载时让StaticOnDropFilesDelegate绑定/解绑OnDropFilesDelegate_Handler()。OnDropFilesDelegate_Handler()会调用OnDropFiles.Broadcast(FileName);
|
||||
```c++
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FStaticOnDropFiles, FString);
|
||||
static FStaticOnDropFiles StaticOnDropFilesDelegate;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDropFiles, FString, FileName);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnDropFiles OnDropFiles;
|
||||
```
|
||||
|
||||
UVrmDropFilesComponent::VRMGetOpenFileName()会在关卡蓝图中被调用,用于调用Winapi的GetOpenFileName()来显示一个打开文件用的窗口,在选取了文件之后会调用DropActor的LoadVrmFronFilePath()。
|
||||
|
||||
##### 运行时拖拽导入
|
||||
FDropMessageHandler的父类为IWindowsMessageHandler是一种用与处理Windows消息的一种接口类。FDropMessageHandler的主要逻辑为:
|
||||
1. 取得UVrmDropFilesComponent组件与World指针。
|
||||
2. 如果世界类型为Game与PIE则调用Winapi的DragAcceptFiles()
|
||||
3. 调用Winapi的DragQueryFile()查询拖拽入的文件,并将文件地址传**UVrmDropFilesComponent::StaticOnDropFilesDelegate.Broadcast(Filepath);**
|
||||
|
||||
在FVRM4ULoaderModule::StartupModule()中执行增加MessageHandler。
|
||||
```c++
|
||||
if (FSlateApplication::IsInitialized()) {
|
||||
static FDropMessageHandler DropMessageHandler;// = FDropMessageHandler();
|
||||
//FWindowsApplication* WindowsApplication = (FWindowsApplication*)GenericApplication.Get();
|
||||
TSharedPtr<GenericApplication> App = FSlateApplication::Get().GetPlatformApplication();
|
||||
|
||||
if (App.IsValid()) {
|
||||
auto WindowsApplication = (FWindowsApplication*)(App.Get());
|
||||
|
||||
//WindowsApplication->SetMessageHandler(DropMessageHandler);
|
||||
WindowsApplication->AddMessageHandler(DropMessageHandler);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 渲染与材质
|
||||
值得学习:
|
||||
- 卡通渲染材质(里面有一些faker技巧)
|
||||
- 拍摄模式
|
||||
- Over Parse,模型压平的二次元模式,视角矫正效果
|
||||
|
||||
VRM4U的MToon Lit、MToon Unlit、Subsurface、Subsurface Profile材质类型都是基于MaterialUtil\MToonUtil\M_VrmMToonBaseOpaque,它是作者实现的一个接近UniVRM Shader效果的材质。里面有一些Hack技巧值得学习。上述这几材质类型的区别在于材质实例的属性不同,以MToon Lit与MToon Unlit的区别为例:
|
||||
1. bUseLight是否勾选
|
||||
2. ShaderModel不同:Default Lit与Unlit
|
||||
|
||||
Unlit、PBR基于一个简单材质MaterialUtil\MToonUtil\M_VrmSimple。
|
||||
|
||||
### 轮廓线与投射阴影
|
||||
使用MToonAttachActor来应用轮廓线和自身阴影(Unlit ShaderModel不会接收其他物体的投影)。自己制作的VRM模型可能会有问题,导致Outline与阴影不能出现,建议使用Vroid官方模型进行测试。
|
||||
|
||||

|
||||
|
||||
- SceneCaptureComponent2D组件用于渲染Depth到RT上,以此来制作Shadow。勾选WBP_VRMMaterial->Model->MToonAttachActor->Advanced->Debug Shadow Cube后,会在角色正上方显示深度贴图Cube。
|
||||

|
||||
|
||||
在MF_VrmMToonBase中搜索bUseShadowMap就可以找到所引用的计算函数
|
||||

|
||||
里面逻辑就是通过Depth贴图计算ShadowMask
|
||||

|
||||
|
||||
- UVrmPoseableMeshComponent:用于实现Outline(AOShadow目前没有作用)。主要使用M_VrmNone,一个Masked Unlit材质,通过模型外扩实现Outline效果。
|
||||
|
||||
### 后处理效果
|
||||
MToonMaterialSystem
|
||||
|
||||

|
||||
|
||||
这些蓝图BP的大致逻辑为,挂载带有PostProcess空间组件的BP,之后往里面添加PostProcess材质。
|
||||
|
||||
Content\Util\Actor\Post\sub目录中有
|
||||
- M_PostBloom
|
||||
- M_ColorGradation
|
||||
- MI_ColorGradation
|
||||
- M_CenterBlur
|
||||
|
||||
### 光照工具与PostToon
|
||||
- VRM4U_CameraLight有演示CharacterLightRigActor,方便用户快速摆出主光、轮廓光,其中轮廓光会一直对着摄像机旋转。
|
||||
VRM4U有关的曝光的参数调节技巧:https://ruyo.github.io/VRM4U/02_envlight/
|
||||
|
||||
- PostToonSystem文档:https://ruyo.github.io/VRM4U/02_toon/
|
||||
|
||||
具体可以参考VRM4U_PostToon.umap,设置步骤如下:
|
||||
1. 在场景中放置一个SkeletalMeshActor。
|
||||
2. 在场景中放置一个BP_PoseCopyToon,并且设置Target Actor为上一步放置的SkeletalMeshActor,并调整参数。
|
||||
|
||||
#### PostToon原理
|
||||
首先BP_PoseCopyToon挂载了5个组件(隐藏了2个非重要组件),SkeletalMesh为主要渲染,VrmPoseableMesh用于接受光照。
|
||||
- SkeletalMesh
|
||||
- VrmPoseableMesh
|
||||
- VrmPoseableMesh_translucent
|
||||
|
||||
1. 设置TargetMeshForLight(SkeletalMesh或者VrmPoseableMesh)
|
||||
2. BP_PoseCopyToon会在一开始遍历TargetMeshForLight的Materials,之后根据材质数目给VrmPoseableMesh添加对应数目的MI_BaseLight材质。(M_BaseLight与MToon材质大致一样,都使用MF_VrmMToonBase)
|
||||
3. 给VrmPoseableMesh_translucent执行上一步类似操作,但添加的材质为MI_PostToon。
|
||||
4. 如果SkeletalMesh中有使用M_VrmNone材质,那VrmPoseableMesh与VrmPoseableMesh_translucent的对应部分也会被设置成M_VrmNone。
|
||||
5. 隐藏Debug用的VrmPoseableMesh_translucent。
|
||||
6. 调用VrmPoseableMesh的VRMSetLightingChannelPrim()。设置了图元的LightingChannel。
|
||||
7. 调用VrmPoseableMesh_translucent的VRMSetPerBoneMotionBlur()。设置SkinnedMesh->bPerBoneMotionBlur,默认是true。
|
||||
|
||||

|
||||
|
||||
##### M_VrmMToonBaseOpaque、M_BaseLight、M_PostToon比较
|
||||
- M_BaseLight:有基础灯光计算(NoL)。**实际使用的是MI_BaseLight**所以会有一些参数改变:比如他是一个PBR材质,有调整过BaseColor、Metalic、Roughness。
|
||||
- M_ToonPost
|
||||
- M_VrmMToonBaseOpaque:实现了TAA透明效果
|
||||
|
||||
M_VrmMToonBaseOpaque
|
||||

|
||||
|
||||
M_BaseLight:用于渲染光照效果
|
||||

|
||||
|
||||
M_ToonPost:用于取得光照效果并且合成在一起。使用SceneColor节点取得上一个Material渲染的结果作为灯光结果(因为是透明材质,所以是在后面渲染的)。
|
||||

|
||||
|
||||
#### 使用Custom Material
|
||||
VRM4U可以使用自定义的材质系统代替MToon材质。
|
||||
|
||||
1. 根据M_VrmSimple的格式,创建自己的CustomMaterial。
|
||||
2. 创建DS_VRMCustom DataAsset并且填入对应的Custom Material Instance(改变Shader Model与Blend Mode)。
|
||||
3. 在操作面板WBP_VRMMaterial的MaterialSettings上,将材质模型设置成Custom,并且填入上面创建的DS_VRMCustom DataAsset。
|
||||
|
||||

|
||||

|
||||
|
||||
### 其他Hack技巧
|
||||
- Anti-ToneMapping:使用UE自带的函数抵消ToneMapping效果。
|
||||
- Exposure:使用EyeAdaptation节点控制亮度。
|
||||
|
||||

|
||||
|
||||
### 拍摄模式
|
||||
1. 使用BP_VrmModelActor。
|
||||
2. 视线跟踪 /VRM4U/Util/Actor/Misc/LookAtPoint放置TargetActor并设置目标模型。
|
||||
3. VRM4U的角色相机使用Pawn类实现:提供若干快捷操作方便用户得到最佳的镜头。https://ruyo.github.io/VRM4U/02_shortcut2/
|
||||
4. 使用MToonMaterialSystem调整阴影效果。
|
||||
5. 使用MorphControl控制角色表情(目标设置为BP_VrmModelActor)。
|
||||
|
||||
### Over Parse
|
||||
文档:https://ruyo.github.io/VRM4U/02_pers/
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Actor位于Util\Actor\:
|
||||
- FOVCharacter
|
||||
- FOVCustom
|
||||
|
||||
FOVCustom使用了VrmCameraCheck组件,并且会在蓝图的Construction Script中绑定SetFovDistance()至CameraMove委托。主要的逻辑位于SetFovDistance()中,主要逻辑Tick()也会使用。
|
||||
1. 将FOVCustom Attach 到 TargetActor上。
|
||||
2. 寻找场景中所有MToonMaterialSystem,并设置FovScaleBias、Fov_distance、Fov_scale参数并应用。
|
||||
|
||||
FOVCharacter逻辑相似,核心逻辑在CustomEvent_1中,他会绑定给VrmCameraCheck组件的CameraMove委托。主要设置了**材质集**的FOV2Begin、FOV2Pow、FOV2Scale、FOV2ScaleLimit、FOV2FarCenter、FOV2FarPoint、MainCameraPosition、MainCameraDirection。相关逻辑位于材质中。
|
||||
|
||||

|
||||
|
||||
## 动画
|
||||
值得学习:
|
||||
- Runtime Retarget功能
|
||||
- Limited Animation(抽帧动画)
|
||||
- Control Rig与Morph Target控制器
|
||||
|
||||
实现动画节点:
|
||||
- VRM4U
|
||||
- AnimNode_VrmCopyHandBone
|
||||
- AnimNode_VrmModifyBoneDynamic
|
||||
- AnimNode_VrmModifyBoneList
|
||||
- AnimNode_VrmModifyHumanoidBone
|
||||
- AnimNode_VrmQuestHandBone
|
||||
- AnimNode_VrmRetargetFromMannequin
|
||||
- AnimNode_VrmSpringBone
|
||||
- AnimNode_VrmVMC
|
||||
- VRM4UImporter
|
||||
- AnimGraphNode_VrmCopyHandBone
|
||||
- AnimGraphNode_VrmModifyBoneDynamic
|
||||
- AnimGraphNode_VrmModifyBoneList
|
||||
- AnimGraphNode_VrmModifyHumanoidBone
|
||||
- AnimGraphNode_VrmQuestHandBone
|
||||
- AnimGraphNode_VrmRetargetFromMannequin
|
||||
- AnimGraphNode_VrmSpringBone
|
||||
- AnimGraphNode_VrmVMC
|
||||
|
||||
### BP_VrmPoseCopy
|
||||
挂载了以下5个组件,有一个子类BP_VrmPoseCopy_PostShadow。
|
||||
- VrmPoseableMesh
|
||||
- BP_VrmAnimControlComponent
|
||||
- BP_VrmUtilComponent
|
||||
- SkeletalMesh
|
||||
|
||||
有关抽帧效果,其关键函数在于BP_VrmAnimControlComponent或者BP_VrmPoseCopy的SetFrameLimite(),函数是一个无线循环函数。
|
||||
1. 判断是否开启EnableFrameLimit,不开启就结束了
|
||||
2. SetFrameLimite()中判断当前播放帧数是否达到nextToPlay变量的值,如果没有则等待;有则执行VrmPoseableMesh的VRMCopyPoseAndMorphFromSkeletalComponent()
|
||||
3. 更新nextToPlay变量并触发OnFrameLimitUpdate委托。
|
||||
4. 在更新完模型的Transform后,跳回第2步。
|
||||
|
||||
**改进思路**:在蓝图中这样实现会有很大的性能问题,使用c++会更好。而且这个逻辑完全可以使用动画蓝图实现,使用PoseCache,之后根据条件,输出更新Pose或是PoseCache。
|
||||
|
||||
UVrmPoseableMeshComponent继承自UPoseableMeshComponent。
|
||||
```c++
|
||||
void UVrmPoseableMeshComponent::VRMCopyPoseAndMorphFromSkeletalComponent(USkeletalMeshComponent* InComponentToCopy) {
|
||||
if (InComponentToCopy) {
|
||||
Super::CopyPoseFromSkeletalComponent(InComponentToCopy);
|
||||
|
||||
MorphTargetWeights = InComponentToCopy->MorphTargetWeights;
|
||||
ActiveMorphTargets = InComponentToCopy->ActiveMorphTargets;
|
||||
}
|
||||
}
|
||||
```
|
||||
CopyPoseFromSkeletalComponent()的主要逻辑就是判断骨骼是否一一对应,如果对应则使用移动构造函数对Target与Source骨骼数据进行交换;不对应就遍历复制骨骼数据。
|
||||
|
||||
#### Limited Animation 抽帧效果
|
||||
文档地址:https://ruyo.github.io/VRM4U/05_limitedanim/
|
||||
|
||||
使用步骤
|
||||
1. 在关卡上放置一个执行通常动画的骨架模型Actor。设置为“始终勾选姿势并刷新骨骼”以将动画移动到屏幕之外。
|
||||
2. 在关卡中放置一个BP_VrmPoseCopy Actor,并TargetActor设置为上一步中的骨骼模型。打开LimitedAnim,输入你要播放的帧率(12、24等,每秒的帧数)。
|
||||
|
||||
#### 运行时重定向
|
||||
文档:https://ruyo.github.io/VRM4U/03_gray/
|
||||
视频(UE5的实时重定向方案):https://www.bilibili.com/video/BV1HZ4y117Yk?spm_id_from=333.999.0.0
|
||||
|
||||
视频中的步骤:
|
||||
1. 给新导入的VRM4U模型添加重定向骨骼链数据。
|
||||
2. 新建IK Rig Asset并选择IK_Mannequin。(假设使用UE5小蓝人来重定向)
|
||||
3. 新建IK Retarget Asset,设置Source为IK_Mannequin的Rig,Target为上一步新建的Rig。
|
||||
4. 修改TPose=>APose。
|
||||
|
||||
5. 在角色类中的Mesh下面新建一个SkeletalMesh组件,并且选择新导入的VRM4U模型。创建一个动画蓝图Asset并指定(我感觉并不需要挂载新创建的SkeletalMesh)。
|
||||
6. 动画蓝图中使用RetargetPoseFromMesh节点。
|
||||
7. 将角色类中Mesh的VisibilityBasedAnimTickOption属性改为AlwaysTickPose=>AlwaysTickPoseAndRefreshBones。
|
||||
8. 隐藏新建的SkeletalMesh组件。
|
||||
|
||||
VRM4U的实时重定向步骤:
|
||||
1. 将BP_VrmMannequinRetarget放入场景中,之后设置Targetmannequin。
|
||||
2. 设置VRM Asset List Override。
|
||||
3. 最后点击GenerateRegargetPoseCopy即可。
|
||||
|
||||

|
||||
|
||||
BP_VrmMannequinRetarget使用了动画蓝图ABP_VRoidSimpleMannequinRetarget,里面使用了VrmRetargetFromMannequin节点。大致逻辑就是遍历所有的骨骼(humanoid_bone)之后进行重定向。本质上与UE5的方法一样,但UE5使用最新的骨骼链算法,无论是便捷与效果都更胜一筹。
|
||||
|
||||
## 其他工具与实现
|
||||
- 面部捕捉
|
||||
- VirtualMotionCapture(VMC) 身体捕捉 https://ruyo.github.io/VRM4U/08_vmc/
|
||||
|
||||
VMC入门资料:
|
||||
1. UE VR Track介绍(视频中使用了SteamVR控制器手柄):https://www.bilibili.com/video/BV1kS4y167SK?spm_id_from=333.337.search-card.all.click
|
||||
2. UE VRN4U VMC功能讲解:https://www.bilibili.com/video/BV1ub4y1Y74K?spm_id_from=333.337.search-card.all.click
|
||||
3. 捕捉数据软件介绍(Up推荐VseeFace,免费、效果好):https://www.bilibili.com/video/BV1DK411u75k?spm_id_from=333.999.0.0
|
||||
4. Vseeface和OBS的设置:https://www.bilibili.com/video/BV16K4y1H7Cb?spm_id_from=333.999.0.0
|
||||
5. VSeeFace配合LeapMotion:https://www.bilibili.com/video/BV1KA41137os?spm_id_from=333.337.search-card.all.click
|
||||
6. 全身动作捕捉软件ThreeDPoseTracker:https://www.bilibili.com/video/BV1vy4y157An?spm_id_from=333.999.0.0
|
||||
|
||||
## 其他模块文件
|
||||
- VRM4U
|
||||
- VrmUtil:导入选项、GetXXX工具函数、骨骼转换映射表
|
||||
- VrmUtilImage:图片处理函数
|
||||
- VRM4UImporter
|
||||
- VRM4UDetailCustomize:针对FVRMRetargetSrcAnimSequence的自定义编辑器
|
||||
- VrmAssetListThumbnailRenderer:Asset缩略图渲染控制
|
||||
|
||||
Util中的工具
|
||||
Actor
|
||||
- latest:作者研发用的文件夹,里面的东西会引起打包出问题,建议在打包的时候删掉。
|
||||
|
||||
# VRM4U 功能列表翻译
|
||||
- Asset输出支持日语名称
|
||||
- 显示许可证与文件的Meta信息
|
||||
- bvh 支持
|
||||
|
||||
## 骨骼动画
|
||||
- MorphControlActor。支持VRoid模型的Morph Target,并且名称与原始数据相同
|
||||
- 视线跟随Actor
|
||||
- 增加动画节点:VRMSpringBone
|
||||
- 默认重定向是 A-pose。启用以更改为常规 T 姿势作为选项
|
||||
- 增加套用ALS功能(添加IKBone,复制VirtualBone,创建骨骼名称为UE4 Mannequin)
|
||||
- 可以从其他骨骼Asset上复制Socket
|
||||
- 实现了 WindActor
|
||||
- LiveLink 面部捕捉支持
|
||||
- 角色相机添加了呼吸选项、更改角色相机的焦点位置功能。
|
||||
- 对应ControlRig不能很好应用的模型(PMX中骨骼层次不同的模型,使用半标准骨骼的模型)
|
||||
- 添加了一个工具来控制Sequence中的面部动画
|
||||
|
||||
## MToon材质
|
||||
默认材质设置为 Unlit,除此之外还是先PBR与SSS模式。
|
||||
|
||||
- 实现 MPC 材质集来调整材质参数
|
||||
- 自己实现ShaderMap以实现Unlit材质模型的投射阴影;可以设置第二个阴影颜色;修复可能无法绘制阴影的问题(没有MaterialSystem可以正常绘制阴影)
|
||||
- EyeAdaptation (对轮廓线部分使用了特殊的眼部自适应调整)
|
||||
- 实现 MatCap 上反射阴影
|
||||
- RimLight
|
||||
- UV Scroll
|
||||
- 启用生成 AO 阴影模型
|
||||
- MToonMaterialSystem 添加了 SSGI 切换选项
|
||||
- 启用以引用 LightRig 中的 DirectionalLightComponent(支持 SunSkyActor)。通过定向光到 Light Rig 的俯仰角添加了亮度校正选项。
|
||||
- 添加了一个参数来更改 FilmicTonemapper 逆变换的强度。从 MToonMaterialSystem 更改。
|
||||
- PostShadow 新增卡通功能
|
||||
- 注)M MoonLit 会根据场景稍微变暗。我决定不参考 SkyLight 来减少负载。要回到过去,请从材质中打开 bUseSkyLightDirect。
|
||||
- Rim light 和 matcap 不再受正常校正的影响
|
||||
|
||||
## VR相关
|
||||
- 在AnimBP中增加MotionController 和 Leap Motion 跟踪
|
||||
|
||||
## 官方上的支持功能
|
||||
您可以导入 VRM 文件
|
||||
- 动画片
|
||||
- 您可以轻松地重新定位。生成 A-pose / T-pose 和 BoneMap。
|
||||
- 面部动画(Morphtarget / BlendShapeGroup)可用。
|
||||
- 您可以为摆动骨骼选择 VRMSpringBone 或 PhysicsAsset。
|
||||
- 有一个通用的控制装置和一个用于操作的 UMG。
|
||||
- 您可以从外部应用程序接收动作捕捉数据。它支持 VMC 协议。
|
||||
- 您可以在运行时从 UEMannequin 重定向到 VRM 模型。
|
||||
- UE5:可以自动生成 IKRetargeter 资产。您可以从 Epic Skeleton 简化重定向过程。
|
||||
- 材料
|
||||
- 再现 M Moon 的素材。阴影颜色规格、轮廓颜色/粗细调整、MatCap 等都适用。
|
||||
- 您可以根据 PBR 背景切换和调整绘图模式。
|
||||
- 现有的后置滤镜和光线追踪可以同时使用。
|
||||
- 移动的
|
||||
- 配合BoneMap缩减功能,不用担心骨骼数量即可显示。
|
||||
- 您可以切换绘图质量。它支持低规格。
|
||||
- 虚拟现实/增强现实
|
||||
- 绘图同时支持 Forward / Deferred。
|
||||
- 由于它是由简单的函数组成的,所以它不会崩溃。
|
||||
- 运行时负载
|
||||
- 任何用户的 VRM 文件都可以从打包的 EXE 文件中读取。
|
||||
- 动画可以在运行时重新定位。
|
||||
- UE兼容性好
|
||||
- 导入后,它将是一个标准的骨架网格体资源。
|
||||
- 对应的UE版本是4.20~4.27,5.0(截至2022/04)很容易支持最新版本。
|
||||
- 依次支持VRM1.0β
|
||||
- VRM1.0数据在一定程度上可以导入。
|
||||
- 您可以保留本地轴或在导入时选择它。
|
||||
- 对应M Moon的新功能。
|
||||
- 实验实现
|
235
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U与其他MMD插件笔记.md
Normal file
235
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U与其他MMD插件笔记.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Assimp入门
|
||||
https://assimp-docs.readthedocs.io/en/latest/about/introduction.html
|
||||
|
||||
# 其他卡通渲染推主
|
||||
https://twitter.com/rukikuri
|
||||
|
||||
# VRoid商城地址
|
||||
https://booth.pm/zh-cn
|
||||
|
||||
# VRM格式地址
|
||||
https://vrm.dev/
|
||||
## Unity3d导入插件
|
||||
导入https://github.com/vrm-c/UniVRM/releases
|
||||
导出Fbx插件:https://github.com/KellanHiggins/UnityFBXExporter
|
||||
|
||||
# VRM4U
|
||||
https://github.com/ruyo/UnrealEngine_VRM4UPlugin
|
||||
https://github.com/ruyo/VRM4U
|
||||
|
||||
## 渲染参考
|
||||
原文(上/下):
|
||||
https://qiita.com/ruyo/items/ec082d81dea3033e1500
|
||||
https://qiita.com/ruyo/items/71a3f2f694d2853b3f1e
|
||||
|
||||
光照:
|
||||
https://qiita.com/ruyo/items/28255f26725a6b6bd475
|
||||
|
||||
### 其他参考
|
||||
MToon:https://dwango.github.io/vrm/univrm/shaders/mtoon/
|
||||
Ue4中的卡通渲染:https://qiita.com/com04/items/a7895160df8d854fe924
|
||||
|
||||
## 曝光与ToneMapper问题解决
|
||||
ToneMapper与曝光会影响贴图的亮度,所以需要尝试干掉或者抵消掉他们的影响。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
手动去除了Gammer矫正以及ToneMapper取消,并且使用 人眼适应节点抵消曝光效果。
|
||||
|
||||
除此之外,还使用RayTracingQualitySwitchReplace节点与人眼适应节点做了Raytracing质量调整。(手机端为1)
|
||||
|
||||
## 阴影
|
||||

|
||||
使用了SceneCaptureComponent2D。
|
||||
在Orthographic(平行投影*)中,将CaptureSource设置为 "SceneDepth in R",TexrureTarget格式设置为 "RTF R32f"。
|
||||
这将导致深度在UnrealUnit中线性地写入缓冲区(默认为1=1cm)。 别紧张。
|
||||
|
||||

|
||||
要用Shadowmap投出一个阴影,你只需要知道从光线中看到的深度,以及投影矩阵的矩阵。
|
||||
我们会把这些东西找回来,传给材料。
|
||||
https://www.shibuya24.info/entry/shadowmap
|
||||
|
||||
官方的SceneCaptureComponent2D会做很多无用的渲染工作,推荐自己重新写一个,以减少不必要的消耗。
|
||||
|
||||
### 蓝图传递逆矩阵
|
||||
|
||||
在蓝图中国计算出SceneCaptureComponent2D的逆矩阵后,将其传入材质中。
|
||||

|
||||
|
||||
### 材质内
|
||||
在一个自定义节点中,将参数打包成float4x4,并进行计算。
|
||||
现在你可以从世界坐标中参考相应的影子图。
|
||||
在材料函数中,结果返回0-1。
|
||||

|
||||
|
||||
|
||||
## 描边
|
||||
在具体节点方面,
|
||||
投影结果 → TransformPosition节点转换为ViewSpace的转换结果
|
||||
Pixel Thickness → VectorLength的值。
|
||||
乘以倒数→部分除以VectorLength的值,再乘以反数→部分除以VectorLength的值。
|
||||
使用ScreenResolusion的原因是为了确保不影响窗口大小和宽高比。
|
||||
我不确定我是否准备好了......
|
||||
在MToon再现方面,我们需要能够选择 "恒定厚度模式 "和 "世界坐标参考模式"。
|
||||

|
||||
|
||||
http://historia.co.jp/archives/5587/
|
||||
|
||||
使用PoseableMeshComponent,与CopyPoseFromSkeletalComponent函数实现外描边。到时候比对一下这个方法与我的重定义图元方法的效率。
|
||||
|
||||
## 轮廓光
|
||||
使用MaterialCapture材质制作轮廓光。
|
||||
|
||||
## 光照模式
|
||||
默认的MToon为无光照模式(自定义光照效果)
|
||||
|
||||
其余的工作将通过增加材料参数来体现数值。 我就不说了,因为这样做是多余的。 (我没有评论了...)
|
||||
也可以应用ShadingShadingShift。
|
||||
|
||||
主源和GI可以通过在自定义节点中写出以下内容来获得。
|
||||
如果你用GetSkySHDiffuseSimple或GetSkySHDiffuse搜索usf文件,会有帮助。
|
||||
不要忘了在最终输出的SkyLight(ResolvedView.SkyLightColor.rgb)中乘以颜色。
|
||||
|
||||
```
|
||||
//主光源.usf
|
||||
return ResolvedView.DirectionalLightColor;
|
||||
```
|
||||
```
|
||||
//GIの影響.usf,然而这个是天光
|
||||
#if SIMPLE_FORWARD_SHADING
|
||||
float4 NormalVector = float4(Normal, 1);
|
||||
|
||||
float3 Intermediate0;
|
||||
Intermediate0.x = dot(View.SkyIrradianceEnvironmentMap[0], NormalVector);
|
||||
Intermediate0.y = dot(View.SkyIrradianceEnvironmentMap[1], NormalVector);
|
||||
Intermediate0.z = dot(View.SkyIrradianceEnvironmentMap[2], NormalVector);
|
||||
|
||||
// max to not get negative colors
|
||||
return max(0, Intermediate0) * ResolvedView.SkyLightColor.rgb;
|
||||
#else
|
||||
float4 NormalVector = float4(Normal, 1);
|
||||
|
||||
float3 Intermediate0, Intermediate1, Intermediate2;
|
||||
Intermediate0.x = dot(View.SkyIrradianceEnvironmentMap[0], NormalVector);
|
||||
Intermediate0.y = dot(View.SkyIrradianceEnvironmentMap[1], NormalVector);
|
||||
Intermediate0.z = dot(View.SkyIrradianceEnvironmentMap[2], NormalVector);
|
||||
|
||||
float4 vB = NormalVector.xyzz * NormalVector.yzzx;
|
||||
Intermediate1.x = dot(View.SkyIrradianceEnvironmentMap[3], vB);
|
||||
Intermediate1.y = dot(View.SkyIrradianceEnvironmentMap[4], vB);
|
||||
Intermediate1.z = dot(View.SkyIrradianceEnvironmentMap[5], vB);
|
||||
|
||||
float vC = NormalVector.x * NormalVector.x - NormalVector.y * NormalVector.y;
|
||||
Intermediate2 = View.SkyIrradianceEnvironmentMap[6].xyz * vC;
|
||||
|
||||
// max to not get negative colors
|
||||
return max(0, Intermediate0 + Intermediate1 + Intermediate2) * ResolvedView.SkyLightColor.rgb;
|
||||
#endif
|
||||
```
|
||||
## 其他光照模型
|
||||
|
||||
|
||||
## Runtime载入思路
|
||||
```
|
||||
NewObject<USkeletalMesh>()
|
||||
|
||||
SkeletalMesh->GetResourceForRendering()
|
||||
->LODRenderData[0]
|
||||
• StaticVertexBuffers
|
||||
顶点信息。 最起码,这将填补。
|
||||
• MultiSizeIndexContainer
|
||||
- 绘图时的顶点信息。 如果你要运行时加载的话,也要埋下这一点。
|
||||
- 从GameThread停止重写的成员从RenderThread重写。
|
||||
• SkeletalMesh->GetImportedModel()->LODModels[0].Sections;
|
||||
- 网格和材料信息。 我也要把这个埋了。
|
||||
• SkeletalMesh->RegisterMorphTarget()
|
||||
- 混合形状信息。 我也要把这个埋了。
|
||||
- UE4的顶点权重为uint8,所以有一个分数。 最后,正常化。
|
||||
```
|
||||
### VRM(glTF)转换时需要注意
|
||||
改变坐标系。
|
||||
- 转换为Z-up
|
||||
- 转换比例尺为虚幻单位(x100)
|
||||
- 支持多种根骨。
|
||||
- 去除不必要的骨头
|
||||
- 如果你不想移除的话,可以添加一个假根骨(root)。
|
||||
|
||||
字符与编码转换
|
||||
|
||||
### 物理资源导入
|
||||
```
|
||||
• bs = NewObject<USkeletalBodySetup>();
|
||||
• ct = NewObject<UPhysicsConstraintTemplate>();
|
||||
• physicsAsset->SkeletalBodySetups.Add(bs);
|
||||
• physicsAsset->ConstraintSetup.Add(ct);
|
||||
```
|
||||
我想让我的骨头不至于发狂。
|
||||
- 物理学变得很粗糙。 当碰撞被猛烈地掩埋时,就会引起注意。
|
||||
- 增加计算次数,会在一定程度上改善它。
|
||||
- 我想保持我想要的形状。
|
||||
- 我的头发因为地心引力而垂下来。
|
||||
- 可移动的范围可以指定,但移动不规范。 杂项:
|
||||
|
||||
VRMSpringBone是Unity的一个实现,目前已经发布了VRMSpringBone。
|
||||
- 理论上可以移植到UE4上。
|
||||
- 源码(VRMSpringBone.cs)只有329行。
|
||||
- 我不知道这样可以吗?
|
||||
|
||||
于是作者移植了VRMSpringBone。
|
||||
|
||||
我现在可以看VRM了。
|
||||
- 你现在可以导入/运行时加载
|
||||
- 我现在也能读出混合形状和碰撞了!
|
||||
- 现在,你可以重新创建VRMSpringBone
|
||||
- 你现在可以干扰UE4的碰撞了。
|
||||
|
||||
StaticMesh的运行时加载也是可能的。
|
||||
- 我可以创建一个自定义的摇摆骨质节点。
|
||||
- 对于那些对PhysicalAsset和AnimDynamics节点的行为不满意的人。
|
||||
- 你为什么不尝试着去做一个呢?
|
||||
|
||||
## 动画部分
|
||||
要想导入动画,可以使用
|
||||
|
||||
- (一) 骨架通用化
|
||||
- (二) 重新定位动画资产。
|
||||
|
||||
但两者都是编辑器功能,无法在runtime中使用。
|
||||
|
||||
### 使用AnimBP实现运行时重定向
|
||||

|
||||
将复制资产设置为AnimInstance,你就可以走了。
|
||||
|
||||
复制源头网状物。
|
||||
设置在编辑器中创建的AnimBP
|
||||
|
||||
要复制的网状物。
|
||||
设置VrmAnimInstanceCopy
|
||||
|
||||
# IM4U
|
||||
https://github.com/bm9/IM4U
|
||||
https://github.com/bm9/UnrealEngine_IM4UPlugin
|
||||
|
||||
# Blender和pmx2fbx
|
||||
|
||||
# Muro_CG
|
||||
使用Unity卡通着色的3D动画表达
|
||||
https://qiita.com/MuRo_CG/items/c417ef6d6cbeed3dd42b
|
||||
|
||||
Unity トゥーンシェーディングを使った3Dアニメ表現
|
||||
https://qiita.com/MuRo_CG/items/c417ef6d6cbeed3dd42b
|
||||
https://github.com/unity3d-jp/unitychan-crs
|
||||
|
||||
https://ja.whotwi.com/MuRo_CG/tweets/popular
|
||||
https://togetter.com/li/1438719
|
||||
|
||||
# Unity_Japan
|
||||
【Unite 2017 Tokyo】VR MAGIC! ~キャラクターに命を吹き込んだこの4年間の記録~
|
||||
https://www.slideshare.net/Unite2017Tokyo/unite-2017-tokyovr-magic4
|
||||
|
||||
https://www.youtube.com/watch?v=N4_x9w7wNNY&feature=youtu.be
|
||||
|
||||
# 米哈游
|
||||
http://www.uniteseoul.com/2018/download_files/T1_0503_2.pdf
|
@@ -0,0 +1,83 @@
|
||||
## 自定义Asset
|
||||
https://zhuanlan.zhihu.com/p/77812246
|
||||
|
||||
- FAssetTypeActions_Base:是对已经创建好的资源操作函数的封装。这个资源在内容浏览器中的颜色,类型名字,显示的缩略图。
|
||||
- UFactory:这个是创建自定义类型文件的入口。以及创建资源的二级菜单。
|
||||
- UBlueprint: 创建继承类,来实现一个新的视图编辑窗口
|
||||
- UXXXX : Public Object : 这里面存储我们要编辑的数据
|
||||
|
||||
VRM中对应类为:
|
||||
- FAssetTypeActions_VrmAssetList、UVrmAssetListThumbnailRenderer
|
||||
- UVRM4UImporterFactory
|
||||
- UVrmAssetListObject
|
||||
|
||||
## Assimp
|
||||
资源工厂类中:UVRM4UImporterFactory::FactoryCreateBinary
|
||||
```
|
||||
{
|
||||
const UVrmRuntimeSettings* Settings = GetDefault<UVrmRuntimeSettings>();
|
||||
|
||||
{
|
||||
FSoftObjectPath r = Settings->AssetListObject; //(TEXT("/VRM4U/VrmObjectListBP.VrmObjectListBP"));
|
||||
UObject *u = r.TryLoad();
|
||||
if (u) {
|
||||
if (Cast<UBlueprint>(u)) {
|
||||
c = (UClass*)(Cast<UBlueprint>(u)->GeneratedClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == nullptr) {
|
||||
FSoftObjectPath r(TEXT("/VRM4U/VrmAssetListObjectBP.VrmAssetListObjectBP"));
|
||||
UObject *u = r.TryLoad();
|
||||
if (u) {
|
||||
c = (UClass*)(Cast<UBlueprint>(u)->GeneratedClass);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == nullptr) {
|
||||
c = UVrmAssetListObject::StaticClass();
|
||||
}
|
||||
|
||||
m = NewObject<UVrmAssetListObject>((UObject*)GetTransientPackage(), c.Get());
|
||||
}
|
||||
|
||||
UVrmAssetListObject* mret = nullptr;
|
||||
if (m) {
|
||||
//auto a = NewObject<UVrmAssetListObject>(MatClass.Object, NAME_None, RF_Transactional);
|
||||
//MatClass.Object;
|
||||
//ULoaderBPFunctionLibrary::LoadVRMFile(nullptr, fullFileName);
|
||||
|
||||
GWarn->BeginSlowTask( NSLOCTEXT("UnrealEd", "ImportVRM", "Importing VRM"), true );
|
||||
|
||||
int ret = true;
|
||||
auto &g = VRMConverter::Options::Get();
|
||||
g.SetVrmOption(ImportUI->GenerateOptionData());
|
||||
|
||||
ULoaderBPFunctionLibrary::SetImportMode(true, Cast<UPackage>(InParent));
|
||||
{
|
||||
ret = ULoaderBPFunctionLibrary::LoadVRMFileLocal(m.Get(), mret, fullFileName);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
蓝图函数库中:bool ULoaderBPFunctionLibrary::LoadVRMFileFromMemory()。
|
||||
```
|
||||
Assimp::Importer mImporter;
|
||||
const aiScene* mScenePtr = nullptr; // delete by Assimp::Importer::~Importer
|
||||
|
||||
mScenePtr = mImporter.ReadFileFromMemory(pFileDataData, dataSize,
|
||||
aiProcess_Triangulate | aiProcess_MakeLeftHanded | aiProcess_CalcTangentSpace | aiProcess_GenSmoothNormals | aiProcess_OptimizeMeshes,
|
||||
e.c_str());
|
||||
|
||||
if (mScenePtr == nullptr) {
|
||||
std::string file;
|
||||
file = utf_16_to_shift_jis(*filepath);
|
||||
|
||||
mScenePtr = mImporter.ReadFile(file, aiProcess_Triangulate | aiProcess_MakeLeftHanded | aiProcess_CalcTangentSpace | aiProcess_GenSmoothNormals | aiProcess_OptimizeMeshes);
|
||||
}
|
||||
```
|
271
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U卡通渲染实现.md
Normal file
271
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U卡通渲染实现.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Assimp入门
|
||||
https://assimp-docs.readthedocs.io/en/latest/about/introduction.html
|
||||
|
||||
# 其他卡通渲染推主
|
||||
https://twitter.com/rukikuri
|
||||
|
||||
# VRM格式地址
|
||||
https://vrm.dev/
|
||||
## Unity3d导入插件
|
||||
导入https://github.com/vrm-c/UniVRM/releases
|
||||
导出Fbx插件:https://github.com/KellanHiggins/UnityFBXExporter
|
||||
|
||||
# VRM4U
|
||||
https://github.com/ruyo/UnrealEngine_VRM4UPlugin
|
||||
https://github.com/ruyo/VRM4U
|
||||
|
||||
## 渲染原理介绍
|
||||
原文(上/下):
|
||||
https://qiita.com/ruyo/items/ec082d81dea3033e1500
|
||||
https://qiita.com/ruyo/items/71a3f2f694d2853b3f1e
|
||||
|
||||
MToon:https://dwango.github.io/vrm/univrm/shaders/mtoon/
|
||||
Ue4中的卡通渲染:https://qiita.com/com04/items/a7895160df8d854fe924
|
||||
|
||||
材质:
|
||||
https://qiita.com/ruyo/items/ddf727e9fa81a24070fb (细节都在这里)
|
||||
https://qiita.com/ruyo/items/28255f26725a6b6bd475
|
||||
|
||||
Unlit
|
||||

|
||||

|
||||
|
||||
PBR
|
||||

|
||||

|
||||
|
||||
SSS
|
||||

|
||||

|
||||
|
||||
## 灯光调整
|
||||

|
||||
|
||||
2种方法:
|
||||
- 开启天光的LowerHemisphereIsSolidColor,但这样会场景效果变得有些奇怪
|
||||
- 添加一个从下往上的方向光
|
||||
|
||||
使用这个方法 以及 PBR材质中的BaseColor与Emissive进行插值来接近HalfLambda的效果。
|
||||
|
||||
## 调整面部法线来柔化面部阴影
|
||||

|
||||

|
||||
|
||||
## 曝光与ToneMapper问题解决
|
||||
ToneMapper与曝光会影响贴图的亮度,所以需要尝试干掉或者抵消掉他们的影响。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
手动去除了Gammer矫正以及ToneMapper取消,并且使用 人眼适应节点抵消曝光效果。
|
||||
|
||||
除此之外,还使用RayTracingQualitySwitchReplace节点与人眼适应节点做了Raytracing质量调整。(手机端为1)
|
||||
|
||||
## 阴影
|
||||

|
||||
使用了SceneCaptureComponent2D。
|
||||
在Orthographic(平行投影*)中,将CaptureSource设置为 "SceneDepth in R",TexrureTarget格式设置为 "RTF R32f"。
|
||||
这将导致深度在UnrealUnit中线性地写入缓冲区(默认为1=1cm)。 别紧张。
|
||||
|
||||

|
||||
要用Shadowmap投出一个阴影,你只需要知道从光线中看到的深度,以及投影矩阵的矩阵。
|
||||
我们会把这些东西找回来,传给材料。
|
||||
https://www.shibuya24.info/entry/shadowmap
|
||||
|
||||
官方的SceneCaptureComponent2D会做很多无用的渲染工作,推荐自己重新写一个,以减少不必要的消耗。
|
||||
|
||||
### 蓝图传递逆矩阵
|
||||
|
||||
在蓝图中国计算出SceneCaptureComponent2D的逆矩阵后,将其传入材质中。
|
||||

|
||||
|
||||
### 材质内
|
||||
在一个自定义节点中,将参数打包成float4x4,并进行计算。
|
||||
现在你可以从世界坐标中参考相应的影子图。
|
||||
在材料函数中,结果返回0-1。
|
||||

|
||||
|
||||
|
||||
## 描边
|
||||
在具体节点方面,
|
||||
投影结果 → TransformPosition节点转换为ViewSpace的转换结果
|
||||
Pixel Thickness → VectorLength的值。
|
||||
乘以倒数→部分除以VectorLength的值,再乘以反数→部分除以VectorLength的值。
|
||||
使用ScreenResolusion的原因是为了确保不影响窗口大小和宽高比。
|
||||
我不确定我是否准备好了......
|
||||
在MToon再现方面,我们需要能够选择 "恒定厚度模式 "和 "世界坐标参考模式"。
|
||||

|
||||
|
||||
http://historia.co.jp/archives/5587/
|
||||
|
||||
使用PoseableMeshComponent,与CopyPoseFromSkeletalComponent函数实现外描边。到时候比对一下这个方法与我的重定义图元方法的效率。
|
||||
|
||||
## 轮廓光
|
||||
使用MaterialCapture材质制作轮廓光。
|
||||
|
||||
## 光照模式
|
||||
默认的MToon为无光照模式(自定义光照效果)
|
||||
|
||||
其余的工作将通过增加材料参数来体现数值。 我就不说了,因为这样做是多余的。 (我没有评论了...)
|
||||
也可以应用ShadingShadingShift。
|
||||
|
||||
主源和GI可以通过在自定义节点中写出以下内容来获得。
|
||||
如果你用GetSkySHDiffuseSimple或GetSkySHDiffuse搜索usf文件,会有帮助。
|
||||
不要忘了在最终输出的SkyLight(ResolvedView.SkyLightColor.rgb)中乘以颜色。
|
||||
|
||||
```
|
||||
//主光源.usf
|
||||
return ResolvedView.DirectionalLightColor;
|
||||
```
|
||||
```
|
||||
//GIの影響.usf,然而这个是天光
|
||||
#if SIMPLE_FORWARD_SHADING
|
||||
float4 NormalVector = float4(Normal, 1);
|
||||
|
||||
float3 Intermediate0;
|
||||
Intermediate0.x = dot(View.SkyIrradianceEnvironmentMap[0], NormalVector);
|
||||
Intermediate0.y = dot(View.SkyIrradianceEnvironmentMap[1], NormalVector);
|
||||
Intermediate0.z = dot(View.SkyIrradianceEnvironmentMap[2], NormalVector);
|
||||
|
||||
// max to not get negative colors
|
||||
return max(0, Intermediate0) * ResolvedView.SkyLightColor.rgb;
|
||||
#else
|
||||
float4 NormalVector = float4(Normal, 1);
|
||||
|
||||
float3 Intermediate0, Intermediate1, Intermediate2;
|
||||
Intermediate0.x = dot(View.SkyIrradianceEnvironmentMap[0], NormalVector);
|
||||
Intermediate0.y = dot(View.SkyIrradianceEnvironmentMap[1], NormalVector);
|
||||
Intermediate0.z = dot(View.SkyIrradianceEnvironmentMap[2], NormalVector);
|
||||
|
||||
float4 vB = NormalVector.xyzz * NormalVector.yzzx;
|
||||
Intermediate1.x = dot(View.SkyIrradianceEnvironmentMap[3], vB);
|
||||
Intermediate1.y = dot(View.SkyIrradianceEnvironmentMap[4], vB);
|
||||
Intermediate1.z = dot(View.SkyIrradianceEnvironmentMap[5], vB);
|
||||
|
||||
float vC = NormalVector.x * NormalVector.x - NormalVector.y * NormalVector.y;
|
||||
Intermediate2 = View.SkyIrradianceEnvironmentMap[6].xyz * vC;
|
||||
|
||||
// max to not get negative colors
|
||||
return max(0, Intermediate0 + Intermediate1 + Intermediate2) * ResolvedView.SkyLightColor.rgb;
|
||||
#endif
|
||||
```
|
||||
## 其他光照模型
|
||||
|
||||
|
||||
## Runtime载入思路
|
||||
```
|
||||
NewObject<USkeletalMesh>()
|
||||
|
||||
SkeletalMesh->GetResourceForRendering()
|
||||
->LODRenderData[0]
|
||||
• StaticVertexBuffers
|
||||
顶点信息。 最起码,这将填补。
|
||||
• MultiSizeIndexContainer
|
||||
- 绘图时的顶点信息。 如果你要运行时加载的话,也要埋下这一点。
|
||||
- 从GameThread停止重写的成员从RenderThread重写。
|
||||
• SkeletalMesh->GetImportedModel()->LODModels[0].Sections;
|
||||
- 网格和材料信息。 我也要把这个埋了。
|
||||
• SkeletalMesh->RegisterMorphTarget()
|
||||
- 混合形状信息。 我也要把这个埋了。
|
||||
- UE4的顶点权重为uint8,所以有一个分数。 最后,正常化。
|
||||
```
|
||||
### VRM(glTF)转换时需要注意
|
||||
改变坐标系。
|
||||
- 转换为Z-up
|
||||
- 转换比例尺为虚幻单位(x100)
|
||||
- 支持多种根骨。
|
||||
- 去除不必要的骨头
|
||||
- 如果你不想移除的话,可以添加一个假根骨(root)。
|
||||
|
||||
字符与编码转换
|
||||
|
||||
### 物理资源导入
|
||||
```
|
||||
• bs = NewObject<USkeletalBodySetup>();
|
||||
• ct = NewObject<UPhysicsConstraintTemplate>();
|
||||
• physicsAsset->SkeletalBodySetups.Add(bs);
|
||||
• physicsAsset->ConstraintSetup.Add(ct);
|
||||
```
|
||||
我想让我的骨头不至于发狂。
|
||||
- 物理学变得很粗糙。 当碰撞被猛烈地掩埋时,就会引起注意。
|
||||
- 增加计算次数,会在一定程度上改善它。
|
||||
- 我想保持我想要的形状。
|
||||
- 我的头发因为地心引力而垂下来。
|
||||
- 可移动的范围可以指定,但移动不规范。 杂项:
|
||||
|
||||
VRMSpringBone是Unity的一个实现,目前已经发布了VRMSpringBone。
|
||||
- 理论上可以移植到UE4上。
|
||||
- 源码(VRMSpringBone.cs)只有329行。
|
||||
- 我不知道这样可以吗?
|
||||
|
||||
于是作者移植了VRMSpringBone。
|
||||
|
||||
我现在可以看VRM了。
|
||||
- 你现在可以导入/运行时加载
|
||||
- 我现在也能读出混合形状和碰撞了!
|
||||
- 现在,你可以重新创建VRMSpringBone
|
||||
- 你现在可以干扰UE4的碰撞了。
|
||||
|
||||
StaticMesh的运行时加载也是可能的。
|
||||
- 我可以创建一个自定义的摇摆骨质节点。
|
||||
- 对于那些对PhysicalAsset和AnimDynamics节点的行为不满意的人。
|
||||
- 你为什么不尝试着去做一个呢?
|
||||
|
||||
## 动画部分
|
||||
要想导入动画,可以使用
|
||||
|
||||
- (一) 骨架通用化
|
||||
- (二) 重新定位动画资产。
|
||||
|
||||
但两者都是编辑器功能,无法在runtime中使用。
|
||||
|
||||
### 使用AnimBP实现运行时重定向
|
||||

|
||||
将复制资产设置为AnimInstance,你就可以走了。
|
||||
|
||||
复制源头网状物。
|
||||
设置在编辑器中创建的AnimBP
|
||||
|
||||
要复制的网状物。
|
||||
设置VrmAnimInstanceCopy
|
||||
|
||||
# IM4U
|
||||
https://github.com/bm9/IM4U
|
||||
https://github.com/bm9/UnrealEngine_IM4UPlugin
|
||||
|
||||
# Blender和pmx2fbx
|
||||
|
||||
## U3d日本分部分项
|
||||
|
||||
『崩壊3rd』開発者が語るアニメ風レンダリングの極意
|
||||
ユニティちゃんトゥーンシェーダー2.0使いこなしスペシャル ~こだわりの活用法を紹介します!~
|
||||
|
||||
〈七つの大罪〉をゲームで! 高品質グラフィックを具現化するための技法と開発最適化のご紹介 by Jaeseong Ryu 他
|
||||
|
||||
|
||||
# Muro_CG
|
||||
使用Unity卡通着色的3D动画表达
|
||||
https://qiita.com/MuRo_CG/items/c417ef6d6cbeed3dd42b
|
||||
|
||||
Unity トゥーンシェーディングを使った3Dアニメ表現
|
||||
https://qiita.com/MuRo_CG/items/c417ef6d6cbeed3dd42b
|
||||
https://github.com/unity3d-jp/unitychan-crs
|
||||
|
||||
https://ja.whotwi.com/MuRo_CG/tweets/popular
|
||||
https://togetter.com/li/1438719
|
||||
|
||||
# Unity_Japan
|
||||
【Unite 2017 Tokyo】VR MAGIC! ~キャラクターに命を吹き込んだこの4年間の記録~
|
||||
https://www.slideshare.net/Unite2017Tokyo/unite-2017-tokyovr-magic4
|
||||
|
||||
https://www.youtube.com/watch?v=N4_x9w7wNNY&feature=youtu.be
|
||||
|
||||
# 米哈游
|
||||
http://www.uniteseoul.com/2018/download_files/T1_0503_2.pdf
|
||||
|
||||
|
||||
米哈游面部阴影实现:
|
||||
Improved Alpha-Tested Magnification for Vector Textures and Special Effects
|
||||
|
||||
https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf
|
BIN
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U技术讲解.pdf
(Stored with Git LFS)
Normal file
BIN
03-UnrealEngine/卡通渲染相关资料/演讲笔记/VRM4U/VRM4U技术讲解.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
03-UnrealEngine/卡通渲染相关资料/演讲笔记/米哈游演讲/米哈游技术总监贺甲:如何实现次世代卡通渲染效果.pdf
(Stored with Git LFS)
Normal file
BIN
03-UnrealEngine/卡通渲染相关资料/演讲笔记/米哈游演讲/米哈游技术总监贺甲:如何实现次世代卡通渲染效果.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
03-UnrealEngine/卡通渲染相关资料/演讲笔记/米哈游演讲/米哈游高质量动态渲染技术.pdf
(Stored with Git LFS)
Normal file
BIN
03-UnrealEngine/卡通渲染相关资料/演讲笔记/米哈游演讲/米哈游高质量动态渲染技术.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
118
03-UnrealEngine/卡通渲染相关资料/演讲笔记/蓝色协议的方案.md
Normal file
118
03-UnrealEngine/卡通渲染相关资料/演讲笔记/蓝色协议的方案.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# 蓝色协议
|
||||
- https://zhuanlan.zhihu.com/p/405540279
|
||||
- https://zhuanlan.zhihu.com/p/229621134
|
||||
|
||||
## 顶点色存储数据
|
||||
用于存储一些低精度数据,插值即可
|
||||
- R:阴影区域标记
|
||||
- G:描边宽度(描边Mask)
|
||||
- B:Ao
|
||||
|
||||
第二套顶点色:
|
||||
- R:深度偏移,用处下文会提到
|
||||
- G:用来区分内轮廓不同部位的ID
|
||||
导入时被存到了UV Channel 1中。
|
||||
|
||||
## BaseColor、ShadowColor
|
||||
- 假设固定一个全局HSV值通过偏移来获取ShadowColor。通过方向光的阴影来对ShadowColor与BaseColor插值。
|
||||
- 使用HalfNol,之后使用step(Threshold,HalfNol)
|
||||

|
||||
- 使用一个GBuffer存储NoL信息来减少相关二值化参数。
|
||||
|
||||
局部阴影(贴图绘制)与自投影阴影的叠加,推荐使用虚幻的阴影与AO叠加算法。
|
||||

|
||||
|
||||
## 多光源策略
|
||||
只有主光在计算中会计算暗部颜色,这样就能防止多光源时,暗部被反复提亮的问题。除此之外,剩下的点光源只用于提亮,并且忽略法线,只计算距离衰减,防止照出难看的结构。这样的效果还是挺二次元的。
|
||||
|
||||
## 高光
|
||||

|
||||

|
||||
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/蓝色协议_自定义形状高光.png]]
|
||||
|
||||
SpcularMask高光,理论上这部分应该在Lighting阶段计算的,但是第二步的绘制SpecularMask、ID?、XY轴心偏移。感觉这部分是材质编辑里计算出来得高光。
|
||||
|
||||
## 天光与间接光处理
|
||||
为了防止环境光把角色照出立体感,所以计算环境光时,会把法线全部看作世界空间上方向来处理。同时增加了一些参数可以进行一些定制化调整。
|
||||
|
||||
## RimLight
|
||||
贴图:
|
||||
- RimLightMask
|
||||
- RimLightWidth
|
||||
|
||||

|
||||

|
||||
使用描边计算的方式最后计算出Rim的范围。
|
||||
|
||||
## 轮廓
|
||||
蓝色协议的轮廓由3部分组成:
|
||||
- **预先绘制**
|
||||
- **Mesh挤出**
|
||||
- **后处理描边**
|
||||
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_Outline.jpg]]
|
||||
|
||||
### 预先绘制
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_DrawOutlineTexture.png]]
|
||||
|
||||
### Mesh挤出
|
||||
蓝色协议用用模型扩边法绘制**头部、下颚、嘴、耳朵**区域。不使用后处理的原因是:解决使用后处理描边后,鼻子处会被勾到的问题。
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_面挤出2.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_面挤出3.jpg]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_面挤出4.jpg]]
|
||||
|
||||
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_面挤出1.png]]
|
||||
>模型扩边用了单独储存共点平均法线的方式来修复分叉,是常见做法。它并没有继续解释,我在这补充一下,这个描边法线想在蒙皮网格上正常使用需要转到切线空间,或者直接储存在切线上。可以参考:【Job/Toon Shading Workflow】自动生成硬表面模型Outline Normal https://zhuanlan.zhihu.com/p/107664564
|
||||
|
||||
如果想做得更好一点,还可以根据法线的角度方差来增强这部分的描线宽度,这样就能把发尖做出来。**也就是制作一个Mesh后处理工具,遍历当前顶点法线与其他附近法线,根据差异度(方差)大小对法线进行伸缩**
|
||||
|
||||
### 后处理描边
|
||||
绘制**非头部**区域,PostOutlinePass位于Lighting与透明物体绘制之间,采用4张贴图进行计算。
|
||||
1. 使用Sobel过滤器进行深度检测描边。只勾数值差别较大的区域。
|
||||
- ![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_DepthTestOutline.png]]
|
||||
- 最左边的是顶点色存的深度偏移值,用它偏移中间的深度图之后就可以勾出嘴、口腔内的、脸的轮廓。
|
||||
2. 使用Sobel过滤器进行Id图检测描边。只勾数值差别较大的区域。
|
||||
- ![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_IDOutline.jpg]]
|
||||
3. 使用Sobel过滤器进行Normal检测描边(使用点积dot的方式)。对同一个ID区域内深度差异小进行检测,通过获取周围点法线求点乘的方式判断出轮廓。
|
||||
- ![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/蓝色协议_Normal检测描边.png]]
|
||||
- ![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_NormalDotTestOutline1.jpg]]
|
||||
- ![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_NormalDotTestOutline.png]]
|
||||
4. 叠加预先画好的轮廓(GBuffer)。
|
||||
- ![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_DrawOutlineInGBuffer.png]]
|
||||
|
||||
最终合成结果:![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/BlueProtocol_FinalyOutline.png]]
|
||||
|
||||
### 眉毛勾线
|
||||
头发将在BasePass之后通过实现一个ToonHairPass进行绘制。(眉毛依然在BasePass中进行绘制)以此来实现透明头发的效果。
|
||||
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/CEDEC2021_BLUEPROTOCOLにおけるアニメ表現手法(実装編)-60.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/CEDEC2021_BLUEPROTOCOLにおけるアニメ表現手法(実装編)-61.png]]
|
||||
|
||||
眉毛的轮廓会绘制在Hair上。
|
||||
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/CEDEC2021_BLUEPROTOCOLにおけるアニメ表現手法(実装編)-62.png]]
|
||||
|
||||
### 使用Responsive AA解决Outline融化问题
|
||||
Responsive AA处理:
|
||||
TAA会导致勾线变糊,所以用了UE的Responsive AA,用stencil标记了勾线部分,这部分在做帧间混合时就少混合一些。虽然会导致锯齿更强,但是动态画面里是可以接受的程度。
|
||||
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/CEDEC2021_BLUEPROTOCOLにおけるアニメ表現手法(実装編)-64.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/CEDEC2021_BLUEPROTOCOLにおけるアニメ表現手法(実装編)-63.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/CEDEC2021_BLUEPROTOCOLにおけるアニメ表現手法(実装編)-65.png]]
|
||||
|
||||
## 额头发与衣服阴影
|
||||
使用接触阴影这种深度偏移后处理阴影来实现。
|
||||
|
||||
## GBuffer
|
||||
```c#
|
||||
GBufferB:Metallic/Specular/Roughness => ToonHairMask OffsetShadowMask/SpcularMask/SpecularValue
|
||||
OutGBufferD = CustomData.xyzw => ShaderColor.rgb/NoL
|
||||
OutGBufferE = GBuffer.PrecomputedShadowFactors.xyzw => /RimLightMask/DiffuseOffset/RimLightWidth
|
||||
OutGBufferF = velocity => OutlineWidth/OutlineID/OutlinePaint/OutlineZShift
|
||||
```
|
||||
PS.需要关闭角色的预计算阴影。
|
||||
|
||||
## 管线
|
||||

|
69
03-UnrealEngine/卡通渲染相关资料/演讲笔记/赛马娘.md
Normal file
69
03-UnrealEngine/卡通渲染相关资料/演讲笔记/赛马娘.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: 赛马娘
|
||||
date: 2022-10-22 12:11:21
|
||||
excerpt: 摘要
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
- https://zhuanlan.zhihu.com/p/441159789
|
||||
- https://zhuanlan.zhihu.com/p/441560356
|
||||
- 原文:https://game.watch.impress.co.jp/docs/kikaku/1366154.html
|
||||
|
||||
|
||||
为了获得到“干净”的描边,我们需要调整法线,但是受法线影响的还有其他要素。如果我们直接调整模型的法线,阴影和边缘会受到干扰。如果我们用模型原法线来做扩边,则会出现描出的边断裂。为了解决这个问题,他们把用于扩边的法线调整后存在一个UV里,扩边时用UV中的法线计算。这样,阴影和描边的计算就可以兼容了。
|
||||
|
||||

|
||||
|
||||
> 左:用OutLine的法线进行全部着色的话,边缘处和阴影就都乱了。
|
||||
> 右:用原来计算阴影的法线进行全部着色的话,阴影虽然很对但描的边会出现断裂。
|
||||
|
||||

|
||||
|
||||
> 对策:用两种法线分别解决
|
||||
> 上面是阴影和边缘等用的基本模型法线,下面做描边时用的处理过的法线。右边是我们存放处理过的法线的新增UV图的预览。
|
||||
|
||||
实际的调整方法就是直接手工调整原模型的法线。其中最有必要调整头发束之类的三角形面变化剧烈的部分。
|
||||
|
||||

|
||||
|
||||
> 左侧调整前,我们可以看到一些接缝处或法线变化剧烈的地方描的线出现断裂,而右侧调整后的法线则效果较好。
|
||||
|
||||
为了和动画风格相近,赛马娘会根据场景与演出来调整描边的颜色。
|
||||
|
||||
## 眼睛
|
||||
此外,马娘的眼睛是使角色具有吸引力的重要元素,但赛马娘游戏特别注重眼睛中的高光(high light)。除了制作漫反射纹理外,也加了张高光专用的纹理。以此组合出多彩的瞳孔表现。
|
||||
|
||||
高光可以改变自身的强度,包括垂直和水平的旋转和平移。在马娘们情绪高涨到快哭时,使用更大的高光和更多的高光粒子等。
|
||||
|
||||

|
||||
|
||||
> 上方为基础高光,下方为补充的粒子高光。
|
||||
|
||||

|
||||
|
||||
> 改变高光强度:通常、强高光,无高光。
|
||||
|
||||
此外,还有单独的高光动画。马娘快要哭出来的时候,高光会小范围内颤动。在兴致勃勃时,高光会闪闪发光并绕眼睛转一圈。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
「寄」になった.jpg
|
||||
|
||||
> 醒目飞鹰发动技能时眼睛的小高光颤动特效
|
||||
|
||||

|
||||
|
||||
> 米浴在演唱会时的眼睛中也有小高光颤动特效
|
||||
|
||||

|
||||
|
||||
> 胜利奖券在赢下比赛时眼中同时使用了两种高光。
|
||||
|
||||
赛马娘通过结合面部表情的情感表达和眼睛高光的情感表达。更加自然的表现出了马娘们的情感,突出了马娘们的个性,让用户更加轻易的入坑。
|
||||
|
||||

|
||||
|
||||
> 总结:表情部分总结,表情系统是让马娘们能更加自然表达出情感的系统。
|
||||
> 利用表情和眼睛来达到各种效果。
|
Reference in New Issue
Block a user