Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
e1bf96ca24
@ -137,3 +137,9 @@ UE曲线驱动的动画:https://docs.unrealengine.com/4.27/zh-CN/AnimatingObje
|
||||
## 表情
|
||||
- https://zhuanlan.zhihu.com/p/113396468
|
||||
- https://arkit-face-blendshapes.com/
|
||||
|
||||
# UE5.3新添加的功能 PoseDriverConnect
|
||||
- https://www.unrealengine.com/marketplace/zh-CN/product/pose-driver-connect
|
||||
- https://www.unrealengine.com/zh-CN/blog/create-more-realistic-animation-in-less-time-with-pose-driver-connect
|
||||
|
||||
主要的功能是通过PoseDriver来驱动其他次级PoseDriver,来此来得到更加好的修形效果。
|
114
03-UnrealEngine/Editor/FBXAnimation导入逻辑.md
Normal file
114
03-UnrealEngine/Editor/FBXAnimation导入逻辑.md
Normal file
@ -0,0 +1,114 @@
|
||||
---
|
||||
title: FBXAnimation导入逻辑
|
||||
date: 2023-12-11 11:28:36
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# BVHImport插件中的案例
|
||||
```c++
|
||||
UObject* UBVHImportFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& FileName, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
|
||||
{
|
||||
if (!shouldImport) {
|
||||
return nullptr;
|
||||
}
|
||||
bool fileSuccess = BVHFile.ImportData(TCHAR_TO_ANSI(*FileName));
|
||||
|
||||
const bool bIsUnattended = (IsAutomatedImport()
|
||||
|| FApp::IsUnattended()
|
||||
|| IsRunningCommandlet()
|
||||
|| GIsRunningUnattendedScript);
|
||||
|
||||
// Check if it's a re-import
|
||||
if (InParent != nullptr)
|
||||
{
|
||||
UObject* ExistingObject = StaticFindObject(UObject::StaticClass(), InParent, *InName.ToString());
|
||||
if (ExistingObject)
|
||||
{
|
||||
//reimport逻辑略
|
||||
}
|
||||
}
|
||||
|
||||
if (fileSuccess)
|
||||
{
|
||||
UAnimSequence* AnimSequence = NewObject<UAnimSequence>(InParent, InName, Flags & ~EObjectFlags::RF_Transactional);
|
||||
if (Skeleton == NULL) {
|
||||
//创建骨骼资产略
|
||||
}
|
||||
else {
|
||||
AnimSequence->SetSkeleton(Skeleton);//Skeleton bvhSkel
|
||||
AnimSequence->SetPreviewMesh(PreviewMesh);
|
||||
}
|
||||
ExtractAnimDataFromBVHFile(AnimSequence);
|
||||
return AnimSequence;
|
||||
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```c++
|
||||
void UBVHImportFactory::ExtractAnimDataFromBVHFile(UAnimSequence* AnimSequence) {
|
||||
if (AnimSequence)
|
||||
{
|
||||
// create animation
|
||||
IAnimationDataController& Controller = AnimSequence->GetController();
|
||||
Controller.OpenBracket(LOCTEXT("ImportBVHAnimation", "Importing BVH Animation"));
|
||||
Controller.ResetModel();
|
||||
|
||||
// Write animation data into animation sequence.
|
||||
// Extract transform of hip to create root motion.
|
||||
const FReferenceSkeleton& RefSkeleton = AnimSequence->GetSkeleton()->GetReferenceSkeleton();
|
||||
const FName RootName = RefSkeleton.GetBoneName(0);
|
||||
const int32 NumOfKeys = BVHFile.Root->FrameOffset.Num();
|
||||
|
||||
AnimSequence->ImportFileFramerate = (float)BVHFile.Header.DataRate;
|
||||
AnimSequence->ImportResampleFramerate = BVHFile.Header.DataRate;
|
||||
|
||||
Controller.SetPlayLength(float(NumOfKeys - 1) / (float)BVHFile.Header.DataRate);
|
||||
Controller.SetFrameRate(FFrameRate(BVHFile.Header.DataRate, 1));
|
||||
|
||||
RecursiveReadKeysFromNode(Controller, BVHFile.Root.Get());
|
||||
|
||||
Controller.NotifyPopulated();
|
||||
Controller.CloseBracket();
|
||||
|
||||
AnimSequence->AdditiveAnimType = EAdditiveAnimationType::AAT_None;
|
||||
AnimSequence->PostEditChange();
|
||||
FAssetRegistryModule::AssetCreated(AnimSequence);
|
||||
AnimSequence->MarkPackageDirty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```c++
|
||||
void UBVHImportFactory::RecursiveReadKeysFromNode(IAnimationDataController& Controller, FNode* Node)
|
||||
{
|
||||
if (Node)
|
||||
{
|
||||
Controller.AddBoneTrack(Node->Name,false);
|
||||
Controller.SetBoneTrackKeys(Node->Name, Node->FrameOffset, Node->FrameQuat, Node->FrameScale,false);
|
||||
if (Node->Children.Num() > 0)
|
||||
{
|
||||
for (NodePtr Child : Node->Children)
|
||||
{
|
||||
RecursiveReadKeysFromNode(Controller, Child.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# UE5中使用FBXSDK导入动画逻辑
|
||||
- UAnimSequence * UEditorEngine::ImportFbxAnimation
|
||||
- UAnimSequence * UnFbx::FFbxImporter::ImportAnimations
|
||||
|
||||
UE5中FBXSDK相关函数调用方式:
|
||||
- bool FFbxImporter::OpenFile(FString Filename)
|
||||
- bool FFbxImporter::ImportFile(FString Filename, bool bPreventMaterialNameClash /*=false*/)
|
||||
|
||||
# 参考
|
||||
1. Interchange\\Runtime\\Source\\Parsers
|
||||
1. InterchangeFbxParser.Build.cs
|
||||
2. FbxInclude.h:FBXSDK头文件包含问题。
|
@ -1,114 +0,0 @@
|
||||
![[ToonEye.png]]
|
||||
|
||||
![[ToonEyes.png]]
|
||||
|
||||
## PBR Refrection(有问题)
|
||||
```
|
||||
//input anteriorChamberDepth 贴图 使用HeightMap
|
||||
//input radius
|
||||
//input mask 贴图
|
||||
//input texcoord
|
||||
//input n 折射率比值
|
||||
//input frontNormalW 贴图float3(0,0,1) Local=>World
|
||||
|
||||
// 角膜区域突起的模型 height = max(-positionL.z – eyeIrisDepth, 0.0);
|
||||
// 球形模型 Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
|
||||
float height = anteriorChamberDepth ;//* saturate( 1.0 - 18.4 * radius * radius );
|
||||
|
||||
float3 normalW=Parameters.WorldNormal;
|
||||
float3 viewW= mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);//CameraViewToTranslatedWorld
|
||||
|
||||
// refractedW
|
||||
float w = n * dot( normalW, viewW );
|
||||
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
|
||||
float3 refractedW = ( w - k ) * normalW - n * viewW;
|
||||
refractedW=-normalize(refractedW);
|
||||
|
||||
//float3 frontNormalW=mul(float(0,0,1),float(3x3)GetPrimitiveData(Parameters.PrimitiveId).LocalToWorld) * -1;
|
||||
float cosAlpha = dot(frontNormalW, refractedW);
|
||||
float dist = height / cosAlpha;
|
||||
float3 offsetW = dist * refractedW;
|
||||
float2 offsetL = mul(offsetW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
|
||||
texcoord += float2(mask, -mask) * offsetL;
|
||||
|
||||
return texcoord;
|
||||
```
|
||||
### 原始代码
|
||||
```
|
||||
// 角膜区域突起的模型
|
||||
// Alternatively, use a displacement map
|
||||
// height = max(-positionL.z – eyeIrisDepth, 0.0);
|
||||
|
||||
// 球形模型
|
||||
// Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
|
||||
height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius );
|
||||
|
||||
// refractedW
|
||||
float w = n * dot( normalW, viewW );
|
||||
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
|
||||
float3 refractedW = ( w - k ) * normalW - n * viewW;
|
||||
|
||||
float cosAlpha = dot(frontNormalW, -refractedW);
|
||||
float dist = height / cosAlpha;
|
||||
float3 offsetW = dist * refractedW;
|
||||
float2 offsetL = mul(offsetW, (float3x2) worldInverse);
|
||||
texcoord += float2(mask, -mask) * offsetL;
|
||||
```
|
||||
## Matcap反射效果
|
||||
```
|
||||
float2 CalcMatcapUV(FMaterialPixelParameters Parameters,float3 normalTexture)
|
||||
{
|
||||
float3 ViewVector=Parameters.CameraVector * -1;
|
||||
float3 ViewSpaceRightVector=normalize(cross(ViewVector , mul( float3(0.0,1.0,0.0) , ResolvedView.ViewToTranslatedWorld )));
|
||||
float3 ViewSpaceUpVector=cross(ViewVector ,ViewSpaceRightVector);
|
||||
|
||||
float3x3 Matrix=float3x3(
|
||||
ViewSpaceRightVector.x,ViewSpaceUpVector.x,ViewVector.x,
|
||||
ViewSpaceRightVector.y,ViewSpaceUpVector.y,ViewVector.y,
|
||||
ViewSpaceRightVector.z,ViewSpaceUpVector.z,ViewVector.z
|
||||
);
|
||||
|
||||
float3 ZeroOneNormal=mul(normalize(Parameters.WorldNormal+normalTexture),Matrix)*0.5+0.5;
|
||||
return float2(ZeroOneNormal.x,ZeroOneNormal.y*-1);
|
||||
}
|
||||
```
|
||||
|
||||
### 这个不太行
|
||||
```
|
||||
//input normalTexture 法线贴图
|
||||
float3 TangentWS=Parameters.TangentToWorld[0];
|
||||
float3 NormalWS=Parameters.TangentToWorld[2];
|
||||
float3 BinormalWS=cross(NormalWS, TangentWS);
|
||||
|
||||
float3 worldNormal;
|
||||
worldNormal.x = dot(float3(TangentWS.x,BinormalWS.x,NormalWS.x), normalTexture);
|
||||
worldNormal.y = dot(float3(TangentWS.y,BinormalWS.y,NormalWS.y), normalTexture);
|
||||
worldNormal.z = dot(float3(TangentWS.z,BinormalWS.z,NormalWS.z), normalTexture);
|
||||
worldNormal = normalize(worldNormal);
|
||||
|
||||
float3 e = normalize(GetWorldPosition(Parameters) - ResolvedView.WorldCameraOrigin);
|
||||
float3 reflectVector = reflect(e, worldNormal);
|
||||
float3 reflectVectorVS = normalize(mul(reflectVector,ResolvedView.TranslatedWorldToView));
|
||||
float m = 2.82842712474619 * sqrt(reflectVectorVS.z + 1.0);
|
||||
float2 cap = reflectVectorVS.xy / m + 0.5;
|
||||
cap=cap*0.5 + 0.5;
|
||||
return cap;
|
||||
```
|
||||
|
||||
## 高光贴图位置调整
|
||||
```
|
||||
//input float2 LeftUp,LeftDown,RightUp,RightDown;
|
||||
|
||||
float3 viewW = mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);
|
||||
float3 viewL = mul(viewW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
|
||||
float2 viewL2D = normalize(viewL.xy);
|
||||
|
||||
float2 Left=lerp(LeftUp,LeftDown,viewL2D.y);
|
||||
float2 Right=lerp(RightUp,RightDown,viewL2D.y);
|
||||
return lerp(Left,Right,viewL2D.x);
|
||||
```
|
||||
## 焦散
|
||||
- dot(Parameters.WorldNormal,View.DirectionalLightDirection);
|
||||
- Fresnel
|
||||
|
||||
乘以焦散Mask即可
|
@ -1,98 +0,0 @@
|
||||
## EOE需求
|
||||
- 渲染功能
|
||||
- 描边功能改进
|
||||
- [x] ScreenPercentage 自适应(移植代码,1小时)
|
||||
- [ ] Fov 自适应 (1~2天)
|
||||
- [ ] 根据投射调整宽度 (1~2天)=> (使用李兄的方案,0天)
|
||||
- [ ] 支持PBR描边控制 (2~4天) => (使用李兄的方案,0天)
|
||||
- 厚涂风格卡通皮肤渲染
|
||||
- [ ] 移植厚涂引擎中的代码 (移植代码,2~5天)
|
||||
- 脸部阴影控制
|
||||
- [ ] 角色在向左/向右旋转头部时,面部不会产生阴影和一些残留的”脏”的阴影信息 (14天)
|
||||
- 眼睛
|
||||
- [ ] 眼白阴影 (5天)=>(使用李兄的方案进行进一步开发,1天)
|
||||
- 头发
|
||||
- [ ] 头发高光调整 (移植代码,1天)=>(使用李兄的方案,0天)
|
||||
- 卡通材质重制
|
||||
- ToonStandard (5~7天)
|
||||
- [ ] 制作母材质以及其他Toon材质的MaterialInstance
|
||||
- ToonSkin (1~2天)
|
||||
- ToonHair(1~2天)
|
||||
- ToonCloth
|
||||
- [ ] 制作母材质以及几套衣服的案例。(1~2天)
|
||||
- [ ] 大约40套衣服,每套衣服(上衣、裙子、鞋子、其他饰品)包含3~6个材质球 (时间不确定,而且体力活,可能是乐华那边做了)
|
||||
- 其他功能
|
||||
- 将UE4插件移植到UE5
|
||||
- [ ] ZeroAnim(大致的修改已经完成,等待测试。)
|
||||
- [ ] JsonAssistPlug(大致的修改已经完成,等待测试。)
|
||||
- 角色动画修型(可选)
|
||||
- [ ] 替换潘翔的动画蓝图,使用UE5小灰人自带的PoseDriver进行修型。(没具体研究过时间不确定,14~28天)=> (7~14天)
|
||||
- ShowCase关卡与文档制作
|
||||
- ShowCase关卡(可选)
|
||||
- [ ] ShowCase关卡
|
||||
- [ ] ContentExample关卡
|
||||
- [[#文档]]
|
||||
- [x] 搭建文档系统
|
||||
- [ ] 编写文档(14天左右)
|
||||
- 后续开发计划(看乐华感不感兴趣)
|
||||
- 表情系统
|
||||
- [ ] 开发接近Live2D与MMD的表情系统与相关制作规范 (乐华未提,但后续应该花时间去开发一下)
|
||||
- [ ] 参考 https://www.youtube.com/watch?v=YGZB8-2Kazk 尤其是0:28、2:32左右。 https://www.youtube.com/watch?v=eC_Om8idXTo
|
||||
- [ ] 程序化眼睛 (乐华未提,但后续应该花时间去开发一下)
|
||||
- 定制渲染功能开发
|
||||
- [ ] 乐华那边测试完之后还需要的渲染功能。
|
||||
- 嘴唇效果提升
|
||||

|
||||

|
||||

|
||||
|
||||
## 耗费工时
|
||||
- 楼嘉杰:35~60天 => 30天 主要负责开发功能、打包引擎、编写文档、规划与制作ShowCase ContentExample关卡。
|
||||
- 华湛飞:5天=> 3天左右(并不是连续的5天),负责协助`楼嘉杰` 制作配套资产与贴图。
|
||||
|
||||
## EOE需要制作的资产
|
||||
DAWA 杭州负责把几个ShaderModel的主材质以及材质实例规范做好。同时也会选择一份有代表性的角色资产,制作贴图与材质。
|
||||
|
||||
- 模型(非必要,看透明后处理描边效果再做决定)
|
||||
- 提取角色下颚与嘴唇处的模型,用于头部外扩描边。
|
||||
- 贴图
|
||||
- 不用重新绘制的贴图
|
||||
- BaseColor
|
||||
- Metallic
|
||||
- Roughness
|
||||
- Opacity
|
||||
- Normal
|
||||
- ShadowColor(暗部颜色指定)
|
||||
- ShadowFalloff(阴影过渡控制)
|
||||
- ToonDataTexture(每个贴图4个通道,根据需求绘制,主要适用于控制角色光影交接、轮廓颜色粗细控制、轮廓id)
|
||||
- ToonDataA
|
||||
- ToonDataB
|
||||
- ToonDataC
|
||||
- 厚涂皮肤相关
|
||||
- Trickness(厚涂皮肤材质专用,使用八猴子烘焙Trickness与Curvature混合,并调整后获得)
|
||||
- 次表面颜色贴图
|
||||
- ToonAssetID贴图
|
||||
- 其他
|
||||
- 轮廓FOV自适应曲线资产
|
||||
- 轮廓距离衰减曲线资产
|
||||
- ToonAsset材质集合(一个卡通渲染用材质集,根据需要添加)
|
||||
- 全局贴图(3张预积分贴图,按照需求进行更改)
|
||||
- 材质
|
||||
|
||||
## 项目要求
|
||||
- 渲染功能需求
|
||||
- 将卡通渲染引擎升级至UE5版本。
|
||||
- 描边功能改进。包括ScreenPercentage、Fov自适应;根据投射调整宽度 ;支持PBR描边控制。
|
||||
- 提升卡通皮肤渲染效果。
|
||||
- 实现面部阴影控制功能,使得角色在各个角度的面部阴影都可以控制。
|
||||
- 卡通材质增加眼白阴影效果。
|
||||
- 提升卡通渲染头发高光效果。
|
||||
- 卡通渲染主材质移植。
|
||||
- 其他需求
|
||||
- 将UE4插件ZeroAnim、JsonAssistPlug升级至UE5版本。
|
||||
- 角色动画蓝图移植。
|
||||
|
||||
|
||||
# 竞品参考
|
||||
- 四禧丸子
|
||||
https://www.bilibili.com/video/BV1uy4y1Q7BU/?spm_id_from=333.788.recommend_more_video.5&vd_source=d47c0bb42f9c72fd7d74562185cee290
|
@ -1,82 +0,0 @@
|
||||
---
|
||||
title: EOE动画计划
|
||||
date: 2023-02-06 20:59:13
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# 修型
|
||||
1. UE5的骨骼。
|
||||
1. 主要的问题是投入/产出(效果)比。
|
||||
2. 是否于动捕软件匹配。
|
||||
3. 
|
||||

|
||||
|
||||
2. 提一嘴骨骼长度与中之人是否匹配。不太清楚 动捕的数据是否会在机器里先做一次重定向?本身UE4的重定向因为算法的局限性,对于身体比例不同的模型会有一些偏移。
|
||||
|
||||

|
||||
- 【初音未来】 「CH4NGE」 运动捕捉 【MMD】
|
||||
https://www.bilibili.com/video/BV1kL4y1A77G/?spm_id_from=333.788.video.desc.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
|
||||
|
||||
- 动捕设备外设
|
||||

|
||||
https://www.bilibili.com/video/BV1Y7411q7m3/?spm_id_from=333.337.search-card.all.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
|
||||
|
||||
# 修型
|
||||
1. 尝试使用曲线来修型。
|
||||
2. 使用PostProcess_AnimBP进行后处理修型。
|
||||
1. 将之前的逻辑移植到后处理动画蓝图中。
|
||||
|
||||
# 保持角色基础结构相同的原因
|
||||
1. 动画资产可以复用。
|
||||
2. 只有一个负责逻辑的主动画蓝图,方便管理。
|
||||
3. 可以使用主动画蓝图设计逻辑,子动画蓝图调节不同参数的功能。
|
||||
4. 对于后续其他衍生品生产会比较友好(手机AR=>Hololive app,ASoul VR演唱会,以及其他延伸游戏)
|
||||
|
||||
## 共享骨架
|
||||
骨架资产的一个重要特性是单个的骨架资产可以由多个骨骼网格体使用,只要其需要拥有相同的整体rig层级。这意味着骨骼命名和骨骼的层级排序必须一致,才能够正确地共享。
|
||||
举个例子,一个骨骼网格体中的一个肢拥有3块骨骼,分别命名为 **1**、**2** 和 **3**:
|
||||
|
||||

|
||||
如果有另一个需要使用相同骨架资源的骨架网格体,则需要保证这些骨骼的命名和排序相同。 然而第二个骨骼网格体可以添加额外或者层级外部的骨骼。如果接收到的动画数据是用于骨骼网格体之外的骨骼,那么该数据会被忽略。
|
||||
|
||||
在这种情况下,你的新层级应该如下所示。在这里,第二个骨骼网格体有着额外的骨骼,但是并没有改变第一个骨架的层级结构,也没有造成冲突。
|
||||
|
||||

|
||||
然而,为使两个骨架网格体使用相同的骨架资源,无法对层级进行重新排序,也无法重命名骨骼。如果第二个骨骼网格体要使用不同的骨骼层级和命名结构,那么需要重新创建一个新的骨骼资产。
|
||||
|
||||

|
||||
如果你在不改变顺序的情况下插入一个骨骼,那么能够正常共享。然而大部分情况下,额外的骨骼可能会导致骨架产生意料之外的变形偏移。我们建议尽量避免这样做。
|
||||

|
||||
结合这些共享规则,再虚幻引擎中有几种方式来在骨骼网格体之间共享骨架。以下是一些细节。
|
||||
|
||||
### 导入期间合并
|
||||
第一种共享骨架的方式是在FBX导入过程期间进行的。导入你的新骨骼网格体时,(包含额外的和外部的骨骼,遵循上述的共享规则),你可以从项目中已有的骨骼网格体中选择一个骨架。虚幻引擎将会将这些骨架合并,并且将全部新骨骼添加到层级中。除此以外,你的骨架的比例会由创建它的原始骨骼网格体来定义。
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
## IK Rig 与Fullbody IK
|
||||
[[实时重定向与骨骼动画共用相关#IK Rig In Animation Blueprint]]
|
||||
|
||||
- 解决高跟鞋与平底鞋转换的问题。(应该可以)
|
||||
- 可以解决动捕中一些穿帮问题。
|
||||

|
||||

|
||||
|
||||
嘉然爬椅子
|
||||
https://www.bilibili.com/video/av507701005/?vd_source=d47c0bb42f9c72fd7d74562185cee290
|
||||
|
||||
但更多的是一些用于在交互游戏中比较有用,比如让中之人去按按钮,使用这个技术可以保证伸手按的地方是对得上的。
|
||||
|
||||
## 基础结构相同
|
||||
Skeleton Compatibility,官方翻译为**可兼容的骨架**,不同骨骼之间共享动画的功能。
|
||||
https://docs.unrealengine.com/5.0/en-US/skeletons-in-unreal-engine/#compatibleskeletons
|
||||
|
||||
[[实时重定向与骨骼动画共用相关#UE5实时重定向]]
|
||||
|
||||
## 其他
|
||||
- Motion Wraping
|
||||
- 机器学习 实现肌肉效果
|
@ -1,445 +0,0 @@
|
||||
# 实用库
|
||||
`#include "/Engine/Private/Random.ush"`
|
||||
`#include "/Engine/Private/SobolRandom.ush"`
|
||||
|
||||
# 眼睛
|
||||
眼睛焦散:
|
||||
|
||||
## 凹模型
|
||||
直接贴图即可。
|
||||
折射
|
||||
模型高光
|
||||
|
||||
## 凸模型
|
||||
1. 使用视差贴图来控制瞳孔效果 + 折射模拟
|
||||
```
|
||||
float2 viewL = mul(viewW, (float3x2) worldInverse);
|
||||
float2 offset = height * viewL;
|
||||
offset.y = -offset.y;
|
||||
texcoord -= parallaxScale * offset;
|
||||
```
|
||||

|
||||
|
||||
2. Physically based refraction
|
||||
```c++
|
||||
// 角膜区域突起的模型
|
||||
// Alternatively, use a displacement map
|
||||
// height = max(-positionL.z – eyeIrisDepth, 0.0);
|
||||
|
||||
// 球形模型
|
||||
// Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
|
||||
height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius );
|
||||
|
||||
// refractedW
|
||||
float w = n * dot( normalW, viewW );
|
||||
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
|
||||
float3 refractedW = ( w - k ) * normalW - n * viewW;
|
||||
|
||||
float cosAlpha = dot(frontNormalW, -refractedW);
|
||||
float dist = height / cosAlpha;
|
||||
float3 offsetW = dist * refractedW;
|
||||
float2 offsetL = mul(offsetW, (float3x2) worldInverse);
|
||||
texcoord += float2(mask, -mask) * offsetL;
|
||||
```
|
||||

|
||||
|
||||
代码中首先计算了height,即前房的高度,PPT中height有两种计算方式,分别对应两种眼睛的模型结构,对应的结构写在注释中了。
|
||||
然后计算了refracted,这个是rtr中快速拟合的计算方法,n是空气与介质折射率的比值,关于refracted的推论可以参考:
|
||||
YivanLee:虚幻4渲染编程(人物篇)【第三卷:Human Eye Rendering】
|
||||
[129 赞同 · 12 评论文章](https://zhuanlan.zhihu.com/p/151786832)
|
||||
|
||||
最后一段,先通过frontNormalW与refractedW的点积计算出α角的cos值(上图中的α应该是标识错误,α是-refractedW与frontNormalW的夹角)。然后已知height,通过比值可以计算出refractedW的模长dist。offsetW即为完整的refractedW向量。最后转换到本地空间,乘上眼睛的Mask,加到原本的UV上。
|
||||
之后就是使用偏转后的UV去采样贴图了。
|
||||
与视差相同,这里也是在本地与世界空间中进行的计算,同样会有轴向问题,主要是normalW、viewW和frontNormalW参与的计算,normal与view可以转换到切线空间计算,而frontNormalW代表的是模型向前的朝向,这个必须要指定,不过图方便的话,把frontNormalW改成切线空间法线也不是不可以。
|
||||
|
||||
### 多层复合模型
|
||||
樱花大战cedec2020分享:https://blog.ch-wind.com/cedec2020-new-sakura-wars-note/
|
||||
|
||||
>本作的眼睛分为三个部分,眼白的部分是一个内凹的形状,瞳孔的部分则分为了向内凹的部分和向外突出的部分。
|
||||

|
||||
|
||||
>瞳孔的高光叠加在其突出的半透明部分上。根据摄像角度的不同,各个部分的贴图会分开进行移动,使得在哪个角度高光都能处在一个刚好的位置。
|
||||
控制上,有针对高光上下左右的移动强度与控制移动范围的参数共同作用。
|
||||
|
||||

|
||||
>从左边开始,是作为基础颜色的Albedo,以及用于Mask瞳孔的Alpha贴图,用于在Albedo上进行叠加的spt贴图,以及两张瞳孔高光,以及反应环境的matcapture贴图。
|
||||
虽然很多动画风格的渲染中会省略掉瞳孔中的虹彩部分,但是本作为了提高角色靠近时的效果,进行了详细的绘制,同时为了体现环境的变化与matcap的贴图进行叠加。
|
||||
高光贴图有两张,分别使用不同的UV动画进行控制,用于表现眼睛的湿润感。虽然是很细微的操作,但是对于表现角色的感情非常的有用。
|
||||
|
||||

|
||||
SunnySideUp UnityChan
|
||||
|
||||
### 其他效果实现
|
||||
#### 眼睛高光效果
|
||||
1. 贴图高光。使用事先绘制的高光形状贴图,贴到最外面的。并且使用ViewDirection来控制。设定4个UV Coord, 根据 View=》眼睛的本地坐标系=》Normalize后的向量进行插值。
|
||||
2. ~~PBR的思路~~
|
||||
|
||||
#### Matcap反射效果
|
||||
Matcap材质+球形法线贴图
|
||||
```c++
|
||||
float3 NormalBlend_MatcapUV_Detail = viewNormal.rgb * float3(-1,-1,1);
|
||||
float3 NormalBlend_MatcapUV_Base = (mul( UNITY_MATRIX_V, float4(viewDirection,0)).rgb*float3(-1,-1,1)) + float3(0,0,1);
|
||||
float3 noSknewViewNormal = NormalBlend_MatcapUV_Base * dot(NormalBlend_MatcapUV_Base, NormalBlend_MatcapUV_Detail) / NormalBlend_MatcapUV_Base.b - NormalBlend_MatcapUV_Detail;
|
||||
float2 ViewNormalAsMatCapUV = noSknewViewNormal.rg * 0.5 + 0.5;
|
||||
```
|
||||
|
||||
#### 焦散效果
|
||||
>焦散的表现反倒简单了,直接画在眼睛贴图上都可以,考虑到卡通表达的自由性,焦散是否存在与焦散的形状都可以没有限制,只要好看就行。
|
||||
下图也是miHoYo的分享,可以简单的理解为直接贴张Mask上去,然后用光照方向和菲涅尔去影响强度变化。
|
||||

|
||||
|
||||
使用Mask贴图、NoL与菲尼尔来控制
|
||||
|
||||
# 其他游戏方案
|
||||
* [蓝色协议](/document/UrealEngineNPR渲染实现/蓝色协议的方案.html)
|
||||
|
||||
# 大致方案
|
||||
- 主要分为在材质编辑器中完成大部分渲染
|
||||
- 传递少量参数到Lighting阶段并进行光照计算
|
||||
- 以上两者结合
|
||||
|
||||
## 待实现功能
|
||||
- [ ] 顶点色控制Outline宽度,使用顶点色G。
|
||||
- [ ] 使用[罪恶装备中的lightmap](#lightmap) 控制高光的区域和阈值,包括顶点色可以精准控制高光的强度。
|
||||
|
||||
## 具体实现
|
||||
### 全局设置实现
|
||||
AWorldSettings存储于ULevel中。可以通过Project Settings->Engine->General Settings->Default Classes来修改成其他的类作为默认类。
|
||||
```c#
|
||||
[/Script/Engine.Engine]
|
||||
WorldSettingsClassName=/Script/MySpiffyGame.MySpiffyGameWorldSettings
|
||||
```
|
||||
C++中修改Settings类:`void ULevel::SetWorldSettings(AWorldSettings* NewWorldSettings)`或许可以考虑子系统。
|
||||
|
||||
### Shader与管线变量添加
|
||||
```c#
|
||||
shadowAttenuation = mainLight.shadowAttenuation;
|
||||
|
||||
float _SystemShadowsLevel_var = (shadowAttenuation*0.5)+0.5+_Tweak_SystemShadowsLevel > 0.001 ? (shadowAttenuation*0.5)+0.5+_Tweak_SystemShadowsLevel : 0.0001;
|
||||
float Set_FinalShadowMask = saturate((1.0 + ( (lerp( _HalfLambert_var, _HalfLambert_var*saturate(_SystemShadowsLevel_var), _Set_SystemShadowsToBase ) - (_BaseColor_Step-_BaseShade_Feather)) * ((1.0 - _Set_1st_ShadePosition_var.rgb).r - 1.0) ) / (_BaseColor_Step - (_BaseColor_Step-_BaseShade_Feather))));
|
||||
```
|
||||
|
||||
Ramp相关变量
|
||||
- shadowAttenuation 默认为1(方向光没有这个参数)与 Tweak_SystemShadowsLevel,先暂时合并为HalfLambertFix
|
||||
- Step
|
||||
- Feather
|
||||
|
||||
|
||||
Shader开关变量
|
||||
- Is_LightColor_BaseColor:BaseColor是否受到LightColor影响
|
||||
- Is_LightColor_ShadeColor:1st_ShadeColor是否受到LightColor影响
|
||||
|
||||
FViewUniformShaderParameter添加数据
|
||||
-
|
||||
### 顶点色
|
||||
用于存储一些低精度数据,插值即可
|
||||
- R:
|
||||
- G:描边宽度
|
||||
- B:
|
||||
|
||||
蓝色协议的R:阴影区域标记 与 B:Ao,而罪恶装备使用贴图来传递信息。
|
||||
|
||||
### lightmap
|
||||
,G为阴影控(AO),R为高光强度参数,金属和光滑材质的部分设置的更大一些。B通道:用于照明控制。最大值为高光,反之,值越小高光越淡。
|
||||

|
||||
|
||||
https://zhuanlan.zhihu.com/p/360229590一文中介绍了崩坏3与原神的计算方式
|
||||
|
||||
崩坏3的LightMap计算方式:
|
||||
```c++
|
||||
half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv.xy);
|
||||
half4 LightMapColor = SAMPLE_TEXTURE2D(_LightMap, sampler_LightMap, input.uv.xy);
|
||||
half3 ShadowColor = baseColor.rgb * _ShadowMultColor.rgb;
|
||||
half3 DarkShadowColor = baseColor.rgb * _DarkShadowMultColor.rgb;
|
||||
|
||||
//如果SFactor = 0,ShallowShadowColor为一级阴影色,否则为BaseColor。
|
||||
float SWeight = (LightMapColor.g * input.color.r + input.lambert) * 0.5 + 1.125;
|
||||
float SFactor = floor(SWeight - _ShadowArea);
|
||||
half3 ShallowShadowColor = SFactor * baseColor.rgb + (1 - SFactor) * ShadowColor.rgb;
|
||||
```
|
||||
|
||||
二级阴影计算:
|
||||
```c++
|
||||
//如果SFactor = 0,DarkShadowColor为二级阴影色,否则为一级阴影色。
|
||||
SFactor = floor(SWeight - _DarkShadowArea);
|
||||
DarkShadowColor = SFactor * (_FixDarkShadow * ShadowColor + (1 - _FixDarkShadow) * ShallowShadowColor) + (1 - SFactor) * DarkShadowColor;
|
||||
|
||||
// 平滑阴影边缘
|
||||
half rampS = smoothstep(0, _ShadowSmooth, input.lambert - _ShadowArea);
|
||||
half rampDS = smoothstep(0, _DarkShadowSmooth, input.lambert - _DarkShadowArea);
|
||||
ShallowShadowColor.rgb = lerp(ShadowColor, baseColor.rgb, rampS);
|
||||
DarkShadowColor.rgb = lerp(DarkShadowColor.rgb, ShadowColor, rampDS);
|
||||
|
||||
//如果SFactor = 0,FinalColor为二级阴影,否则为一级阴影。
|
||||
SFactor = floor(LightMapColor.g * input.color.r + 0.9f);
|
||||
half4 FinalColor;
|
||||
FinalColor.rgb = SFactor * ShallowShadowColor + (1 - SFactor) * DarkShadowColor;
|
||||
```
|
||||
|
||||
|
||||
**罪恶装备**:
|
||||
对阴影判断阈值的偏移。(见前面着色部分,顶点AO+手绘修正)
|
||||
G : 轮廓线根据与相机的距离扩大多少的系数
|
||||
B : 等高线 Z 轴偏移值
|
||||
A : 轮廓厚度系数。0.5为标准,1为最大厚度,0为无等高线
|
||||
|
||||
### 描边
|
||||
**蓝色协议**:
|
||||
1. 使用Sobel过滤器进行深度检测描边。
|
||||
2. 使用Sobel过滤器进行Id图检测描边。
|
||||
3. 使用Sobel过滤器进行Normal检测描边。用于处理一些难以分ID,深度差又很小的地方,通过获取周围点法线求点乘的方式判断出轮廓。![[08-Assets/Images/ImageBag/UrealEngineNPR/蓝色协议_Normal检测描边.png)
|
||||
4. 预先画好的轮廓(GBuffer)。
|
||||
|
||||
所以使用需要 OutlineId、OutlineWidth(感觉可以传递一个全局Outline信息贴图再通过ID查表来获取,但只能在角色较少时使用)、OutlinePaint 、OutlineZShift(个人感觉不需要)
|
||||
|
||||
### 边缘光
|
||||
|
||||
### RimLighting
|
||||
|
||||
### 接触阴影
|
||||
|
||||
### 面部阴影
|
||||
使用Face ShaderModel,修改法线。描边使用ObjectPivot 缩放进行扩边。
|
||||
|
||||
### 后处理
|
||||
FSceneView存储FFinalPostProcessSettings FinalPostProcessSettings,场景的后处理信息。
|
||||
FBloomOutputs AddBloomPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FBloomInputs& Inputs)
|
||||
|
||||
# GBuffer
|
||||
```c#
|
||||
OutGBufferA = WorldNormal/PerObjectGBufferData
|
||||
OutGBufferB = Metallic/Specular/Roughness/EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask);
|
||||
OutGBufferC = BaseColor/GBufferAO
|
||||
OutGBufferD = GBuffer.CustomData;
|
||||
OutGBufferE = GBuffer.PrecomputedShadowFactors;
|
||||
```
|
||||
```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
|
||||
```
|
||||
|
||||
```
|
||||
| GBuffer | 表头 |
|
||||
| -------- | ------------------------------------------------------------------------------------- |
|
||||
| GBufferB | OffsetShadowMask SpcularMask SpecularValue EncodeShadingModelIdAndSelectiveOutputMask |
|
||||
| GBufferD | ShaderColor.rgb NoL |
|
||||
| GBufferE | |
|
||||
| GBufferF | ID |
|
||||
```
|
||||
|
||||
## BaseColor与ShadowColor
|
||||
- 原神里ShadowColor还会接收其他物体的阴影投射,没有自投影;蓝色协议可能也没有自投影。
|
||||
|
||||
BaseColor与ShadowColor的过渡需要Step、Feather、Offset等参数,可以直接制作一个HalfLambert的渐变贴图之后使用View传递。因为有多个贴图所以还需要ID贴图指定。但这样需要考虑一个问题:
|
||||
|
||||
- 一个物体上的同一个ID区域的BaseColor与ShadowColor是否都是一样的
|
||||
- 如果不一样就需要再传递一个ShadowColor.rgb到GBuffer里。
|
||||
- 不管如何手绘的补充暗部也是需要加到GBuffer中的
|
||||
|
||||
这决定传递到View里面的渐变贴图是彩色还是暗色
|
||||
|
||||
### 预计算贴图方案(构想)
|
||||
Toon渲染一般会使用HalfLambda。之后使用Feather、Step等参数对过渡边界进行调整
|
||||
使用 渐变贴图查表来实现 渐变、二阶化。以此代替羽化、step等参数。
|
||||
使用ID贴图指定,或者通过BaseColor值来查询?
|
||||
|
||||
## 高光
|
||||
- PBR高光(使用Roughness控制是否可行?是否需要传入GBuffer一个Mask贴图)
|
||||
- 自定义高光:高光贴图、高光颜色、参数化高光形状、多层高光
|
||||
|
||||
## 描边
|
||||
- 原神的描边好像是后处理
|
||||
- 蓝色协议
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/原神_描边.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/原神截图_描边.png]]
|
||||
|
||||
TODO:考虑使用顶点色来控制宽度,使用顶点色G
|
||||
|
||||
## 多光源
|
||||
主方向光:提供照明与Shadow,其他光只提供照亮效果。
|
||||
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/原神截图_光照.png]]
|
||||
|
||||
## 有关眉毛、表情需要使用 模板功能
|
||||
UTS使用模板
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情_StencilOut.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情_StencilMask.png]]
|
||||
|
||||
```
|
||||
Stencil
|
||||
{
|
||||
Ref[_StencilNo] //设置渲染的模板缓存值,0~255
|
||||
Comp[_StencilComp] //模板测试的通过条件,有除了equal,还有Greater、Less、Always、Never等,类似ZTest。
|
||||
Pass[_StencilOpPass] //表示通过模板测试和Z测试(注意是都通过)的像素,怎么处置它的模板值。
|
||||
Fail[_StencilOpFail] //表示通过了模板测试但没通过Z测试的像素,怎么处置它的模板值。
|
||||
}
|
||||
|
||||
_UTS_StencilMode mode = (_UTS_StencilMode)(material.GetInt(ShaderPropStencilMode));
|
||||
switch (mode)
|
||||
{
|
||||
case _UTS_StencilMode.Off:
|
||||
// material.SetInt(ShaderPropStencilNo,0);
|
||||
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.Disabled);
|
||||
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Keep);
|
||||
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Keep);
|
||||
break;
|
||||
case _UTS_StencilMode.StencilMask:
|
||||
// material.SetInt(ShaderPropStencilNo,0);
|
||||
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.Always);
|
||||
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Replace);
|
||||
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Replace);
|
||||
break;
|
||||
case _UTS_StencilMode.StencilOut:
|
||||
// material.SetInt(ShaderPropStencilNo,0);
|
||||
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.NotEqual);
|
||||
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Keep);
|
||||
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Keep);
|
||||
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
七大罪中使用使用了深度测试 Greater Equal.(默认是 less Equal)。但这个方式可能在UE4里不太行,因为UE4的深度测试是全局的?
|
||||
|
||||
这个应该需要创建一个MeshProcessor来实现:
|
||||
```c#
|
||||
FSingleLayerWaterPassMeshProcessor::FSingleLayerWaterPassMeshProcessor(const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, const FMeshPassProcessorRenderState& InPassDrawRenderState, FMeshPassDrawListContext* InDrawListContext)
|
||||
: FMeshPassProcessor(Scene, Scene->GetFeatureLevel(), InViewIfDynamicMeshCommand, InDrawListContext)
|
||||
, PassDrawRenderState(InPassDrawRenderState)
|
||||
{
|
||||
if (SingleLayerWaterUsesSimpleShading(Scene->GetShaderPlatform()))
|
||||
{
|
||||
// Force non opaque, pre multiplied alpha, transparent blend mode because water is going to be blended against scene color (no distortion from texture scene color).
|
||||
FRHIBlendState* ForwardSimpleWaterBlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha>::GetRHI();
|
||||
PassDrawRenderState.SetBlendState(ForwardSimpleWaterBlendState);
|
||||
}
|
||||
}
|
||||
|
||||
//默认是CF_DepthNearOrEqual,做这个效果可能就要使用CF_DepthFartherOrEqual
|
||||
void SetupBasePassState(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, const bool bShaderComplexity, FMeshPassProcessorRenderState& DrawRenderState)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilAccess(BasePassDepthStencilAccess);
|
||||
if (bShaderComplexity)
|
||||
{
|
||||
// Additive blending when shader complexity viewmode is enabled.
|
||||
DrawRenderState.SetBlendState(TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_Zero, BF_One>::GetRHI());
|
||||
// Disable depth writes as we have a full depth prepass.
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI());
|
||||
}
|
||||
}
|
||||
```
|
||||
设置位置在于FXXXPassMeshProcessor::Process()中的SetDepthStencilStateForBasePass()中。需要bMaskedInEarlyPass为false,GetDepthStencilAccess为DepthRead。
|
||||
```c#
|
||||
const bool bMaskedInEarlyPass = (MaterialResource.IsMasked() || Mesh.bDitheredLODTransition) && MaskedInEarlyPass(GShaderPlatformForFeatureLevel[FeatureLevel]);
|
||||
|
||||
if (bEnableReceiveDecalOutput)
|
||||
{
|
||||
// Set stencil value for this draw call
|
||||
// This is effectively extending the GBuffer using the stencil bits
|
||||
const uint8 StencilValue = GET_STENCIL_BIT_MASK(RECEIVE_DECAL, PrimitiveSceneProxy ? !!PrimitiveSceneProxy->ReceivesDecals() : 0x00)
|
||||
| STENCIL_LIGHTING_CHANNELS_MASK(PrimitiveSceneProxy ? PrimitiveSceneProxy->GetLightingChannelStencilValue() : 0x00);
|
||||
|
||||
if (bMaskedInEarlyPass)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<
|
||||
false, CF_Equal,
|
||||
true, CF_Always, SO_Keep, SO_Keep, SO_Replace,
|
||||
false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
|
||||
0xFF, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)
|
||||
>::GetRHI());
|
||||
DrawRenderState.SetStencilRef(StencilValue);
|
||||
}
|
||||
else if (DrawRenderState.GetDepthStencilAccess() & FExclusiveDepthStencil::DepthWrite)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<
|
||||
true, CF_GreaterEqual,
|
||||
true, CF_Always, SO_Keep, SO_Keep, SO_Replace,
|
||||
false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
|
||||
0xFF, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)
|
||||
>::GetRHI());
|
||||
DrawRenderState.SetStencilRef(StencilValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<
|
||||
false, CF_GreaterEqual,
|
||||
true, CF_Always, SO_Keep, SO_Keep, SO_Replace,
|
||||
false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
|
||||
0xFF, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)
|
||||
>::GetRHI());
|
||||
DrawRenderState.SetStencilRef(StencilValue);
|
||||
}
|
||||
}
|
||||
else if (bMaskedInEarlyPass)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Equal>::GetRHI());
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# toonshading技术拆解
|
||||
{% simplemindmap %}
|
||||
```
|
||||
- toonshading技术拆解
|
||||
- 描边
|
||||
- 外描边:后处理描边 、 Mesh挤出 2种方式,原神采用了Mesh挤出。
|
||||
- 内描边:后处理、SDF描边、本村线 3种方式
|
||||
- 使用Mesh基础、后处理、模型绘制。(后处理传递Id贴图)
|
||||
- 分阶着色
|
||||
- 二阶化:
|
||||
- 多阶化:
|
||||
- 自定义次表面(暗部)颜色
|
||||
- 颜色过渡(羽化):
|
||||
- lut:待讨论
|
||||
- 高光
|
||||
- 高光贴图
|
||||
- 高光颜色
|
||||
- 参数化高光形状
|
||||
- 多层高光
|
||||
- 阴影
|
||||
- 自定义阴影颜色
|
||||
- 自定义阴影范围
|
||||
- 自定义阴影形状
|
||||
- 阴影过渡(sdf阴影)
|
||||
- 边缘光
|
||||
- 边缘光是否受到光照影响
|
||||
- 多层边缘光
|
||||
- 边缘光衰减(入射角度更明显 or 背光角度更明显)
|
||||
- 多光源支持
|
||||
- PBR支持
|
||||
- PBR和NPR混合
|
||||
- AO
|
||||
- matcap支持
|
||||
- 根据相机角度调整模型(非必要)
|
||||
- Shader内编辑法线
|
||||
- N=_scale * L + N
|
||||
- 眼睛
|
||||
- 反射 环境反射或者matcap支持
|
||||
- 内阴影 AO实现或者画死的内阴影
|
||||
- 瞳孔 瞳孔缩放
|
||||
- 视差和效果 凹凸效果
|
||||
- 高光 自定义高光形状&位置
|
||||
- 高光流动效果
|
||||
- 头发
|
||||
- 各项异性头发
|
||||
- 高光扰动
|
||||
- 高光贴图
|
||||
- 自定义高光属性
|
||||
- 高光天使环
|
||||
- 无各项异性头发
|
||||
- 自定义高光参数
|
||||
- 高光贴图
|
||||
- 高光天使环
|
||||
- 特殊效果
|
||||
- 眉毛/睫毛不受遮挡
|
||||
- 自发光
|
||||
- 阴影内素描效果
|
||||
- 额外效果
|
||||
- 后处理 辉光效果
|
||||
- 之后提到了SunFlare,应该是那个屏幕后处理效果。卡通渲染很依赖体积光,所以自然会有好的效果,假也没关系,假才是对的。谷歌搜SNN Filter https://www.shadertoy.com/view/MlyfWd
|
||||
- 旁边的Kuwahara应该是个类似的算法,64采样。
|
||||
```
|
||||
{% endsimplemindmap %}
|
159
03-UnrealEngine/卡通渲染相关资料/卡通渲染开发总览.md
Normal file
159
03-UnrealEngine/卡通渲染相关资料/卡通渲染开发总览.md
Normal file
@ -0,0 +1,159 @@
|
||||
---
|
||||
title: 卡通渲染开发总览
|
||||
date: 2023-12-08 09:59:57
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 目录
|
||||
- [[卡通渲染相关资料]]里面的有原神、碧蓝幻想、罪恶装备、赛马娘、黑丝指的参考。
|
||||
- 做完基础功能之后,再逐个寻找新的功能点与方向。
|
||||
- [[知乎FlashYiYi的卡通渲染分享]]里面有许多Fake技巧
|
||||
- 参考对象分析
|
||||
- 星穹铁道
|
||||
- 破晓传说
|
||||
- 蓝色协议
|
||||
- 非人学园2
|
||||
- 少女前线2 追放
|
||||
- 明日方舟终末地
|
||||
|
||||
>PS. 先实现一波Cel,之后再实现Thickly - Coated。
|
||||
|
||||
# 最近开发计划
|
||||
1. 移植一波JasonMa的ShaderModel。
|
||||
1. https://zhuanlan.zhihu.com/p/658700282
|
||||
2. https://zhuanlan.zhihu.com/p/597568404
|
||||
2. 添加Debug View https://zhuanlan.zhihu.com/p/668782106
|
||||
3. [_UE5_ Shader Print系统](https://zhuanlan.zhihu.com/p/637929634)
|
||||
4. GBufferView实现。
|
||||
5. 实现一波Anti-Lut。
|
||||
6. Toon曝光偏移、模仿李兄的暗处的ShadowColor适配。
|
||||
|
||||
# 卡通渲染引擎功能总览(按照渲染顺序进行排序)
|
||||
- GBuffer For ToonShaderModel
|
||||
- [[GBuffer&Material&BasePass]]
|
||||
- ShaderModel
|
||||
- Cel
|
||||
- Thickly - Coated
|
||||
- 描边
|
||||
- [[描边]]
|
||||
- ![[描边#实现功能]]
|
||||
- 阴影
|
||||
-
|
||||
- 边缘光
|
||||
- 其他特性
|
||||
- [[Toon眼睛渲染]]
|
||||
- [[Toon眉毛渲染]]
|
||||
|
||||
## 待实现功能
|
||||
知乎提到的渲染功能:
|
||||
- 发尖勾线 https://zhuanlan.zhihu.com/p/405518306
|
||||
- SDF 描边
|
||||
- https://zhuanlan.zhihu.com/p/113190695
|
||||
- https://zhuanlan.zhihu.com/p/360229590
|
||||
- 屏幕空间深度边缘光 Screen Space Depth Rimlight
|
||||
- https://zhuanlan.zhihu.com/p/139290492
|
||||
- 原神实现
|
||||
- https://zhuanlan.zhihu.com/p/435005339
|
||||
- 遮挡时的模糊网点效果
|
||||
- https://zhuanlan.zhihu.com/p/370140711
|
||||

|
||||
|
||||

|
||||
|
||||
按照需求优先级进行排列:
|
||||
- [ ] Anit-Lut功能
|
||||
- [ ] Anit-ToneMapping
|
||||
- [ ] 完整的Anit-Lut功能(虚拟拍摄也会用到)
|
||||
- [ ] 描边改进
|
||||
- [ ] 后处理部分,使用蓝色协议的方案
|
||||
- [ ] MultiDraw BackFace部分
|
||||
- [ ] 绘制轮廓方法落地:本村线、SDF
|
||||
- [ ] 使用添加MeshDrawPass的方式实现比较完美的BackFace
|
||||
- [ ] ToonData与ToonWorldSettings改进
|
||||
- [ ] 增加多个SSS 预积分贴图功能
|
||||
- [ ] 将材质以外的参数都集中到ToonWorldSettings类中,以方便调节
|
||||
- [ ] 面部光影方案改进
|
||||
- [ ] 在GBuffer中添加额外面部自定义法线
|
||||
- [ ] SDF面光方案
|
||||
- [ ] 角色面部Anit-Perspective 与 手指放大效果
|
||||
- [ ] 模仿VRM4U的参数
|
||||
- [ ] 参考AnimMaker
|
||||
- [ ] https://www.patreon.com/posts/56089741
|
||||
- [ ] 后处理
|
||||
- [ ] 原神 辉光效果
|
||||
- [ ] TAA抗锯齿 => Responsive AA,解决Outline模糊问题
|
||||
- [ ] 影视级Bloom实现
|
||||
- [ ] 眉毛效果
|
||||
- [ ] 可以考虑使用TranslucencySortPriority来解决
|
||||
- [ ] 通过MeshDrawPass实现眉毛最前显示效果
|
||||
- [ ] 实现眉毛描边效果
|
||||
- [ ] 额发效果
|
||||
- [ ] 额发阴影效果(衣服阴影效果)
|
||||
- [ ] 实现半透明额发
|
||||
- [ ] 天光与间接光处理
|
||||
- [ ] 为了防止环境光把角色照出立体感,所以计算环境光时,会把法线全部看作世界空间上方向来处理。同时增加了一些参数可以进行一些定制化调整。
|
||||
- [ ] 使用顶点色与第二套UV来修改一些可以实时修改的效果
|
||||
- [ ] 实现PBR <=> Cel 卡通渲染效果的参数切换
|
||||
- [ ] 使用Kawaii插件实现柔体效果
|
||||
- [ ] 后处理边缘光落地
|
||||
- [ ] SSGI 卡通渲染适配?实现AnimMaker 中的一个效果
|
||||
|
||||
- [ ] 丝袜 https://zhuanlan.zhihu.com/p/636157482
|
||||
|
||||
[[厚涂风格研究与开发笔记]]
|
||||
|
||||
- ShaderModel
|
||||
- 分阶着色
|
||||
- 二阶化:
|
||||
- 多阶化:
|
||||
- 自定义次表面(暗部)颜色
|
||||
- 颜色过渡(羽化):
|
||||
- lut:待讨论
|
||||
- 高光
|
||||
- 高光贴图
|
||||
- 高光颜色
|
||||
- 参数化高光形状
|
||||
- 多层高光
|
||||
- Shader内编辑法线
|
||||
- N=_scale * L + N
|
||||
- 描边
|
||||
- 外描边:后处理描边 、 Mesh挤出 2种方式,原神采用了Mesh挤出。
|
||||
- 内描边:后处理、SDF描边、本村线 3种方式
|
||||
- 使用Mesh基础、后处理、模型绘制。(后处理传递Id贴图)
|
||||
- 阴影
|
||||
- 自定义阴影颜色
|
||||
- 自定义阴影范围
|
||||
- 自定义阴影形状
|
||||
- 阴影过渡(sdf阴影)
|
||||
- 边缘光
|
||||
- 边缘光是否受到光照影响
|
||||
- 多层边缘光
|
||||
- 边缘光衰减(入射角度更明显 or 背光角度更明显)
|
||||
- 多光源支持
|
||||
- 根据相机角度调整模型(非必要)
|
||||
- 眼睛
|
||||
- 反射 环境反射或者matcap支持
|
||||
- 内阴影 AO实现或者画死的内阴影
|
||||
- 瞳孔 瞳孔缩放
|
||||
- 视差和效果 凹凸效果
|
||||
- 高光 自定义高光形状&位置
|
||||
- 高光流动效果
|
||||
- 头发
|
||||
- 各项异性头发
|
||||
- 高光扰动
|
||||
- 高光贴图
|
||||
- 自定义高光属性
|
||||
- 高光天使环
|
||||
- 无各项异性头发
|
||||
- 自定义高光参数
|
||||
- 高光贴图
|
||||
- 高光天使环
|
||||
- 特殊效果
|
||||
- 眉毛/睫毛不受遮挡
|
||||
- 自发光
|
||||
- 阴影内素描效果
|
||||
- 后处理效果
|
||||
- 后处理 辉光效果
|
||||
- 之后提到了SunFlare,应该是那个屏幕后处理效果。卡通渲染很依赖体积光,所以自然会有好的效果,假也没关系,假才是对的。谷歌搜SNN Filter https://www.shadertoy.com/view/MlyfWd
|
||||
- 旁边的Kuwahara应该是个类似的算法,64采样。
|
@ -1,154 +0,0 @@
|
||||
# 功能表
|
||||
厚涂部分写在:[[厚涂风格研究与开发笔记]]
|
||||
|
||||
|
||||
#todo 保存UE资产代码
|
||||
```c++
|
||||
if (const auto Package = Outer->GetPackage())
|
||||
{
|
||||
Package->SetDirtyFlag(true);
|
||||
|
||||
UEditorLoadingAndSavingUtils::SavePackages({Package}, true);
|
||||
}
|
||||
```
|
||||
|
||||
知乎提到的渲染功能:
|
||||
- 发尖勾线 https://zhuanlan.zhihu.com/p/405518306
|
||||
- SDF 描边
|
||||
- https://zhuanlan.zhihu.com/p/113190695
|
||||
- https://zhuanlan.zhihu.com/p/360229590
|
||||
- 屏幕空间深度边缘光 Screen Space Depth Rimlight
|
||||
- https://zhuanlan.zhihu.com/p/139290492
|
||||
- 原神实现
|
||||
- https://zhuanlan.zhihu.com/p/435005339
|
||||
- 遮挡时的模糊网点效果
|
||||
- https://zhuanlan.zhihu.com/p/370140711
|
||||

|
||||
|
||||

|
||||
|
||||
按照需求优先级进行排列:
|
||||
- [ ] Anit-Lut功能
|
||||
- [ ] Anit-ToneMapping
|
||||
- [ ] 完整的Anit-Lut功能(虚拟拍摄也会用到)
|
||||
- [ ] 描边改进
|
||||
- [ ] 后处理部分,使用蓝色协议的方案
|
||||
- [ ] MultiDraw BackFace部分
|
||||
- [ ] 绘制轮廓方法落地:本村线、SDF
|
||||
- [ ] 使用添加MeshDrawPass的方式实现比较完美的BackFace
|
||||
- [ ] ToonData与ToonWorldSettings改进
|
||||
- [ ] 增加多个SSS 预积分贴图功能
|
||||
- [ ] 将材质以外的参数都集中到ToonWorldSettings类中,以方便调节
|
||||
- [ ] 面部光影方案改进
|
||||
- [ ] 在GBuffer中添加额外面部自定义法线
|
||||
- [ ] SDF面光方案
|
||||
- [ ] 角色面部Anit-Perspective 与 手指放大效果
|
||||
- [ ] 模仿VRM4U的参数
|
||||
- [ ] 参考AnimMaker
|
||||
- [ ] https://www.patreon.com/posts/56089741
|
||||
- [ ] 后处理
|
||||
- [ ] 原神 辉光效果
|
||||
- [ ] TAA抗锯齿 => Responsive AA,解决Outline模糊问题
|
||||
- [ ] 影视级Bloom实现
|
||||
- [ ] 眉毛效果
|
||||
- [ ] 可以考虑使用TranslucencySortPriority来解决
|
||||
- [ ] 通过MeshDrawPass实现眉毛最前显示效果
|
||||
- [ ] 实现眉毛描边效果
|
||||
- [ ] 额发效果
|
||||
- [ ] 额发阴影效果(衣服阴影效果)
|
||||
- [ ] 实现半透明额发
|
||||
- [ ] 天光与间接光处理
|
||||
- [ ] 为了防止环境光把角色照出立体感,所以计算环境光时,会把法线全部看作世界空间上方向来处理。同时增加了一些参数可以进行一些定制化调整。
|
||||
- [ ] 使用顶点色与第二套UV来修改一些可以实时修改的效果
|
||||
- [ ] 实现PBR <=> Cel 卡通渲染效果的参数切换
|
||||
- [ ] 使用Kawaii插件实现柔体效果
|
||||
- [ ] 后处理边缘光落地
|
||||
- [ ] SSGI 卡通渲染适配?实现AnimMaker 中的一个效果
|
||||
|
||||
## 其他工具制作
|
||||
- [ ] 动画蓝图
|
||||
- [ ] 可交互的动画节点
|
||||
- [ ] Vroid与MMD系列格式的导入工具
|
||||
|
||||
## EOE问题
|
||||
1. 米诺 发布会时段,穿模了。解决方法,给桌子增加深度偏移材质。
|
||||
2. 脸部投影关了好像,角色没有SSS效果,反ToneMapper。
|
||||
3. 适当增加一些手腿与头发布料的效果。
|
||||
4. 眼睛的瞳孔(会缩放)以及高光(会移动)可以改进。
|
||||
|
||||
|
||||
# 改进需求
|
||||
1. 实现脸与脖子之间描边
|
||||
2. 鼻子外部描边
|
||||
3. 手指与手指之间
|
||||
|
||||
## 李兄实现Outline思路
|
||||
### Depth与Normal描边
|
||||
ToonOutlineMain()
|
||||
```c++
|
||||
float3x3 laplacianOperator = float3x3(-1, -1, -1,
|
||||
-1, 8, -1,
|
||||
float3x3 Gx = float3x3( -1, +0, +1,
|
||||
-1, -1, -1);
|
||||
-2, +0, +2,
|
||||
-1, +0, +1);
|
||||
float3x3 Gy = float3x3( +1, +2, +1,
|
||||
+0, +0, +0,
|
||||
-1, -2, -1);
|
||||
```
|
||||
使用GetPixelValue()取得Normal与Depth,之后使用拉普拉斯算子与Sobel算子进行边缘检测:
|
||||
```c++
|
||||
float4 fs0 = s0 * laplacianOperator[0][0];
|
||||
float4 fs1 = s1 * laplacianOperator[0][1];
|
||||
float4 fs2 = s2 * laplacianOperator[0][2];
|
||||
float4 fs3 = s3 * laplacianOperator[1][0];
|
||||
float4 fs4 = s4 * laplacianOperator[1][1];
|
||||
float4 fs5 = s5 * laplacianOperator[1][2];
|
||||
float4 fs6 = s6 * laplacianOperator[2][0];
|
||||
float4 fs7 = s7 * laplacianOperator[2][1];
|
||||
float4 fs8 = s8 * laplacianOperator[2][2];
|
||||
|
||||
float4 sampledValue = fs0 + fs1 + fs2 + fs3 + fs4 + fs5 + fs6 + fs7 + fs8;
|
||||
OutlineMask0 = saturate(1.0 - length(sampledValue)); //Line is black
|
||||
```
|
||||
```c++
|
||||
float4 ds0x = s0 * Gx[0][0];
|
||||
float4 ds1x = s1 * Gx[0][1];
|
||||
float4 ds2x = s2 * Gx[0][2];
|
||||
float4 ds3x = s3 * Gx[1][0];
|
||||
float4 ds4x = s4 * Gx[1][1];
|
||||
float4 ds5x = s5 * Gx[1][2];
|
||||
float4 ds6x = s6 * Gx[2][0];
|
||||
float4 ds7x = s7 * Gx[2][1];
|
||||
float4 ds8x = s8 * Gx[2][2];
|
||||
float4 SGX = ds0x + ds1x + ds2x + ds3x + ds4x + ds5x + ds6x + ds7x + ds8x;
|
||||
|
||||
float4 ds0y = s0 * Gy[0][0];
|
||||
float4 ds1y = s1 * Gy[0][1];
|
||||
float4 ds2y = s2 * Gy[0][2];
|
||||
float4 ds3y = s3 * Gy[1][0];
|
||||
float4 ds4y = s4 * Gy[1][1];
|
||||
float4 ds5y = s5 * Gy[1][2];
|
||||
float4 ds6y = s6 * Gy[2][0];
|
||||
float4 ds7y = s7 * Gy[2][1];
|
||||
float4 ds8y = s8 * Gy[2][2];
|
||||
float4 SGY = ds0y + ds1y + ds2y + ds3y + ds4y + ds5y + ds6y + ds7y + ds8y;
|
||||
|
||||
OutlineMask1 = saturate(2.0 - step(0.9, length(sqrt(SGX * SGX + SGY * SGY))));
|
||||
```
|
||||
这个算法巧妙的地方在于对计算卷积核之后使用length(float4(Normal,Depth))来取得结果;Sobel也是:length(sqrt(SGX * SGX + SGY * SGY)。
|
||||
|
||||
最后使用```OutColor.rgba = OutlineMask0;//lerp(OutlineMask0, OutlineMask1, 0.5);```进行混合。使用OutlineIDMap.a作为宽度控制项,并乘以通过Depth重映射后的变量作为宽度Fix因子。
|
||||
|
||||
### ID描边
|
||||
对ToonIDTexture进行Sobel描边。(只使用了Sobel)
|
||||
|
||||
### 混合结果
|
||||
|
||||
## 蓝色协议的做法
|
||||
### 模型外扩
|
||||
|
||||
### 后处理
|
||||
1. 使用Sobel算子进行深度检测,**只勾数值差别较大的区域**:脸部顶点色定义区域 与 模型外部轮廓。
|
||||
2. 使用Sobel进行ID贴图检测。**只勾数值差别较大的区域**。
|
||||
3. 进行法线点积(dot)检测。**在 深度差异小 以及 同一个ID区域内进行检测**:手指区域。
|
@ -34,10 +34,10 @@
|
||||
- [ ] 开源JTRP:Unity HDRP ToonShading Render Pipeline https://zhuanlan.zhihu.com/p/126229177
|
||||
---
|
||||
重力眩晕分析
|
||||
- [ ]【01】开篇:重力眩晕2中的渲染效果概览 https://zhuanlan.zhihu.com/p/94581861
|
||||
- [ ]【02】卡通渲染基本光照模型的实现 https://zhuanlan.zhihu.com/p/95986273
|
||||
- [ ]【03】卡通渲染LightMap的使用 https://zhuanlan.zhihu.com/p/97338996
|
||||
- [ ]【04】卡通渲染 次表面散射效果的简易实现 https://zhuanlan.zhihu.com/p/97892884
|
||||
- [ ] 01开篇:重力眩晕2中的渲染效果概览 https://zhuanlan.zhihu.com/p/94581861
|
||||
- [ ] 02卡通渲染基本光照模型的实现 https://zhuanlan.zhihu.com/p/95986273
|
||||
- [ ] 03 卡通渲染LightMap的使用 https://zhuanlan.zhihu.com/p/97338996
|
||||
- [ ] 04 卡通渲染 次表面散射效果的简易实现 https://zhuanlan.zhihu.com/p/97892884
|
||||
|
||||
---
|
||||
|
||||
@ -78,9 +78,6 @@
|
||||
- [ ] 「黑丝」的材质如何用shader实现?数学模型是怎样的?https://www.zhihu.com/question/35094847/answer/61161188
|
||||
- [ ] 遮挡时的模糊网点效果 https://zhuanlan.zhihu.com/p/370140711
|
||||
- [ ] 《Honey Select》捏人剖析 https://zhuanlan.zhihu.com/p/28471808
|
||||
|
||||
|
||||
|
||||
## 参考项目
|
||||
- 《莱莎的炼金工坊12》
|
||||
- 《破晓传说》
|
||||
|
@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Material&BasePass
|
||||
date: 2023-12-08 17:34:58
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# GBuffer
|
||||
```c#
|
||||
OutGBufferA = WorldNormal/PerObjectGBufferData
|
||||
OutGBufferB = Metallic/Specular/Roughness/EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask);
|
||||
OutGBufferC = BaseColor/GBufferAO
|
||||
OutGBufferD = GBuffer.CustomData;
|
||||
OutGBufferE = GBuffer.PrecomputedShadowFactors;
|
||||
```
|
||||
```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
|
||||
```
|
||||
|
||||
```
|
||||
| GBuffer | 表头 |
|
||||
| -------- | ------------------------------------------------------------------------------------- |
|
||||
| GBufferB | OffsetShadowMask SpcularMask SpecularValue EncodeShadingModelIdAndSelectiveOutputMask |
|
||||
| GBufferD | ShaderColor.rgb NoL |
|
||||
| GBufferE | |
|
||||
| GBufferF | ID |
|
||||
```
|
||||
|
||||
## BaseColor与ShadowColor
|
||||
- 原神里ShadowColor还会接收其他物体的阴影投射,没有自投影;蓝色协议可能也没有自投影。
|
||||
|
||||
BaseColor与ShadowColor的过渡需要Step、Feather、Offset等参数,可以直接制作一个HalfLambert的渐变贴图之后使用View传递。因为有多个贴图所以还需要ID贴图指定。但这样需要考虑一个问题:
|
||||
|
||||
- 一个物体上的同一个ID区域的BaseColor与ShadowColor是否都是一样的
|
||||
- 如果不一样就需要再传递一个ShadowColor.rgb到GBuffer里。
|
||||
- 不管如何手绘的补充暗部也是需要加到GBuffer中的
|
||||
|
||||
这决定传递到View里面的渐变贴图是彩色还是暗色
|
||||
|
||||
### 预计算贴图方案(构想)
|
||||
Toon渲染一般会使用HalfLambda。之后使用Feather、Step等参数对过渡边界进行调整
|
||||
使用 渐变贴图查表来实现 渐变、二阶化。以此代替羽化、step等参数。
|
||||
使用ID贴图指定,或者通过BaseColor值来查询?
|
||||
|
||||
## 高光
|
||||
- PBR高光(使用Roughness控制是否可行?是否需要传入GBuffer一个Mask贴图)
|
||||
- 自定义高光:高光贴图、高光颜色、参数化高光形状、多层高光
|
||||
|
||||
## 描边
|
||||
- 原神的描边好像是后处理
|
||||
- 蓝色协议
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/原神_描边.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/原神截图_描边.png]]
|
||||
|
||||
TODO:考虑使用顶点色来控制宽度,使用顶点色G
|
||||
|
||||
## 顶点色
|
||||
用于存储一些低精度数据,插值即可
|
||||
- R:
|
||||
- G:描边宽度
|
||||
- B:
|
||||
|
||||
蓝色协议的R:阴影区域标记 与 B:Ao,而罪恶装备使用贴图来传递信息。
|
||||
|
||||
## lightmap
|
||||
### 罪恶装备
|
||||
,G为阴影控(AO),R为高光强度参数,金属和光滑材质的部分设置的更大一些。B通道:用于照明控制。最大值为高光,反之,值越小高光越淡。
|
||||

|
||||
|
||||
https://zhuanlan.zhihu.com/p/360229590一文中介绍了崩坏3与原神的计算方式
|
||||
|
||||
崩坏3的LightMap计算方式:
|
||||
```c++
|
||||
half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv.xy);
|
||||
half4 LightMapColor = SAMPLE_TEXTURE2D(_LightMap, sampler_LightMap, input.uv.xy);
|
||||
half3 ShadowColor = baseColor.rgb * _ShadowMultColor.rgb;
|
||||
half3 DarkShadowColor = baseColor.rgb * _DarkShadowMultColor.rgb;
|
||||
|
||||
//如果SFactor = 0,ShallowShadowColor为一级阴影色,否则为BaseColor。
|
||||
float SWeight = (LightMapColor.g * input.color.r + input.lambert) * 0.5 + 1.125;
|
||||
float SFactor = floor(SWeight - _ShadowArea);
|
||||
half3 ShallowShadowColor = SFactor * baseColor.rgb + (1 - SFactor) * ShadowColor.rgb;
|
||||
```
|
||||
|
||||
二级阴影计算:
|
||||
```c++
|
||||
//如果SFactor = 0,DarkShadowColor为二级阴影色,否则为一级阴影色。
|
||||
SFactor = floor(SWeight - _DarkShadowArea);
|
||||
DarkShadowColor = SFactor * (_FixDarkShadow * ShadowColor + (1 - _FixDarkShadow) * ShallowShadowColor) + (1 - SFactor) * DarkShadowColor;
|
||||
|
||||
// 平滑阴影边缘
|
||||
half rampS = smoothstep(0, _ShadowSmooth, input.lambert - _ShadowArea);
|
||||
half rampDS = smoothstep(0, _DarkShadowSmooth, input.lambert - _DarkShadowArea);
|
||||
ShallowShadowColor.rgb = lerp(ShadowColor, baseColor.rgb, rampS);
|
||||
DarkShadowColor.rgb = lerp(DarkShadowColor.rgb, ShadowColor, rampDS);
|
||||
|
||||
//如果SFactor = 0,FinalColor为二级阴影,否则为一级阴影。
|
||||
SFactor = floor(LightMapColor.g * input.color.r + 0.9f);
|
||||
half4 FinalColor;
|
||||
FinalColor.rgb = SFactor * ShallowShadowColor + (1 - SFactor) * DarkShadowColor;
|
||||
```
|
||||
|
||||
|
||||
**罪恶装备**:
|
||||
对阴影判断阈值的偏移。(见前面着色部分,顶点AO+手绘修正)
|
||||
G : 轮廓线根据与相机的距离扩大多少的系数
|
||||
B : 等高线 Z 轴偏移值
|
||||
A : 轮廓厚度系数。0.5为标准,1为最大厚度,0为无等高线
|
||||
|
||||
### 蓝色协议
|
||||
[[蓝色协议的方案]]
|
||||
|
||||
### 米哈游
|
123
03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/Toon眉毛渲染.md
Normal file
123
03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/Toon眉毛渲染.md
Normal file
@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Toon眉毛渲染
|
||||
date: 2023-12-08 17:48:07
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
## 有关眉毛、表情需要使用 模板功能
|
||||
UTS使用模板
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情_StencilOut.png]]
|
||||
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情_StencilMask.png]]
|
||||
|
||||
```
|
||||
Stencil
|
||||
{
|
||||
Ref[_StencilNo] //设置渲染的模板缓存值,0~255
|
||||
Comp[_StencilComp] //模板测试的通过条件,有除了equal,还有Greater、Less、Always、Never等,类似ZTest。
|
||||
Pass[_StencilOpPass] //表示通过模板测试和Z测试(注意是都通过)的像素,怎么处置它的模板值。
|
||||
Fail[_StencilOpFail] //表示通过了模板测试但没通过Z测试的像素,怎么处置它的模板值。
|
||||
}
|
||||
|
||||
_UTS_StencilMode mode = (_UTS_StencilMode)(material.GetInt(ShaderPropStencilMode));
|
||||
switch (mode)
|
||||
{
|
||||
case _UTS_StencilMode.Off:
|
||||
// material.SetInt(ShaderPropStencilNo,0);
|
||||
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.Disabled);
|
||||
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Keep);
|
||||
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Keep);
|
||||
break;
|
||||
case _UTS_StencilMode.StencilMask:
|
||||
// material.SetInt(ShaderPropStencilNo,0);
|
||||
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.Always);
|
||||
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Replace);
|
||||
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Replace);
|
||||
break;
|
||||
case _UTS_StencilMode.StencilOut:
|
||||
// material.SetInt(ShaderPropStencilNo,0);
|
||||
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.NotEqual);
|
||||
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Keep);
|
||||
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Keep);
|
||||
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
七大罪中使用使用了深度测试 Greater Equal.(默认是 less Equal)。但这个方式可能在UE4里不太行,因为UE4的深度测试是全局的?
|
||||
|
||||
这个应该需要创建一个MeshProcessor来实现:
|
||||
```c#
|
||||
FSingleLayerWaterPassMeshProcessor::FSingleLayerWaterPassMeshProcessor(const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, const FMeshPassProcessorRenderState& InPassDrawRenderState, FMeshPassDrawListContext* InDrawListContext)
|
||||
: FMeshPassProcessor(Scene, Scene->GetFeatureLevel(), InViewIfDynamicMeshCommand, InDrawListContext)
|
||||
, PassDrawRenderState(InPassDrawRenderState)
|
||||
{
|
||||
if (SingleLayerWaterUsesSimpleShading(Scene->GetShaderPlatform()))
|
||||
{
|
||||
// Force non opaque, pre multiplied alpha, transparent blend mode because water is going to be blended against scene color (no distortion from texture scene color).
|
||||
FRHIBlendState* ForwardSimpleWaterBlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha>::GetRHI();
|
||||
PassDrawRenderState.SetBlendState(ForwardSimpleWaterBlendState);
|
||||
}
|
||||
}
|
||||
|
||||
//默认是CF_DepthNearOrEqual,做这个效果可能就要使用CF_DepthFartherOrEqual
|
||||
void SetupBasePassState(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, const bool bShaderComplexity, FMeshPassProcessorRenderState& DrawRenderState)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilAccess(BasePassDepthStencilAccess);
|
||||
if (bShaderComplexity)
|
||||
{
|
||||
// Additive blending when shader complexity viewmode is enabled.
|
||||
DrawRenderState.SetBlendState(TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_Zero, BF_One>::GetRHI());
|
||||
// Disable depth writes as we have a full depth prepass.
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI());
|
||||
}
|
||||
}
|
||||
```
|
||||
设置位置在于FXXXPassMeshProcessor::Process()中的SetDepthStencilStateForBasePass()中。需要bMaskedInEarlyPass为false,GetDepthStencilAccess为DepthRead。
|
||||
```c#
|
||||
const bool bMaskedInEarlyPass = (MaterialResource.IsMasked() || Mesh.bDitheredLODTransition) && MaskedInEarlyPass(GShaderPlatformForFeatureLevel[FeatureLevel]);
|
||||
|
||||
if (bEnableReceiveDecalOutput)
|
||||
{
|
||||
// Set stencil value for this draw call
|
||||
// This is effectively extending the GBuffer using the stencil bits
|
||||
const uint8 StencilValue = GET_STENCIL_BIT_MASK(RECEIVE_DECAL, PrimitiveSceneProxy ? !!PrimitiveSceneProxy->ReceivesDecals() : 0x00)
|
||||
| STENCIL_LIGHTING_CHANNELS_MASK(PrimitiveSceneProxy ? PrimitiveSceneProxy->GetLightingChannelStencilValue() : 0x00);
|
||||
|
||||
if (bMaskedInEarlyPass)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<
|
||||
false, CF_Equal,
|
||||
true, CF_Always, SO_Keep, SO_Keep, SO_Replace,
|
||||
false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
|
||||
0xFF, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)
|
||||
>::GetRHI());
|
||||
DrawRenderState.SetStencilRef(StencilValue);
|
||||
}
|
||||
else if (DrawRenderState.GetDepthStencilAccess() & FExclusiveDepthStencil::DepthWrite)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<
|
||||
true, CF_GreaterEqual,
|
||||
true, CF_Always, SO_Keep, SO_Keep, SO_Replace,
|
||||
false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
|
||||
0xFF, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)
|
||||
>::GetRHI());
|
||||
DrawRenderState.SetStencilRef(StencilValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<
|
||||
false, CF_GreaterEqual,
|
||||
true, CF_Always, SO_Keep, SO_Keep, SO_Replace,
|
||||
false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
|
||||
0xFF, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)
|
||||
>::GetRHI());
|
||||
DrawRenderState.SetStencilRef(StencilValue);
|
||||
}
|
||||
}
|
||||
else if (bMaskedInEarlyPass)
|
||||
{
|
||||
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Equal>::GetRHI());
|
||||
}
|
||||
```
|
202
03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/Toon眼睛渲染.md
Normal file
202
03-UnrealEngine/卡通渲染相关资料/渲染功能/其他渲染功能/Toon眼睛渲染.md
Normal file
@ -0,0 +1,202 @@
|
||||
# 资料整理
|
||||
|
||||
## 凹模型
|
||||
直接贴图即可。
|
||||
折射
|
||||
模型高光
|
||||
|
||||
## 凸模型
|
||||
1. 使用视差贴图来控制瞳孔效果 + 折射模拟
|
||||
```
|
||||
float2 viewL = mul(viewW, (float3x2) worldInverse);
|
||||
float2 offset = height * viewL;
|
||||
offset.y = -offset.y;
|
||||
texcoord -= parallaxScale * offset;
|
||||
```
|
||||

|
||||
|
||||
2. Physically based refraction
|
||||
```c++
|
||||
// 角膜区域突起的模型
|
||||
// Alternatively, use a displacement map
|
||||
// height = max(-positionL.z – eyeIrisDepth, 0.0);
|
||||
|
||||
// 球形模型
|
||||
// Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
|
||||
height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius );
|
||||
|
||||
// refractedW
|
||||
float w = n * dot( normalW, viewW );
|
||||
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
|
||||
float3 refractedW = ( w - k ) * normalW - n * viewW;
|
||||
|
||||
float cosAlpha = dot(frontNormalW, -refractedW);
|
||||
float dist = height / cosAlpha;
|
||||
float3 offsetW = dist * refractedW;
|
||||
float2 offsetL = mul(offsetW, (float3x2) worldInverse);
|
||||
texcoord += float2(mask, -mask) * offsetL;
|
||||
```
|
||||

|
||||
|
||||
代码中首先计算了height,即前房的高度,PPT中height有两种计算方式,分别对应两种眼睛的模型结构,对应的结构写在注释中了。
|
||||
然后计算了refracted,这个是rtr中快速拟合的计算方法,n是空气与介质折射率的比值,关于refracted的推论可以参考:
|
||||
YivanLee:虚幻4渲染编程(人物篇)【第三卷:Human Eye Rendering】
|
||||
[129 赞同 · 12 评论文章](https://zhuanlan.zhihu.com/p/151786832)
|
||||
|
||||
最后一段,先通过frontNormalW与refractedW的点积计算出α角的cos值(上图中的α应该是标识错误,α是-refractedW与frontNormalW的夹角)。然后已知height,通过比值可以计算出refractedW的模长dist。offsetW即为完整的refractedW向量。最后转换到本地空间,乘上眼睛的Mask,加到原本的UV上。
|
||||
之后就是使用偏转后的UV去采样贴图了。
|
||||
与视差相同,这里也是在本地与世界空间中进行的计算,同样会有轴向问题,主要是normalW、viewW和frontNormalW参与的计算,normal与view可以转换到切线空间计算,而frontNormalW代表的是模型向前的朝向,这个必须要指定,不过图方便的话,把frontNormalW改成切线空间法线也不是不可以。
|
||||
|
||||
### 多层复合模型
|
||||
樱花大战cedec2020分享:https://blog.ch-wind.com/cedec2020-new-sakura-wars-note/
|
||||
|
||||
>本作的眼睛分为三个部分,眼白的部分是一个内凹的形状,瞳孔的部分则分为了向内凹的部分和向外突出的部分。
|
||||

|
||||
|
||||
>瞳孔的高光叠加在其突出的半透明部分上。根据摄像角度的不同,各个部分的贴图会分开进行移动,使得在哪个角度高光都能处在一个刚好的位置。
|
||||
控制上,有针对高光上下左右的移动强度与控制移动范围的参数共同作用。
|
||||
|
||||

|
||||
>从左边开始,是作为基础颜色的Albedo,以及用于Mask瞳孔的Alpha贴图,用于在Albedo上进行叠加的spt贴图,以及两张瞳孔高光,以及反应环境的matcapture贴图。
|
||||
虽然很多动画风格的渲染中会省略掉瞳孔中的虹彩部分,但是本作为了提高角色靠近时的效果,进行了详细的绘制,同时为了体现环境的变化与matcap的贴图进行叠加。
|
||||
高光贴图有两张,分别使用不同的UV动画进行控制,用于表现眼睛的湿润感。虽然是很细微的操作,但是对于表现角色的感情非常的有用。
|
||||
|
||||

|
||||
SunnySideUp UnityChan
|
||||
|
||||
### 其他效果实现
|
||||
#### 眼睛高光效果
|
||||
1. 贴图高光。使用事先绘制的高光形状贴图,贴到最外面的。并且使用ViewDirection来控制。设定4个UV Coord, 根据 View=》眼睛的本地坐标系=》Normalize后的向量进行插值。
|
||||
2. ~~PBR的思路~~
|
||||
|
||||
#### Matcap反射效果
|
||||
Matcap材质+球形法线贴图
|
||||
```c++
|
||||
float3 NormalBlend_MatcapUV_Detail = viewNormal.rgb * float3(-1,-1,1);
|
||||
float3 NormalBlend_MatcapUV_Base = (mul( UNITY_MATRIX_V, float4(viewDirection,0)).rgb*float3(-1,-1,1)) + float3(0,0,1);
|
||||
float3 noSknewViewNormal = NormalBlend_MatcapUV_Base * dot(NormalBlend_MatcapUV_Base, NormalBlend_MatcapUV_Detail) / NormalBlend_MatcapUV_Base.b - NormalBlend_MatcapUV_Detail;
|
||||
float2 ViewNormalAsMatCapUV = noSknewViewNormal.rg * 0.5 + 0.5;
|
||||
```
|
||||
|
||||
#### 焦散效果
|
||||
>焦散的表现反倒简单了,直接画在眼睛贴图上都可以,考虑到卡通表达的自由性,焦散是否存在与焦散的形状都可以没有限制,只要好看就行。
|
||||
下图也是miHoYo的分享,可以简单的理解为直接贴张Mask上去,然后用光照方向和菲涅尔去影响强度变化。
|
||||

|
||||
|
||||
使用Mask贴图、NoL与菲尼尔来控制
|
||||
|
||||
# 其他参考整理
|
||||
![[ToonEye.png|400]]
|
||||
|
||||
![[ToonEyes.png|400]]
|
||||
|
||||
## PBR Refrection(有问题)
|
||||
```
|
||||
//input anteriorChamberDepth 贴图 使用HeightMap
|
||||
//input radius
|
||||
//input mask 贴图
|
||||
//input texcoord
|
||||
//input n 折射率比值
|
||||
//input frontNormalW 贴图float3(0,0,1) Local=>World
|
||||
|
||||
// 角膜区域突起的模型 height = max(-positionL.z – eyeIrisDepth, 0.0);
|
||||
// 球形模型 Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
|
||||
float height = anteriorChamberDepth ;//* saturate( 1.0 - 18.4 * radius * radius );
|
||||
|
||||
float3 normalW=Parameters.WorldNormal;
|
||||
float3 viewW= mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);//CameraViewToTranslatedWorld
|
||||
|
||||
// refractedW
|
||||
float w = n * dot( normalW, viewW );
|
||||
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
|
||||
float3 refractedW = ( w - k ) * normalW - n * viewW;
|
||||
refractedW=-normalize(refractedW);
|
||||
|
||||
//float3 frontNormalW=mul(float(0,0,1),float(3x3)GetPrimitiveData(Parameters.PrimitiveId).LocalToWorld) * -1;
|
||||
float cosAlpha = dot(frontNormalW, refractedW);
|
||||
float dist = height / cosAlpha;
|
||||
float3 offsetW = dist * refractedW;
|
||||
float2 offsetL = mul(offsetW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
|
||||
texcoord += float2(mask, -mask) * offsetL;
|
||||
|
||||
return texcoord;
|
||||
```
|
||||
### 原始代码
|
||||
```
|
||||
// 角膜区域突起的模型
|
||||
// Alternatively, use a displacement map
|
||||
// height = max(-positionL.z – eyeIrisDepth, 0.0);
|
||||
|
||||
// 球形模型
|
||||
// Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
|
||||
height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius );
|
||||
|
||||
// refractedW
|
||||
float w = n * dot( normalW, viewW );
|
||||
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
|
||||
float3 refractedW = ( w - k ) * normalW - n * viewW;
|
||||
|
||||
float cosAlpha = dot(frontNormalW, -refractedW);
|
||||
float dist = height / cosAlpha;
|
||||
float3 offsetW = dist * refractedW;
|
||||
float2 offsetL = mul(offsetW, (float3x2) worldInverse);
|
||||
texcoord += float2(mask, -mask) * offsetL;
|
||||
```
|
||||
## Matcap反射效果
|
||||
```
|
||||
float2 CalcMatcapUV(FMaterialPixelParameters Parameters,float3 normalTexture)
|
||||
{
|
||||
float3 ViewVector=Parameters.CameraVector * -1;
|
||||
float3 ViewSpaceRightVector=normalize(cross(ViewVector , mul( float3(0.0,1.0,0.0) , ResolvedView.ViewToTranslatedWorld )));
|
||||
float3 ViewSpaceUpVector=cross(ViewVector ,ViewSpaceRightVector);
|
||||
|
||||
float3x3 Matrix=float3x3(
|
||||
ViewSpaceRightVector.x,ViewSpaceUpVector.x,ViewVector.x,
|
||||
ViewSpaceRightVector.y,ViewSpaceUpVector.y,ViewVector.y,
|
||||
ViewSpaceRightVector.z,ViewSpaceUpVector.z,ViewVector.z
|
||||
);
|
||||
|
||||
float3 ZeroOneNormal=mul(normalize(Parameters.WorldNormal+normalTexture),Matrix)*0.5+0.5;
|
||||
return float2(ZeroOneNormal.x,ZeroOneNormal.y*-1);
|
||||
}
|
||||
```
|
||||
|
||||
### 这个不太行
|
||||
```
|
||||
//input normalTexture 法线贴图
|
||||
float3 TangentWS=Parameters.TangentToWorld[0];
|
||||
float3 NormalWS=Parameters.TangentToWorld[2];
|
||||
float3 BinormalWS=cross(NormalWS, TangentWS);
|
||||
|
||||
float3 worldNormal;
|
||||
worldNormal.x = dot(float3(TangentWS.x,BinormalWS.x,NormalWS.x), normalTexture);
|
||||
worldNormal.y = dot(float3(TangentWS.y,BinormalWS.y,NormalWS.y), normalTexture);
|
||||
worldNormal.z = dot(float3(TangentWS.z,BinormalWS.z,NormalWS.z), normalTexture);
|
||||
worldNormal = normalize(worldNormal);
|
||||
|
||||
float3 e = normalize(GetWorldPosition(Parameters) - ResolvedView.WorldCameraOrigin);
|
||||
float3 reflectVector = reflect(e, worldNormal);
|
||||
float3 reflectVectorVS = normalize(mul(reflectVector,ResolvedView.TranslatedWorldToView));
|
||||
float m = 2.82842712474619 * sqrt(reflectVectorVS.z + 1.0);
|
||||
float2 cap = reflectVectorVS.xy / m + 0.5;
|
||||
cap=cap*0.5 + 0.5;
|
||||
return cap;
|
||||
```
|
||||
|
||||
## 高光贴图位置调整
|
||||
```
|
||||
//input float2 LeftUp,LeftDown,RightUp,RightDown;
|
||||
|
||||
float3 viewW = mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);
|
||||
float3 viewL = mul(viewW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
|
||||
float2 viewL2D = normalize(viewL.xy);
|
||||
|
||||
float2 Left=lerp(LeftUp,LeftDown,viewL2D.y);
|
||||
float2 Right=lerp(RightUp,RightDown,viewL2D.y);
|
||||
return lerp(Left,Right,viewL2D.x);
|
||||
```
|
||||
## 焦散
|
||||
- dot(Parameters.WorldNormal,View.DirectionalLightDirection);
|
||||
- Fresnel
|
||||
|
||||
乘以焦散Mask即可
|
@ -15,6 +15,10 @@
|
||||
![[Netease_ToonEffect.png]]
|
||||
|
||||
|
||||
![[少女前线追放gif1.png]]
|
||||
![[少女前线追放gif2.png]]
|
||||
![[少女前线追放gif3.png]]
|
||||
|
||||
《推子》星星瞳眼睛效果参考:https://www.bilibili.com/video/BV11o4y187uS/?vd_source=d47c0bb42f9c72fd7d74562185cee290
|
||||
物理效果插件:Phys control component in UE 5.1 content examples
|
||||
- [x] 异度之刃3 CEDEC渲染分享 https://zhuanlan.zhihu.com/p/588630949
|
89
03-UnrealEngine/卡通渲染相关资料/渲染功能/描边/描边.md
Normal file
89
03-UnrealEngine/卡通渲染相关资料/渲染功能/描边/描边.md
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: 描边
|
||||
date: 2023-12-08 16:49:45
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 实现功能
|
||||
- 后处理描边
|
||||
- MeshDraw描边
|
||||
|
||||
# 杂项
|
||||
## 李兄实现Outline思路
|
||||
### Depth与Normal描边
|
||||
ToonOutlineMain()
|
||||
```c++
|
||||
float3x3 laplacianOperator = float3x3(-1, -1, -1,
|
||||
-1, 8, -1,
|
||||
float3x3 Gx = float3x3( -1, +0, +1,
|
||||
-1, -1, -1);
|
||||
-2, +0, +2,
|
||||
-1, +0, +1);
|
||||
float3x3 Gy = float3x3( +1, +2, +1,
|
||||
+0, +0, +0,
|
||||
-1, -2, -1);
|
||||
```
|
||||
使用GetPixelValue()取得Normal与Depth,之后使用拉普拉斯算子与Sobel算子进行边缘检测:
|
||||
```c++
|
||||
float4 fs0 = s0 * laplacianOperator[0][0];
|
||||
float4 fs1 = s1 * laplacianOperator[0][1];
|
||||
float4 fs2 = s2 * laplacianOperator[0][2];
|
||||
float4 fs3 = s3 * laplacianOperator[1][0];
|
||||
float4 fs4 = s4 * laplacianOperator[1][1];
|
||||
float4 fs5 = s5 * laplacianOperator[1][2];
|
||||
float4 fs6 = s6 * laplacianOperator[2][0];
|
||||
float4 fs7 = s7 * laplacianOperator[2][1];
|
||||
float4 fs8 = s8 * laplacianOperator[2][2];
|
||||
|
||||
float4 sampledValue = fs0 + fs1 + fs2 + fs3 + fs4 + fs5 + fs6 + fs7 + fs8;
|
||||
OutlineMask0 = saturate(1.0 - length(sampledValue)); //Line is black
|
||||
```
|
||||
```c++
|
||||
float4 ds0x = s0 * Gx[0][0];
|
||||
float4 ds1x = s1 * Gx[0][1];
|
||||
float4 ds2x = s2 * Gx[0][2];
|
||||
float4 ds3x = s3 * Gx[1][0];
|
||||
float4 ds4x = s4 * Gx[1][1];
|
||||
float4 ds5x = s5 * Gx[1][2];
|
||||
float4 ds6x = s6 * Gx[2][0];
|
||||
float4 ds7x = s7 * Gx[2][1];
|
||||
float4 ds8x = s8 * Gx[2][2];
|
||||
float4 SGX = ds0x + ds1x + ds2x + ds3x + ds4x + ds5x + ds6x + ds7x + ds8x;
|
||||
|
||||
float4 ds0y = s0 * Gy[0][0];
|
||||
float4 ds1y = s1 * Gy[0][1];
|
||||
float4 ds2y = s2 * Gy[0][2];
|
||||
float4 ds3y = s3 * Gy[1][0];
|
||||
float4 ds4y = s4 * Gy[1][1];
|
||||
float4 ds5y = s5 * Gy[1][2];
|
||||
float4 ds6y = s6 * Gy[2][0];
|
||||
float4 ds7y = s7 * Gy[2][1];
|
||||
float4 ds8y = s8 * Gy[2][2];
|
||||
float4 SGY = ds0y + ds1y + ds2y + ds3y + ds4y + ds5y + ds6y + ds7y + ds8y;
|
||||
|
||||
OutlineMask1 = saturate(2.0 - step(0.9, length(sqrt(SGX * SGX + SGY * SGY))));
|
||||
```
|
||||
这个算法巧妙的地方在于对计算卷积核之后使用length(float4(Normal,Depth))来取得结果;Sobel也是:length(sqrt(SGX * SGX + SGY * SGY)。
|
||||
|
||||
最后使用```OutColor.rgba = OutlineMask0;//lerp(OutlineMask0, OutlineMask1, 0.5);```进行混合。使用OutlineIDMap.a作为宽度控制项,并乘以通过Depth重映射后的变量作为宽度Fix因子。
|
||||
|
||||
### ID描边
|
||||
对ToonIDTexture进行Sobel描边。(只使用了Sobel)
|
||||
|
||||
### 混合结果
|
||||
|
||||
## 蓝色协议的做法
|
||||
### 模型外扩
|
||||
|
||||
### 后处理
|
||||
1. 使用Sobel算子进行深度检测,**只勾数值差别较大的区域**:脸部顶点色定义区域 与 模型外部轮廓。
|
||||
2. 使用Sobel进行ID贴图检测。**只勾数值差别较大的区域**。
|
||||
3. 进行法线点积(dot)检测。**在 深度差异小 以及 同一个ID区域内进行检测**:手指区域。
|
||||
|
||||
1. 使用Sobel过滤器进行深度检测描边。
|
||||
2. 使用Sobel过滤器进行Id图检测描边。
|
||||
3. 使用Sobel过滤器进行Normal检测描边。用于处理一些难以分ID,深度差又很小的地方,通过获取周围点法线求点乘的方式判断出轮廓。
|
||||
|
||||
首先,如果只是修改模型顶点的法线的话,模型顶点数就那么多,比如上面的模型已经做得很细致了,但顶点数也就5k,传递球形法线做比较柔和的光照效果还比较合适,想表现复杂的结构是比较困难的。
|
||||
当然也有修改模型拓扑等方法,但我觉得这些方法都不是特别优雅。
|
||||
我个人觉得使用法线贴图会更有潜力一些,自己私底下也有在研究把SDF烘培成法线贴图的方法,目前的结果我自己并不是很满意,如果后续有一些有意思的结果,我会再出一篇文章跟大家分享。
|
||||
|
||||

|
||||
|
||||
通过SDF生成的法线贴图
|
||||

|
||||
使用SDF法线贴图shading的效果
|
||||
> 为了方便烘培成世界空间的法线,烘培到切线空间会更合适
|
||||
|
||||
### 1.2 使用SDF的方案
|
||||
SDF的方案是目前市面上使用得最多的了,新出的二游基本都会用这种方式。
|
||||
在实现上,SDF的方案并不困难,但是由于我自己使用unreal引擎比较多,自己想通过改管线的方法来实现的话很难改得很优雅,所以我以后还会继续研究法线贴图方案。
|
||||
在表现上,目前SDF的方案最明显的问题是没有Z轴上的变化,这也是这篇文章主要想解决的问题。本文的实现思路其实很简单,目前的SDF都只画了一组水平轴的光照,那么我们把其他所有轴都画出来就行了。
|
||||

|
||||
|
||||
使用全角度的SDF图集代替水平轴的SDF
|
||||
两三年前看到原神的SDF方案时就想到了这个方法,相信平时比较关注卡通渲染的各位都想到过这个方法,但是几年过去了,没有看到任何一篇文章实现了这个方案,看来大伙也都觉得把所有方向的光照都画出来工作量太大了不敢尝试,所以只能我自己尝试实现给大伙看了。
|
||||
|
||||
## 二、实现流程
|
||||
### 2.1 流程概况
|
||||
三句话简单概括一下:
|
||||
1.画好各个角度的光照,如下我一共画了65张,稍微偷懒点少画写应该也没问题。
|
||||
|
||||

|
||||
|
||||
在blender中绘制光照图
|
||||
2.把画完的光照图转成SDF,然后拼成一张图集
|
||||
|
||||

|
||||
|
||||
生成的SDF和SDF图集
|
||||
3.通过光源角度<E8A792>和<EFBFBD>计算合适的uv,采样4次进行插值。
|
||||
|
||||

|
||||
|
||||
采样四次SDF图集计算光照结果
|
||||
|
||||
### 2.2 光照图绘制
|
||||
绘制光照没有目前没想到什么很好的生成方式,纯靠手绘,我没啥美术能力,画这65张图,花了我一整个星期的空闲时间,如果有美术大佬愿意画的这东西的话可能效果会更好一些。(这一堆的模型,有点制作表情blend shape的感觉了)
|
||||
光照图我画了9行9列(第一行和最后一行只有一张,所有一共9x7+2=65张)
|
||||
光照图的每一行代表平行光从下面的半球从上到下的9种不同的经度投射到脸上,每一列代表9不同的纬度,第一行和最后一行平行光从正上和正下投射过来,所以只有一张。
|
||||
|
||||

|
||||
绘制的光照图相当于光照从左半球上各点照射过来的结果
|
||||
|
||||

|
||||
|
||||
有一点需要注意的,可以看到我绘制光照图的时候涂出了uv的边界,这样生成的SDF在uv边界效果不会出问题(不想让SDF超出uv边界的话等生成了SDF之后再把超出边界的部分去掉就行了):
|
||||
|
||||

|
||||
|
||||
绘制光照图时建议涂出uv边界
|
||||
下面这个是反面例子:
|
||||

|
||||
|
||||
脸部上方uv边界处SDF插值效果不好
|
||||
下面放点动画里的光影变化作为参考:
|
||||
|
||||

|
||||
|
||||
奇蛋物语
|
||||
|
||||

|
||||
|
||||
奇蛋物语、偶像大师、Mygo
|
||||
|
||||

|
||||
|
||||
轻音少女
|
||||
### 2.3 SDF图集制作
|
||||
|
||||

|
||||
|
||||
使用8ssedt算法生成SDF
|
||||
|
||||
首先,我们需要把黑白的光照图转为SDF,我使用的是8ssedt算法生成的,关于8ssedt算法的详细可以看下面的这两篇文章:
|
||||
https://zhuanlan.zhihu.com/p/337944099
|
||||
https://blog.csdn.net/qq_41835314/article/details/128548073
|
||||
|
||||
接下来把所有的光照图拼成一张图集:
|
||||
|
||||

|
||||
|
||||
SDF图集
|
||||
这里解释一下,我为什么不生成这种SDF(这种SDF在下文都使用“插值后的SDF”特指)。
|
||||
|
||||

|
||||
|
||||
“插值后的SDF”
|
||||
使用这种插值后的SDF其实是有一个条件的:后面的光照一定要覆盖前面的光照。
|
||||
比如下面这套SDF图中,光照图的范围从左到右,一定是逐渐增大的
|
||||
|
||||

|
||||
|
||||
https://zhuanlan.zhihu.com/p/411188212
|
||||
这样导致的结果就是当光从前方照过来的时候,如下图左边整个脸必须是完全亮的,不可能出现右边这种脸的后方没被照亮的效果。
|
||||
|
||||

|
||||
|
||||
“使用插值后的SDF的缺点”
|
||||
再举一个例子,下面3张图之间没有包含关系,所有转成SDF再插值之后的效果是右边这样,是不是看起来就不对劲。
|
||||
|
||||

|
||||
|
||||
使用没有包含关系的图像生成“插值后的SDF”
|
||||
看看效果,确实不对劲:
|
||||

|
||||
“插值后的SDF”错误的效果
|
||||
导致这种错误的原因在于生成“插值后的SDF”的算法:
|
||||
- 算法在255次循环中计算出了不同光源角度对应的光照结果,这一步是没问题的
|
||||
- 但是为了能够将255种不同的结果保存在一张贴图里,算法里的做法是将所有结果累加起来,这一步破坏了插值信息
|
||||
|
||||
```cpp
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int i = 0; i < 255; i++) {
|
||||
if (nextTexIndex >= int(grayImages.size())) {
|
||||
break;
|
||||
}
|
||||
float weight = lerpStep / levelStep;
|
||||
// 这里采样了相邻的两张SDF
|
||||
int curPixel = grayImages[curTexIndex].at<uchar>(y, x);
|
||||
int nextPixel = grayImages[nextTexIndex].at<uchar>(y, x);
|
||||
int lerpPixel = curPixel * weight + nextPixel * (1 - weight);
|
||||
// 在这里计算出了不同光源角度对应光照结果
|
||||
result += lerpPixel > 127 ? 0 : 1;
|
||||
// 结果进行累加
|
||||
lerpStep++;
|
||||
if (lerpStep >= levelStep)
|
||||
{
|
||||
lerpStep = 0;
|
||||
curTexIndex++;
|
||||
nextTexIndex++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
lerpedSDF.at<Vec3b>(y, x)[0] = int(result);
|
||||
lerpedSDF.at<Vec3b>(y, x)[1] = int(result);
|
||||
lerpedSDF.at<Vec3b>(y, x)[2] = int(result);
|
||||
|
||||
result = 0;
|
||||
curTexIndex = 0;
|
||||
nextTexIndex = 1;
|
||||
lerpStep = 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
如果只做水平轴的SDF,其实“后面的光照范围必须大于前面的光照”的这个条件其实无伤大雅,而且用一张贴图代替了9张SDF,性价比很高
|
||||
但是我们做全角度的SDF光照,本身就需要做图集,直接用SDF就好了,而且不会受上面条件的影响,效果会更好。
|
||||
|
||||
### 2.4 工具
|
||||
|
||||
以上转SDF和拼图集的操作我写了一个小工具,你们直接用我的工具来做就行了,不用再浪费时间去重复造轮子了。
|
||||
|
||||

|
||||
|
||||
生成SDF和图集的小工具
|
||||
工具我和工程放在一起,解压SDFTool.zip然后双击main.exe就能打开
|
||||
|
||||

|
||||
|
||||
美术的同学直接使用打包好的工具就行了
|
||||
工具具体的使用文档可以在我的github上查看,源码也在上面,工具写得不是很鲁棒,如果出了什么问题的话有能力的朋友直接改源码吧。
|
||||
|
||||

|
||||
|
||||
### 2.5 计算光源角度采样SDF图集
|
||||
下面很多操作都跟正常的SDF脸部光照一样,我就不写得太详细了,具体直接打开我的工程看就行了。
|
||||
1.在蓝图里把角色脸部向前的向量和先左的向量传到角色的材质里
|
||||

|
||||
|
||||
就是把下图下面这两个箭头的方向传到材质里:
|
||||

|
||||
|
||||
- 叉乘朝左和朝前的向量,得到朝上的向量
|
||||
- 将光源方向与脸部朝左向量点乘,然后step一下,用于后面判断光源是从脸部左边还是右边照过来的
|
||||
- 将光源方向与脸部朝前、朝上的向量点乘,得到光源的水平夹角的$cos\theta$和垂直夹角的$cos\phi$
|
||||

|
||||
|
||||
- $cos\theta$和$cos\phi$它们是余弦值,不是线性的,所以我喜欢使用arccos把它们变成线性的角度:
|
||||

|
||||
|
||||
- 接下来采样的部分看起来比较杂乱
|
||||

|
||||
|
||||
我们稍微拆开来看:
|
||||
- 这里是定义了两个常数,应该图集是9x9的,所有定义了Row=1,Row-1的意思是Row-1=8
|
||||

|
||||
- 当光源在右边时,镜像翻转uv
|
||||
- 然后uv除以行数9,就得到了贴图左上角的第一张SDF的uv
|
||||

|
||||
|
||||
- 接下来要计算离当前光源角度最近的4张SDF的uv,比如当水平角度<E8A792>等于100度,垂直角度<E8A792>等于120度时会采样下面画的四个点的SDF,然后按照权重来插值,跟双线性插值很像。
|
||||

|
||||
|
||||
采样当前光源角度最近的四张SDF
|
||||
- 这里计算离光源最近的行数和列数
|
||||

|
||||
|
||||
- 接下来计算出uv,采样贴图并插值出光照结果最后上色
|
||||

|
||||
|
||||

|
BIN
03-UnrealEngine/卡通渲染相关资料/渲染功能/阴影控制/SDF面部阴影/二维SDF面部阴影方案.pdf
Normal file
BIN
03-UnrealEngine/卡通渲染相关资料/渲染功能/阴影控制/SDF面部阴影/二维SDF面部阴影方案.pdf
Normal file
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 6.3 MiB |
@ -149,3 +149,8 @@ def unregister():
|
||||
global thread2
|
||||
thread2.join()
|
||||
```
|
||||
|
||||
# Blender CMD 启用插件
|
||||
>./blender -b --addons animation_nodes,meshlint [file] --python [myscript.py]
|
||||
|
||||
这样就可以指定启用某某插件
|
43
06-DCC/Blender/Blender删除骨骼权重脚本.md
Normal file
43
06-DCC/Blender/Blender删除骨骼权重脚本.md
Normal file
@ -0,0 +1,43 @@
|
||||
```python
|
||||
import bpy
|
||||
#import os
|
||||
import sys
|
||||
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:] # get all args after "--"
|
||||
# print(argv) # --> ['example', 'args', '123']
|
||||
SourceFBXPath = argv[0]
|
||||
ExportFBXPath = argv[1]
|
||||
RemoveWeightBoneNames = argv[2]
|
||||
RemoveWeightBoneNameList = RemoveWeightBoneNames.split(',')
|
||||
#RemoveWeightBoneNameList = ['calf_twist_01_l','calf_twist_01_r','calf_twist_02_l','calf_twist_02_r','lowerarm_twist_01_l','lowerarm_twist_01_r']
|
||||
|
||||
bpy.ops.scene.new(type='EMPTY')
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete()
|
||||
|
||||
bpy.ops.import_scene.fbx(filepath=SourceFBXPath)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH':
|
||||
for BoneName in RemoveWeightBoneNameList:
|
||||
vg = obj.vertex_groups.get(BoneName)
|
||||
if vg is not None:
|
||||
obj.vertex_groups.remove(vg)
|
||||
|
||||
bpy.ops.export_scene.fbx(filepath=ExportFBXPath,add_leaf_bones=False)
|
||||
|
||||
# ob = bpy.context.object
|
||||
# if ob.type == 'ARMATURE':
|
||||
# armature = ob.data
|
||||
|
||||
# bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# for bone in armature.bones:
|
||||
# if fnmatch.fnmatchcase(bone.name, "something"):
|
||||
# armature.delete(bone)
|
||||
|
||||
# for bone in armature.edit_bones:
|
||||
# if fnmatch.fnmatchcase(bone.name, "something"):
|
||||
# armature.edit_bones.remove(bone)
|
||||
```
|
56
08-Assets/FbxInclude 1.h
Normal file
56
08-Assets/FbxInclude 1.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
// Temporarily disable a few warnings due to virtual function abuse in FBX source files
|
||||
#pragma warning( push )
|
||||
|
||||
#pragma warning( disable : 4263 ) // 'function' : member function does not override any base class virtual member function
|
||||
#pragma warning( disable : 4264 ) // 'virtual_function' : no override available for virtual member function from base 'class'; function is hidden
|
||||
|
||||
// Include the fbx sdk header
|
||||
// temp undef/redef of _O_RDONLY because kfbxcache.h (included by fbxsdk.h) does
|
||||
// a weird use of these identifiers inside an enum.
|
||||
#ifdef _O_RDONLY
|
||||
#define TMP_UNFBX_BACKUP_O_RDONLY _O_RDONLY
|
||||
#define TMP_UNFBX_BACKUP_O_WRONLY _O_WRONLY
|
||||
#undef _O_RDONLY
|
||||
#undef _O_WRONLY
|
||||
#endif
|
||||
|
||||
//Robert G. : Packing was only set for the 64bits platform, but we also need it for 32bits.
|
||||
//This was found while trying to trace a loop that iterate through all character links.
|
||||
//The memory didn't match what the debugger displayed, obviously since the packing was not right.
|
||||
#pragma pack(push,8)
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
// _CRT_SECURE_NO_DEPRECATE is defined but is not enough to suppress the deprecation
|
||||
// warning for vsprintf and stricmp in VS2010. Since FBX is able to properly handle the non-deprecated
|
||||
// versions on the appropriate platforms, _CRT_SECURE_NO_DEPRECATE is temporarily undefined before
|
||||
// including the FBX headers
|
||||
|
||||
// The following is a hack to make the FBX header files compile correctly under Visual Studio 2012 and Visual Studio 2013
|
||||
#if _MSC_VER >= 1700
|
||||
#define FBX_DLL_MSC_VER 1600
|
||||
#endif
|
||||
|
||||
|
||||
#endif // PLATFORM_WINDOWS
|
||||
|
||||
// FBX casts null pointer to a reference
|
||||
THIRD_PARTY_INCLUDES_START
|
||||
#include <fbxsdk.h>
|
||||
THIRD_PARTY_INCLUDES_END
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#ifdef TMP_UNFBX_BACKUP_O_RDONLY
|
||||
#define _O_RDONLY TMP_FBX_BACKUP_O_RDONLY
|
||||
#define _O_WRONLY TMP_FBX_BACKUP_O_WRONLY
|
||||
#undef TMP_UNFBX_BACKUP_O_RDONLY
|
||||
#undef TMP_UNFBX_BACKUP_O_WRONLY
|
||||
#endif
|
||||
|
||||
#pragma warning( pop )
|
56
08-Assets/FbxInclude.h
Normal file
56
08-Assets/FbxInclude.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
// Temporarily disable a few warnings due to virtual function abuse in FBX source files
|
||||
#pragma warning( push )
|
||||
|
||||
#pragma warning( disable : 4263 ) // 'function' : member function does not override any base class virtual member function
|
||||
#pragma warning( disable : 4264 ) // 'virtual_function' : no override available for virtual member function from base 'class'; function is hidden
|
||||
|
||||
// Include the fbx sdk header
|
||||
// temp undef/redef of _O_RDONLY because kfbxcache.h (included by fbxsdk.h) does
|
||||
// a weird use of these identifiers inside an enum.
|
||||
#ifdef _O_RDONLY
|
||||
#define TMP_UNFBX_BACKUP_O_RDONLY _O_RDONLY
|
||||
#define TMP_UNFBX_BACKUP_O_WRONLY _O_WRONLY
|
||||
#undef _O_RDONLY
|
||||
#undef _O_WRONLY
|
||||
#endif
|
||||
|
||||
//Robert G. : Packing was only set for the 64bits platform, but we also need it for 32bits.
|
||||
//This was found while trying to trace a loop that iterate through all character links.
|
||||
//The memory didn't match what the debugger displayed, obviously since the packing was not right.
|
||||
#pragma pack(push,8)
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
// _CRT_SECURE_NO_DEPRECATE is defined but is not enough to suppress the deprecation
|
||||
// warning for vsprintf and stricmp in VS2010. Since FBX is able to properly handle the non-deprecated
|
||||
// versions on the appropriate platforms, _CRT_SECURE_NO_DEPRECATE is temporarily undefined before
|
||||
// including the FBX headers
|
||||
|
||||
// The following is a hack to make the FBX header files compile correctly under Visual Studio 2012 and Visual Studio 2013
|
||||
#if _MSC_VER >= 1700
|
||||
#define FBX_DLL_MSC_VER 1600
|
||||
#endif
|
||||
|
||||
|
||||
#endif // PLATFORM_WINDOWS
|
||||
|
||||
// FBX casts null pointer to a reference
|
||||
THIRD_PARTY_INCLUDES_START
|
||||
#include <fbxsdk.h>
|
||||
THIRD_PARTY_INCLUDES_END
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#ifdef TMP_UNFBX_BACKUP_O_RDONLY
|
||||
#define _O_RDONLY TMP_FBX_BACKUP_O_RDONLY
|
||||
#define _O_WRONLY TMP_FBX_BACKUP_O_WRONLY
|
||||
#undef TMP_UNFBX_BACKUP_O_RDONLY
|
||||
#undef TMP_UNFBX_BACKUP_O_WRONLY
|
||||
#endif
|
||||
|
||||
#pragma warning( pop )
|
BIN
08-Assets/Images/ImageBag/ToonRendering/少女前线追放gif1.png
Normal file
BIN
08-Assets/Images/ImageBag/ToonRendering/少女前线追放gif1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 MiB |
BIN
08-Assets/Images/ImageBag/ToonRendering/少女前线追放gif2.png
Normal file
BIN
08-Assets/Images/ImageBag/ToonRendering/少女前线追放gif2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 MiB |
BIN
08-Assets/Images/ImageBag/ToonRendering/少女前线追放gif3.png
Normal file
BIN
08-Assets/Images/ImageBag/ToonRendering/少女前线追放gif3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 MiB |
0
document/UrealEngineNPR渲染实现/蓝色协议的方案.html.md
Normal file
0
document/UrealEngineNPR渲染实现/蓝色协议的方案.html.md
Normal file
Loading…
x
Reference in New Issue
Block a user