vault backup: 2023-12-08 18:00:40

This commit is contained in:
BlueRose 2023-12-08 18:00:41 +08:00
parent dd531a6e2b
commit b0f7f0f52d
10 changed files with 553 additions and 820 deletions

View File

@ -1,98 +0,0 @@
## EOE需求
- 渲染功能
- 描边功能改进
- [x] ScreenPercentage 自适应移植代码1小时
- [ ] Fov 自适应 1~2天
- [ ] 根据投射调整宽度 1~2天=> 使用李兄的方案0天)
- [ ] 支持PBR描边控制 (2~4天) => 使用李兄的方案0天)
- 厚涂风格卡通皮肤渲染
- [ ] 移植厚涂引擎中的代码 移植代码2~5天
- 脸部阴影控制
- [ ] 角色在向左/向右旋转头部时,面部不会产生阴影和一些残留的”脏”的阴影信息 (14天)
- 眼睛
- [ ] 眼白阴影 5天=>使用李兄的方案进行进一步开发1天
- 头发
- [ ] 头发高光调整 移植代码1天=>使用李兄的方案0天)
- 卡通材质重制
- ToonStandard 5~7天
- [ ] 制作母材质以及其他Toon材质的MaterialInstance
- ToonSkin 1~2天
- 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

@ -1,82 +0,0 @@
---
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

@ -1,445 +0,0 @@
# 实用库
`#include "/Engine/Private/Random.ush"`
`#include "/Engine/Private/SobolRandom.ush"`
# 眼睛
眼睛焦散:
## 凹模型
直接贴图即可。
折射
模型高光
## 凸模型
1. 使用视差贴图来控制瞳孔效果 + 折射模拟
```
float2 viewL = mul(viewW, (float3x2) worldInverse);
float2 offset = height * viewL;
offset.y = -offset.y;
texcoord -= parallaxScale * offset;
```
![](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

@ -6,14 +6,92 @@ tags:
rating: ⭐
---
# 目录
- [[卡通渲染相关资料]]里面的有原神、碧蓝幻想、罪恶装备、赛马娘、黑丝指的参考。
- 做完基础功能之后,再逐个寻找新的功能点与方向。
- [[知乎FlashYiYi的卡通渲染分享]]里面有许多Fake技巧
- 参考对象分析
- 星穹铁道
- 破晓传说
- 蓝色协议
- 非人学园2
>PS. 先实现一波Cel之后再实现Thickly - Coated。
# 功能
# 最近开发计划
1. 移植一波JasonMa的ShaderModel。
2. 实现一波Anti-Lut。
# 卡通渲染引擎功能总览
- ShaderModel
- Cel
- Thickly - Coated
- 描边
- 外描边:后处理描边 、 Mesh挤出 2种方式原神采用了Mesh挤出。
- 内描边后处理、SDF描边、本村线 3种方式
- 使用Mesh基础、后处理、模型绘制。后处理传递Id贴图
- [[描边]]
- ![[描边#实现功能]]
- 阴影
-
- 边缘光
- 其他特性
- [[Toon眼睛渲染]]
- [[Toon眉毛渲染]]
## 待实现功能
知乎提到的渲染功能:
- 发尖勾线 https://zhuanlan.zhihu.com/p/405518306
- SDF 描边
- https://zhuanlan.zhihu.com/p/113190695
- https://zhuanlan.zhihu.com/p/360229590
- 屏幕空间深度边缘光 Screen Space Depth Rimlight
- https://zhuanlan.zhihu.com/p/139290492
- 原神实现
- https://zhuanlan.zhihu.com/p/435005339
- 遮挡时的模糊网点效果
- https://zhuanlan.zhihu.com/p/370140711
![](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 中的一个效果
[[厚涂风格研究与开发笔记]]
- ShaderModel
- 分阶着色
- 二阶化:
- 多阶化:
@ -25,6 +103,12 @@ rating: ⭐
- 高光颜色
- 参数化高光形状
- 多层高光
- Shader内编辑法线
- N=_scale * L + N
- 描边
- 外描边:后处理描边 、 Mesh挤出 2种方式原神采用了Mesh挤出。
- 内描边后处理、SDF描边、本村线 3种方式
- 使用Mesh基础、后处理、模型绘制。后处理传递Id贴图
- 阴影
- 自定义阴影颜色
- 自定义阴影范围
@ -35,13 +119,7 @@ rating: ⭐
- 多层边缘光
- 边缘光衰减(入射角度更明显 or 背光角度更明显)
- 多光源支持
- PBR支持
- PBR和NPR混合
- AO
- matcap支持
- 根据相机角度调整模型(非必要)
- Shader内编辑法线
- N=_scale * L + N
- 眼睛
- 反射 环境反射或者matcap支持
- 内阴影 AO实现或者画死的内阴影
@ -63,9 +141,7 @@ rating: ⭐
- 眉毛/睫毛不受遮挡
- 自发光
- 阴影内素描效果
- 额外效果
- 后处理效果
- 后处理 辉光效果
- 之后提到了SunFlare应该是那个屏幕后处理效果。卡通渲染很依赖体积光所以自然会有好的效果假也没关系假才是对的。谷歌搜SNN Filter https://www.shadertoy.com/view/MlyfWd
- 旁边的Kuwahara应该是个类似的算法64采样。
# 开发计划

View File

@ -0,0 +1,113 @@
---
title: Material&BasePass
date: 2023-12-08 17:34:58
excerpt:
tags:
rating: ⭐
---
# GBuffer
```c#
OutGBufferA = WorldNormal/PerObjectGBufferData
OutGBufferB = Metallic/Specular/Roughness/EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask);
OutGBufferC = BaseColor/GBufferAO
OutGBufferD = GBuffer.CustomData;
OutGBufferE = GBuffer.PrecomputedShadowFactors;
```
```c#
GBufferB:Metallic/Specular/Roughness=>ToonHairMask OffsetShadowMask/SpcularMask/SpecularValue
OutGBufferD = CustomData.xyzw=》ShaderColor.rgb/NoL
OutGBufferE = GBuffer.PrecomputedShadowFactors.xyzw=》 /RimLightMask/DiffuseOffset/RimLightWidth
OutGBufferF = velocity => OutlineWidth/OutlineID/OutlinePaint/OutlineZShift
```
```
| GBuffer | 表头 |
| -------- | ------------------------------------------------------------------------------------- |
| GBufferB | OffsetShadowMask SpcularMask SpecularValue EncodeShadingModelIdAndSelectiveOutputMask |
| GBufferD | ShaderColor.rgb NoL |
| GBufferE | |
| GBufferF | ID |
```
## BaseColor与ShadowColor
- 原神里ShadowColor还会接收其他物体的阴影投射没有自投影;蓝色协议可能也没有自投影。
BaseColor与ShadowColor的过渡需要Step、Feather、Offset等参数可以直接制作一个HalfLambert的渐变贴图之后使用View传递。因为有多个贴图所以还需要ID贴图指定。但这样需要考虑一个问题
- 一个物体上的同一个ID区域的BaseColor与ShadowColor是否都是一样的
- 如果不一样就需要再传递一个ShadowColor.rgb到GBuffer里。
- 不管如何手绘的补充暗部也是需要加到GBuffer中的
这决定传递到View里面的渐变贴图是彩色还是暗色
### 预计算贴图方案(构想)
Toon渲染一般会使用HalfLambda。之后使用Feather、Step等参数对过渡边界进行调整
使用 渐变贴图查表来实现 渐变、二阶化。以此代替羽化、step等参数。
使用ID贴图指定或者通过BaseColor值来查询
## 高光
- PBR高光使用Roughness控制是否可行是否需要传入GBuffer一个Mask贴图
- 自定义高光:高光贴图、高光颜色、参数化高光形状、多层高光
## 描边
- 原神的描边好像是后处理
- 蓝色协议
![[08-Assets/Images/ImageBag/UrealEngineNPR/原神_描边.png]]
![[08-Assets/Images/ImageBag/UrealEngineNPR/原神截图_描边.png]]
TODO考虑使用顶点色来控制宽度使用顶点色G
## 顶点色
用于存储一些低精度数据,插值即可
- R:
- G:描边宽度
- B:
蓝色协议的R:阴影区域标记 与 B:Ao而罪恶装备使用贴图来传递信息。
## lightmap
### 罪恶装备
![](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为无等高线
### 米哈游

View File

@ -0,0 +1,123 @@
---
title: Toon眉毛渲染
date: 2023-12-08 17:48:07
excerpt:
tags:
rating: ⭐
---
## 有关眉毛、表情需要使用 模板功能
UTS使用模板
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情.png]]
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情_StencilOut.png]]
![[08-Assets/Images/ImageBag/UrealEngineNPR/UTS表情_StencilMask.png]]
```
Stencil
{
Ref[_StencilNo] //设置渲染的模板缓存值0~255
Comp[_StencilComp] //模板测试的通过条件有除了equal还有Greater、Less、Always、Never等类似ZTest。
Pass[_StencilOpPass] //表示通过模板测试和Z测试注意是都通过的像素怎么处置它的模板值。
Fail[_StencilOpFail] //表示通过了模板测试但没通过Z测试的像素怎么处置它的模板值。
}
_UTS_StencilMode mode = (_UTS_StencilMode)(material.GetInt(ShaderPropStencilMode));
switch (mode)
{
case _UTS_StencilMode.Off:
// material.SetInt(ShaderPropStencilNo,0);
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.Disabled);
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Keep);
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Keep);
break;
case _UTS_StencilMode.StencilMask:
// material.SetInt(ShaderPropStencilNo,0);
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.Always);
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Replace);
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Replace);
break;
case _UTS_StencilMode.StencilOut:
// material.SetInt(ShaderPropStencilNo,0);
material.SetInt(ShaderPropStencilComp, (int)_StencilCompFunction.NotEqual);
material.SetInt(ShaderPropStencilOpPass, (int)_StencilOperation.Keep);
material.SetInt(ShaderPropStencilOpFail, (int)_StencilOperation.Keep);
break;
}
```
七大罪中使用使用了深度测试 Greater Equal.(默认是 less Equal)。但这个方式可能在UE4里不太行因为UE4的深度测试是全局的
这个应该需要创建一个MeshProcessor来实现
```c#
FSingleLayerWaterPassMeshProcessor::FSingleLayerWaterPassMeshProcessor(const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, const FMeshPassProcessorRenderState& InPassDrawRenderState, FMeshPassDrawListContext* InDrawListContext)
: FMeshPassProcessor(Scene, Scene->GetFeatureLevel(), InViewIfDynamicMeshCommand, InDrawListContext)
, PassDrawRenderState(InPassDrawRenderState)
{
if (SingleLayerWaterUsesSimpleShading(Scene->GetShaderPlatform()))
{
// Force non opaque, pre multiplied alpha, transparent blend mode because water is going to be blended against scene color (no distortion from texture scene color).
FRHIBlendState* ForwardSimpleWaterBlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha>::GetRHI();
PassDrawRenderState.SetBlendState(ForwardSimpleWaterBlendState);
}
}
//默认是CF_DepthNearOrEqual做这个效果可能就要使用CF_DepthFartherOrEqual
void SetupBasePassState(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, const bool bShaderComplexity, FMeshPassProcessorRenderState& DrawRenderState)
{
DrawRenderState.SetDepthStencilAccess(BasePassDepthStencilAccess);
if (bShaderComplexity)
{
// Additive blending when shader complexity viewmode is enabled.
DrawRenderState.SetBlendState(TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_Zero, BF_One>::GetRHI());
// Disable depth writes as we have a full depth prepass.
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI());
}
}
```
设置位置在于FXXXPassMeshProcessor::Process()中的SetDepthStencilStateForBasePass()中。需要bMaskedInEarlyPass为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());
}
```

View File

@ -0,0 +1,202 @@
# 资料整理
## 凹模型
直接贴图即可。
折射
模型高光
## 凸模型
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与菲尼尔来控制
# 其他参考整理
![[ToonEye.png|400]]
![[ToonEyes.png|400]]
## PBR Refrection有问题
```
//input anteriorChamberDepth 贴图 使用HeightMap
//input radius
//input mask 贴图
//input texcoord
//input n 折射率比值
//input frontNormalW 贴图float3(0,0,1) Local=>World
// 角膜区域突起的模型 height = max(-positionL.z eyeIrisDepth, 0.0);
// 球形模型 Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
float height = anteriorChamberDepth ;//* saturate( 1.0 - 18.4 * radius * radius );
float3 normalW=Parameters.WorldNormal;
float3 viewW= mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);//CameraViewToTranslatedWorld
// refractedW
float w = n * dot( normalW, viewW );
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
float3 refractedW = ( w - k ) * normalW - n * viewW;
refractedW=-normalize(refractedW);
//float3 frontNormalW=mul(float(0,0,1),float(3x3)GetPrimitiveData(Parameters.PrimitiveId).LocalToWorld) * -1;
float cosAlpha = dot(frontNormalW, refractedW);
float dist = height / cosAlpha;
float3 offsetW = dist * refractedW;
float2 offsetL = mul(offsetW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
texcoord += float2(mask, -mask) * offsetL;
return texcoord;
```
### 原始代码
```
// 角膜区域突起的模型
// Alternatively, use a displacement map
// height = max(-positionL.z eyeIrisDepth, 0.0);
// 球形模型
// Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius );
// refractedW
float w = n * dot( normalW, viewW );
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
float3 refractedW = ( w - k ) * normalW - n * viewW;
float cosAlpha = dot(frontNormalW, -refractedW);
float dist = height / cosAlpha;
float3 offsetW = dist * refractedW;
float2 offsetL = mul(offsetW, (float3x2) worldInverse);
texcoord += float2(mask, -mask) * offsetL;
```
## Matcap反射效果
```
float2 CalcMatcapUV(FMaterialPixelParameters Parameters,float3 normalTexture)
{
float3 ViewVector=Parameters.CameraVector * -1;
float3 ViewSpaceRightVector=normalize(cross(ViewVector , mul( float3(0.0,1.0,0.0) , ResolvedView.ViewToTranslatedWorld )));
float3 ViewSpaceUpVector=cross(ViewVector ,ViewSpaceRightVector);
float3x3 Matrix=float3x3(
ViewSpaceRightVector.x,ViewSpaceUpVector.x,ViewVector.x,
ViewSpaceRightVector.y,ViewSpaceUpVector.y,ViewVector.y,
ViewSpaceRightVector.z,ViewSpaceUpVector.z,ViewVector.z
);
float3 ZeroOneNormal=mul(normalize(Parameters.WorldNormal+normalTexture),Matrix)*0.5+0.5;
return float2(ZeroOneNormal.x,ZeroOneNormal.y*-1);
}
```
### 这个不太行
```
//input normalTexture 法线贴图
float3 TangentWS=Parameters.TangentToWorld[0];
float3 NormalWS=Parameters.TangentToWorld[2];
float3 BinormalWS=cross(NormalWS, TangentWS);
float3 worldNormal;
worldNormal.x = dot(float3(TangentWS.x,BinormalWS.x,NormalWS.x), normalTexture);
worldNormal.y = dot(float3(TangentWS.y,BinormalWS.y,NormalWS.y), normalTexture);
worldNormal.z = dot(float3(TangentWS.z,BinormalWS.z,NormalWS.z), normalTexture);
worldNormal = normalize(worldNormal);
float3 e = normalize(GetWorldPosition(Parameters) - ResolvedView.WorldCameraOrigin);
float3 reflectVector = reflect(e, worldNormal);
float3 reflectVectorVS = normalize(mul(reflectVector,ResolvedView.TranslatedWorldToView));
float m = 2.82842712474619 * sqrt(reflectVectorVS.z + 1.0);
float2 cap = reflectVectorVS.xy / m + 0.5;
cap=cap*0.5 + 0.5;
return cap;
```
## 高光贴图位置调整
```
//input float2 LeftUp,LeftDown,RightUp,RightDown;
float3 viewW = mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);
float3 viewL = mul(viewW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
float2 viewL2D = normalize(viewL.xy);
float2 Left=lerp(LeftUp,LeftDown,viewL2D.y);
float2 Right=lerp(RightUp,RightDown,viewL2D.y);
return lerp(Left,Right,viewL2D.x);
```
## 焦散
- dot(Parameters.WorldNormal,View.DirectionalLightDirection);
- Fresnel
乘以焦散Mask即可

View File

@ -1,64 +1,15 @@
# 功能表
厚涂部分写在:[[厚涂风格研究与开发笔记]]
知乎提到的渲染功能:
- 发尖勾线 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系列格式的导入工具
---
title: 描边
date: 2023-12-08 16:49:45
excerpt:
tags:
rating: ⭐
---
# 实现功能
- 后处理描边
- MeshDraw描边
# 杂项
## 李兄实现Outline思路
### Depth与Normal描边
ToonOutlineMain()
@ -129,3 +80,10 @@ OutlineMask1 = saturate(2.0 - step(0.9, length(sqrt(SGX * SGX + SGY * SGY))));
1. 使用Sobel算子进行深度检测**只勾数值差别较大的区域**:脸部顶点色定义区域 与 模型外部轮廓。
2. 使用Sobel进行ID贴图检测。**只勾数值差别较大的区域**。
3. 进行法线点积dot检测。**在 深度差异小 以及 同一个ID区域内进行检测**:手指区域。
1. 使用Sobel过滤器进行深度检测描边。
2. 使用Sobel过滤器进行Id图检测描边。
3. 使用Sobel过滤器进行Normal检测描边。用于处理一些难以分ID深度差又很小的地方通过获取周围点法线求点乘的方式判断出轮廓。![[08-Assets/Images/ImageBag/UrealEngineNPR/蓝色协议_Normal检测描边.png)
4. 预先画好的轮廓GBuffer
所以使用需要 OutlineId、OutlineWidth感觉可以传递一个全局Outline信息贴图再通过ID查表来获取但只能在角色较少时使用、OutlinePaint 、OutlineZShift个人感觉不需要

View File

@ -1,114 +0,0 @@
![[ToonEye.png]]
![[ToonEyes.png]]
## PBR Refrection有问题
```
//input anteriorChamberDepth 贴图 使用HeightMap
//input radius
//input mask 贴图
//input texcoord
//input n 折射率比值
//input frontNormalW 贴图float3(0,0,1) Local=>World
// 角膜区域突起的模型 height = max(-positionL.z eyeIrisDepth, 0.0);
// 球形模型 Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
float height = anteriorChamberDepth ;//* saturate( 1.0 - 18.4 * radius * radius );
float3 normalW=Parameters.WorldNormal;
float3 viewW= mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);//CameraViewToTranslatedWorld
// refractedW
float w = n * dot( normalW, viewW );
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
float3 refractedW = ( w - k ) * normalW - n * viewW;
refractedW=-normalize(refractedW);
//float3 frontNormalW=mul(float(0,0,1),float(3x3)GetPrimitiveData(Parameters.PrimitiveId).LocalToWorld) * -1;
float cosAlpha = dot(frontNormalW, refractedW);
float dist = height / cosAlpha;
float3 offsetW = dist * refractedW;
float2 offsetL = mul(offsetW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
texcoord += float2(mask, -mask) * offsetL;
return texcoord;
```
### 原始代码
```
// 角膜区域突起的模型
// Alternatively, use a displacement map
// height = max(-positionL.z eyeIrisDepth, 0.0);
// 球形模型
// Plot[Max[1.0 - 18.4 * r * r, 0.0], {r, 0, 0.3}]
height = anteriorChamberDepth * saturate( 1.0 - 18.4 * radius * radius );
// refractedW
float w = n * dot( normalW, viewW );
float k = sqrt( 1.0 + ( w - n ) * ( w + n ) );
float3 refractedW = ( w - k ) * normalW - n * viewW;
float cosAlpha = dot(frontNormalW, -refractedW);
float dist = height / cosAlpha;
float3 offsetW = dist * refractedW;
float2 offsetL = mul(offsetW, (float3x2) worldInverse);
texcoord += float2(mask, -mask) * offsetL;
```
## Matcap反射效果
```
float2 CalcMatcapUV(FMaterialPixelParameters Parameters,float3 normalTexture)
{
float3 ViewVector=Parameters.CameraVector * -1;
float3 ViewSpaceRightVector=normalize(cross(ViewVector , mul( float3(0.0,1.0,0.0) , ResolvedView.ViewToTranslatedWorld )));
float3 ViewSpaceUpVector=cross(ViewVector ,ViewSpaceRightVector);
float3x3 Matrix=float3x3(
ViewSpaceRightVector.x,ViewSpaceUpVector.x,ViewVector.x,
ViewSpaceRightVector.y,ViewSpaceUpVector.y,ViewVector.y,
ViewSpaceRightVector.z,ViewSpaceUpVector.z,ViewVector.z
);
float3 ZeroOneNormal=mul(normalize(Parameters.WorldNormal+normalTexture),Matrix)*0.5+0.5;
return float2(ZeroOneNormal.x,ZeroOneNormal.y*-1);
}
```
### 这个不太行
```
//input normalTexture 法线贴图
float3 TangentWS=Parameters.TangentToWorld[0];
float3 NormalWS=Parameters.TangentToWorld[2];
float3 BinormalWS=cross(NormalWS, TangentWS);
float3 worldNormal;
worldNormal.x = dot(float3(TangentWS.x,BinormalWS.x,NormalWS.x), normalTexture);
worldNormal.y = dot(float3(TangentWS.y,BinormalWS.y,NormalWS.y), normalTexture);
worldNormal.z = dot(float3(TangentWS.z,BinormalWS.z,NormalWS.z), normalTexture);
worldNormal = normalize(worldNormal);
float3 e = normalize(GetWorldPosition(Parameters) - ResolvedView.WorldCameraOrigin);
float3 reflectVector = reflect(e, worldNormal);
float3 reflectVectorVS = normalize(mul(reflectVector,ResolvedView.TranslatedWorldToView));
float m = 2.82842712474619 * sqrt(reflectVectorVS.z + 1.0);
float2 cap = reflectVectorVS.xy / m + 0.5;
cap=cap*0.5 + 0.5;
return cap;
```
## 高光贴图位置调整
```
//input float2 LeftUp,LeftDown,RightUp,RightDown;
float3 viewW = mul(float3(0,0,1),(float3x3)View.ViewToTranslatedWorld);
float3 viewL = mul(viewW, (float3x2)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
float2 viewL2D = normalize(viewL.xy);
float2 Left=lerp(LeftUp,LeftDown,viewL2D.y);
float2 Right=lerp(RightUp,RightDown,viewL2D.y);
return lerp(Left,Right,viewL2D.x);
```
## 焦散
- dot(Parameters.WorldNormal,View.DirectionalLightDirection);
- Fresnel
乘以焦散Mask即可