This commit is contained in:
2023-06-29 11:55:02 +08:00
commit 36e95249b1
1236 changed files with 464197 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
## 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即可

View File

@@ -0,0 +1,98 @@
## 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天
- ToonHair1~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
- [ ] 程序化眼睛 (乐华未提,但后续应该花时间去开发一下)
- 定制渲染功能开发
- [ ] 乐华那边测试完之后还需要的渲染功能。
- 嘴唇效果提升
![500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20230212194504.jpg)
![500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20230212194513.jpg)
![500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20230212194517.jpg)
## 耗费工时
- 楼嘉杰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

View File

@@ -0,0 +1,82 @@
---
title: EOE动画计划
date: 2023-02-06 20:59:13
excerpt:
tags:
rating: ⭐
---
# 修型
1. UE5的骨骼。
1. 主要的问题是投入/产出(效果)比。
2. 是否于动捕软件匹配。
3. ![400](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230207123346.png)
![400](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230207123404.png)
2. 提一嘴骨骼长度与中之人是否匹配。不太清楚 动捕的数据是否会在机器里先做一次重定向本身UE4的重定向因为算法的局限性对于身体比例不同的模型会有一些偏移。
![500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230207110957.png)
- 【初音未来】 「CH4NGE」 运动捕捉 【MMD】
https://www.bilibili.com/video/BV1kL4y1A77G/?spm_id_from=333.788.video.desc.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
- 动捕设备外设
![500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230207111312.png)
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 appASoul VR演唱会以及其他延伸游戏)
## 共享骨架
骨架资产的一个重要特性是单个的骨架资产可以由多个骨骼网格体使用只要其需要拥有相同的整体rig层级。这意味着骨骼命名和骨骼的层级排序必须一致才能够正确地共享。
举个例子一个骨骼网格体中的一个肢拥有3块骨骼分别命名为 **1**、**2** 和 **3**
![共享骨架示例1](https://docs.unrealengine.com/5.0/Images/animating-characters-and-objects/SkeletalMeshAnimation/AssetsFeatures/Skeleton/ShareExample1.png)
如果有另一个需要使用相同骨架资源的骨架网格体,则需要保证这些骨骼的命名和排序相同。 然而第二个骨骼网格体可以添加额外或者层级外部的骨骼。如果接收到的动画数据是用于骨骼网格体之外的骨骼,那么该数据会被忽略。
在这种情况下,你的新层级应该如下所示。在这里,第二个骨骼网格体有着额外的骨骼,但是并没有改变第一个骨架的层级结构,也没有造成冲突。
![共享骨架示例2](https://docs.unrealengine.com/5.0/Images/animating-characters-and-objects/SkeletalMeshAnimation/AssetsFeatures/Skeleton/ShareExample2.png)
然而,为使两个骨架网格体使用相同的骨架资源,无法对层级进行重新排序,也无法重命名骨骼。如果第二个骨骼网格体要使用不同的骨骼层级和命名结构,那么需要重新创建一个新的骨骼资产。
![共享骨架示例3](https://docs.unrealengine.com/5.0/Images/animating-characters-and-objects/SkeletalMeshAnimation/AssetsFeatures/Skeleton/ShareExample3.png)
如果你在不改变顺序的情况下插入一个骨骼,那么能够正常共享。然而大部分情况下,额外的骨骼可能会导致骨架产生意料之外的变形偏移。我们建议尽量避免这样做。
![共享骨架示例4](https://docs.unrealengine.com/5.0/Images/animating-characters-and-objects/SkeletalMeshAnimation/AssetsFeatures/Skeleton/ShareExample4.png)
结合这些共享规则,再虚幻引擎中有几种方式来在骨骼网格体之间共享骨架。以下是一些细节。
### 导入期间合并
第一种共享骨架的方式是在FBX导入过程期间进行的。导入你的新骨骼网格体时包含额外的和外部的骨骼遵循上述的共享规则你可以从项目中已有的骨骼网格体中选择一个骨架。虚幻引擎将会将这些骨架合并并且将全部新骨骼添加到层级中。除此以外你的骨架的比例会由创建它的原始骨骼网格体来定义。
![ShareVar1.png](https://docs.unrealengine.com/5.0/Images/animating-characters-and-objects/SkeletalMeshAnimation/AssetsFeatures/Skeleton/ShareVar1.png)
![ShareVar2.png](https://docs.unrealengine.com/5.0/Images/animating-characters-and-objects/SkeletalMeshAnimation/AssetsFeatures/Skeleton/ShareVar2.png)
## IK Rig 与Fullbody IK
[[实时重定向与骨骼动画共用相关#IK Rig In Animation Blueprint]]
- 解决高跟鞋与平底鞋转换的问题。(应该可以)
- 可以解决动捕中一些穿帮问题。
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230207112012.png)
![image.png](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20230207112108.png)
嘉然爬椅子
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
- 机器学习 实现肌肉效果

View 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 Solidhttps://hub.vroid.com/en/characters/515144657245174640/models/6438391937465666012
- AvatarSample_Fhttps://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与若干组件实现。
- VRM4UEditorSequence编辑器函数EvaluateCurvesFromSequence()实现。
- VRM4UMiscLog日志类型定义。
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
![](https://ruyo.github.io/VRM4U/assets/images/small/04i_option.png)
同时可以通过下面的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官方模型进行测试。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_MToonAttachActor.png)
- SceneCaptureComponent2D组件用于渲染Depth到RT上以此来制作Shadow。勾选WBP_VRMMaterial->Model->MToonAttachActor->Advanced->Debug Shadow Cube后会在角色正上方显示深度贴图Cube。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_ShowDebugShadowCube.png)
在MF_VrmMToonBase中搜索bUseShadowMap就可以找到所引用的计算函数
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_bUseShadowMap.png)
里面逻辑就是通过Depth贴图计算ShadowMask
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_ShadowMask.png)
- UVrmPoseableMeshComponent用于实现OutlineAOShadow目前没有作用。主要使用M_VrmNone一个Masked Unlit材质通过模型外扩实现Outline效果。
### 后处理效果
MToonMaterialSystem
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_PostProcessSystem.png)
这些蓝图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. 设置TargetMeshForLightSkeletalMesh或者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。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_ToonPost.gif)
##### M_VrmMToonBaseOpaque、M_BaseLight、M_PostToon比较
- M_BaseLight有基础灯光计算NoL。**实际使用的是MI_BaseLight**所以会有一些参数改变比如他是一个PBR材质有调整过BaseColor、Metalic、Roughness。
- M_ToonPost
- M_VrmMToonBaseOpaque实现了TAA透明效果
M_VrmMToonBaseOpaque
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_M_VrmMToonBaseOpaque.png)
M_BaseLight用于渲染光照效果
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_M_BaseLight.png)
M_ToonPost用于取得光照效果并且合成在一起。使用SceneColor节点取得上一个Material渲染的结果作为灯光结果因为是透明材质所以是在后面渲染的
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_M_ToonPost.png)
#### 使用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。
![](https://ruyo.github.io/VRM4U/assets/images/02d_custom1.png)
![](https://ruyo.github.io/VRM4U/assets/images/02d_custom2.png)
### 其他Hack技巧
- Anti-ToneMapping使用UE自带的函数抵消ToneMapping效果。
- Exposure使用EyeAdaptation节点控制亮度。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/8B1F13475DF3420E9F90FD29A1A0D7B4.octet-stream)
### 拍摄模式
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/
![](https://ruyo.github.io/VRM4U/assets/images/small/02p_fix1.png)
![](https://ruyo.github.io/VRM4U/assets/images/small/02p_fix2.png)
![](https://ruyo.github.io/VRM4U/assets/images/small/02p_n2.png)
![](https://ruyo.github.io/VRM4U/assets/images/small/02p_n1.png)
![](https://ruyo.github.io/VRM4U/assets/images/small/02p_n3.png)
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。相关逻辑位于材质中。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/VRM4U_FovFix.png)
## 动画
值得学习:
- 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的RigTarget为上一步新建的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即可。
![](https://ruyo.github.io/VRM4U/assets/images/small/03r_setM1.png)
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配合LeapMotionhttps://www.bilibili.com/video/BV1KA41137os?spm_id_from=333.337.search-card.all.click
6. 全身动作捕捉软件ThreeDPoseTrackerhttps://www.bilibili.com/video/BV1vy4y157An?spm_id_from=333.999.0.0
## 其他模块文件
- VRM4U
- VrmUtil导入选项、GetXXX工具函数、骨骼转换映射表
- VrmUtilImage图片处理函数
- VRM4UImporter
- VRM4UDetailCustomize针对FVRMRetargetSrcAnimSequence的自定义编辑器
- VrmAssetListThumbnailRendererAsset缩略图渲染控制
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.275.0截至2022/04很容易支持最新版本。
- 依次支持VRM1.0β
- VRM1.0数据在一定程度上可以导入。
- 您可以保留本地轴或在导入时选择它。
- 对应M Moon的新功能。
- 实验实现

View 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
### 其他参考
MToonhttps://dwango.github.io/vrm/univrm/shaders/mtoon/
Ue4中的卡通渲染https://qiita.com/com04/items/a7895160df8d854fe924
## 曝光与ToneMapper问题解决
ToneMapper与曝光会影响贴图的亮度所以需要尝试干掉或者抵消掉他们的影响。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/1DC2666D4C03467FA93C5A3B160DF5B8.octet-stream)
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/8B1F13475DF3420E9F90FD29A1A0D7B4.octet-stream)
手动去除了Gammer矫正以及ToneMapper取消并且使用 人眼适应节点抵消曝光效果。
除此之外还使用RayTracingQualitySwitchReplace节点与人眼适应节点做了Raytracing质量调整。手机端为1
## 阴影
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/093F34C991EB4A9E8F024D6D5E156BEB.octet-stream)
使用了SceneCaptureComponent2D。
在Orthographic平行投影*将CaptureSource设置为 "SceneDepth in R"TexrureTarget格式设置为 "RTF R32f"。
这将导致深度在UnrealUnit中线性地写入缓冲区默认为1=1cm。 别紧张。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/9A6D0FAA881148ABBDB8CF64E5C9CB1D.octet-stream)
要用Shadowmap投出一个阴影你只需要知道从光线中看到的深度以及投影矩阵的矩阵。
我们会把这些东西找回来,传给材料。
https://www.shibuya24.info/entry/shadowmap
官方的SceneCaptureComponent2D会做很多无用的渲染工作推荐自己重新写一个以减少不必要的消耗。
### 蓝图传递逆矩阵
在蓝图中国计算出SceneCaptureComponent2D的逆矩阵后将其传入材质中。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/9DB8EE6F87D44F5BA8D4E2A68842A79F.octet-stream)
### 材质内
在一个自定义节点中将参数打包成float4x4并进行计算。
现在你可以从世界坐标中参考相应的影子图。
在材料函数中结果返回0-1。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/537BB41619BF456399438FE6D0974CE1.octet-stream)
## 描边
在具体节点方面,
投影结果 → TransformPosition节点转换为ViewSpace的转换结果
Pixel Thickness → VectorLength的值。
乘以倒数→部分除以VectorLength的值再乘以反数→部分除以VectorLength的值。
使用ScreenResolusion的原因是为了确保不影响窗口大小和宽高比。
我不确定我是否准备好了......
在MToon再现方面我们需要能够选择 "恒定厚度模式 "和 "世界坐标参考模式"。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/545E0EC3BB5C47C089BC47504FA4B742.octet-stream)
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所以有一个分数。 最后,正常化。
```
### VRMglTF转换时需要注意
改变坐标系。
- 转换为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实现运行时重定向
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/E65AE6C12BF84F12B26B2D0E80FC1AF8.octet-stream)
将复制资产设置为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 トゥーンシェーディングを使ったDアニメ表現
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

View File

@@ -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);
}
```

View 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
MToonhttps://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
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/1E4FA28EC67B429A9AA80930E9EAF9F3.octet-stream)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/57CA6879C4F1427FA26539F0741C08EE.octet-stream)
PBR
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/35016887610E4A888B2347F20B6172B3.octet-stream)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/C793A5679CA94AFC84610F1E47C6407B.octet-stream)
SSS
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/4A8C1C4C6D34423B9DABFE8131C6EF64.octet-stream)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/DEDE97C7294E4D17844D75DAF46FE040.octet-stream)
## 灯光调整
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/0C39B9753A3A49A8BEF2B020BF88D4D4.octet-stream)
2种方法
- 开启天光的LowerHemisphereIsSolidColor但这样会场景效果变得有些奇怪
- 添加一个从下往上的方向光
使用这个方法 以及 PBR材质中的BaseColor与Emissive进行插值来接近HalfLambda的效果。
## 调整面部法线来柔化面部阴影
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/84D880E4938943ECB433A5B8E5379B0F.octet-stream)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/993BEE4A49794E348A78ACA8CCB4D7E9.octet-stream)
## 曝光与ToneMapper问题解决
ToneMapper与曝光会影响贴图的亮度所以需要尝试干掉或者抵消掉他们的影响。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/1DC2666D4C03467FA93C5A3B160DF5B8.octet-stream)
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/8B1F13475DF3420E9F90FD29A1A0D7B4.octet-stream)
手动去除了Gammer矫正以及ToneMapper取消并且使用 人眼适应节点抵消曝光效果。
除此之外还使用RayTracingQualitySwitchReplace节点与人眼适应节点做了Raytracing质量调整。手机端为1
## 阴影
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/093F34C991EB4A9E8F024D6D5E156BEB.octet-stream)
使用了SceneCaptureComponent2D。
在Orthographic平行投影*将CaptureSource设置为 "SceneDepth in R"TexrureTarget格式设置为 "RTF R32f"。
这将导致深度在UnrealUnit中线性地写入缓冲区默认为1=1cm。 别紧张。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/9A6D0FAA881148ABBDB8CF64E5C9CB1D.octet-stream)
要用Shadowmap投出一个阴影你只需要知道从光线中看到的深度以及投影矩阵的矩阵。
我们会把这些东西找回来,传给材料。
https://www.shibuya24.info/entry/shadowmap
官方的SceneCaptureComponent2D会做很多无用的渲染工作推荐自己重新写一个以减少不必要的消耗。
### 蓝图传递逆矩阵
在蓝图中国计算出SceneCaptureComponent2D的逆矩阵后将其传入材质中。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/9DB8EE6F87D44F5BA8D4E2A68842A79F.octet-stream)
### 材质内
在一个自定义节点中将参数打包成float4x4并进行计算。
现在你可以从世界坐标中参考相应的影子图。
在材料函数中结果返回0-1。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/537BB41619BF456399438FE6D0974CE1.octet-stream)
## 描边
在具体节点方面,
投影结果 → TransformPosition节点转换为ViewSpace的转换结果
Pixel Thickness → VectorLength的值。
乘以倒数→部分除以VectorLength的值再乘以反数→部分除以VectorLength的值。
使用ScreenResolusion的原因是为了确保不影响窗口大小和宽高比。
我不确定我是否准备好了......
在MToon再现方面我们需要能够选择 "恒定厚度模式 "和 "世界坐标参考模式"。
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/545E0EC3BB5C47C089BC47504FA4B742.octet-stream)
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所以有一个分数。 最后,正常化。
```
### VRMglTF转换时需要注意
改变坐标系。
- 转换为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实现运行时重定向
![image](D:/youdaonote-pull-master/youdaonote/youdaonote-images/E65AE6C12BF84F12B26B2D0E80FC1AF8.octet-stream)
将复制资产设置为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 トゥーンシェーディングを使ったDアニメ表現
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

View File

@@ -0,0 +1,445 @@
# 实用库
`#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;
```
![](https://pic2.zhimg.com/v2-80c97cefce517b57de36593a44425ce5_r.jpg)
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;
```
![](https://pic2.zhimg.com/80/v2-e14db6acc5d6f7400009c2a3ff3e2b89_720w.jpg)
代码中首先计算了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/
>本作的眼睛分为三个部分,眼白的部分是一个内凹的形状,瞳孔的部分则分为了向内凹的部分和向外突出的部分。
![](../../public/UrealEngineNPR渲染实现/樱花大战眼睛效果.png)
>瞳孔的高光叠加在其突出的半透明部分上。根据摄像角度的不同,各个部分的贴图会分开进行移动,使得在哪个角度高光都能处在一个刚好的位置。
控制上,有针对高光上下左右的移动强度与控制移动范围的参数共同作用。
![](../../public/UrealEngineNPR渲染实现/樱花大战眼睛贴图.png)
>从左边开始是作为基础颜色的Albedo以及用于Mask瞳孔的Alpha贴图用于在Albedo上进行叠加的spt贴图以及两张瞳孔高光以及反应环境的matcapture贴图。
虽然很多动画风格的渲染中会省略掉瞳孔中的虹彩部分但是本作为了提高角色靠近时的效果进行了详细的绘制同时为了体现环境的变化与matcap的贴图进行叠加。
高光贴图有两张分别使用不同的UV动画进行控制用于表现眼睛的湿润感。虽然是很细微的操作但是对于表现角色的感情非常的有用。
![](../../public/UrealEngineNPR渲染实现/UntiyChanSSS_Eye.jpg)
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上去然后用光照方向和菲涅尔去影响强度变化。
![](https://pic4.zhimg.com/80/v2-c4859a4c140a895547136557e9c4f01b_720w.jpg)
使用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_BaseColorBaseColor是否受到LightColor影响
- Is_LightColor_ShadeColor1st_ShadeColor是否受到LightColor影响
FViewUniformShaderParameter添加数据
-
### 顶点色
用于存储一些低精度数据,插值即可
- R:
- G:描边宽度
- B:
蓝色协议的R:阴影区域标记 与 B:Ao而罪恶装备使用贴图来传递信息。
### lightmap
![](https://pic2.zhimg.com/80/v2-56012886fafbaf36932f03b0ad65a165_720w.jpg),G为阴影控AOR为高光强度参数金属和光滑材质的部分设置的更大一些。B通道用于照明控制。最大值为高光反之值越小高光越淡。![](https://pic4.zhimg.com/80/v2-748ebbdd4da3efe74054c8215be8b023_720w.jpg)
![](https://pic2.zhimg.com/80/v2-74e1a9fba264af2b18e66616d9f86831_720w.jpg)
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为falseGetDepthStencilAccess为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 %}

View File

@@ -0,0 +1,154 @@
# 功能表
厚涂部分写在:[[厚涂风格研究与开发笔记]]
#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
![](https://pic4.zhimg.com/80/v2-c28b540503341f32431aa34ad2a24fa7_720w.jpg)
![](https://pic1.zhimg.com/80/v2-0703306dff26079866330cdd3a3c2cd8_720w.jpg)
按照需求优先级进行排列:
- [ ] 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区域内进行检测**:手指区域。

View File

@@ -0,0 +1,137 @@
### 其他项目分享
- [ ] 「生と死」を物語る陰影表現とは――『Xenoblade3ブレイド3』のキャラクターを魅せる2灯トゥーンシェーディング、世界を描くアップサンプリング【CEDEC+KYUSHU 2022】 https://gamemakers.jp/article/2023_03_22_33475/?fbclid=IwAR0v4cetIP5nKgz1N_xT45FMaoB2ElqZHedu60ZBfhix1hxsxRn6zx5c2ak
### 卡通渲染总结
---
- [ ] 米哈游技术总监首次分享:移动端高品质卡通渲染的实现与优化方案 https://zhuanlan.zhihu.com/p/37001473
- [ ] [Unite 2018] 米哈游在Unity中实现高品质的卡通渲染 https://www.bilibili.com/video/BV1Df4y1X7G3/
- [ ] 【从负一到零的卡通渲染】卡渲笔记https://zhuanlan.zhihu.com/p/369443326
- [ ] 【从零到零点一的原神卡渲还原】记录还原的尝试和思考 https://zhuanlan.zhihu.com/p/376094989
- [ ] 原神角色渲染Shader分析还原 https://zhuanlan.zhihu.com/p/360229590
- [ ] 原神图形技术简析及杂谈 https://zhuanlan.zhihu.com/p/260824391
- [ ] 卡通渲染小记一(原神篇) https://zhuanlan.zhihu.com/p/374466780
- [ ] [译] 崩坏3的卡通渲染实现方式拆解 https://zhuanlan.zhihu.com/p/120908528
-
---
- [ ] 卡通渲染相关小结 https://zhuanlan.zhihu.com/p/83619204
- [ ] Unity卡通渲染总结 NPR Toon Shading https://zhuanlan.zhihu.com/p/330599077
- [ ] 卡通渲染学习总结 https://zhuanlan.zhihu.com/p/163791090
- [ ] 【02】从零开始的卡通渲染-着色篇1 https://zhuanlan.zhihu.com/p/110025903
- [ ] 【03】从零开始的卡通渲染-着色篇2 https://zhuanlan.zhihu.com/p/111633226
- [ ] 【04】从零开始的卡通渲染-PBR篇 https://zhuanlan.zhihu.com/p/115238808
- [ ] 卡通渲染-----关于常规着色效果的那点公式 https://zhuanlan.zhihu.com/p/258615988
- [ ] Godot还原 碧蓝幻想Versus GGX 卡通渲染 https://zhuanlan.zhihu.com/p/390781757
---
新版罪恶装备
- [ ] 风格化角色渲染整理之罪恶装备Strive https://zhuanlan.zhihu.com/p/384202640
---
- [ ] 简单谈谈原神的渲染部分 https://zhuanlan.zhihu.com/p/259589537
- [ ] 到目前为止的二次元渲染总结 https://zhuanlan.zhihu.com/p/126668414
- [ ] 用SDF处理卡通内描线的锯齿问题 https://zhuanlan.zhihu.com/p/113190695
- [ ] 借AI梦境档案谈谈NPR渲染 https://zhuanlan.zhihu.com/p/87619107
---
- [ ] 【JTRP】Unity HDRP卡通渲染总结 https://zhuanlan.zhihu.com/p/385186333
- [ ] 开源JTRPUnity 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
---
### 描边
- [ ] High-Quality Real-Time Outline Rendering 描边学习记录 https://zhuanlan.zhihu.com/p/318769754
- [ ] 在shader中实现五种描边方法
- [ ] 【01】从零开始的卡通渲染-描边篇 https://zhuanlan.zhihu.com/p/109101851
- [ ] GPU Pro 1 使用几何着色器的NPR效果 | NPR Effects Using the Geometry Shader——使用几何着色器进行描边
- [ ] 在UE4引擎中做卡通描边的一点心得 https://zhuanlan.zhihu.com/p/234535777
- [ ] 修复发尖的描边法线 https://zhuanlan.zhihu.com/p/405518306
### 眼睛头发面部阴影
- [ ] 神作面部阴影渲染还原 https://zhuanlan.zhihu.com/p/279334552
- [ ] 二次元角色卡通渲染—面部篇 https://zhuanlan.zhihu.com/p/411188212
- [ ] 二次元角色卡通渲染—眼睛篇 https://zhuanlan.zhihu.com/p/402861632
- [ ] Unity URP】以Render Feature实现卡通渲染中的刘海投影 https://zhuanlan.zhihu.com/p/232450616
### 场景
- [ ] UE4 吉卜力 风格化shader ~ 树 https://zhuanlan.zhihu.com/p/402180364
- [ ] [游戏美术文档]UE4风格化草地 https://zhuanlan.zhihu.com/p/355609504
- [ ] 虚幻4重现“哈尔的移动城堡”花园附风格化草地制作分享https://zhuanlan.zhihu.com/p/272734944
- [ ] 虚幻4还原风格化“云海群山”场景附岩石贴图制作思路https://zhuanlan.zhihu.com/p/337054665
- [ ] UE4中用Niagara实现procedural浪花 https://zhuanlan.zhihu.com/p/100700549
- [ ] Kuwahara 滤波UE4卡通渲染基础教程 Part4Paint Filter https://zhuanlan.zhihu.com/p/74807856
- [ ] 卡通风格场景的定制化技术部分 https://zhuanlan.zhihu.com/p/338334377
- [ ] PBR光照体系下的卡通渲染光照模型 https://zhuanlan.zhihu.com/p/166147653
- [ ] 【翻译】在UE4 中创建风格化的丛林环境 https://zhuanlan.zhihu.com/p/261083501
### 国外文章翻译
- [ ] Cygames21年11月技术分享上篇https://zhuanlan.zhihu.com/p/441159789
- [ ] 「细节到眼泪、眼睛、汗水赛马娘3D角色模型技术分享」——Cygames21年11月技术分享下篇https://zhuanlan.zhihu.com/p/441560356
- [ ] 【翻译】NPR角色渲染总结!现在是迈向更高境界之时https://zhuanlan.zhihu.com/p/345915866
- [ ] [简单机翻/多图]《使《蓝色协议》成为“剧场动画品质”的手法是》https://zhuanlan.zhihu.com/p/339603529
- [ ] 翻译《使用UE4开发圣剑传说3的经验分享•角色渲染部分》+效果实现 https://zhuanlan.zhihu.com/p/360587048
- [ ] 卡通渲染手游七大罪的技术介绍,一 https://zhuanlan.zhihu.com/p/161326626
### 其他技术
- [ ] 「黑丝」的材质如何用shader实现数学模型是怎样的https://www.zhihu.com/question/35094847/answer/61161188
- [ ] 遮挡时的模糊网点效果 https://zhuanlan.zhihu.com/p/370140711
- [ ] 《Honey Select》捏人剖析 https://zhuanlan.zhihu.com/p/28471808
## 参考项目
- 《莱莎的炼金工坊12》
- 《破晓传说》
- 《蓝色协议》https://zhuanlan.zhihu.com/p/229621134?tdsourcetag=s_pctim_aiomsg
- 《kurtzpel》
- 《守望先锋》
- 《二之国》
- 原神掰法线 https://www.bilibili.com/video/BV1Nr4y1K7Wc
## ue4与u3d参考项目
- [x] UnityChanToonShader v1、v2、URP
- [x] UnityURPToonLitShaderExample:https://github.com/ColinLeung-NiloCat/UnityURPToonLitShaderExample
- [x] JTRP:https://github.com/Jason-Ma-233/JasonMaToonRenderPipeline
- [x] VRM4U
## 抓帧工具
- 使用 Intel GPA 与 分析3D程序和抓取模型https://www.cnblogs.com/TracePlus/p/4233606.html
## u3d资料
- 走近卡通渲染——关于Trick的二三事https://learn.u3d.cn/tutorial/cel-shading-trick
- 卡通渲染——风格和影视化探索 https://learn.u3d.cn/tutorial/cel-shading-cinematics
- 【Unite Tokyo 2018】ToonShader对话环节#1『RealtimeToonShader彻谈』Part2:https://matrix64.github.io/Unite18ToonShader_p2-post/
- 〈七つの大罪〉をゲームで!高品質グラフィックを具現化するための技法と開発最適化のご紹介 Unity Learning Materials:https://learning.unity3d.jp/3227/
- スマホVTuber向け揺れモシステムを「ユニティちゃんライセンス」で無料公開 Unity Learning Materials:https://learning.unity3d.jp/668/
- (466) 【Unite 2017 Tokyo】VR MAGIC キャラクターに命を吹き込んだこの4年間の記録 - YouTube:https://www.youtube.com/watch?v=nWR816af2dU&feature=youtu.be
- Unity Chan研究3思维脑图 - 知乎:https://zhuanlan.zhihu.com/p/105047326
- (466) 【Unite 2017 Tokyo】Unityで楽しむンフォトリアルな絵づくり講座トゥーンシェーダー・マニアクス - YouTube:https://www.youtube.com/watch?v=6aNB9LhSx7g
- 【Unite 2017 Tokyo】Unityで楽しむンフォトリアルな絵づくり講座トゥーンシェーダー・マニアクス:https://www.slideshare.net/Unite2017Tokyo/unite-2017-tokyounity-75800622
## UE4资料
- 关于matcap材质的效果优化 - 知乎:https://zhuanlan.zhihu.com/p/57775690
- UE4_Ramp生成器_仿原神皮肤shaderhttps://zhuanlan.zhihu.com/p/248998437
- UE4-Niagara与材质在二次元的新展开http://asher.gg/blog/wp-content/uploads/2019/11/UE4-Niagara%E4%B8%8E%E6%9D%90%E8%B4%A8%E5%9C%A8%E4%BA%8C%E6%AC%A1%E5%85%83%E7%9A%84%E6%96%B0%E5%B1%95%E5%BC%80.pdf
- UnrealEngine4】从虚幻四的着色模型到NPRhttps://zhuanlan.zhihu.com/p/30965016
### UE4场景
百度网盘:
- 风格化场景包
- 仿原神蒙德场景Stylized Medieval Village 4.26
- 吉普力风格场景Stylized Village by Meshingun Studio 4.23-4.26.zip https://mega.nz/folder/iiBCiRpa#B0aaIOv32tJhybTe9gRZXg
- 比较烂的 stylized-colorful-camp-pack https://www.bilibili.com/video/BV1Gv411T77m?spm_id_from=333.851.dynamic.content.click
## 其他资料
- 知乎收藏夹
- GUILTY GEAR Xrd REVELATOR 3D进化出的非照片真实视觉 https://www.cnblogs.com/TracePlus/p/5697705.html
- 【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密前篇1https://www.cnblogs.com/TracePlus/p/4205798.html
- 【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密前篇2https://www.cnblogs.com/TracePlus/p/4205834.html
- 【翻译】西川善司的「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密后篇https://www.cnblogs.com/TracePlus/p/4205978.html
- artstation市场https://www.artstation.com/marketplace/p/9nrrR/genshin-impact-character-shader-for-eevee
风蚀之月Blog
- UE4-Fest-圣剑传说3笔记:https://blog.ch-wind.com/unreal-fest-trials-of-mana-3-note/
- UE4-Fest-NPR笔记:https://blog.ch-wind.com/ue4-fest-npr-note/
- CEDEC2020-新樱花大战笔记Toon风格的PBR:https://blog.ch-wind.com/cedec2020-new-sakura-wars-note/

View File

@@ -0,0 +1,204 @@
---
title: 卡通面部阴影控制
date: 2023-03-08 10:18:36
excerpt:
tags:
rating: ⭐
---
# 相关资料
- [虚幻5渲染编程(风格化渲染篇) 第七卷: Toon shadow control](https://zhuanlan.zhihu.com/p/519728086)
- [二次元角色卡通渲染—面部篇](https://zhuanlan.zhihu.com/p/411188212)
- [掰法线的方法 SP Normal Convert Shader](https://note.com/sfna32121/n/n8d46090005d1)
- 绘制SDF贴图方法
- [卡通脸部阴影贴图生成 渲染原理](https://zhuanlan.zhihu.com/p/389668800)
- [【教程】使用csp等高线填充工具制作三渲二面部阴影贴图](https://www.bilibili.com/video/BV16y4y1x7J1/)
- SDF Shadow Shader
- [神作面部阴影渲染还原](https://zhuanlan.zhihu.com/p/279334552)
# SDF面部阴影
## Signed Distance Fields
- 生成距离场算法: http://www.codersnotes.com/notes/signed-distance-fields/
>这实质上就是找目标点及左上方四个点中SDF最小的值。PASS0就是按照从上到下从左到右的顺序遍历整个图像遍历完成之后对于所有物体外的点如果距离它最近的物体是在它的左上方那么它的SDF值就已确定。类似的PASS1就是按照从下到上从右到左的顺序依次比较右下方的四个点遍历完成之后对于所有物体外的点如果距离它最近的物体是在它的右下方那么它的SDF也已经确定了。两个PASS结合那么整个图像的SDF就都计算出来了。其实这里称SDF并不准确因为只算了物体外到物体边界的距离是正值并没有signed一说只有做完下一步计算物体内到物体外的距离两个距离相减才是SDF
>第二个grid的GenerateSDF就很好理解了就是**计算物体内部到外部的距离**。因为一个点要么在物体内要么在物体外所以两次的SDF值要么全为零在边界上要么一个为0一个为距离值。用grid1(pixel).sdf - grid2(pixel).sdf就能得到完整的SDF。
1. 读取传入的贴图并且构建2个RT一个黑inside Zero一个白Outside infinitely当亮度大于0.5128时则反转。
2. 分别对这2个RT执行 GenerateSDF()。
3. 将2个RT记录的距离值相减获得最终的SDF。
链接中的代码:
```c++
struct Point
{
int dx, dy;
int DistSq() const { return dx*dx + dy*dy; }
};
struct Grid
{
Point grid[HEIGHT][WIDTH];
};
```
```c++
void Compare( Grid &g, Point &p, int x, int y, int offsetx, int offsety )
{
Point other = Get( g, x+offsetx, y+offsety );
other.dx += offsetx;
other.dy += offsety;
if (other.DistSq() < p.DistSq())
p = other;
}
//采用分离式计算SDF
//GenerateSDF:计算并且取得当前UV坐标附近(第一次找到)像素最小值(模型使用 0值填充即dx,dy=0,放到RT中。
void GenerateSDF( Grid &g )
{
// Pass 0
for (int y=0;y<HEIGHT;y++)
{
for (int x=0;x<WIDTH;x++)
{
Point p = Get( g, x, y );
Compare( g, p, x, y, -1, 0 );
Compare( g, p, x, y, 0, -1 );
Compare( g, p, x, y, -1, -1 );
Compare( g, p, x, y, 1, -1 );
Put( g, x, y, p );
}
for (int x=WIDTH-1;x>=0;x--)
{
Point p = Get( g, x, y );
Compare( g, p, x, y, 1, 0 );
Put( g, x, y, p );
}
}
// Pass 1
for (int y=HEIGHT-1;y>=0;y--)
{
for (int x=WIDTH-1;x>=0;x--)
{
Point p = Get( g, x, y );
Compare( g, p, x, y, 1, 0 );
Compare( g, p, x, y, 0, 1 );
Compare( g, p, x, y, -1, 1 );
Compare( g, p, x, y, 1, 1 );
Put( g, x, y, p );
}
for (int x=0;x<WIDTH;x++)
{
Point p = Get( g, x, y );
Compare( g, p, x, y, -1, 0 );
Put( g, x, y, p );
}
}
}
```
```c++
int dist1 = (int)( sqrt( (double)Get( grid1, x, y ).DistSq() ) );
int dist2 = (int)( sqrt( (double)Get( grid2, x, y ).DistSq() ) );
int dist = dist1 - dist2;
```
## SDF面部阴影
本质是使用SDF的过度原理(对X张贴图生成SDF贴图使用HalfNoL来查询一个0,180区间的阴影。
### 效果
- SDF面部阴影效果32秒处:https://www.bilibili.com/video/BV1JG4y1r7EP/?spm_id_from=333.337.search-card.all.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
![[SDFShadowEffect.mp4]]
### 绘制方法
- 等高线绘制方法:https://www.bilibili.com/video/BV16y4y1x7J1/?spm_id_from=autoNext&vd_source=d47c0bb42f9c72fd7d74562185cee290
- 注意:等高线曲线需要关闭绘制抗锯齿。
- 使用色阶就可以把各个角度的SDF贴图提取出来。
- Maya烘焙绘制方法应该也可以用UE
1. 给脸部模型一个Lambert材质并且根据X轴角度要求设置好方向光。
2. 使用Arnold的RenderToTexture将贴图输出。
3. 使用PS的色阶功能将颜色二值化并在基础上进行修改。
4. **对8张贴图两两计算SDF值之后进行重映射** 。最终亮度=亮度/8 * index
5. 将结果合并到一起。
### 代码
```c++
float3 UP = float3(0,1,0); // cs脚本里动态修改
float3 Front = float3(0,0,1);
float3 Left = cross(UP, Front);
float3 Right = -cross(UP, Front);
float FrontL = dot(normalize(Front.xz), normalize(L.xz));
float LeftL = dot(normalize(Left.xz), normalize(L.xz));
float RightL = dot(normalize(Right.xz), normalize(L.xz));
float lightAttenuation = (FrontL > 0) * min(
(surfaceData._lightMap.r > LeftL),
1-(1 - surfaceData._lightMap.r < RightL)
);
```
## Multi SDFFaceShadow
- X轴(正面)1°~90°8张 =>
- X轴(背面-1°~-90°):考虑**单个阴影贴图**或者**边缘光计算方式的阴影**。
- Y轴30°、 90°、150°+ 背面阴影
- Y轴(多张贴图)30、50°、70°、90°、110°、130°+ 背面阴影
- -60° 、-40° 、-20° 、0、20°、40°
- 考虑硬编码写死,所以可以使用不同步长的角度。
- 贴图精度可以考虑8bit=>16bit
>Y轴结果插值可以考虑使用扩散Shader
https://www.shadertoy.com/view/tlyfRw
ShaderBits的 UV出血填充贴图方法。
# 距离场+法线混合计算
https://www.youtube.com/watch?v=T2iI9hbNqLI
![[SDF_Normal_Shadow.webp]]![[SDF_Normal_Shadow_Effect.mp4]]
虽然分享中并没有说明具体怎么计算,不过有了这个思路,做出类似的效果并不是难事,以下简单还原的过程。
首先主要代码如下:(图中还有两块区域,即额头偏亮的区域和鼻部底层暗下去的区域,由于并不清楚这两部分的应用场景,下面的计算就忽略这两部分区域,另外也忽略了精度问题)
```c++
float3 shaodwRamp = tex2D(_ShaodwRamp, input.uv);
float3 lightDirH = normalize(float3(L.x, 0, L.z));
float NoL = dot(N, lightDirH);
float triArea = saturate((shaodwRamp.g - 0.5) * 2);
float noseArea = saturate(shaodwRamp.g * 2);
//lightAtten
float lightAtten = dot(lightDirH, forward);
lightAtten = saturate(pow(abs(fmod(lightAtten + _TriAreaOffset, 1)
- 0.5) * _TriAreaLimitUp, _TriAreaPow) * _TriAreaLimitDown - _TriAreaLimitDown + 1);
//uvMask
float filpU = saturate(sign(dot(L, left)));
float cutU = step(0.5, input.uv.x);
float uvMask = lerp(1 - cutU, cutU, filpU);
//NoL
float faceShadow = step(0, NoL);
//三角区域
float triAreaLight = step(lightAtten, triArea) * uvMask;
//鼻部区域
float noseAreaLight = step(lightAtten, 1 - noseArea) * (1 - uvMask);
float noseAreaShadow = (step(lightAtten, noseArea) - 1) * uvMask + 1;
//最后的阴影混合
float Shadow = min(max(max(faceShadow, triAreaLight), noseAreaLight), noseAreaShadow);
```
`SDF取值的思路和上一小节SDF的思路是类似的也是利用lightAtten的结果来取值只不过这张图分别记录了两个区域的值中间值是0.5,三角区域的阈值在(0.5, 1],鼻尖区域的阈值在[0, 0.5)。将这两部分区域重新映射回[0, 1]的区间上就可以使用之前的step操作了然后利用计算的uvMask屏蔽掉不需要的区域最后混合三个部分三角亮区鼻部亮区鼻部暗区的结果就可以了。`
另外由于SDF与NdotL的结果要互相配合所以要对lightAtten的结果重新映射以便控制重新映射的结果如下图的紫色曲线链接内有每个参数的值
![](https://pic1.zhimg.com/80/v2-e09ca89f907402120b95de8b6b844fbc_720w.webp)
# Matcap
又到了万能的Matcap出场了《七つの大罪 光と闇の交戦 : グラクロ》的技术分享中讲述了这种方式,作者在分享中讲到使用这种方式的原因:「根据(动画)演出中非现实的阴影设定,比起物理事实更重视感情的传达」
在大部分情况下卡通画面不需要保证光照的正确性只要画面中的效果是感观舒适就是可行的。所以在非动态光照的下使用Matcap表现角色的光影结构切分是非常简单并且效果还不错的方式。
![](https://pic4.zhimg.com/80/v2-8761284b26369d168ef9ad41e26f287f_720w.webp)

View File

@@ -0,0 +1,324 @@
---
title: YusufUmar_kama分析
date: 2022-08-15 13:31:52
excerpt:
tags: 卡通渲染
rating: ⭐⭐
---
- Skin
- Hair
- Cloth
- Glod
- Lotus
- LotusLeaf
- Water Surface
# 皮肤
根节点使用了Principed BSDFGGXRandomWalkFixed Radius。Subsurface值为0.01开启了SSS效果其他为默认参数。
使用Ucupaint Skin连接Pin
- Color
- Roughness
- Normal
## 要点:
1. 基底皮肤颜色float3(0.846873,0.602632,0.512498)、Roughness 0.3、Metallic 0 。Subsurface0.01
2. **皮肤哑光效果**使用Noise贴图并且使用ddx/ddy实现。
3. [[#yP Layer Fake Lighting全身]]效果模拟出皮肤的SSS以及环境光照效果增强厚涂皮肤的质感。
4. [[#yP Layer Red Skin (肩部区域与手指)]]效果模拟皮肤的SSS效果增强厚涂皮肤的质感。
5. [[#yP Layer Skin AO 皮肤上的AO效果]]模拟皮肤上的AO
## 贴图
>项目使用了4K贴图并且将4x4个贴图会在一起并且通过平移UV与使用不同UV通道方式来切换。
- `~Ucupaint Skin Image Atlas` Face UV0.25,0.75贴图作为Alpha来赋予**脸部**Roughness内部Overrider 0.5)。
- `~Ucupaint Skin Image Atlas` Face UV0 00.5腮红Mask
- `~Ucupaint Skin Image Atlas` Face UV0.75,0.75腮红斜杠Mask
- `~Ucupaint Skin Image Atlas` Face UV0.25,0.75嘴唇红Mask
- `~Ucupaint Skin Image Atlas2`UVMap UV0.25,0贴图作为Alpha来赋予**身体**Roughness内部Overrider 0.6Alpha * 0.75)。
- `~Ucupaint Skin Image Atlas2` UVMap UV0.25,0.25贴图作为Alpha来调整**身体水渍**Roughness内部Overrider 0Alpha
- `~Ucupaint Skin Image Atlas` UVMap UV (0,0.75) 是脸上的水渍贴图,用作**脸部水渍**高度、透明度、Roughness最后计算出Color、Roughness、Normal。内部BaseColor Overrider0 Roughness Overrider0.1Alpha
## Group
### ~yPL Check Input Normal
- StartNormalFilter因为最外部的Normal输入是float3(999,999,999)。所以输出的是float3(0.5,0.5,1.0)。
```hlsl
float3 StartNormalFilter(float3 InNormal)
{
float Ratio=InNormal.z > 250 ? 1 : 0;
return lerp(InNormal,float3(0.5,0.5,1.0),Ratio);
}
```
### ~yPL Tangent Process_Copy
通过UV来控制切线法线与次级法线。该节点需要对应的UV通道Body模型里除了UVMap有Face、Pupil、~TL Temp Paint UV。
- Face Tangent Process2个参数输入都是1。
- Pupil Tangent Process2个参数输入都是1。
- UVMap Tangent Process2个参数输入都是1。
https://www.bilibili.com/video/BV1RD4y1d7c3?spm_id_from=333.337.search-card.all.click&vd_source=d47c0bb42f9c72fd7d74562185cee290
### ~yP Lyaer Noise
经过测试,好像没有区别
1. 使用Neighbor UV(将UVMap的Tangent、Bitangent转换到Face的切线空间)来获得东西南北4个偏移方向。传入Layer Noise Source得到Source、东西南北5个Noise随机Height值。
2. 将Noise随机高度值从(0,1)->(-1,1)之后与PrevHeight融合这里不融合
3. Height=Height/MaxHeight之后将Height从(-1,1)->(0,1)
4. 使用ddx/ddy的方式计算出Face切线空间的Noise Bump Normal再使用Tangent与Bitangent计算出世界法线。
#### ~yPL Neighbor UV (Other UV)
使用`~yPL World2Tangent`计算 2个新向量
- FaceTangent->(UVMapTangent,UVMapBitangent)=x
- FaceBitangent->(UVMapTangent,UVMapBitangent)=y
之后乘以1/Width或1/Height之后偏移UV。
##### ~yPL World2Tangent
将一个世界空间向量转换到指定的切线空间中。~~将Face UV的次级法线与身体UV的切线法线与次级法线做点乘来取得新的向量。~~
```c++
float3 World2Tangent(float3 Bitangent,float3 UVMapTangent,float3 UVMapBitangent)
{
float x=dot(Bitangent,UVMapTangent);
float y=dot(Bitangent,UVMapBitangent);
float z=dot(Bitangent,float(0.5,0.5,1));
return float3(x,y,z);
}
```
https://zhuanlan.zhihu.com/p/447671623
#### ~yP Layer Noise Source
将坐标传入NoiseTexture取得噪点值NoiseTexture的参数Scale5=>2000
- Source_group
- Source_n
- Source_s
- Source_e
- Source_w
#### ~yPL Height Process Smooth
使用`~yPL Height Process`计算Source、东西南北5个之后使用`~yPL Pack ONSEW`将Source、N、S合成一个HeightONSE、S合成ES`~yPL Pack NSEW`将Source、N、S合成AlphaONSE、S合成ES。
##### ~yPL Height Process
将Height (0~1)=>(-1,1)区间
- Height= (Value-0.5) * 2 * ValueMaxHeight;
- Alpha=Alpha * RemainingAlpha * Intensity;
- NormalAlpha=Alpha * Intensity;
#### ~yPL Height Mix Smooth
- 解包ONS、EW
- 计算lerp(Prev,Height,Alpha)
- 打包成ONS、EW
#### ~yPL Normal Process Smooth
- 解包ONS、ES
- Value=(Value /MaxHeight) * 0.5 +0.5;
- 调用`~yPL Fine Bump`
##### ~yPL Fine Bump
Tangent与Bitangent为Face UV通道的。
- Vector=float3( (w-e) * BumpHeight, (s-n) * BumpHeight, 1);
- Vector=Normalize(Vector);
- ~yPL Tangent2World(Vector,Tangent,Bitangent);
- return Vector;
###### ~yPL Tangent2World
将一个切线空间向量转换到指定的世界空间中。
https://zhuanlan.zhihu.com/p/447671623
## 贴图Roughness赋予Group
外部传入0.3未调整区域会一直为0.3。
1. 使用`~yP Layer Face Roughness`使用Face UV偏移0.25,0.75)通道采样`~Ucupaint Skin Image Atlas`贴图作为Alpha来调整脸部Roughness内部Overrider 0.5)。
2. 使用`~yP Layer Face Roughness`使用UVMap UV偏移0.25,0通道采样`~Ucupaint Skin Image Atlas2`贴图作为Alpha来调整Roughness内部Overrider 0.6Alpha * 0.75)。
3. 使用`~yP Layer Face Roughness`使用UVMap UV偏移0.25,0.25)通道采样`~Ucupaint Skin Image Atlas2`贴图作为Alpha来调整Roughness内部Overrider 0Alpha
### ~yP Layer Face Roughness脸部Roughness
- Overrider0.5
最外层传入Roughness为0.3Roughness使用`~Ucupaint Skin Image Atlas`贴图对0.3~0.5进行插值。
1. 使用偏移后Face 通道UV(Location 0.25 m 0.75 m 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。
2. return Lerp( Roughness, 0.5, Alpha);
#### ~yPL Mod Override Color
使用Gamma调整OverriderColor颜色Intensity是OverriderColor与原始颜色的插值。
### ~yP Layer Solid Color 8.001身体Roughness
- Overrider0.6
1. 使用偏移后UVMap 通道UV(Location 0.25 m 0 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas2`贴图作为Alpha。
2. return Lerp( Roughness, 0.6f, Alpha * 0.75);
### ~yP Layer Solid Color 8身体Roughness 水滴光滑效果)
- Overrider0
1. 使用偏移后UVMap 通道UV(Location 0 0 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas2`贴图作为Alpha。
2. return Lerp( Roughness, 0, Alpha );
### ~yP Layer Solid Color 6.002脸部Roughness 水滴处的Color、Roughness、Normal
- BaseColor Overrider0
- Roughness Overrider0.1
该节点用于制作水渍效果Color最终使用TransitionAO输出的结果;
1. 使用`~yPL Neighbor UV`计算东南西北方向的UV偏移值。
2. 使用Source与东南西北4个坐标偏移过坐标在偏移(0,0.75)后,采样`~Ucupaint Skin Image Atlas`贴图。
#### ~yPL Transition AO_Copy
将InputRPG与AO Color(默认为0)进行混合。**增加水滴处的AO效果**
### ~yP Layer Solid Color.002(眼白白色)
将眼白区域从皮肤颜色设置成眼白的颜色。
### ~yP Layer Blush腮红
脸上的腮红效果。
1. 对float3(0.749082 0.025 1)执行Gamma0.455,作为绘制颜色。
2. 使用偏移后Face 通道UV(Location 0 0.5 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。与外部传入颜色进行混合。
### ~yP Layer Solid Color 7腮红上的红色斜杠
1. 使用偏移后Face 通道UV(Location 0.75 0.75 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。与外部传入颜色进行混合。
### ~yP Layer Lips嘴唇 红色)
1. 使用偏移后Face 通道UV(Location 0.25 0.5 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。与外部传入颜色进行混合。
### ~yP Layer Tongue舌头 红色 以及法线调整)
这里使用了顶点色作为Mask。
1. 使用偏移后Face 通道UV(Location 0.25 0.5 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。
### ~yP Layer Nose Blush鼻尖处的红
1. 使用偏移后Face 通道UV(Location 0.5 0.5 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。
### ~yP Layer Solid Color 6眼白上部的黑色阴影
1. 使用偏移后Face 通道UV(Location 0.75 0.25 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。
---
### ~yP Layer Pupil
虹膜底层灰色Roughness为0。
### ~yP Layer Solid Color 1.001
虹膜底色(红)
### ~yP Layer Solid Color 2
虹膜下部高光(粉色)
### ~yP Layer Solid Color 3
虹膜下部的自定义高光(白色)
### ~yP Layer Solid Color 4
虹膜底部高光(白色)
### ~yP Layer Solid Color.003
瞳孔(黑色)
---
### ~yP Layer Group
将之前计算身体、脸部细节与瞳孔合并到一起。
1. ~~将NormalHeight Group与NormalHeight Alpha Group这2个Pin没有连接外部数据默认都为0解包Source与4方向 的用于计算法线的Height值以及Alpha。~~
2. 使用Mask Pupil顶点色作为Mask以及Alpha将身体、脸部细节与瞳孔混的BaseColor、Metallic、Roughness、Normal合在一起。
3. 瞳孔区域的Metallic为0Normal为0。Roughness为0。
### ~yP Layer Solid Color 1眉毛
1. 使用偏移后Face 通道UV(Location 0.25 0 0,Scale 0.25 0.25 1) Source以及东西南北4方向偏移过的UV去采样`~Ucupaint Skin Image Atlas`与`~Ucupaint Skin Image Atlas 1`。实际上用的是`~Ucupaint Skin Image Atlas 1`mask。
2. 使用Extra Lines耳朵里的一些线条Mask为了让耳朵有立体感作为Mask以20%的占比与眉毛Mask合并到一起。
3. Color使用 float3(0.073) 与传入颜色混合Roughness使用0.66与传入Roughess混合Norma使用模型Normal与之前计算的Normal混合。
### ~yP Layer Fake Lighting全身
模拟出皮肤SSS以及环境光照效果。
1. 使用`~yPL Hemi_Copy.005`计算取得Ramp混合Alpha。
2. 传入ColorRamp节点取得Color与AlphaColor之后还会执行Gamma(0.454)。
3. 使用Head的顶点色做Mask减淡50%。之后再对整个Alpha * 0.5。
4. 使用Alpha对传入颜色进行混合最后输出。
#### ~yPL Hemi_Copy.005
1. 指定一个摄像机空间的法线向量 ViewNormal转换到世界空间。与模型法线做点乘。
2. 将点乘结果(-1,1) -> (0,1)后输出作为Ramp混合Alpha。
### ~yP Layer Red Skin (肩部区域与手指)
模拟出皮肤SSS的红润效果。
1. 使用偏移后UVMap 通道UV(Location 0.75 0.5 0,Scale 0.25 0.25 1)采样`~Ucupaint Skin Image Atlas`贴图作为Alpha。
2. Alpha=Alpha * 0.25;
3. return lerp( Color,指定的红色,alpha);
### ~yP Layer Solid Color 6.004(耳朵)
耳朵加上些AO。
### ~yP Layer Skin AO 皮肤上的AO效果
和[[#~yP Layer Fake Lighting全身]]一样:
1. 采用dot(ViewVector,Normal)传入ColorRamp得到Color作为Alpa。
2. Alpha=Alpha * 0.75;
3. return lerp(Color,指定AO颜色,Alpha);
# 头发
与身体大致相同,但区别在于:
- Anisotropic0=>1
- Tangent连接Tangent节点ForTangent UV通道
- 连接Pin
- Color
- Roughness
- ColorAlpha
## 要点:
- `~yP Layer Gradient.003`提亮批发部分亮度,提高层次感觉。
- `~yP Layer Solid Color.005`模拟出一个正面打光的AO阴影效果。
## Group
### ~yP Layer Solid Color 1.002
1. 使用顶点色Mask VCol除了眉毛处有点黑其他都是白色作为Alpha2传入`~yPL Straight Over Mix`与外部传入的Color与ColorAlpha进行计算。()
2. 最终输出Color与ColorAlpha。
### ~yP Layer Gradient.003
对披下的长发区域进行梯度提亮。
1. 使用`~yP Modifiers Gradient`计算Color与Alpha之后与输入的Color进行混合。
### ~yP Layer Fake Lighting.001
模拟出一个头顶往下打光的灯光效果。
1. 使用dot(WorldNormal,指定世界坐标正面朝上的Vector)制作Alpha
2. 将Alpha传入ColorRamp取得Ramp值。
3. Color=Ramp * 0.1+Color;
#### ~yPL Hemi_Copy.002
1. 使用dot(WorldNormal,指定世界坐标正面向上的Vector)
2. 将点乘值 (-1,1)=>(0,1)
3. 进行**正反面**fix后输出。反面不应该收到光照
### ~yP Layer Solid Color.005
同`~yP Layer Fake Lighting.001`模拟出一个正面打光的AO阴影效果。
# 衣服
- 次表面方法换成了Christensen-Burley但依然没有开启次表面
- 有意思的是开启了ClearCoat材质效果让衣服有了一些变色的感觉
## 要点
1. `~yP Layer Solid Color 3.001` 叠加了一个从下往上的Ramp让上部分的衣服显得更紫更加不透一些。
2. `~yP Layer Solid Color 2.001` 实现出一种菲尼尔效果。
3. `~yP Layer Fake Lighting 1.001` AO贴图与Fake灯光混合。(应该只有AO效果)
4. `~yP Layer Fake Lighting 1`
## GroupNode
### yP Layer Solid Color 1.003_remove
用于设置2个袖口的颜色与Alpha 。
### ~yP Layer Solid Color.006
用于设置身前肚兜的颜色与Alpha。
1. Fake了一个从下往上打的灯光并且设置了Ramp
2. 使用`~yPL Straight Over Mix`对外部传入的Color与Alpha进行混合。
### ~yP Layer Solid Color 3.001
叠加了一个从下往上的Ramp(根据UV),让上部分的衣服显得更紫,更加不透一些。
### ~yP Layer Solid Color 4.001
对2个袖子的开头与结尾处叠加一些紫色。根据`~Ucupaint Cloth Image Atlas` (0.25,0.25)
### ~yP Layer Pattern Sleeve
袖子上的图案。
### ~yP Layer Solid Color 5.001
胸前衣服的图案。
### ~yP Layer Solid Color 2.001
实现出一种菲尼尔效果。
1. dot(指定摄像机空间向量 float3(0,0,1),WorldNormal),传入Ramp,作为Alpha。
2. 与传入Alpha、Color以及内部指定颜色进行混合。
### ~yP Layer Fake Lighting 1.001
1. dot(指定摄像机空间向量 float3(0,0,-1),WorldNormal),传入Ramp,作为Alpha。
2. 使用贴图`Cloth AO`的值 与 Ramp混合。
### ~yP Layer Fake Lighting 1
对衣服上的褶皱添加高光效果。
### ~yP Layer Solid Color 6.003
增加菲尼尔AO效果。

View File

@@ -0,0 +1,162 @@
---
title: YusufUmar其他作品分析
date: 2022-08-23 16:52:42
excerpt:
tags: 卡通渲染
rating: ⭐
---
# Saber
## 要点
- Saber_~yP Layer Solid Color 7:使用dot(摄像机空间float3(0,0,1),WorldNormal)来FakeLight效果表现为胸部附近的SSS效果。
- **Saber_~yP Layer Skin AO**增加身体区域的SSS与AO效果。**使用AO Mask贴图作为Ramp的Alpha取得Ramp颜色**还是用UV Mapping Color生成颜色传入Ramp来取得一个黑白Alpha与之前的Color以4:1的比例混合。**做到一种次表面散射衰减以及AO的双层效果**。
## Eye
- Saber_~yP Layer Solid Color.004
- Saber_~yP Layer Solid Color 1.002
- Saber_~yP Layer Solid Color 11
## Face
- Saber_~yP Layer Solid Color 2.002
- Saber_~yP Layer Solid Color 3.002
- Saber_~yP Layer Solid Color 12通过贴图Mask来增加自发光的方式来提亮某一些部位。
## Body
- Saber_~yP Layer Solid Color 9:调整身体Roughness以及眼睫毛BaseColor
- Saber_~yP Layer Solid Color 10调整身体Roughness以及高光
- Saber_~yP Layer Solid Color 5.001
## FakeLight
- Saber_~yP Layer Solid Color 7FakeSSS
- Saber_~yP Layer Fake Lighting.002FakeLight
# Saber2
## 要点
- ~yP Layer Solid Color.006绘制边缘处的粉色Layer模拟了SSS效果。
- ~yP Layer Solid Color.005绘制边缘处的较暗粉色Layer模拟了AO与GI。
## Body
- ~yP Layer Solid Color.006
- ~yP Layer Solid Color.005
- ~yP Layer Solid Color 3.002:使用自发光对皮肤进行整体提亮。
## Eye
- ~yP Layer Solid Color 9
- ~yP Layer Solid Color 10
- ~yP Layer Solid Color 4.002:眼白
- ~yP Layer Solid Color 5.002
- ~yP Layer Solid Color 11睫毛
## Face
- ~yP Layer Solid Color 2.003:腮红
- ~yP Layer Solid Color 8唇色
# Shuten DoujiTexture
displacement主要是做出腿甲的凹凸效果。
AO节点用于调整AO效果让暗处更加暗亮度更加亮。
## 要点
- ~yP Layer Solid Color 8.001FakeLighting以增加SSS质感。
- ~yP Layer BodySkin AO增加身体的细节AO已增加立体感。
- ~yP Layer BodySkin Cavity使用CavityMap提亮指定区域。
- ~yP Layer Solid Color 9.001dot(World,指定法线) + 2层Ramp实现的一个类似描边的效果来增加质感。
## Knee
- ~yP Layer Solid Color.002
- ~yP Layer Solid Color 3.001
- ~yP Layer Noise.001
- ~yP Layer Solid Color 13
- ~yP Layer Solid Color 11.001
- ~yP Layer Solid Color 11
- ~yP Layer Solid Color 14
- ~yP Layer pattern_knee_indentation.png
- ~yP Layer pattern_knee膝盖上的腿甲
- ~yP Layer Tileable eroded scratch metal texture background .000
## Face
- ~yP Layer Solid Color 1.002:眼线
- ~yP Layer Solid Color 6.001:眼白
- ~yP Layer Solid Color 8.002:眼白阴影效果。
- ~yP Layer Solid Color 4.001:腮红
- ~yP Layer Solid Color 7.001:腮红上的漫画线
- ~yP Layer Solid Color 10.001:嘴唇颜色
## Horn
- ~yP Layer Solid Color 2.001
- ~yP Layer Solid Color 12
## Body
- ~yP Layer BodySkin AO增加身体的细节AO已增加立体感。
- ~yP Layer BodySkin Cavity使用CavityMap提亮指定区域。
- ~yP Layer Solid Color 5.001
- ~yP Layer Solid Color 8.001FakeLighting以增加SSS质感。
- ~yP Layer Solid Color 9.002:边缘光效果。
- ~yP Layer Solid Color 9.001dot(World,指定法线) + 2层Ramp实现的一个类似描边的效果来增加质感。
# workou
## 要点
- ~yP Layer Skin AOAO效果
- ~yP Layer Blush.003膝盖与肩膀上的神色区域模拟了一些SSS效果。
- ~yP Layer Fake LightingFakeLighting全身效果模拟了SSS效果。
## Body
- ~yP Layer Skin AOAO效果
- ~yP Layer Solid Color 9贴图控制的局部照亮效果
- ~yP Layer Blush.003膝盖与肩膀上的神色区域模拟了一些SSS效果。
- ~yP Layer Fake LightingFakeLighting全身效果模拟了SSS效果。
- ~yP Layer Fake Lighting 1.001使用FakeLighting的方式实现了类似边缘描线的效果。
- ~yP Layer Roughness Tweak.002调整腿部的Roughness。
- ~yP Layer Roughness Tweak.003
- ~yP Layer Solid Color 2FakeLighting调整B的SSS效果。
- ~yP Layer Solid Color 7身上的痣
- ~yP Layer Fake Lighting 2.001:胸部的高光点
## Mirror
- ~yP Layer Solid Color 4
- ~yP Layer Solid Color 3腮红
## Face
- ~yP Layer Eye眼白
- ~yP Layer Pupil虹膜颜色
- ~yP Layer Brow眼睫毛
- ~yP Layer Solid Color 8Unknow
- ~yP Layer Pupil Dot瞳孔
- ~yP Layer Solid Color.006:虹膜下方的焦散高光
- ~yP Layer Solid Color 1.002:眼睛两侧的眼白
- ~yP Layer Solid Color 1.006:眼睛上方的阴影
- ~yP Layer Blush腮红
- ~yP Layer Blush.002:第二层腮红
- ~yP Layer NailUnknow
- ~yP Layer Tongue估计是舌头
- ~yP Layer Teeth调整牙齿区域的亮度
- ~yP Layer Roughness Tweak调整脸部腮红的Roughness
- ~yP Layer Roughness Tweak.001
- ~yP Layer Fake Lighting 2侧脸的阴影
# YangGuiFei
## 要点
- ~yP Layer Skin AO身体AO
- ~yP Layer Fake Lighting通过FakeLighting模拟SSS效果。
## Body
- ~yP Layer Skin AO身体AO
- ~yP Layer Fake Lighting通过FakeLighting模拟SSS效果。
- ~yP Layer Fake Lighting 2大腿上的高光
- ~yP Layer Fake Lighting 3.001:大腿边缘光效果
## Face
- ~yP Layer Solid Color 4唇色
- ~yP Layer Solid Color 3腮红
- ~yP Layer Eye眼白
- ~yP Layer Pupil虹膜颜色
- ~yP Layer Brow睫毛
- ~yP Layer Pupil Dot瞳孔
- ~yP Layer Solid Color 6大概率是眼睛的阴影
- ~yP Layer Solid Color.006:虹膜下部的焦散高光效果
- ~yP Layer Solid Color 1.002:眼睛上部的阴影
- ~yP Layer Solid Color 1.006:眼睛上部的阴影
- ~yP Layer Blush腮红
- ~yP Layer Blush.001:第二层腮红
- ~yP Layer NailUnknow
- ~yP Layer Fake Lighting通过FakeLighting模拟SSS效果。
- ~yP Layer Fake Lighting 1.001Eye相关的FakeLighting效果未知。
- ~yP Layer Fake Lighting 3脸部边缘光效果。

View File

@@ -0,0 +1,97 @@
---
title: YusufUmar厚涂手法总结
date: 2022-08-24 14:29:06
excerpt:
tags: 卡通渲染
rating: ⭐⭐
---
## 前言
总结主要以YusufUmar的kama的皮肤材质为主。其他作品作为补充。
## 皮肤
材质模型使用Blender默认的Principed BSDF节点(可以理解为多个BSDF进行混合)
- 标准的迪士尼模型基底材质参数为皮肤颜色float3(0.846873,0.602632,0.512498)、Roughness 0.3、Metallic 0 。Subsurface0.01
- SSS材质的模型为RandomWalkFixed Radius以0.01~0.005的比例进行混合。
下图为最终结果->使用默认参数的Principed BSDF Node
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/YusufUmar/kama/kama_FinalyToDefault.gif)
### 增加质感
1. ~yP Layer Noise为法线增加Noise效果体现为**皮肤哑光效果**。
2. yP Layer Fake Lighting效果模拟出皮肤的SSS以及环境光照效果增强厚涂皮肤的质感。
3. yP Layer Red Skin 肩部区域与手指效果模拟皮肤的SSS效果增强厚涂皮肤的质感。
4. yP Layer Skin AO 皮肤上的AO效果模拟皮肤上的AO
#### Noise
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/YusufUmar/kama/kama_noise.png)
#### FakeLighting
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/YusufUmar/kama/yP_Layer_Fake_Lighting.gif)
#### RedSkin
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/YusufUmar/kama/yP_Layer_Red_Skin.gif)
#### SkinAO
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/YusufUmar/kama/yP_Layer_Skin_AO.gif)
#### Outline
使用一个稍微扩大的模型渲染描边效果。
## 头发与衣服
头发与衣服也使用了多种Ramp效果叠加。
## 后处理效果
## 思路转化
YusufUmar的思路还是偏向于3D辅助绘制也就是将一个Shader效果以GroupNode为单位作为一个图层之后层层叠加出效果。效果主要分为
- 原始光照Blender中灯光、环境光效果。
- FakeLighting通过制定dot(摄像机空间向量,WorldNormal)的方式获取到一个Alpha,之后通过映射成一个ColorRamp再叠加上去。(虽然效果看起来可以,但这么做其实是错误的,但移动端可以使用)。
- RedSkin使用绘制贴图顶点绘制的方式制定若干区域与Alpha之后通过映射成一个ColorRamp再叠加上去。
- SkinAO通过制定dot(摄像机空间向量,WorldNormal)的方式获取到一个Alpha,之后通过映射成一个ColorRamp再叠加上去。
主要的思路就是使用各种数据算出的Alpha来映射ColorRamp并叠加到角色上。这需要原画美术的支持来实现在物理正确的基础上**加料**。
### 光照
主光可以UE的ShaderModel中根据全局变量中的DirectionLightVector来判断进而进行调整设置主光Ramp
但有一个问题,**主光的效果如何和其他点光效果平衡?**
YusufUmar的作品中也会使用FakeLightingShader计算+Mask来实现局部照亮这种额外打光效果。**是否可以通过LightingChannel在ShaderModel中判断**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/YusufUmar/saber/Saber_~yP_Layer_Fake_Lighting.002.gif)
因为这种补光的点光只会在特定镜头出现,出了过场动画肯定要去掉。**脑洞搞成Pose驱动的灯光效果**
### FakeSSS效果
#### FakeLighting环境光SSS效果
YusufUmar的方法不精确比如耳朵以及锁骨区域这些应该比较透的的确却不一点都不透。
个人认为最好的方法是:
- 使用UE的SSS渲染功能的点采样求出透明度作为映射参数之一。
- 使用一个可调整的曲线Asset作为皮肤的LUT来实现SSS效果。
- 或许SSS部分也需要类似**Lambert -> HalfLambert** 的调整?
#### RedSkin
在UE使用Layered Material方式将不同颜色的SSS材质混合到一起。
- 让美术绘制混合用的贴图。
- 使用Layered Material进行混合。
这个绘制类型的Ramp效果多次出现能很好的提供那种厚涂的效果。
### AO
使用贴图+SSAO。
**最好能做到使用AO来偏移SSS效果而不是进行直接的阴影叠加。**
### 质感增加
**皮肤哑光效果**使用Noise贴图并且使用ddx/ddy实现。
## 其他改进
因为作者能力与Blender的限制所以没有考虑天光、GI、HalfLambert等因素。所以
1. 将ShaderModel的Lambert -> HalfLambert。
2.
美术可能需要调整的相关Ramp
- SSS效果
- 主光Ramp
- 天光与环境光
- AO (只能修改默认参数)

View File

@@ -0,0 +1,34 @@
---
title: 厚涂画师作品分析
date: 2022-10-13 20:40:47
excerpt:
tags: 卡通渲染
rating: ⭐⭐
---
## 分类标准
每个作者的作品区分:
- 场景的时间与环境
- 室内/室外
- 大致时间
渲染特征使用以下进行评级:
- 画面颜色分布
- 亮处与暗部的亮度分布
- 饱和度分布
- 色相
- 次表面
- 次表面强度
- 粉嫩程度
- 皮肤高光
- 天光
- AO
- Bloom/Glow
- 环境光
- 其他绘制
- 丝袜
- 湿润的皮肤
- 后处理
- Bloom
## 画师作品具体分析

View File

@@ -0,0 +1,19 @@
---
title: 厚涂笔记
date: 2022-08-10 15:25:48
excerpt:
tags: 卡通渲染
rating: ⭐
---
## 厚涂的绘画流程
[B站厚涂教程](https://www.bilibili.com/video/BV1PY411J7Af?p=1)的流程
1. 绘制基本色块![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/底色.png)
2. 绘制阴影区域(这步完成就有基本的赛璐璐效果了)![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/光影与高光.png)
3. 对阴影区域进行涂抹(方向为暗->亮)以及模糊处理,使之具有比较自然的过度效果。 ![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/平滑光影.png)
4. 绘制第二层光影细节![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/第二层光影细化.png)
5. 使用喷枪工具给皮肤添加腮红效果部分阴影区域也可以使用之前绘制的Mask进行饱和度与颜色调节。![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/添加腮红等皮肤细节.png)
6. 添加头发以及眼睛细节![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/添加头发细节.png)
7. 添加天光、反射、其他灯光效果以及物体高光,来凸显质感与灯光气氛。![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/添加反射天光物体高光与整体提亮.png)![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/添加边缘光与其他细节.png)
8. 最后添加细节![|800](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/最后添加细节.png)

View File

@@ -0,0 +1,155 @@
由简单的二值化到厚涂二渲染
**NPR**作为一个视觉效果导向的渲染方向其实是有着很强的商业化前景。因为对于真实感的要求不高只是“看上去好看就可以”所以很多时候复杂的物理现象可以直接绕过或是使用很快但效果并不好近似折衷。在NPR中由于绝大部分是非真实感的渲染大多数时候对一个提供真实感补正的物理现象具体表现效果要求并不高。因此在性能开销方面以及移动端方面都有着很强的应用前景。
NPR的大部分效果其实对于TA的物理数学功底要求并不算很高 ,那么究竟是对什么要求高呢?答案是:**审美**。可以说既然你要做NPR至少要对要做的NPR的艺术领域有足够高的热爱和理解。比如你要做二次元风格的渲染的话就要看过各种各样画风的动漫对于哪种画风更受喜爱也要有所了解自己也要清楚自己喜欢什么画风也要清楚这种画风的优势所在。另外即使是各种非真实感的画风通常各种效果也是基于物理现象的夸张化而已都能够找到物理现象的本源一般来说将相应的物理现象在PBR的实现方法进行各种套路魔改就可以实现相应的在NPR里面的效果。
二次元渲染模型和写实向渲染模型另外还有一个自然而然的区别即写实向渲染通常是为实现一个效果而使用不同的物理方法二次元渲染模型则通常实现不同的效果而使用不同的套路因为真实感是亘古不变的但“好看”的定义却大有不同。常为我们所接受的米哈游《崩坏3》、《原神》、《星穹铁道》中都是使用了“赛璐璐风格二次元渲染”其表现为“二值化”将亮部与暗部使用离散的方式分割以得到扁平化的效果。
![](https://pic2.zhimg.com/80/v2-6cefd12cb2da8997dae133b769b15795_720w.jpg)
原神中的角色刻晴
但其实二值化也未必只有两个值,只要体现了**离散**特性的光照模型都可以称之为二值化,是将一个光照阶段进行了二值化。动漫中其实也会出现高光部,来表现一个表面凸出且反射度较高的特性(例如兄贵光滑的肌肉)
![](https://pic4.zhimg.com/80/v2-c84d2f3148ec6d0dad305ef41c286f0b_720w.jpg)
引用:https://zhuanlan.zhihu.com/p/437313927
二值化通常的做法其实就是一个条件判断将N dot L 的值与阈值判断高于阈值的呈现亮部特点低于阈值的呈现暗部特点。但是现在的做法通常是将条件判断做成了一个映射Map称作**Ramp Map**如果要用RampMap实现常规的二值化只需将RampMap做成只有两个颜色的就可以。如果要做三值化只需要做成三个颜色。甚至如果要用常规的兰伯特模型只需要做成黑到白的渐变就相当于没有进行这次映射。RampMap比起条件判断是更加灵活的。
![](https://pic2.zhimg.com/80/v2-80dc668969bbb178df69dbc7848a8459_720w.jpg)
游戏《嗜血代码》中野体现了一定的二值化特性但是阴影处相对更加柔和这只需要你在二值化的RampMap跳变处做一个小小的渐变就可以实现。
赛璐璐风格作为日式二次元动漫最常见的画风,其实是相对最为人接受的。《原神》的爆火也证明了它的可行性。它的优势是显而易见的。
1.渲染成本较低(简单的光照模型,自然会比真实向渲染的开销更低)
2.下限高,不会出现太穿帮的渲染(二值化,非你即我,十分可控)
3.普遍接受度高(简单的画风往往不至于令人厌恶)
但是,很少有人分析赛璐璐风格的缺点,他的缺点其实也是存在的
1.上限较低
在玩家口味普遍刁钻的现在简单的二值化渲染有时不能满足他们对细腻画风的追求《幻塔》和《崩坏3》的早期角色就是例子。
![](https://pic1.zhimg.com/80/v2-76849afe98a9b301b0c20edf631c19b8_720w.png)
《幻塔》的着色
![](https://pic2.zhimg.com/80/v2-544187ece8f7fc3ef149e04229b23c69_720w.jpg)
冰八
冰巴刚出那时候大家对手机游戏角色的要求并不高。可以看到幻塔的角色大概和冰巴精细度差不多但幻塔的美术就被吐槽了。我个人认为是时代发展的原因。再来看看崩坏3的后续角色
![](https://pic2.zhimg.com/80/v2-1a8115107320c103f5993df9a3ecb4c9_720w.jpg)
艾莉希亚
这其中有一个十分明显的变化,即模型的精细程度。
![](https://pic1.zhimg.com/80/v2-d0733cec88788bf13ac5bae5d97e2ffc_720w.png)
大面积的白色
![](https://pic3.zhimg.com/80/v2-78fc066ddd1cd3d0e36fb9c7c070696e_720w.png)
大面积的单调肉色
![](https://pic4.zhimg.com/80/v2-cea2018eb46c6e4ee80b12a7baa0ce3b_720w.png)
不太负责任的直接上一个渐变色的袜子
当模型细节堆砌的不够多,简单的二值化就会显得效果过于简单,会产生一定的粗糙感。因此,二值化对角色设计有要求:即细节必须足够多,也就是说角色必须佩戴足够多的饰品,衣服上必须有足够多的花纹,这其实是对角色设计的一种限制。
与艾莉希亚相同,《原神》也是通过这种取巧的手段来实现在二值化下留白而不给人带来太多粗糙感的。
![](https://pic1.zhimg.com/80/v2-ec3f507d0e1dc2ba6be043946100935c_720w.jpg)
提纳里身上各种各样的饰品,白色绸缎上华丽的花纹
![](https://pic1.zhimg.com/80/v2-e35307427747d8b83f09bc97423d0d1c_720w.jpg)
十分复杂的细节
![](https://pic4.zhimg.com/80/v2-f7287f1a5d6da4b841016f2de6889267_720w.jpg)
就算是做个黑丝也要往上面加星星避免单调感
在《原神》中,你无法找到任何一个角色是简单的,这无疑会对角色设计带来更高的验收要求和限制。
即使赛璐璐风格渲染有着一定的局限性,它依然是二次元渲染的必修课,
非常推荐知乎大佬flashyiyi的文章
[到目前为止的二次元渲染总结 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/126668414)
[一些较少人提过的二次元渲染方法 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/539950545)
在第一篇文章中,他提到:
![](https://pic4.zhimg.com/80/v2-34604f26c1c97b5472c95ab9e0f89e27_720w.jpg)
实际上pixiv上有很多传统二值化的绘画作品但是在热门榜上经常霸榜的画风却往往是绘画更加细腻的风格。
![](https://pic2.zhimg.com/80/v2-341a850405f7c431fd148fee0b7b84ed_720w.jpg)
一张曾经在热门栏的原神插画
米哈游也在积极探索在NPR下更细腻的渲染表现比如
![](https://pic2.zhimg.com/80/v2-8a56d7d033bde005e7c1cadf33dd6e9d_720w.jpg)
米哈游的《桃源恋歌》中的八重樱
![](https://pic4.zhimg.com/80/v2-116e078a48f0e7ba92162e46ee0ad17b_720w.jpg)
可爱的鹿鸣
但也都并不能称得上是完美的“二次元渲染”
《桃源恋歌》中的三层Ramp手段使得肉体不仅具有渐变的特性也保留了跳变的特性但由于时代较早这个ramp我觉得不算特别好看。另外头部和身体的渲染区别有点太大有点像是“接头霸王”
而《鹿鸣》更像是个邻家小妹皮肤的次表面散射材质确实Q弹以及各种高技术力的唯美表现但是这个不够二次元……在某些时候给人一种既真实又虚假的“恐怖谷”的感觉。(这个只是我个人的感受,因人而异,希望大家不要喷我)
![](https://pic2.zhimg.com/80/v2-c3500683cd6e55cae7bdeb50601c9455_720w.jpg)
高能手办团
游戏《高能手办团》中探索插画风格渲染的方式是直接使用了PBR然后在头发等部位故意使用塑料的材质做出手办的感觉其实直接使用PBR在日厂的二次元作品是比较常见的。
![](https://pic4.zhimg.com/80/v2-3cfeb269c68aaa8c2a6750f740994867_720w.jpg)
PBR VS NPR 各有千秋
PBR给人的感觉是更加细腻而NPR给人的感觉是更加二次元。
那么在PBR与NPR之间我们如何找到一种折衷实际上pixiv已经给了我们一种答案厚涂。
厚涂的角色由于具有一定的真实感物理特性,使得它在无论真实感的场景下还是在二次元感的渲染场景下,都不会显得过于违和。大面积的肉色堆叠,由于能够看到肌肤的明暗变化,也不会让人觉得粗糙和单调。同时,作为新兴的二次元画风:厚涂,也不至于会让人觉得不够“二次元”。
![](https://pic4.zhimg.com/80/v2-74a23b1c6a43d4bc8e7bd4fe238a6737_720w.jpg)
厚涂风格的玛修同学
本专题将会分享我在厚涂二次元渲染风格探索中的心得体会~从人物的各种部位到场景的各种材质,只要有时间并且有相关研究成果就会分享出来~
赛璐璐风格的二次元渲染套路例如面部法线修正以及一些简单常用的PBR套路例如次表面散射模型是本专题的前置知识不过不用担心用到前置知识的地方我会进行资料的引用。
求三连~

View File

@@ -0,0 +1,251 @@
注意:本专题也许会大面积展示人体皮肤渲染结果,这可能会引起你的不适。
### 0.准备工作
在专题开始之前你需要完成二值化的光照模型以及outline
[【01】从零开始的卡通渲染-描边篇 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/109101851)
[【02】从零开始的卡通渲染-着色篇1 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/110025903)
可以看这位大佬的二值化渲染模型。
另外,如果对赛璐璐风格的二次元渲染仍存疑惑,我的建议是
[RealToon (An Anime/Toon Shader) | VFX 着色器 | Unity Asset Store](https://link.zhihu.com/?target=https%3A//assetstore.unity.com/packages/vfx/shaders/realtoon-an-anime-toon-shader-65518)
![](https://pic3.zhimg.com/80/v2-5a057f55bc36863698d13556ecdedcb2_720w.jpg)
realtoon效果
这个18刀的着色器包含了完整源码还内置了对实时光线追踪的支持18刀的学费不算贵喔。
光追二次元的内容放到第九篇说吧,这里就不展开了。
### 1.皮肤的平滑光照
显而易见,插画风格与赛璐璐风格最大的区别就在于,皮肤对于不同的光照情况有着不同的表现。
![](https://pic3.zhimg.com/80/v2-8eea128f8d24397f1ed1b80f8c7e3aaa_720w.jpg)
原神的插画
![](https://pic4.zhimg.com/80/v2-540476418232914ff9a66a6474c5cfbf_720w.jpg)
原神正作渲染结果
我们对皮肤颜色进行抓取
![](https://pic2.zhimg.com/80/v2-275d25057df5b4d4f18063614435efdd_720w.png)
原神插画的皮肤颜色
![](https://pic1.zhimg.com/80/v2-dbb9e7284672b2114d79f6a057211c94_720w.png)
原神渲染的皮肤颜色
可以看到原神的插画相比原神游戏中的渲染结果其色彩能够展示出一定的PBR特点例如Specular高光还有次表面散射的效果。不过画面整体依然很二次元因此我们是可以从赛璐璐风格着色器的基础上进行魔改得到我们期待的厚涂风格皮肤的。
因此我们在原本赛璐璐风格的渲染基础上直接引入Specular高光和次表面散射。与PBR不同的是
1.我们不必对其物理真实性太过较真,只要他能对我们的画面进行补正就是好文明。
2.保证所有效果可以通过参数调整权重因为NPR保证视觉好看是第一要务。
因此Specular可以简单用 SpecularColorSpecularIndensitydot(normal,lightDir) 实现也可以用 SpecularColorSpecularIndensitydot(normal,viewDir) ,我个人倾向于后者。
其中 SpecularColor 是Color SpecularIndensity 是灰度图。
实现。当然如果物体的SpecularColor会因部位而异那么可以和 SpecularIndensity 一起合并成一个含颜色的贴图。
次表面散射方面可以考虑使用pre-integrated模型。
![](https://pic1.zhimg.com/80/v2-566a80722b5574f44c144ce5c9d1e524_720w.jpg)
预积分的次表面散射
BSSRDF与BTDF以及pre-integrated模型的学习 [三鳥:实时皮肤渲染综述](https://zhuanlan.zhihu.com/p/462124664)
简单的说一下BSSRDF。
![](https://pic1.zhimg.com/80/v2-b3e1e3977b24b430c70d4154f216b384_720w.jpg)
射入皮肤的光呈现漫反射的性质
当光照射到皮肤表面后,光会在皮肤下的油脂层进行多次散射,在它射出时,它体现漫反射的性质。不过,之所以他体现出漫反射的性质,是因为光射入皮肤后,多次散射会导致最终值差距较大,因此在皮肤下的油脂层中存在着各种方向混乱的散射,最终射出的光就会呈现漫反射的性质了。
![](https://pic2.zhimg.com/80/v2-be59512df5df1287987c167cf7661055_720w.jpg)
BSSRDF导致的结果
这就导致光束大概照到人体表面是这个样子的。那么如何得到这个预积分图呢除了上面文章中提到的screen space blur的办法也可以使用基于拟合函数的方法。
[【译 】Disney2015-将BRDF扩展至集成次表面散射的BSDF - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/345518461)
这两种方法,都可以得到一个类似下图的渲染结果。
![](https://pic3.zhimg.com/80/v2-84b704f9aca547ad096a092a07be0ea6_720w.png)
可爱的光圈
然后在渲染结果上以以1r采样水平方向上以**受光强度**N⋅L采样就可以得到预积分图。
如果你实在整不明白,你直接把别人的预积分结果拿去用也无所谓……反正大学时期大家应该也都抄过别人的**作业**吧。
![](https://pic4.zhimg.com/80/v2-6265fc4132e3a88eac42d8b647ed6dbb_720w.jpg)
在我们得到预积分图后,我们就可以考虑如何使用。
![](https://pic3.zhimg.com/80/v2-182f4b5498b892488109e7f1a5860046_720w.jpg)
预积分贴图
常见的pre-integrated模型刚好是以 N·L 为横坐标的这与我们的RampMap一致。此时此刻我们的赛璐璐风格RampMap应该还是一个1N大小的LUT将这个一维LUT与pre-integrated二维LUT直接逐行相乘得到一个新的RampMap但是1N的RampMap拓展为了N*N纵坐标的1/r是在球面上的如何应用到我们的复杂网格渲染中呢
请参考[Pre-Integrated Skin Shading 的常见问题解答 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/384541607) Q&A第五节
![](https://pic2.zhimg.com/80/v2-d038e84789085ca99b79a1c55475d1e9_720w.jpg)
Jeffrey Zhuang的Q&A
因此 ΔNormal/ΔPosition 作为纵坐标就可以了
仅仅是换一张图,就解决了皮肤的次表面散射问题。
至此,皮肤的平滑光照问题就已经解决。
![](https://pic1.zhimg.com/80/v2-7c82b53ceddd3e6ee2f789145ccccde4_720w.jpg)
赛璐璐Only
![](https://pic2.zhimg.com/80/v2-966e029dc7ad41453825fe8351e4b415_720w.jpg)
加入了BSSRDF和Specular后的效果
(这里头发和五官的渲染已经是完结版本,所以有种降维打击的感觉)
可以看到在加入SSS和Specular之后我们的皮肤已经有一股插画的感觉了并且开销并不算很高。
求求审核哥哥姐姐了,给个过吧,真的很需要展示皮肤各个角度的渲染效果……
### 2.皮肤的厚涂化表现
实际上到此为止的皮肤效果就已经不错了但是不够二次元了因为我们上述的操作都是PBR的真实感太高有时候也不是一件好事。举个例子在下一篇我会谈到一个叫做face normal fix的操作这会导致脸很平面。如果你的肉体立体感太足的化就会显得你是一个**接头霸王**。
**实际上,在绘画过程中,画师不仅仅要考虑到皮肤的真实感,还要考虑到画面整体的协调性。**
假如我们使用了基于物理的次表面散射,这会让我们的皮肤在**高强度的直射光**下的物理表现更为明显。但是与此同时,在反射光下的影响就会变得更弱。
**注意**!下述方法论非常**不**基于物理,这可能会引起你的不适!
为了让皮肤的质感更为可控我们采用第二章N*N的LUT但这张LUT我们不基于物理去生成而是跟着**感觉**走
实际上,我们需要一个散射光强度与直射光相补正,而这个光就是天空光。
1.天空光是蓝色的,所以我们考虑让皮肤看上去有蓝色的散射感。
2.与此同时在皮肤边缘我希望有一个体现BTDF的效果在二次元下我希望这个颜色是粉粉的。
3.散射感在直射光射到的地方肯定会体现的不充足,在直射光射不到的地方才会比较多。
4.第二条说的BTDF肯定也得是和光照强度绑定的呀
综上四条我们下一步控制皮肤质感的LUT必然有一维是 N·L 用以表示直射强度。考虑到皮肤边缘需要BTDF另一个维度应该是 N·V 
这样的话我们依然需要LUT只是这次的LUT不需要积分毕竟也不基于物理
直接凭感觉随便拿PS什么的画一下就好了。
这个补正方法的灵感来源于一个效果很好的Fake SSS的着色
[Shader Forge - A visual, node-based shader editor | Page 54 - Unity Forum](https://link.zhihu.com/?target=https%3A//forum.unity.com/threads/shader-forge-a-visual-node-based-shader-editor.222049/page-54%23post-1903768)
![](https://pic4.zhimg.com/80/v2-40d06be17c63ffbfdf7278fa94cc586b_720w.jpg)
二次元可以使用的LUT
以天空为背景色,直射光使用肤色作为相应,然后边缘使用粉粉嫩嫩的颜色。
至于这块儿棕色,因为我们是半兰伯特模型,所以一般而言不会采样到太多。
最后一步就是厚涂化了,我们回首再对我们的厚涂做一点考察
![](https://pic3.zhimg.com/80/v2-9033475c1eeafb56e7efaa4b4ccc0d7e_720w.jpg)
厚涂玛修
厚涂是发源于**西方油画**的一种绘画技法。 画家在画布上使用很厚的颜料,利用笔刷或画刀进行涂抹,就像抹奶油一样,故名“厚涂”
在这样的绘画工艺下,导致厚涂色块十分分明。因为是用很厚的颜料,所以渐变实际上是离散的,这就和我们的赛璐璐有一点像了。
离散的同时,由于两个颜料的相互融合,边界却又十分的柔和
![](https://pic3.zhimg.com/80/v2-3e2584ae4d9f900e2671d69b652f01d6_720w.png)
柔和的边界
我们直接对上面的补正LUT做处理就好了。先downsampling再用朴素的方式放大然后用一下小半径的均值滤波就可以得到一张有厚涂特色的补正LUT了
把这张LUT和上面pre-integrated以相同的方式(但纵坐标轴不同)也应用进去就完成90%的效果啦
![](https://pic2.zhimg.com/80/v2-34eae06ce1c6db841df4743564281ded_720w.jpg)
可以看到这时候效果已经很不错了。
### 3.一点小方法
1另类高光
![](https://pic3.zhimg.com/80/v2-fcafe3f2e9e0cf12887b6bb95ef248a2_720w.png)
肩头上有高光
可以看到有些二次元厚涂作品有明显不基于物理的高光,这是因为二次元美少女每一个都滑溜溜的。
这个效果在我们使用了不基于物理的补正LUT后非常简单在LUT的左上角点一个光点就可以了。
效果:
2屏幕空间补光
![](https://pic3.zhimg.com/80/v2-2a91d9ae7ff5a51329e8201c0a66ff46_720w.png)
肩头存在补光
二次元角色的光照实际上都是以好看为第一要务的。其实许多动漫都存在“两个人面对面,但是却都向光”的情况。对于二次元美少女而言,光补的越多,就越好看(雾)
[Unity URP Shader 与 HLSL 自学笔记六 等宽屏幕空间边缘光 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/365339160)
可以在这篇文章学习这个套路。因为我也是学的别人的,就不在这里班门弄斧啦。
![](https://pic2.zhimg.com/80/v2-70afc5f583d1c9e3b73e4be836e880ad_720w.jpg)
补光后的效果,会让人感觉更有绘画感。
此外将这个效果复制一份并以 normal·viewDir lerp一下可以做出BDTF的效果
![](https://pic3.zhimg.com/80/v2-73195fb50795c9d53e2c85530ba9f1e2_720w.jpg)
### 4.最终效果预览
最终渲染结果
![](https://pic4.zhimg.com/80/v2-f43bb29ee45db8a8c5dd131e95c6b4e7_720w.jpg)
为了保证气氛一致,角色受环境光的影响很大
![](https://pic1.zhimg.com/80/v2-947d28c133bfd4864b5218635ed493bc_720w.jpg)
最终的渲染效果之看看你的
感谢大家的学习!下一期会更新对角色面部的处理!
渲染群群号817341015

View File

@@ -0,0 +1,168 @@
---
title: 厚涂风格渲染文档
date: 2022-09-13 15:25:32
excerpt: 摘要
tags:
rating: ⭐
---
# 厚涂元素分析
# 原理说明
## Material
>与ToonStandard、ToonHair不同Roughness与Metallic的作用恢复成UE默认的逻辑。
这个材质可以理解为在一个Lambert材质上叠上了一层层的效果。
- BaseColor基础颜色贴图
- Metallic控制反射强度。
- Roughness控制高光形状
- Specular控制高光强度。
- Opacity具体参考[[#Material#Opacity]]
- ShadowSideColorToonSkin为SubsurfaceColor次表面颜色贴图用于控制身体各部位的次表面颜色。比如嘴唇、手指等部位因为毛细血管较多会显得比较红。美术需要根据需求进行绘制。
- ToonDataA
- RShadowOffset用于偏移赛璐璐的光影分界面位置。 ToonSkin未使用。
- G用于指定ToonDataID与ToonWorldSettings中ToonData的Index对应。
- B为天光渲染结果与ShadowSideColor的混合因子。ToonSkin未使用。
- A未使用
- ToonDataB
- RGBOutlineColor
- AOutlineMask
- ToonDataC
- RGB描边用的IDTexture
- AOutlineWidth
ToonCustomOutput
这3个参数与间接照明有关体积光照贴图因为没有对应场景所以没办法进行测试。
- InSubsurfaceColorForIndirect修改次表面颜色对于DiffuseColorForIndirect的影响,默认为0,也就是不影响。
- InDiffuseIndirectLightingMask漫反射间接照明Mask
- InSubsurfaceIndirectLightingMask次表面间接照明Mask
### Opacity
该贴图用于控制次表面的Transmission所使用的的预积分Ramp值越大次表面颜色会越明显。使用Marmoset Toolbag烘焙出曲率贴图Curvate、厚薄度贴图Thickness为基础进行辅助绘制最终得到Opacity贴图。
我们需要对贴图进行Clamp去除较低的颜色Thickness选择大腿处的颜色为最低颜色之后将其重映射。
![|300](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/Kama_Thickness.png)
Curvate贴图我们只需要0.5~1部分的数据所以在Clamp(0.5,1)之后再将其重映射。
![|300](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/Kama_Curvature.png)
![|1500](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/ToonRendering/Subsurface_OpacityNode.png)
## ToonWorldSettings说明
为了解决延迟渲染不能传递大量参数的问题本人设计这种通过ID查询对应全局贴图来获取数据的方法。对应的参数在ToonRenderingWorldSettings中设置并且绘制即可。
ToonData目前只有2个参数是有效果
- FalloffRampCurve控制光影过渡曲线。
- BloomMultiplier控制指定部位的“辉光”效果的强度。
ToonSkinData
- FalloffRampCurve控制灯光光照的光影过渡曲线。置空则使用默认的**Lambert**衰竭曲线进行计算。
- TransmissionRampCurve控制灯光光照的次表面效果的渐变曲线。置空则使用**1-Lambert** 衰竭曲线进行计算。
- SkyLight
- SubsurfaceColorAlphaForSkyLight控制天光产生的SSS效果。
- SubsurfaceColorOpacityMultiplier控制Opacity对于天光SSS的影响强度。
- IBL参考[[#ReflectionEnvironmentAndSky#IBL]]
- IBLMultiplierHDR贴图反射效果强度。
- IBLMultiplierRangeByLuminanceMax
- IBLMultiplierRangeSmoothHDR贴图反射过渡效果。
- IBLMultiplierRangeAOMaskAO对于HDR贴图反射的影响倍率。
- BloomMultiplier控制指定部位的“辉光”效果的强度。
### 全局贴图说明
这些贴图都存放在`Engine\Content\EngineMaterials\ToonTexture`中。为了保证效果需要确保这些贴图的CompressionSettings为`VectorDisplacementmap(RGBA8)`。其中ToonDataTexture与ToonSkinData贴图的Filter设置成`Nearest`
- ToonStandard与ToonHair相关
- ToonRampTextureToonStandard与ToonHair的存储调整阴影分界面偏移与软硬度Ramp数据的贴图通过ID来采样指定的行。
- ToonDataTexture ToonStandard与ToonHair的存储Toon数据贴图通过ID来采样指定的行。
- ToonSkin相关
- PreIntegratedToonSkinBRDFTexture ToonSkin专用的PreIntegrated贴图。
- ToonSkinFalloffMaskTexture ToonSkin专用的存储调整阴影分界面偏移与软硬度Ramp数据的贴图通过ID来采样指定的行。
- ToonSkinTransmissionMaskTexture ToonSkin用于调整Transmission Alpha的贴图通过ID来采样指定的行。
- ToonSkinDataTextureToonSkin的存储Toon数据贴图通过ID来采样指定的行。
## 渲染功能
### BasePass
### DiffuseIndirectAndAO
- SSGI建议单帧画面开启。
开启SSGI
![|800](https://raw.githubusercontent.com/blueroseslol/ImageBag/master/ImageBag/ToonRendering/SkyLight_DisableDistanceFields(SSGI).png)
关闭SSGI
![|800](https://raw.githubusercontent.com/blueroseslol/ImageBag/master/ImageBag/ToonRendering/SkyLight_EnableDistanceFields(SSGI).png)
### Lighting
- ShadeModel ToonSkin
- 预积分实现。
### ReflectionEnvironmentAndSky
- 天光需要为Dynamic并且开启Shadow选项。
- 是否开启距离场对会让天光产生AO效果.理论上开启距离场AO后的结果才是正确的但效果并不理想所以需要调整一下天光下半球的颜色也可以作为添加细节的方法最好还是通过HDR贴图
半球0灰度
![|800](https://raw.githubusercontent.com/blueroseslol/ImageBag/master/ImageBag/ToonRendering/SkyLight_EnableDistanceFields(SSGI).png)
半球20灰度
![|800](https://raw.githubusercontent.com/blueroseslol/ImageBag/master/ImageBag/ToonRendering/SkyLight_EnableDistanceFields_20GrayLowSphere(SSGI).png)
天光对于SSS效果影响提升其SSS效果也可以使用ToonRenderingWorldSettings 的SubsurfaceColorAlphaForSkyLight与SubsurfaceColorOpacityMultiplier。
关闭天光:
![|200](https://raw.githubusercontent.com/blueroseslol/ImageBag/master/ImageBag/ToonRendering/SSS_SkyLightDisable.png)
开启天光:
![|200](https://raw.githubusercontent.com/blueroseslol/ImageBag/master/ImageBag/ToonRendering/SSS_SkyLightEnable.png)
#### IBL
主要是为了实现类似sakimichan的部分画作有这种天光产生的效果
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20220930171440.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20220930171503.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20220930171515.png)
这样的效果一般可以通过打光来实现下图是拍摄于5~6月 早上720~740分。可以看到与上图类似的冷暖打光效果。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/ToonRendering/SkyLight_WarmCoolEffect.png)
经过若干尝试最后使用像素亮度来判断暗部与亮度Mask。最后使用ToonRenderingWorldSettings 中的IBL相关属性来控制效果。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/ToonRendering/SceneColorLumenMaskWithMask.png)
这里用了一个0.01的Smooth以及5的Multiper实际上不会那么明显。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/ToonRendering/IBLMultiperEffect.png)
其他游戏比如破晓传说也使用IBL来进行辅助打光。
将包括辅助光在内的灯光烘烤成立方体地图制作光照环境HDR贴图
- 旋转以配合光源的光轴
- 将光面与光源的方向对齐
- 有方向性的正确遮蔽
- 亮度和对比度:艺术家调整的
- 皮肤和眼睛:对比度低
- 在UE4中这可以很容易地在编辑器中改变。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221020151650.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221020151725.png)
### 后处理
相关的选项位于后处理盒子-RenderingFeatures-Toon中。
#### ToonBloom
只有一个UseToonBloom用于开启/关闭ToonBloom效果。其余的参数与自带的Bloom通用。与自带的Bloom的区别在于可以通过ToonData来控制Bloom强度以此来实现一些降低某一些区域亮度很高的区域自发光的Bloom效果不会出现过曝情况。反之也可以使得一些不太量的地方拥有较大的Bloom效果。
因为使用PBR的方式来处理Bloom很容易导致泛光部分发白。而我们需要的其实是一个带颜色的光晕。
![](https://pic4.zhimg.com/80/v2-0d8475dd3d5ea6670face528f575c48f_720w.webp)
#### SNN
油画效果。实现是的是一个近弱远强的油画效果。
- SNNPower用于开关SNN效果。
- SNNSampleRadiusSNN的采样半径远处。采样半径越大油画效果越强。
- SNNMinSampleRadiusSNN的近距离插值的最小采样半径。0即为近距离不采样。
- SNNStartInterpolateDistanceSNN强度开始插值的距离
- SNNEndInterpolateDistanceSNN强度结束插值的距离
## 用户使用说明
### 前置步骤
1. 在插件管理器确认UTToonRendering插件已经启用并在ProjectSettings->Engine->GeneralSettings->DefaultClasses中将WorldSettingsClass设置成`ToonRenderingWorldSettings`
2. 检查天光是否为Dynamic以及是否开启Shadow同时调节Lower Hemisphere Is Solid Color让其稍微亮一些。如果使用距离场阴影那角色需要有胶囊体场景也需要构建较为准确的距离场
### 角色制作顺序
1. 绘制贴图 建议使用Kama的材质
1. 除了基础的BaseColor、Metallic、Roughness、Specular、AO贴图外SubsurfaceColor与Opacity尤为重要。
2. 根据需求来绘制ToonDataB的OutlineColor、IDMapForOutline与OutlineMask贴图。
2. 在ToonRenderingWorldSettings中设置新的ToonSkinData并且在材质中修改ToonDataID使其对应。
1. FalloffRampCurve与TransmissionRampCurve比较重要。
2. 调节SubsurfaceColorAlphaForSkyLight 与SubsurfaceColorOpacityMultiplier 。
3. 适当调节后处理。
### 虚拟拍摄问题
在Project - Engine - Rendering - PostProcessing 中将Enable Alpha Channel Support in PostProcessing 设置成`Allow Throught Tonemapper`。默认是`Disable`

View File

@@ -0,0 +1,512 @@
---
title: 知乎FlashYiYi的卡通渲染分享
date: 2022-10-12 14:43:12
excerpt:
tags: 卡通渲染
rating: ⭐⭐
---
## 目录
- [[#一些较少人提过的二次元渲染方法]]https://zhuanlan.zhihu.com/p/539950545
- [[#低成本皮肤渲染 Pre-integrated Skin]]https://zhuanlan.zhihu.com/p/35628106
- [[#卡通风格场景的定制化技术部分]]https://zhuanlan.zhihu.com/p/338334377
- [[#PBR光照体系下的卡通渲染光照模型]]https://zhuanlan.zhihu.com/p/166147653
## 低成本皮肤渲染 Pre-integrated Skin
GPU Gem3的预积分文章https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-14-advanced-techniques-realistic-real-time-skin
曲率烘焙代码Unity
```c++
v2f vert(appdata_full v)
{
v2f o;
o.uv = fmod(v.texcoord.xy, 1);
o.pos = float4(o.uv * 2 - 1, 0, 1);
o.pos.y = -o.pos.y;
o.vertex = v.vertex.xyz;
o.normal = v.normal;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 worldPos = i.vertex;
float3 worldBump = normalize(i.normal);
float cuv = length(fwidth(worldBump)) / length(fwidth(worldPos)) / 10000 * _CurveFactor;
return fixed4(cuv, cuv, cuv,1);
}
```
## PBR光照体系下的卡通渲染光照模型
### 处理偏色
所有HDR的卡通渲染游戏都会遇到这个问题ToneMaping以后饱和度变低了。更糟糕的是纹理的对比度也降低了导致图片丢失了大量细节甚至连嘴都看不到了。
通常只能在贴图上反向增加饱和度和对比度,但非常不直观,而且精度损失严重。饱和度可以后处理加回去,对比度可没辙。
我用了个自己的骚方法拟合ACES公式的逆运算。
> color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14
这是ACES的一个简化公式只保证了基本亮度。我用Excel拟合了它的逆运算结果如下
> 3.4475 * color * color * color - 2.7866 * color * color + 1.2281 * color - 0.0056
不准确但是差不多了。然后用它来处理传入的固有色贴图通过和原本的贴图颜色lerp选择一个合适的混合比例就可以在最终显示上还原出和原贴图几乎一样的结果。
它的缺点是同样参数在暗处会显得对比度有些过高想做的完美还需要根据光照强度来调整lerp的比例注意是光照强度而不是最终辐射度因为我们要保证是的纹理内容的对比度乘过纹理后就没法得知这个原始的对比度了
如果可以回避偏色问题那么绘制贴图的时候就可以直接采用原画的结果。和PBR不同卡通渲染是个极度依赖原画能力的课题你没有固定的材质库可用也没有什么预定参数决定结果美观的就是贴图的质量出现偏色是致命的。
而且暗部对比过高的现象也证明了,仅仅靠改贴图颜色无法解决这个问题。
而且在ACES下好看的材质纹理对比度需要非常高也非常难看一般美术很难直接画或者选出符合要求的颜色大家都是在用色板的经验数值取色实际操作根本不可能做到符合ACES的偏色要求。
所以这可能是本文最有价值的东西。
当然更聪明的做法是根本不用ACES甚至不用ToneMaping。一个固定亮度而且没有室内外切换的卡通渲染游戏其实根本不需要ToneMaping一切都可以原样画出来毕竟物理光照规则对它来讲并没有意义。
严格来讲甚至不需要线性空间。在卡通渲染下实际上伽马空间的光照结果更加美观。否则PS也不会用伽马作为图层的混合方式了。
但是如果你还打算用PBR来节约画师的工作量还希望借用PBR的那些“好东西”就需要保留ToneMaping和正确的物理光照模型。现在的修正偏色方案就是个折中的方法。
### 自定义Bloom
这基本是个卡通渲染游戏都会做的东西但这次我分别处理了物体的面光和背光Bloom可以让亮部单独产生Bloom的效果。
从“物理正确”角度其实也是需要这样做的。因为Bloom的强弱其实代表了受光的强弱但是卡通渲染的光照强度并不会完全通过颜色表达出来。为了保持画面的饱和度亮部的实际亮度并不会太高而暗部的亮度常常也不能过暗。
因此将Bloom和颜色剥离开来反而更加“物理正确”。
卡通渲染还特别喜欢让头发和皮肤具有更高的Bloom强度使得它们看上去更加具有光泽。
而为了保持发光物体的饱和度实际颜色值不能过高从而也需要用高Bloom值来补齐原本想要的泛光。
我也想过是否可以把实际亮度和颜色在光照体系下就直接拆分开把亮度当作RGB之外的第4个颜色分量然后用“亮度”值直接算Bloom这样处理Additive物体时也更加方便现在我直接屏蔽了Additive物体的Bloom值写入
但这样改的东西满多的所有涉及光照的部分都要改而且这需要让Bloom值是个HDR的值……所以暂时没动。
真要认真做,可能那才是最完美的方案。
## 卡通风格场景的定制化技术部分
### ToneMapping
![](https://pic2.zhimg.com/80/v2-cabc10a6f3ad9ac5b739f71dcb8fc635_720w.webp)
在使用标准ACES的时候如果亮度调的较低暗部细节就会严重丢失图2而如果调高亮度至能看清暗部细节亮部细节就会丢失图3
其中一个方案是可以考虑在无偏色的原始颜色和偏色后的颜色根据亮度做一个插值保证小于1的区域是无偏色的然后渐渐过渡到需要偏色的区域。然而我试了下好像我之前的反转校色方案依然可用图3
![](https://pic4.zhimg.com/80/v2-52c0fc8d665043ffba10ed969df9e06b_720w.webp)
做法是在图2的基础上对人物纹理的颜色输出做以下处理处理强度可以通过和原颜色lerp来控制
>color = 3.4475 * color ^ 3 - 2.7866 * color ^ 2 + 1.2281 * color - 0.0056
其实这个公式相当于预处理了一次纹理将纹理的高光和暗部区域拉长了这样就不怕明暗细节被ACES破坏了。
本来想换个正常点的解决方案,结果……至少这也是个可行的解决方案。
### 半影色调Penumbra Tint
![](https://pic3.zhimg.com/80/v2-7d4641bde1784daf9b9837d46ac2af22_720w.webp)
就是上图这样的东西。Unity在HDRP里竟然提供了这个功能生造了个词叫Penumbra Tint也是满符合Unity自己的人设的。
我是当初研究新海诚动画的画面特征的时候发现的这个东西。这个元素,也是大部分模仿新海诚风格渲染作品所缺失的最后一步。
![](https://pic2.zhimg.com/80/v2-2b4ac4a170f46bab84226cec11af6385_720w.webp)
![](https://pic2.zhimg.com/80/v2-99f4d3b9a8da3584db411401525350d5_720w.webp)
![](https://pic2.zhimg.com/80/v2-f733a54747208c5f575df9e3c8b16381_720w.webp)
![](https://pic4.zhimg.com/80/v2-e6dfe13ac6ad99bf3c60128958043087_720w.webp)
我起初也觉得这个元素是个见缝插针加饱和度的“艺术设计”,直到我在实景中看到了这个现象(拍的不好,肉眼看更明显)
![](https://pic4.zhimg.com/80/v2-8c6bdeae988eec851780dac1ed07509b_720w.webp)
我也不知道这玩意儿是怎么出来的,泊油马路也不是次表面材质啊?
总之,从结果上来看,人眼似乎是可以接受这种“在明暗交界处饱和度提升甚至换个颜色”的表现的。所以作为风格化作品,强化这个表现也就一点问题都没有。
这个元素比较类似次表面材质的效果,所以也可以用类似(差不多是完全一样)的方式来实现。分为两部分:
- 投影部分在软阴影部分叠个额外颜色就好了次表面就是这么做的区别就是UE的实现里亮部也同样需要叠加颜色。
- 而非投影部分的次表面特性有两个实现。一个是预积分LUT相比普通Ramp还需要用曲率修正LUT UV的V坐标否则半影部分在缓慢曲率上会过宽。另一个就是基于屏幕空间卷积的SSS可以很完美的实现这个效果。
所以这个玩意儿大概真的就是个次表面材质特性的艺术延申?万物皆次表面?
![](https://pic4.zhimg.com/80/v2-67949f18cd7e666aa46f85948439eaaf_720w.webp)
或者也可以是上图这样的情景:虽然整体光照都是橙色,但是亮面一侧由于亮度过高导致了泛白,半影部分恰好卡在了不会泛白的亮度区域,保留了原色,最终形成了这个视觉效果。
总之,这两个视觉效果如果用物理打光实现,出现条件较为苛刻。所以在技术实现上,要提供“脱离亮度限制,硬把这个效果挖出来”的功能。其颜色和强度应该由光源和材质双方决定。
如果不希望增加延迟光照下GBuffer的数量也可以完全由光照决定半影颜色和强度仅通过材质蒙版来过滤不希望出现这个效果的物体。这个效果也可以仅通过增加半影处饱和度来实现这样不依赖GBuffer也可以让不同物体的半影颜色互不相同。
![](https://pic1.zhimg.com/80/v2-77d40b908ae38391e0ee49df35d16694_720w.webp)
原神人物上的Ramp其实也是基于这个现象的产物。对于缺乏细节的二值化光照这是一个很好的补充细节。
### GI
GI的比重是区分风格化渲染和真实感渲染的一个很重要的标志。这里的GI主要指漫反射GI
GI是表达真实感很重要的一个要素只要把GI做对画面就会显得真实。换言之只要GI是对的不管怎么做看着都会像照片——而这是风格化渲染需要重点避免的地方。
一个方案是干脆去掉GI手动补光退化回PBR之前的模式典型的例子就是原神。缺点自然是光照平且单调。但是正因为单调控制起来也容易每个地方的颜色都可以单独处理。
另一个方案是反过来加强GI这同样也能达到“不真实”的目的色彩区域的融合也可以让多个物件浑然一体。缺点就是控制起来比较困难只能做成啥样就啥样。
日式卡通风格是从纸上作画转移过来的自然平涂色块会比较多毕竟人手绘制GI难度较高所以风格比较接近无GI的效果。如果想符合日系风格GI就是一个需要压低的元素。
比如下图的情况如果放任GI的染色效果这么大片的绿色草地必然会影响到房子墙壁的颜色无法保持这么高的色彩对比差异。
![](https://pic3.zhimg.com/80/v2-7f84f80a7c68f5486d111a4f1f760606_720w.webp)
### Bloom
Bloom从物理角度应该是一种高亮度产生的视觉现象。
但因为NPR体系下的颜色经过了各种特殊指定并不和实际现实亮度直接对应很容易出现纯白区域不希望Bloom而高饱和低亮度区域反而希望Bloom的情况。
其次Bloom同时也是一种画面上的低频信息为了营造一种柔和的画面“氛围”我们也希望增加这种低频信息的比例。而强行拉高整体Bloom强度又会导致高亮度区域过曝。
所以我们需要一个自定义的Bloom缩放系数。因为从崩三开始的游戏都实装了这个特性是否要实现它一般并不会有疑问。
具体实现的时候最佳方案其实是准备一个单独的Half通道以储存一个HDR范围的亮度值并在frag中将原始颜色的亮度值计算出并存入。只有这样才能保证Alpha Blend, Additive以及各种特殊混合模式下的Bloom值的正确性。
通常我们并不愿意花费一个16bit的通道来实现这个效果只愿意使用一个8bit的通道所以只会储存一个Bloom的缩放值。这样在透明混合的时候就会出现各种麻烦。
**目前我比较倾向的方案是:**
放弃透明物体的Bloom值自定义功能透明物体阶段并不写入Bloom值这样也节约了带宽。而为了最终计算Bloom结果正确必须将透明物体和不透明物体分别渲染然后在Bloom阶段重新组合。为什么必须这样解释起来很麻烦信我就行了
这个分离透明物体渲染的功能UE本来就提供了只需要修改Bloom部分的RT组合实现还是很简单的。而且通过配置r.SeparateTranslucencyScreenPercentage还可以获得半透物体降分辨率功能还可以根据帧率自动进行基本上不会不开分离渲染导致的性能代价也就无所谓了。
事实上,这就是原神的方案,所以估计也没啥人会反对。
原神的透明物体分辨率过低的问题确实严重。我觉得可以将部分需要精度的透明材质转为Mask材质来解决问题Mask通过TAA抖动模拟单层半透的效果还是不错的
虽然FlareBloomVolume Light从物理角度是不同的光学现象但它们都是画面的低频信息提供方也可以近似处理。
风格化渲染实际上非常需要这类低频信息增强画面的柔和感以及缓解纯色块的单调感。除了BloomFlare和体积光同样也是实现这一点的很好用的工具。
严格来讲最理想的方案其实是执行两次Bloom一次无视物理亮度给画面整体做一次模糊叠加第二次根据亮度做正常Bloom模拟实际的辉光物理现象。但因为Bloom的性能成本较高当然希望一次做完。
配置Bloom参数的时候要考虑倒这一点。两个Bloom的功能其实是不同的。
## 投影
投影的风格化依然遵守分频规则。
- 投影或者保持边缘清晰,或者保持一个大范围的模糊过渡,而不要在两种模式之间暧昧不清。
- 同时,需要表达一个简单清晰的形状。清晰边缘不要有尖锐的锯齿,模糊边缘的透明过渡不要有坑坑洼洼的锯齿转角。
- 同时,投影的强度依然不要暧昧不清。或者是清晰可见的,或者就直接没有,这样才能避免脏污感。
这些条件加在一起给投影制造了很多写实渲染没有的麻烦。
人物通常都需要清晰的投影边线,避免和二值化的画面元素冲突。而人物的装饰尖角又是常见元素,投影面多为弧面,这导致投影在不同方向下非常容易出现下面的凌乱边缘。
<iframe src="https://vdn.vzuu.com/SD/863f8180-510e-11eb-9c56-e6e44722d8a0.mp4?disable_local_cache=1&bu=078babd7&c=avc.0.0&f=mp4&expiration=1665570973&auth_key=1665570973-0-0-fb8c37c9b91f50fb4d73f7a55b85b02e&v=ali&pu=078babd7" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
注意看小腿和后脑勺上的投影(确实许多人会看不到问题在哪吧……)
利用距离场简化投影形状是比较直接的解决方案,但性能较差。目前比较靠谱的是手动隐藏部分难看的投影,或者使用代理投影物体。
### AO
上面说过了现在我们要的不光是AO它同时也包含了Local光投影的部分。
具体的要求则是:
- 需要明显的AO但是只能出现在墙角柜子后面这种地方范围要比较大且边缘要尽量柔软。
- 而小的结构周围则不能出现AO人物身上尤其不能有。
### 笔触化
总有人想靠“水彩”“油画”“蜡笔”创造特殊的风格化效果。玩家想要“和画一样”的体验,那就将画面做得和画一样。
虽然是有实时解决方案,但实际效果却不尽人意。
![](https://pic1.zhimg.com/80/v2-295fd2c85bfc63a254f4d718e47e8b44_720w.webp)
### 日式动漫的摄影技术
https://zhuanlan.zhihu.com/p/20202161
![](https://pic3.zhimg.com/80/v2-8f44b62b6faba115b7c32c80902eafc6_720w.webp)
![](https://pic3.zhimg.com/80/v2-a75d23c16631689eed5ea01ff9d7be6a_720w.webp)
![](https://pic2.zhimg.com/80/v2-7a38041340a1a6fad6f3273c4b9f8889_720w.webp)
### 还可以参考
有人在Unity尝试实现了对应的方法
https://zhuanlan.zhihu.com/p/363790714
![](https://pic3.zhimg.com/80/v2-7b2e4291cc98020a787b3d3c754c28e6_720w.webp)
较为微弱的光感
![](https://pic2.zhimg.com/80/v2-ea2139cc43bdc7a318c9494d186eb25d_720w.webp)
添加了ハイライト
个人认为添加了这个Highlight之后才跟《动画基础知识大百科》那案例差不多……
![](https://pic1.zhimg.com/80/v2-2f6672d52b9849c51a9d6d0576d2cbe8_720w.webp)
影パラ将角色稍微压暗一些
具体的实现方法由于相对复杂一些,将在下文详细阐述
总之,将光感+ハイライト叠加上去,将影パラ乘上去,就获得我们的最终结果了
![](https://pic3.zhimg.com/80/v2-0657804df33b91af3fb2e51cb3955b2a_720w.webp)
## 一些较少人提过的二次元渲染方法
### 逆向Tonemaping
常态的做法是利用调色图层和切换不同画笔颜色来尝试一个比较好的结果,但这其实是在用人工做机器应该做的事情,费时费工而且控制精度还不高。
既然目标就是让呈现效果和原画中一致,那就用算法算出一致的效果就好了。
[【UE4】FilmicTonemapperを打ち消してみた - てんちょーの技術日誌](https://link.zhihu.com/?target=https%3A//shop-0761.hatenablog.com/entry/2019/10/14/221751)
UE4本来就提供了一个现成的逆向ACES函数可供调用效率并不低也可以牺牲一些精确度将其更换成单矩阵版本的。
```text
return FilmToneMapInverse(col);
```
(需要在Custom Node节点中include "Engine/Private/TonemapCommon.ush")
上面的文章则自己实现了一个可以对应更改过Tonemaping曲线情况的更复杂的逆向ACES算法性能比原版低了很多。但可以沿着它的思路再进行优化。
### 加权生成描边法线
[flashyiyi修复发尖的描边法线](https://zhuanlan.zhihu.com/p/405518306)
之前讲过一次,用法线所处的三角形夹角作为权重进行加权混合。
其实这个算法也是许多DCC软件自动法线平滑的算法我们一开始不用纯粹是自己菜直接平均的结果怎么可能是对的。
### SDF内描边
[flashyiyi用SDF处理卡通内描线的锯齿问题](https://zhuanlan.zhihu.com/p/113190695)
这个方案最初就是我提的,实际应用效果还不错,也就是侧面观察的时候会有些瑕疵。
除了可以确保描线不出现马赛克外还可以随视角距离变更宽度和Backface描边的缩放效果保持统一几乎无法相互分辨
这个方法生成的描线在靠近观察的时候很容易产生扭曲,但这种扭曲的质感反而会形成一股笔触的感觉,也不全是坏事(接受不了老老实实画本村线去)
![](https://pic4.zhimg.com/80/v2-888a931a98b849e41d604a98808c3ae3_720w.webp)
原图的描线并没有擦除,凑合看看 现在我使用的SDF生成工具是下面这个
[https://github.com/Chlumsky/msdfgen](https://link.zhihu.com/?target=https%3A//github.com/Chlumsky/msdfgen)
还可以选择生成多通道SDF来保留尖角。但实际情况下需要锐利尖角的情况很少我用的依然是单色SDF的版本。
![](https://pic2.zhimg.com/80/v2-04de66ad9ac642b68db5d053e4f2fa4d_720w.webp)
### 烘焙顶点曲率
![](https://pic2.zhimg.com/80/v2-d6b59ffcf779cfce327114a723fb1e91_720w.webp)
Ramp的窄过渡区域在部分情景下实际上是希望保持等宽的否则在平直平面上会散开。
烘焙曲率基本可以解决这个问题。
不要烘焙到纹理,顶点曲率基本够用,还省去了模糊的步骤。在计算平滑法线的同时顺便计算一下就好了。
除了Ramp外也可以用来控制边缘光照的宽度。
### 半视角方向自阴影
现在一般都会用一个逐物体的Shadowmap来绘制角色的自阴影以便获得较高的精度。
但锐利的自阴影依然容易在光照方向和切线接近平行的时候,出现杂边。
改变逐物体阴影的光照方向,将原始的光照方向和视角方向相加归一化作为阴影方向,基本可以避免混乱边缘的出现,一般人也看不出区别(代价是即使光照不变,视角变化的时候阴影位置也会有轻微变动,但意外的并不会有违和感)
### 反转阴影投射屏蔽背面Shadowmap
把ShadowCast时的背面剔除改为正面剔除。
这样一般凸多边形物体的投影就不会固定生成在自己的背光处了但依然会生成到其他物体上。如此一来我们就可以通过改变NoL的光照范围让人物背后也可以获得更大范围的光照这也是强制调整光照仰角所必须的。
![](https://pic1.zhimg.com/80/v2-22d9c50d3bd9f24f7c9c75ce43d47db8_720w.webp)
但人物并不是完全的凸多边形,所以自阴影其实还是会出现在物体背面,只不过不是“全都会出现”而已。实际情况只要调参不要太过分就不会露馅。
(当然,如果没自阴影,一开始也没这个问题。但自阴影还是要有的。)
### RAMP下的多光照体系
二次元渲染用的二分法光照有一些固有的问题:
- 多个二分法光照同时出现,很容易导致光照边线的交错,变得难看。一般只会有一个二分法的光照。
- Ramp本身就是一个单光照体系下的产物Ramp颜色本身就包含了环境光的结果。那么环境光应该如何和直接光混合呢如果环境光是额外叠加上去的最终呈现的效果就不是Ramp图所期望的。
>我选择的办法是Ramp结果独立计算计算完直接乘到其他混合完毕的光照结果上。这样能完整保留Ramp本身的特征。
>Ramp外的光照如果直接根据NoL计算始终会有塑胶感。现在常见的方法是忽略法线或者取视角方向作为法线。我选择的是后者。环境光也同样处理。
如此一来,整个人物会使用一个统一的,平直的光照结果。这样就能保持二次元渲染的平面特征,同时和周围物体的亮度保持一致。
>更关键的是,它解决了“面光处看到的阴影不能暗(通透)”和“背光和阴影下的人物依然要变暗,保持和周围物体亮度一致”之间的矛盾。因为面光处的整个人是整体变亮的,而背光处是整体变暗的,通过视角变化以不易察觉的方式在两个亮度之间过渡。
### 法线无关的光照渐变区域
我将用于代替光照方向的视角方向,改为了一个基于视角的筒状模型:
这样就可以让角色获得一些和法线无关的光照渐变区域,不至于任何时候都只有统一的光照颜色。
![](https://pic1.zhimg.com/80/v2-2d7e7ff1a37eb3909144449600356c0c_720w.webp)
看视频才能懂是怎么做的根据灯光在屏幕空间的位置为圆心根据半径绘制一个Alpha来控制光照强度。
- **表现不出光照的方向性**
用和周围光照相关的高亮度边缘光来体现光照方向,身体上出现的高光也使用同样的规则。
![](https://pic3.zhimg.com/80/v2-fe0f38bbbca473c045a72d29e17aa59e_720w.webp)
此外这个方案本质修改的是法线所以可以很大程度兼容基于SH的烘焙光照。烘焙和实时在默认配置下只有少量的区别。
而这个方案改完后会很大程度忽略物体法线但细节法线又是表现材质的重要环节。因此我后来又将细节法线重新应用于修改后的法线上尽可能保留了原本的材质感对于PBR的部分这点是很重要的
这个平面光也可以和原本的正常光照混合使用(以多一点塑料感为代价增加一些光照变化,通常也可以接受)
比较类似
![](https://pic1.zhimg.com/80/v2-02b7b308f614aa436db3329c865f23e4_720w.webp)
<iframe src="https://vdn3.vzuu.com/SD/856046aa-99ed-11eb-b557-e2a3e4b29ac2.mp4?disable_local_cache=1&bu=078babd7&c=avc.0.0&f=mp4&expiration=1665648277&auth_key=1665648277-0-0-c02d32bd3343ac9f90b23c6aef54ac37&v=tx&pu=078babd7" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
最终方案 将渐变的起点轨迹调整为圆弧
既然方块不行,那干脆用圆得了,让渐变的起始点都在圆上,那它的渐变旋转想必是非常平滑的,
那么这次的代码就非常简洁了:
```cpp
float2 newUV_SS = (scrPos - centerPosSS) / float2(lightRampWidth, lightRampHeight);
float2 intersectPoint = normalize(lightDirVS.xy) * _RampCircleSize.x;
float lightRamp = max(0, 1 + dot(normalize(lightDirVS.xy), newUV_SS - IntersectPoint));
```
![动图封面](https://pic4.zhimg.com/v2-6667dc4086f2b1a69a1528e78c2afd8b_b.jpg)
经过简单的调整,这个方案果然让渐变的旋转“自然”了许多。
而影パラ就是用光感的值进行一些调整后获得,就不再多说了。
那么接下来讲讲关于Highlight的部分
其实Highlight很明显就是边缘光那先按边缘光的做法做着
关于边缘光,其实大家也非常熟悉了,个人采用的算法是
**pow(saturate(1 - dot(viewDirectionWS, normal)), _RimLightSmoothness)**
非常易懂的算法,笔者也尝试过使用次表面散射来模拟,但发现跟普通边缘光的效果相差无几,还是采用了上述简单易懂的边缘光算法。
![](https://pic1.zhimg.com/80/v2-e076bf1764ac9069fec2572cfc76d7c8_720w.webp)
接下来结合之前算的渐变值来进行边缘光的范围限制就行了
![](https://pic3.zhimg.com/80/v2-237a9f48c3aed1fb10f45f9bb3992f1a_720w.webp)
效果正如各位之前所看到的那样
### 影颜色叠加
二次元渲染的阴影颜色是不能直接乘在原始颜色上的,因为会越来越暗,饱和度也会变低。
一个常见的做法是亮部一张图暗部一张图不采用乘法而使用Lerp作为两者的插值这样就能确保暗处的颜色可以自由配置。即使在使用Ramp的情况下也可以将Ramp的A通道指定明暗贴图的插值依据。
但这种做法多多少少有些笨重,两张图画起来也麻烦。
而原神为了省掉ShadowColor这张图采用了多个Ramp选择+Index标记这种方法最终也会遇到同样的问题。
其实这个问题用GGXX的方案会比较好。它混合阴影的方式是先加一个白色然后再乘阴影颜色这样阴影就能突破原颜色的限制最终结果很大概率是符合要求的。加的这个白色的亮度会决定阴影的亮度这样二级阴影的颜色也可以被同时确定。
Ramp方案下依然可以用A通道来表示这个附加的白色的强弱最终更好地通过Ramp来规定暗部颜色。
### 从烘焙光照中获取光照方向
在没有直线光存在的情况下Ramp光照将会消失效果会很不好。
通常方案是寻找场景里最近的一个点光源来作为Ramp光照的依据可以用Volume来实现。原神也是有这个效果的。
然而实际上烘焙的SH光照数据里SH1就是此处光源的大致方向。将它拿出来归一化一下就可以用了。
### 用UV2来偏移高光
目前卡通高光一般都是画死的,视角只会影响亮度。
画死当然不好。所以一个折中方案是让高光区域可以随视角进行一定的偏移(也有缩放的做法,总之要让它有一定变化)
我没有选择把高光画在主纹理上而是画在另一个地方并使用UV2。这张图在UV2上的角度和缩放将会决定它在视角变化时的偏移方向和幅度于是也可以用来模拟肩膀处的光点以及大腿上的竖条高光。
而且这样一来高光图将会使用独立的贴图精度和范围。不会出现整个人高光就这么几处却要占据覆盖人体的整张通道图的情况。而且它还可以和细节图案纹理共用UV2。
并不是什么特别的做法但我看基本没人提网络上的全是FLowmap一类的玩意儿。图案整体偏移用第二套UV控制才是最简单的做法。
### 透射
![](https://pic1.zhimg.com/80/v2-bdebe2d54a02b05f7450125c0cacc50c_720w.webp)
一个比较简单的效果让薄衣服可以违反NoL的规则始终被照亮。手动标记区域。
可以很容易做出所谓“通透感”。
基于这个原理,我还在人物边缘根据法线加了一些次表面散射的效果,但说实在话效果并不明显。
![](https://pic1.zhimg.com/80/v2-52e53a3eac52781da5152250c95763c0_720w.webp)
估计很难看出两者的区别,而且还需要画区域屏蔽鼻子处的漏光
## 头部FOV修正
![](https://pic1.zhimg.com/80/v2-12e54f56ceee302d4a365a2112579e4c_720w.webp)
将脑袋压扁来修复FOV造成的畸变是离线常用的方法但我还没见谁真的在常态的游戏流程中使用。主要原因在俩
- 如果你一直使用50左右的低FOV畸变问题并不显著。动作游戏多使用低FOV只有射击游戏才会用90的高FOV。所以这不是一个急需解决的问题。剧情过场中也可以本来也应该降低FOV。
- 压扁物体会导致各种各样的问题(如排序问题和穿模),深度值可不是随便能改的东西。
对于前者我必须得说本来人家离线动画里用的FOV也不高。人家选择压扁脑袋是为了获得“极致”的表现。畸变不明显又不代表没有畸变。
对于后者,这就是个技术问题。而它是可以解决的。
我选择的方案不是压扁模型A移动到B而是使用结果等价的“平移”(B')来取代压扁,保持深度的正确。
实际使用的时候是既压扁也平移选择其中顶点偏移距离最短的结果即B''
![](https://pic4.zhimg.com/80/v2-909193d5d4fba671ae290f59fd11d833_720w.webp)
篇幅有限我就不解释了,就是个线性代数问题。
![](https://pic4.zhimg.com/80/v2-6ffd372408eaccf1407981a91fb0c33b_720w.webp)
### 柔光光照
卡通光照大幅忽略了物体法线,在使用颜色光照的时候,很容易导致整个物体的颜色都变得非常单一。
参考离线2D动画的打光技巧我选择让光照结果通过柔光的方法应用到BaseColor上。
但最后,我分析柔光的混合公式,总结了一个近似的,能够更直接地实现我的目的的公式。
A * lerp(B,Lum(B),A)
即,原图中的色彩通道亮度越高,光照的饱和度就越低。这样图象中较亮的部分就不会受到光照颜色的影响,但暗处则会正常受到影响,效果如下:
![](https://pic4.zhimg.com/80/v2-0607e852eff4118d27edf3b9703800eb_720w.webp)
![](https://pic3.zhimg.com/80/v2-4cc757acad06e8c4321195179bb5a75e_720w.webp)
如果光源是白光,则和之前没有任何区别。
不过实际使用的时候这个效果也会导致人物很难被颜色光改变颜色需要和正常的光照结果再Lerp一下。
### Diffusion
GGXX和蓝色协议都在分享里提到了Diffusion解释说是另一个Bloom但却没有后续的细节。
我参考动画流程采用了小卷积高斯模糊后和原图像素取Max的做法变亮混合模式
因为是取Max并不会提高亮处的亮度只会拉高暗部的亮度符合我们的需要。而且这样的效果是普通Bloom无法实现的普通Bloom亮部怎么调都肯定会更亮
不过为了做出蓝色协议原图的效果,我还额外再用了一次叠加混合(相乘)。亮度会下降一些,且颜色变深。具体怎么用看实际情况吧。
![](https://pic2.zhimg.com/80/v2-a39a8168cc4666c696198b61b21d72e5_720w.webp)
![](https://pic2.zhimg.com/80/v2-a829e3a3fe0ef1bc97bdaf14eb70d181_720w.webp)
由于只是一个小卷积模糊和简单计算,额外性能成本其实不高。
### SNN预处理纹理
![](https://pic3.zhimg.com/80/v2-7f561429b53aeb91e5ba97c2c702cc0e_720w.webp)
就是蓝色协议介绍的场景纹理处理方法,效果是让远处的纹理呈现色块化质感,看上去更像画。
我采用的是在图片预处理阶段对各级Mip分别应用SNN滤镜的方法低Mip和高Mip的卷积核一样。但由于图片大小不同低Mip变化小高Mip变化大。
最后就能自然地形成上图的效果,且渲染成本为零。
### 染色雾
重点在于NPR场景会使用一个写实绝对不可能用的手段
对渲染结果直接用叠加混合(乘法混合)。
因为这是物理世界中不存在的现象,但是却是动画后期非常常见的做法。
所谓染色雾指的是计算雾效的时候不用Additive混合也不用Alpha Blend混合而是用Multiply混合当然全用Multiply也不行这只是一个额外的效果
这使得你可以很轻易地控制场景各部分的颜色,而不导致原有信息大幅丢失。
实现方面用一个Multiply混合的读深度的透明物体就可以也可以在原本的雾效计算中加入此元素。
之前也有人搞过Multiply光照本质上和Multiply雾是一回事
![](https://pic1.zhimg.com/80/v2-9a60dfbf7cd3ebb23abd1818992e8ee0_720w.webp)
当然美术能不能用好这个工具就是另一个问题了。
(我并不是在嘲讽,而是这类体系外的工具,对于已经形成体系的美术来讲确实很难融入他的体系内。而缺失这部分工具正是场景始终不“卡通”的其中一个重要原因)
蓝色协议的间接光按距离染色也是这个机制的一个应用。

View File

@@ -0,0 +1,103 @@
---
title: 知乎骨鱼子的厚涂实现
date: 2022-10-12 14:25:32
excerpt:
tags: 卡通渲染
rating: ⭐
---
## 前言
主要使用多个LUT混合的方式来实现厚涂的效果。
![300](https://pic4.zhimg.com/80/v2-40d06be17c63ffbfdf7278fa94cc586b_720w.jpg) ![500](https://pic2.zhimg.com/80/v2-34eae06ce1c6db841df4743564281ded_720w.jpg) ![500](https://pic1.zhimg.com/80/v2-7c82b53ceddd3e6ee2f789145ccccde4_720w.jpg)
## 文章摘要
[[厚涂风格实时二次元渲染(0)-前言]]
[[厚涂风格实时二次元渲染(2)-厚涂风的皮肤渲染]]
## 什么是厚涂与半厚涂
https://zhuanlan.zhihu.com/p/467348694
厚涂是发源于**西方油画**的一种绘画技法。 画家在画布上使用很厚的颜料,利用笔刷或画刀进行涂抹,就像抹奶油一样,故名“厚涂”
在这样的绘画工艺下,导致厚涂色块十分分明。因为是用很厚的颜料,所以渐变实际上是离散的,这就和我们的赛璐璐有一点像了。
离散的同时,由于两个颜料的相互融合,边界却又十分的柔和
![](https://pic3.zhimg.com/80/v2-3e2584ae4d9f900e2671d69b652f01d6_720w.png)
柔和的边界
为了让皮肤的质感更为可控我们采用第二章N*N的LUT但这张LUT我们不基于物理去生成而是跟着**感觉**走
实际上,我们需要一个散射光强度与直射光相补正,而这个光就是天空光。
1.天空光是蓝色的,所以我们考虑让皮肤看上去有蓝色的散射感。
2.与此同时在皮肤边缘我希望有一个体现BTDF的效果在二次元下我希望这个颜色是粉粉的。
3.散射感在直射光射到的地方肯定会体现的不充足,在直射光射不到的地方才会比较多。
4.第二条说的BTDF肯定也得是和光照强度绑定的呀
综上四条我们下一步控制皮肤质感的LUT必然有一维是 N·L 用以表示直射强度。考虑到皮肤边缘需要BTDF另一个维度应该是 N·V 
这样的话我们依然需要LUT只是这次的LUT不需要积分毕竟也不基于物理
直接凭感觉随便拿PS什么的画一下就好了。
这个补正方法的灵感来源于一个效果很好的Fake SSS的着色
[Shader Forge - A visual, node-based shader editor | Page 54 - Unity Forum](https://link.zhihu.com/?target=https%3A//forum.unity.com/threads/shader-forge-a-visual-node-based-shader-editor.222049/page-54%23post-1903768)
![300](https://pic4.zhimg.com/80/v2-40d06be17c63ffbfdf7278fa94cc586b_720w.jpg)
二次元可以使用的LUT
以天空为背景色,直射光使用肤色作为相应,然后边缘使用粉粉嫩嫩的颜色。
至于这块儿棕色,因为我们是半兰伯特模型,所以一般而言不会采样到太多。
二次元可以使用的LUT
把这张LUT和上面pre-integrated以相同的方式(但纵坐标轴不同)也应用进去就完成90%的效果啦
![500](https://pic2.zhimg.com/80/v2-34eae06ce1c6db841df4743564281ded_720w.jpg)
赛璐璐Only
![500](https://pic1.zhimg.com/80/v2-7c82b53ceddd3e6ee2f789145ccccde4_720w.jpg)
### 3.一点小方法
1另类高光
![](https://pic3.zhimg.com/80/v2-fcafe3f2e9e0cf12887b6bb95ef248a2_720w.png)
肩头上有高光
可以看到有些二次元厚涂作品有明显不基于物理的高光,这是因为二次元美少女每一个都滑溜溜的。
这个效果在我们使用了不基于物理的补正LUT后非常简单在LUT的左上角点一个光点就可以了。
2屏幕空间补光
![](https://pic3.zhimg.com/80/v2-2a91d9ae7ff5a51329e8201c0a66ff46_720w.png)
肩头存在补光
二次元角色的光照实际上都是以好看为第一要务的。其实许多动漫都存在“两个人面对面,但是却都向光”的情况。对于二次元美少女而言,光补的越多,就越好看(雾)
[Unity URP Shader 与 HLSL 自学笔记六 等宽屏幕空间边缘光 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/365339160)
可以在这篇文章学习这个套路。因为我也是学的别人的,就不在这里班门弄斧啦。
![](https://pic2.zhimg.com/80/v2-70afc5f583d1c9e3b73e4be836e880ad_720w.jpg)
补光后的效果,会让人感觉更有绘画感。
此外将这个效果复制一份并以 normal·viewDir lerp一下可以做出BDTF的效果
![](https://pic3.zhimg.com/80/v2-73195fb50795c9d53e2c85530ba9f1e2_720w.jpg)
厚涂作品:
https://www.zhihu.com/question/411686903/answer/1666001786
## 要素组成分析
### 1.皮肤的平滑光照
显而易见,插画风格与赛璐璐风格最大的区别就在于,皮肤对于不同的光照情况有着不同的表现。
>常见的pre-integrated模型刚好是以 N·L 为横坐标的这与我们的RampMap一致。此时此刻我们的赛璐璐风格RampMap应该还是一个1N大小的LUT将这个一维LUT与pre-integrated二维LUT直接逐行相乘得到一个新的RampMap但是1N的RampMap拓展为了N*N纵坐标的1/r是在球面上的如何应用到我们的复杂网格渲染中呢
>为了让皮肤的质感更为可控我们采用第二章N*N的LUT但这张LUT我们不基于物理去生成而是跟着**感觉**走
实际上,我们需要一个散射光强度与直射光相补正,而这个光就是天空光。
1. 天空光是蓝色的,所以我们考虑让皮肤看上去有蓝色的散射感。
2. 与此同时在皮肤边缘我希望有一个体现BTDF的效果在二次元下我希望这个颜色是粉粉的。
3. 散射感在直射光射到的地方肯定会体现的不充足,在直射光射不到的地方才会比较多。
4. 第二条说的BTDF肯定也得是和光照强度绑定的呀
综上四条我们下一步控制皮肤质感的LUT必然有一维是 N·L 用以表示直射强度。考虑到皮肤边缘需要BTDF另一个维度应该是 N·V 
这样的话我们依然需要LUT只是这次的LUT不需要积分毕竟也不基于物理
直接凭感觉随便拿PS什么的画一下就好了。
这个补正方法的灵感来源于一个效果很好的Fake SSS的着色
[[Shader Forge - A visual, node-based shader editor | Page 54 - Unity Forum](https://link.zhihu.com/?target=https%3A//forum.unity.com/threads/shader-forge-a-visual-node-based-shader-editor.222049/page-54%23post-1903768)](https://forum.unity.com/threads/shader-forge-a-visual-node-based-shader-editor.222049/page-54#post-1903768)
![](https://pic4.zhimg.com/80/v2-40d06be17c63ffbfdf7278fa94cc586b_720w.jpg)

View File

@@ -0,0 +1,332 @@
## 前言
之前想使用UE4实现IOchair通过Blender晶格变形实现的二次元角色面部透视矫正效果。在使用HLSL写了1/3晶格效果和IOchair沟通后才发现这个功能只需要通过在摄像机坐标系下调整模型顶点的Z值即可。但要在Ue4的材质编辑器中实现这个功能就必须知道Ue4的VertexShader处理过程于是我花了些时间研究了一下。
**效果实现思路**
一句话解释就是在模型的顶点坐标转换到摄像机坐标后在乘以投影矩阵前对其Z值进行调整以实现纸片人的效果。
![image](https://pic3.zhimg.com/80/v2-3af4ff0324f55ac7c33c7cf21dad4d32_720w.jpg)
参考了以下这篇文章:
https://zhuanlan.zhihu.com/p/268433650?utm_source=ZHShareTargetIDMore
## IOchair的补充
透视矫正的作用补充一下,还有:
1. 减弱AO空间扁了AO变浅更二维
2. 清理前发投影,刘海在额头上的投影变得整齐美观
3. 顺滑高光,高光连续感更强
下图为IOchair的演示图
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ViewFix1.jpg)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ViewFix2.png)
个人认为:以上说法是建立在使用默认光照的情况,一般的卡通渲染都会选择重写光照来控制面部阴影表现。
## BasePassVertexShader
代码位于BasePassVertexShader.usf中。可以看得出WorldPosition就是模型的世界坐标WorldPositionExcludingWPO为没有WorldPositionOffset的版本。WorldPosition加上offset后乘以MVP矩阵最后输出Output.Position。
以下是我精简过的代码以方便阅读:
```hlsl
/** Entry point for the base pass vertex shader. */
void Main(
FVertexFactoryInput Input,
OPTIONAL_VertexID,
out FBasePassVSOutput Output,
out float OutGlobalClipPlaneDistance : SV_ClipDistance)
{
uint EyeIndex = 0;
ResolvedView = ResolveView();
FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates);
float4 WorldPosition = WorldPositionExcludingWPO;
float4 ClipSpacePosition;
float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPosition.xyz, TangentToLocal);
// Isolate instructions used for world position offset
// As these cause the optimizer to generate different position calculating instructions in each pass, resulting in self-z-fighting.
// This is only necessary for shaders used in passes that have depth testing enabled.
{
WorldPosition.xyz += GetMaterialWorldPositionOffset(VertexParameters);
}
{
float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPosition);
ClipSpacePosition = INVARIANT(mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip));
Output.Position = INVARIANT(ClipSpacePosition);
}
#if USE_GLOBAL_CLIP_PLANE
OutGlobalClipPlaneDistance = dot(ResolvedView.GlobalClippingPlane, float4(WorldPosition.xyz - ResolvedView.PreViewTranslation.xyz, 1));
#endif
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
Output.BasePassInterpolants.PixelPositionExcludingWPO = WorldPositionExcludingWPO.xyz;
#endif
#endif
Output.FactoryInterpolants = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);
OutputVertexID( Output );
}
```
其他两个函数:
```
float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
return TransformLocalToTranslatedWorld(Intermediates.UnpackedPosition);
}
float4 VertexFactoryGetRasterizedWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float4 InWorldPosition)
{
return InWorldPosition;
}
```
## 矩阵计算
### TranslatedViewProjectMatrix
以下是相关的计算代码:
```c++
ViewUniformShaderParameters.TranslatedWorldToClip = InViewMatrices.GetTranslatedViewProjectionMatrix();
inline const FMatrix& GetTranslatedViewProjectionMatrix() const
{
return TranslatedViewProjectionMatrix;
}
TranslatedViewProjectionMatrix = GetTranslatedViewMatrix() * GetProjectionMatrix();
inline const FMatrix& GetTranslatedViewMatrix() const
{
return TranslatedViewMatrix;
}
inline const FMatrix& GetProjectionMatrix() const
{
return ProjectionMatrix;
}
```
### Project矩阵
UE4的矩阵与U3D使用的标准OPENGL式的矩阵不同
1. OpenGL中Z坐标被映射到[-1, 1]。而Ue4映射到[0,1],这使得"W"值变为1而不是-1。
2. Ue4对其所有的透视矩阵应用了一个矩阵转置。
UE4 | Value | Unity | Value
---|---|---|---
[0,0]| 1.0f / FMath::Tan(HalfFOV) |[0,0]| 2.0F * near / (right - left);
[1,1]| Width / FMath::Tan(HalfFOV) / Height| [1,1]| 2.0F * near / (top - bottom);
[2,0]| 0.0F| [0,2] | (right + left) / (right - left);
[2,1]| 0.0F| [1,2] | (top + bottom) / (top - bottom);
[2,2]| ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ))| [2,2]| -(far + near) / (far - near);
[3,2]| -MinZ * ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ)) | [2,3]| -(2.0F * far * near) / (far - near);
[2,3]| 1.0f| [3,2]| -1.0f
#### 自定义投射矩阵效果
继承ULocalPlayer类重写CalcSceneView函数
```
FSceneView * UOffAxisLocalPlayer::CalcSceneView(FSceneViewFamily * ViewFamily, FVector &amp;amp;OutViewLocation, FRotator &amp;amp;OutViewRotation, FViewport * Viewport, FViewElementDrawer * ViewDrawer, EStereoscopicPass StereoPass)
{
FSceneView* View = ULocalPlayer::CalcSceneView(ViewFamily, OutViewLocation, OutViewRotation, Viewport, ViewDrawer, StereoPass);
if (View)
{
FMatrix CurrentMatrix = View-&amp;gt;ViewMatrices.GetProjectionMatrix();
float FOV = FMath::DegreesToRadians(60.0f);
FMatrix ProjectionMatrix = FReversedZPerspectiveMatrix(FOV, 16.0f, 9.0f, GNearClippingPlane);
View->UpdateProjectionMatrix(ProjectionMatrix);
}
return View;
}
```
最后在Project Settings->General Settings->Local Player Class设置定义的新类即可。
详细内容可以参考http://www.geodesic.games/2019/03/27/projection-matrices-in-unreal-engine/<br>
作者还写了插件https://github.com/GeodesicGames/SimpleOffAxisProjection
#### 具体计算代码
```
/** Calculates the projection matrix using this view info's aspect ratio (regardless of bConstrainAspectRatio) */
ENGINE_API FMatrix CalculateProjectionMatrix() const;
FMatrix FMinimalViewInfo::CalculateProjectionMatrix() const
{
FMatrix ProjectionMatrix;
ProjectionMatrix = FReversedZPerspectiveMatrix(
FMath::Max(0.001f, FOV) * (float)PI / 360.0f,
AspectRatio,
1.0f,
GNearClippingPlane );
}
```
```
FORCEINLINE FPerspectiveMatrix::FPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ, float MaxZ)
: FMatrix(
FPlane(1.0f / FMath::Tan(HalfFOV), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, Width / FMath::Tan(HalfFOV) / Height, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ)), 1.0f),
FPlane(0.0f, 0.0f, -MinZ * ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ)), 0.0f)
)
{ }
FORCEINLINE FReversedZPerspectiveMatrix::FReversedZPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ, float MaxZ)
: FMatrix(
FPlane(1.0f / FMath::Tan(HalfFOV), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, Width / FMath::Tan(HalfFOV) / Height, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? 0.0f : MinZ / (MinZ - MaxZ)), 1.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? MinZ : -MaxZ * MinZ / (MinZ - MaxZ)), 0.0f)
)
{ }
```
### View矩阵
```
void FViewMatrices::UpdateViewMatrix(const FVector& ViewLocation, const FRotator& ViewRotation)
{
ViewOrigin = ViewLocation;
FMatrix ViewPlanesMatrix = FMatrix(
FPlane(0, 0, 1, 0),
FPlane(1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
const FMatrix ViewRotationMatrix = FInverseRotationMatrix(ViewRotation) * ViewPlanesMatrix;
ViewMatrix = FTranslationMatrix(-ViewLocation) * ViewRotationMatrix;
// Duplicate HMD rotation matrix with roll removed
FRotator HMDViewRotation = ViewRotation;
HMDViewRotation.Roll = 0.f;
HMDViewMatrixNoRoll = FInverseRotationMatrix(HMDViewRotation) * ViewPlanesMatrix;
ViewProjectionMatrix = GetViewMatrix() * GetProjectionMatrix();
InvViewMatrix = ViewRotationMatrix.GetTransposed() * FTranslationMatrix(ViewLocation);
InvViewProjectionMatrix = GetInvProjectionMatrix() * GetInvViewMatrix();
PreViewTranslation = -ViewOrigin;
TranslatedViewMatrix = ViewRotationMatrix;
InvTranslatedViewMatrix = TranslatedViewMatrix.GetTransposed();
OverriddenTranslatedViewMatrix = FTranslationMatrix(-PreViewTranslation) * ViewMatrix;
OverriddenInvTranslatedViewMatrix = InvViewMatrix * FTranslationMatrix(PreViewTranslation);
// Compute a transform from view origin centered world-space to clip space.
TranslatedViewProjectionMatrix = GetTranslatedViewMatrix() * GetProjectionMatrix();
InvTranslatedViewProjectionMatrix = GetInvProjectionMatrix() * GetInvTranslatedViewMatrix();
}
```
### 相关变量定义位置
UE4已经帮我们计算好相关的矩阵一些矩阵与摄像机相关变量位于一下文件中
```
// SceneData.USH
float4x4 LocalToWorld;
float4x4 WorldToLocal;
// SceneView.cpp
// ResolvedView
ViewUniformShaderParameters.ViewToTranslatedWorld = InViewMatrices.GetOverriddenInvTranslatedViewMatrix();
ViewUniformShaderParameters.TranslatedWorldToClip = InViewMatrices.GetTranslatedViewProjectionMatrix();
ViewUniformShaderParameters.WorldToClip = InViewMatrices.GetViewProjectionMatrix();
ViewUniformShaderParameters.ClipToWorld = InViewMatrices.GetInvViewProjectionMatrix();
ViewUniformShaderParameters.TranslatedWorldToView = InViewMatrices.GetOverriddenTranslatedViewMatrix();
ViewUniformShaderParameters.TranslatedWorldToCameraView = InViewMatrices.GetTranslatedViewMatrix();
ViewUniformShaderParameters.CameraViewToTranslatedWorld = InViewMatrices.GetInvTranslatedViewMatrix();
ViewUniformShaderParameters.ViewToClip = InViewMatrices.GetProjectionMatrix();
ViewUniformShaderParameters.ViewToClipNoAA = InViewMatrices.GetProjectionNoAAMatrix();
ViewUniformShaderParameters.ClipToView = InViewMatrices.GetInvProjectionMatrix();
ViewUniformShaderParameters.ClipToTranslatedWorld = InViewMatrices.GetInvTranslatedViewProjectionMatrix();
ViewUniformShaderParameters.ViewForward = InViewMatrices.GetOverriddenTranslatedViewMatrix().GetColumn(2);
ViewUniformShaderParameters.ViewUp = InViewMatrices.GetOverriddenTranslatedViewMatrix().GetColumn(1);
ViewUniformShaderParameters.ViewRight = InViewMatrices.GetOverriddenTranslatedViewMatrix().GetColumn(0);
ViewUniformShaderParameters.InvDeviceZToWorldZTransform = InvDeviceZToWorldZTransform;
ViewUniformShaderParameters.WorldViewOrigin = InViewMatrices.GetOverriddenInvTranslatedViewMatrix().TransformPosition(FVector(0)) - InViewMatrices.GetPreViewTranslation();
ViewUniformShaderParameters.WorldCameraOrigin = InViewMatrices.GetViewOrigin();
ViewUniformShaderParameters.TranslatedWorldCameraOrigin = InViewMatrices.GetViewOrigin() + InViewMatrices.GetPreViewTranslation();
```
### 具体实现与效果
将这个Custom节点直接连到WorldPositionOffset即可。
```
//float3 WorldPosition
//float ZClampValue
//float3 ObjectPosition
// const float4x4 localToWorld=Primitive.LocalToWorld;
const float4x4 WorldToView=ResolvedView.TranslatedWorldToView;
const float4x4 ViewToWorld=ResolvedView.ViewToTranslatedWorld;
float3 ViewSpacePosition=INVARIANT(mul(WorldPosition, WorldToView));
float pivortZ=INVARIANT(mul(ObjectPosition, WorldToView)).z;
ViewSpacePosition.z=(ViewSpacePosition.z-pivortZ)/ZClampValue+pivortZ;
return INVARIANT(mul(ViewSpacePosition,ViewToWorld))-WorldPosition;
```
这里我使用原神中莫娜作为演示模型使用的是Unlit自发光材质
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ViewAxisZScale_1.png)
原始模型
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ViewAxisZScale_0.5.png)
缩放0.5
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ViewAxisZScale_0.3.png)
缩放0.3
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/ViewAxisZScale_0.2.png)
缩放0.2
## 思考与改进
这个压扁操作本质上为了降低模型的透视效果,使得角色的脸部更加的平面化。但目前这个做法比较粗暴,还有很大的改进空间:
1. 我们只需要调整角色脸面向相机且较为靠近相机的顶点。
2. 透视效果与fov、相机坐标下模型的Z具有线性关系可以根据这些关系来调整“压扁值”。
3. 调整的方向是鼻子、嘴、眼睛等具有重点轮廓区域。不对脸部进行较大数值的调整,使得阴影不会产生较大的形变。
### 重新计算法线
IOchair通过Blender晶格变形实现该效果的。而DCC工具中的晶格变形会重建顶点法线。所以我也需要在UE4中实现这个功能。经过404查询得知在GPU精粹有中解决方法
1. 微分附近顶点求得法线。
2. 使用雅可比矩阵。
第一种方法比较简单只需要通过ddx与ddy计算WorldPosition即可得到切线法线值。
第二种方法我会在学习之后补上。
回顾第一种方法时还找到有意思的资料:
- 通过ddx、ddy与Cross去除平滑法线效果
![image](https://answers.unrealengine.com/storage/attachments/3975-normalsfromwpsxwits.png)
![image](http://i.imgur.com/cMhE4pD.png)
https://answers.unrealengine.com/questions/29087/disable-terrain-smoothing.html#answer-30100
- 通过Noise生成世界法线的方法
![](https://i1.wp.com/odederell3d.blog/wp-content/uploads/2020/09/noise_bump_3d_A.jpg?ssl=1)
https://odederell3d.blog/tag/ddy/
PS.晶格变形用的是FreeFormDeformation算法直接搜索Lattice Deformation是找不到资料的。
### 3.3.1 视场(Field of View)的影响
先来解释一下视场视场即FOV在摄影中指相机可以接收影像的角度范围也可以称为视野。
> 由于显示比例的原因FOV在水平与垂直方向上的数值不同以下未作说明的情况下默认为水平FOV。
FOV越大视野越宽透视的变形就越大下图可以很好的展示这种特征。
![](https://pic1.zhimg.com/80/v2-871bfeba957c0f5faada5c975b7cee4c_720w.webp)
How Focal Length Affects Viewing Angle
下图是人眼的水平视场的范围示意±30°是中央视野区域±60°是双眼水平视场。所以游戏中的FOV也遵循这个规则能够修改的范围通常都在60 - 120之间。
![动图封面](https://pic3.zhimg.com/v2-4e87c33baf0972c7765cd5f8e5ccf20e_b.jpg)
人眼的水平视场能力(Horizontal Field of View, FOV)
而FOV的高低会很大程度上的会影响角色表现特别是特写时的面部区域。卡通角色更适用于在较低的FOV中表现效果而第三人称视角中通常使用到的FOV数值要远大于适合卡通表现的FOV。
下面的视频中是 5 - 60 之间的 FOV (Vertical) 变换可以明显的看出FOV变化时面部轮廓的变形。
_5 - 60的 FOV (Vertical)在16 : 9的显示比例下换算成水平FOV是 8.88 - 91.52 之间_
很多卡通角色的模型在制作时面部正面会选择做扁一部分原因就是为了抵消高FOV带来的面部轮廓变形。

View 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宽度修正
- 支持模型内部轮廓线
- 可通过定点色微调
### 模型内部轮廓线
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221013174424.png)
使用PixelShader对顶点色的B通道进行二值化所得。
### 顶点色
R轮廓粗细
G把不需要轮廓压入模型
B控制轮廓粗细
## 灯的轮廓光效果
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221013175811.png)
View空间的方向光强度Ramp。FlashYiYi有说过**存疑**)。
- 方向をZ軸デフォルトにすれ䜀、逆光表現䛾リムライト
- 方向を設定すれ䜀、平行光源䛾ライティング
根据方向光的DirectionVector来Fake一个方向光Ramp如果方向是完全背对角色则使用RimLighting效果。
## 边缘光与轮廓线的混合
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/20221013180433.png)

View 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)
![](https://pic3.zhimg.com/80/v2-c87e62b1110364d42d3ce337a9d8ce02_720w.jpg)
- 使用一个GBuffer存储NoL信息来减少相关二值化参数。
局部阴影贴图绘制与自投影阴影的叠加推荐使用虚幻的阴影与AO叠加算法。
![](https://pic3.zhimg.com/80/v2-b623cbfbc8d647a8500d51ad7dbb1726_720w.jpg)
## 多光源策略
只有主光在计算中会计算暗部颜色,这样就能防止多光源时,暗部被反复提亮的问题。除此之外,剩下的点光源只用于提亮,并且忽略法线,只计算距离衰减,防止照出难看的结构。这样的效果还是挺二次元的。
## 高光
![](https://pic1.zhimg.com/80/v2-dda842980b2843496bae37f1577a5efc_720w.jpg)
![](https://pic3.zhimg.com/80/v2-4592dd54dc4b3b9d54c37dbe276f75f6_720w.jpg)
![[08-Assets/Images/ImageBag/UrealEngineNPR/BlueProtocol/蓝色协议_自定义形状高光.png]]
SpcularMask高光理论上这部分应该在Lighting阶段计算的但是第二步的绘制SpecularMask、ID、XY轴心偏移。感觉这部分是材质编辑里计算出来得高光。
## 天光与间接光处理
为了防止环境光把角色照出立体感,所以计算环境光时,会把法线全部看作世界空间上方向来处理。同时增加了一些参数可以进行一些定制化调整。
## RimLight
贴图:
- RimLightMask
- RimLightWidth
![](https://pic2.zhimg.com/80/v2-1cd4bbf9e245b44641b43dbd4cd68e19_720w.jpg)
![](https://pic3.zhimg.com/80/v2-39dc4100774d64e8dafd3f56771a5c56_720w.jpg)
使用描边计算的方式最后计算出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
```
关闭角色的预计算阴影。
## 管线
![](https://pic2.zhimg.com/80/v2-e0df1cfdf229e2c50f6a70ca0ee99101_720w.jpg)

View 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中的法线计算。这样阴影和描边的计算就可以兼容了。
![](https://pic1.zhimg.com/80/v2-91891d96bc4e5ef18e08b66a44366720_720w.webp)
> 用OutLine的法线进行全部着色的话边缘处和阴影就都乱了。
> 右:用原来计算阴影的法线进行全部着色的话,阴影虽然很对但描的边会出现断裂。
![](https://pic2.zhimg.com/80/v2-58d4f4d014f1e6ea2a104fa8f0fd4cf5_720w.webp)
> 对策:用两种法线分别解决
> 上面是阴影和边缘等用的基本模型法线下面做描边时用的处理过的法线。右边是我们存放处理过的法线的新增UV图的预览。
实际的调整方法就是直接手工调整原模型的法线。其中最有必要调整头发束之类的三角形面变化剧烈的部分。
![](https://pic4.zhimg.com/80/v2-fc78712c521f0e009ef5cf7676fba27b_720w.webp)
> 左侧调整前,我们可以看到一些接缝处或法线变化剧烈的地方描的线出现断裂,而右侧调整后的法线则效果较好。
为了和动画风格相近,赛马娘会根据场景与演出来调整描边的颜色。
## 眼睛
此外,马娘的眼睛是使角色具有吸引力的重要元素,但赛马娘游戏特别注重眼睛中的高光(high light)。除了制作漫反射纹理外,也加了张高光专用的纹理。以此组合出多彩的瞳孔表现。
高光可以改变自身的强度,包括垂直和水平的旋转和平移。在马娘们情绪高涨到快哭时,使用更大的高光和更多的高光粒子等。
![](https://pic3.zhimg.com/80/v2-b92c167904ea91151f797468757bfea6_720w.webp)
> 上方为基础高光,下方为补充的粒子高光。
![](https://pic4.zhimg.com/80/v2-6cba08ee60bd61f7a5aa23f54c70343b_720w.webp)
> 改变高光强度:通常、强高光,无高光。
此外,还有单独的高光动画。马娘快要哭出来的时候,高光会小范围内颤动。在兴致勃勃时,高光会闪闪发光并绕眼睛转一圈。
![](https://pic2.zhimg.com/80/v2-50cb7c328d458bbdff044af57999654d_720w.webp)
![](https://pic3.zhimg.com/80/v2-feb2b398b0864b3960a206957ea30a02_720w.webp)
「寄」になった.jpg
> 醒目飞鹰发动技能时眼睛的小高光颤动特效
![](https://pic4.zhimg.com/80/v2-e59919739004221b5438968ee313b07b_720w.webp)
> 米浴在演唱会时的眼睛中也有小高光颤动特效
![](https://pic1.zhimg.com/80/v2-d6eaa81707338e44192a42f70af6a5f8_720w.webp)
> 胜利奖券在赢下比赛时眼中同时使用了两种高光。
赛马娘通过结合面部表情的情感表达和眼睛高光的情感表达。更加自然的表现出了马娘们的情感,突出了马娘们的个性,让用户更加轻易的入坑。
![](https://pic3.zhimg.com/80/v2-c6f9a21ab6e2303a2077614d38bdb0b2_720w.webp)
> 总结:表情部分总结,表情系统是让马娘们能更加自然表达出情感的系统。
> 利用表情和眼睛来达到各种效果。