• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
IslandZ
博客园    首页    新随笔    联系   管理    订阅  订阅
【更新中】【Unity/UE】基础仿原神渲染

前言

【本文持续更新中】

终于把一直想做一做的仿原神渲染做了一下。

原神出来也有段时间了,各路大佬的逆向早就做完了,所以最近做的其实复刻大佬们的工程,难度并不大。

废话不多说,先看效果。

Unity

 UE

 (UE的边缘光老是闪就关了)

 

两个版本都没有加上雾效,泛光之间的后处理效果,本篇随笔也不会讲述相关内容,有能力的可以自己加上,效果应该会上一个档次

UE最近一段时间才开始用起来,材质蓝图比我想象的要简单一些,但是UE的PBR渲染几乎是高度封装的,导致几乎相同的流程UE会和Unity效果差异很明显。

这次的UE版本效果也就差强人意吧,后续继续做UE项目的话可能会改进。

前言

       本文旨在分析原神在“角色渲染”这一方面所运用到的一些方法,并且几乎不涉及间接光照,并没有一个可用的风格化管线,仅仅是一些trick思路,在日后做一些风格化渲染上可以借鉴学习。

       复刻差不多是在去年这个时候完成的,当时把代码贴上去说以后补上详细说明过程,但是一直没做。趁此机会复盘一下实现流程,然后就像上面说的,以后再尝试实现一套可用的风格化管线。

实现流程

资产

       贴图模型网上应该比较轻松能找到,本着分享的原则,这里给出我的途径。

       模之屋官方模型:https://www.aplaybox.com/u/680828836

       原神官方会放出官方模型方便MMD二创,大部分模型也都会附带贴图,但特殊贴图可能没有。这个大概率不会是游戏内实际使用的模型,但是几乎不影响使用。

       民间解包:

GenshinTextures/Texture2D/Avatar at master · TGSRedStone/GenshinTextures · GitHub

这个是著名的原神贴图解包版本,但好像很久没有更新了,这个上面可以扒到所有需要的贴图。

自食其力:抓帧贴图

现在用Renderdoc+mumu模拟器应该很容易能抓到原神,IntelGPA也是一个选择,但是我没成功过。另外还有一个感觉用的人不多,Snapdragon Profiler,用来抓手机的,感觉比较好用。

 

 

最近绝区零公测了,发现抓出来都是空帧,被米家算计了。找了个测试版本抓了一下,放出来当做示例。

Matcap

       Material Capture,本质是一张贴图,上面提前烘焙好了材质信息,当我们需要的时候直接采样用。这是一个非常廉价的,不需要任何光照信息就可以获得一个看上去好像像那么回事的效果。

       在风格化渲染中,大部分光照都是假的,通过matcap可以直接赋予角色一些看起来对的风格化的信息。比如伪高光,伪金属光泽,皮肤质感等。

       在原神中,这张图的效果没有那么明显,对于皮肤和非皮肤分别使用下面两张图:

可以看到相比烘焙出来的贴图,这两张贴图的信息非常少。

       看下效果:

得到了一个像是阴影一样的效果。

使用也非常简单,只需要根据视角空间法线采样就可以了。

//MatcapUV 

float3 ViewDir_CameraSpace = mul(unity_WorldToCamera, i.worldNormal); 

ViewDir_CameraSpace = ViewDir_CameraSpace * 0.5 + 0.5; 

float2 MatcapUV = ViewDir_CameraSpace.xy; 

 

//ToonColor SkinColor 

float3 ToonColor = tex2D(_ToonTex, MatcapUV); 

float3 SkinColor = tex2D(_SkinTex, MatcapUV); 

 

 

ILM

       这张图我看到有lightmap和“魔法图”两种叫法,但其实际用途应该是和光照贴图没有任何关系的。

       ILM由四个通道组成:

       R:高光mask、G:AO、B:高光强度、A:Ramp阈值

       主要的作用是通过贴图直接赋予材质属性,可以在贴图上直接把高光形状,金属部分形状直接画出来,然后A通道配合Ramp图控制风格化过渡。

       这个图通常长下面这个样子:

 

 

       R通道,值很大的部分就是金属部分,更详细的强度信息被储存在B通道中。G通道能够很明显看出是AO,比如衣领的遮挡带来的死黑区域,还有一些边缘过渡。

Ramp

一般会有两条Ramp图,暖色和冷色分别对应白天和黑夜,也就是说当环境光发生变化时,角色光照只会切换Ramp图,并不会计算环境光照。这里的这张,一张图算两张,白天五行,夜晚五行。

Ramp是风格化渲染的重要部分,风格化明显的渲染在明暗过渡部分会做出硬边缘效果或者分层的效果,

 

通过采样Ramp图可以得到我们想要的过渡效果。

通常使用半兰伯特进行采样

tex2d(_RampTex, float2(HalfLambert, 0.5));

       因为半兰伯特的范围是(0,1),然后在(0,1)之间随便取一个值即可。随便取一个值是因为我们平时用的Ramp基本都是X轴过渡,Y轴通常没有信息,理论上只需要一个像素就够了。

       而对于原神的这张图,Y轴10个像素分别是10行Ramp,我们可以采样一张图得出多种过渡。至于该采样哪行,这个信息被储存在ILM的A通道中,我们扔到PS中可以看每个灰度的阈值是多少,就可以通过灰度值将这些部分分开。

       代码如下:

//NightRamp_V
float ILM_Alpha_0 = 0.15;
float ILM_Alpha_1 = 0.40;
float ILM_Alpha_2 = 0.60;
float ILM_Alpha_3 = 0.85;
float ILM_Alpha_4 = 1.0;
float ILM_Value_0 = 1.0;
float ILM_Value_1 = 4.0;
float ILM_Value_2 = 3.0;
float ILM_Value_3 = 5.0;
float ILM_Value_4 = 2.0;
ILM_Value_0 = 0.55 - ILM_Value_0 / 10;
ILM_Value_1 = 0.55 - ILM_Value_1 / 10;
ILM_Value_2 = 0.55 - ILM_Value_2 / 10;
ILM_Value_3 = 0.55 - ILM_Value_3 / 10;
ILM_Value_4 = 0.55 - ILM_Value_4 / 10;
float NightRamp_V = lerp(ILM_Value_4, ILM_Value_3, step(ILM_A, ILM_Alpha_3));
NightRamp_V = lerp(NightRamp_V, ILM_Value_2, step(ILM_A, ILM_Alpha_2));
NightRamp_V = lerp(NightRamp_V, ILM_Value_1, step(ILM_A, ILM_Alpha_1));
NightRamp_V = lerp(NightRamp_V, ILM_Value_0, step(ILM_A, ILM_Alpha_0));
 
//DayRamp_V
float DayRamp_V = NightRamp_V + 0.5;

       只需要算白天或者晚上就行,然后加减0.5。

     然后根据光线方向进行一个简单的差值就是正常的过渡效果。但是本篇没有做环境光照,所以如果只复刻角色渲染的话,大概率会看到场景一片死黑。

 

       同时这张图的过渡范围几乎全在最右边,而一般的Ramp过渡都在中间,因为半兰伯特的阴影过渡在0.5的位置。对于这一点,我们可以理解为,这张图仅包含了过渡部分的信息,最右边即为阴影边界,所有的受光面全部采样采到最右端,是没有过渡的颜色,而我们需要的过渡部分仅在背光面。说实话,这样做我觉得谈不上有太多优化吧,只能算是一种制作方式。

       修改采样方法也很简单:

float HalfLambert_Ramp = smoothstep(0.0, 0.5, HalfLambert);

       这样超出0.5的部分就会变成1.0,全部采样到最右端。

       Ramp效果:

       然后加上AO,AO原本长这个样子,但我们不会让这边一片死黑,所以采用Ramp图最左端的颜色作为AO颜色。

 

高光

       通过ILM的R通道判断是否为金属

//IsMetallic
float IsMetallic = step(0.95, ILM_R);

       通过一张MetalTex,乘上BaseColor得到金属颜色。

//Metallic
float3 Metallic = lerp(0, tex2D(_MetalTex, MatcapUV).r * BaseColor, IsMetallic);

       这张图也没什么高深之处,根据Matcap原理我们可以知道,这张图就是把视角空间的法线分层,效果上就是正对视角的高光最亮,然后两边更暗。

 

 

       高光使用BlinnPhong,乘上ILM的B通道得到高光。

//BlinnPhong
float3 HalfDir = normalize(ViewDir + LightDir);
float NOH = dot(i.worldNormal, HalfDir);
float BlinnPhong = step(0, NOL) * pow(max(0, NOH), _Gloss);

边缘光

这里用的边缘光虽然不是菲涅尔,但是原理也很简单,一般称作屏幕空间等距边缘光。

本质上我们是想要获取从物体的边缘开始的某个范围,而确定物体轮廓经常会用到深度缓冲,物体边缘的深度与场景的深度差别很大,这时就能确定物体边缘。

程序上,我们将屏幕空间UV沿视角空间法线方向偏移一段,然后采样偏移后的UV,比较偏移前后得到的深度,根据设定好的阈值进行边缘光差值即可。

代码如下:

float rawDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.scrPos);
                
float linearDepth = LinearEyeDepth(rawDepth);
float Offset = lerp(-1, 1, step(0, viewNormal.x)) * rimOffset / _ScreenParams.x;
 
float4 screenOffset = float4(Offset, 0, 0, 0);
float offsetDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.scrPos + screenOffset);
float offsetLinearDepth = LinearEyeDepth(offsetDepth);
 
float rim = saturate(offsetLinearDepth - linearDepth);
rim = step(rimThreshold, rim) * clamp(rim * rimStrength, 0, rimMax);

 

SDF

       应用在面部阴影的方案应该是米哈游首创吧?如今已经成为一众二次元游戏的标准流程了。

       这张图本质上是数张面部阴影图叠起来的,每一张阴影图都只有0和1,根据设定好的阈值选择用哪张面部阴影图。

       肯定是用光照方向和阈值比较了,我们从外部获取到角色的面部朝向,然后和光照方向点积就可以得到角度,根据这个角度去和SDF比较,得到阴影图数据。

       加俩物体获取朝向,然后获取相对位置,代码如下:

Vector3 forwardVector = HeadForward.position - HeadTransform.position;
Vector3 rightVector = HeadRight.position - HeadTransform.position;
forwardVector = forwardVector.normalized;
rightVector = rightVector.normalized;
FaceMaterial.SetVector("_ForwardVector", forwardVector);
FaceMaterial.SetVector("_RightVector", rightVector);

       SDF代码如下:

       注意当光源从左边转到右边时,采样的UV方向要反转

//SDF
float3 UpVector = cross(_ForwardVector, _RightVector);
 
float3 LpU = dot(LightDir, UpVector) / pow(length(UpVector), 2) * UpVector;
float3 LpHeadHorizon = LightDir - LpU;
float value = acos(dot(normalize(LpHeadHorizon), normalize(_RightVector))) / UNITY_PI;
float exposeRight = step(value, 0.5);
float value_R = pow(1 - value * 2, 3);
float value_L = pow(value * 2 - 1, 3);
float mixValue = lerp(value_L, value_R, exposeRight);
float sdfRembrandtLeft = tex2D(_SDF_Tex, float2(1 - i.uv.x, i.uv.y)).r;
float sdfRembrandtRight = tex2D(_SDF_Tex, i.uv).r;
float mixSDF = lerp(sdfRembrandtRight, sdfRembrandtLeft, exposeRight);
float SDF = step(mixValue, mixSDF);
SDF = lerp(0, SDF, step(0, dot(normalize(LpHeadHorizon), normalize(_ForwardVector))));
 
float4 FaceShadowTex = tex2D(_FaceShadow_Tex, i.uv);
SDF *= FaceShadowTex.g;
SDF = lerp(SDF, 1, FaceShadowTex.a);

       效果如下:

描边

       描边对于风格化渲染来说可谓之重中之重,加上描边一看就很有风格的样子(

       这里的描边方法其实也比较传统,采用背面法线外扩的方法,分两个Pass分别渲染正背面。

       出来效果会发现描边断裂成一段一段的很不好看,所以需要将法线平滑然后存起来用作描边。

       平滑工具使用:https://www.bilibili.com/read/cv24126974/

       我对于Unity的Mesh操作还是不熟,这里使用了别人现成的脚本,大体思路就是,对于每个顶点,根据其在三角形中连接的两条边的角度算出一个权重,然后遍历所有的顶点,算出权重之和,然后每个顶点的法线根据自己的权重乘上这个比例。

      

       描边的宽度会随摄像机远近变化,这是因为我们在NDC空间下进行法线外扩的操作,这个时候已经经过了透视除法,避免这种情况只需要把w分量乘回来就可以了。

       代码如下:

float4 pos = UnityObjectToClipPos(v.vertex);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.tangent.xyz);
//将法线变换到NDC空间,乘以w,消除透视影响
float3 ndcNormal = normalize(TransformViewToProjection(viewNormal.xyz)) * pos.w;
float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));
//求得屏幕宽高比
float aspect = abs(nearUpperRight.y / nearUpperRight.x);
ndcNormal.x *= aspect;
pos.xy += 0.001 * clamp(_OutlineOffset * ndcNormal.xy, -50, 50);
 
o.position = pos;

      

       做到这里的时候,发现角色五官有很奇怪的描边,理想情况这个地方的描边从正面应该不可见。临时扣了一块mask把描边删了,但会导致侧面描边不可见。

       差不多扣成这个样子,有点丑(扣了好久没扣对位置)

 

       简单mask一下:

float FaceMask = tex2Dlod(_Face_Mask_Tex, float4(v.uv, 0, 0)).a;
_OutlineOffset = lerp(0, _OutlineOffset, FaceMask);

       描边效果如下:

 

 

       最终效果如下:

 

总结

       能够发现其实光角色渲染这些东西也不是很有技术含量的,其实就原神这套贴图随便拿个光照模型贴上去效果都会不错的,所以风格化主要还是看美术的审美。不过毕竟是原神,去年的我还什么都不懂,对于这种比较容易出效果的复刻还是有一定成就感的,别人问起来风格化渲染的一些点自己好歹能听懂两句。未来可能会尝试做一套可以用的风格化管线吧,而且越学越觉得,理论技术,甚至能够复刻的一些技术,都是要能够落地才能发挥价值的。原神这套东西厉害在他有一套标准的资产生产流程,仅仅是复刻某些技术的话难免变成玩具。未来会尽量以能够落地为原则继续学习吧。 

       同文博客:https://www.cnblogs.com/IslandZ/p/17608128.html

 

参考文献

[1] 原神Shader渲染还原解析. https://zhuanlan.zhihu.com/p/435005339

[2] 原神角色渲染Shader分析还原. https://zhuanlan.zhihu.com/p/360229590

[3] Unity描边平滑法线存UV7脚本. https://www.bilibili.com/read/cv24126974/

源码

  1 Shader "Custom/Cel_Base"
  2 {
  3     Properties
  4     {
  5         _AmbientColor ("Ambient Color", Color) = (1,1,1,1)
  6         _AmbientFac("Ambient Fac", Range(0, 1)) = 0
  7         _SphereTex("Sphere Tex", 2D) = "white" {}
  8         _SphereTexFac("Sphere Fac", Range(0, 1)) = 0
  9         _OutlineOffset("Outline Offset", Float) = 0.01
 10         _OutlineShadowColor("Outline Shadow", Color) = (1, 1, 1, 1)
 11         _BaseTex ("Base Tex", 2D) = "white" {}
 12         [Toggle(_True)]_IsSkin("Is Skin", Float) = 1
 13         _ToonTex ("Toon Tex", 2D) = "white" {}
 14         _SkinTex ("Skin Tex", 2D) = "white" {}
 15         _MatcapFac("Matcap Fac", Range(0, 1)) = 0
 16         _MetalTex("Metal Tex", 2D) = "white" {}
 17         _ILM_Tex("ILM Tex", 2D) = "white" {}
 18         _ILM_R_TMP("ILM R", 2D) = "white" {}
 19         [Toggle(_True)]_IsEye("Is Eye", Float) = 1
 20         _Eye_Mask_Tex("Eye Mask Tex", 2D) = "white" {}
 21         _Gloss("Gloss", Float) = 50
 22         _KsNonMetallic("Non Metallic", Float) = 1
 23         _KsMetallic("Metallic", Float) = 1
 24         _KsHairMetallic("Hair Metallic", Float) = 1
 25         [Toggle(_True)]_IsHair("Is Hair", Float) = 1
 26         _RampTex ("Ramp Tex", 2D) = "white" {}
 27         _ShadowColor ("Shadow Color", Color) = (1, 1, 1, 1)
 28         _RimColor ("Rim Color", Color) = (1, 1, 1, 1)
 29         _RimFac ("Rim Fac", Float) = 0
 30         
 31     }
 32     SubShader
 33     {
 34         Tags {"LightMode" = "ForwardBase" "RenderType" = "Opaque" }
 35         Pass
 36         {
 37             Cull Off
 38             CGPROGRAM
 39             #include "UnityCG.cginc"
 40             #include "Lighting.cginc"
 41             #include "AutoLight.cginc"
 42             
 43             #pragma vertex vert
 44             #pragma fragment frag
 45             #pragma multi_compile_fwdbase
 46 
 47             sampler2D _CameraDepthTexture;
 48             
 49             sampler2D _BaseTex;
 50             sampler2D _ToonTex;
 51             sampler2D _RampTex;
 52             sampler2D _SkinTex;
 53             sampler2D _SphereTex;
 54             sampler2D _ILM_Tex;
 55             sampler2D _ILM_R_TMP;
 56             sampler2D _MetalTex;
 57             float4 _AmbientColor;
 58             float4 _ShadowColor;
 59             float4 _RimColor;
 60             float _MatcapFac;
 61             float _AmbientFac;
 62             float _SphereTexFac;
 63             float _Gloss;
 64             float _KsNonMetallic;
 65             float _KsMetallic;
 66             float _KsHairMetallic;
 67             float _IsHair;
 68             float _IsSkin;
 69             float _RimFac;
 70 
 71             struct a2v
 72             {
 73                 float4 vertex : POSITION;
 74                 float3 normal : NORMAL;
 75                 float2 texcoord : TEXCOORD0;
 76             };
 77 
 78             struct v2f
 79             {
 80                 float4 pos : SV_POSITION;
 81                 float4 scrPos : TEXCOORD3;
 82                 float4 worldPos : TEXCOORD2;
 83                 float3 worldNormal : TEXCOORD1;
 84                 float2 uv : TEXCOORD0;
 85                 SHADOW_COORDS(4)
 86             };
 87 
 88             v2f vert(a2v v)
 89             {
 90                 v2f o;
 91                 o.pos = UnityObjectToClipPos(v.vertex);
 92                 o.scrPos = ComputeScreenPos(o.pos);
 93                 o.uv = v.texcoord;
 94                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
 95                 o.worldNormal = mul(unity_ObjectToWorld, v.normal);
 96                 TRANSFER_SHADOW(o);
 97                 return o;
 98             }
 99 
100             float4 frag(v2f i) : SV_Target
101             {
102                 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
103                 //Context
104                 float3 LightDir = normalize(_WorldSpaceLightPos0);
105                 float3 ViewDir = normalize(_WorldSpaceCameraPos);
106                 float3 viewNormal = mul(unity_WorldToCamera, i.worldNormal);
107                 float NOL = dot(i.worldNormal, LightDir);
108                 float NOV = dot(i.worldNormal, ViewDir);
109 
110                 //Lambert HalfLambert
111                 float Lambert = max(0, NOL);
112                 float HalfLambert = pow((NOL * 0.5 + 0.5), 1);
113 
114                 //Ramp采样用 HalfLambert_Ramp
115                 
116                 float HalfLambert_Ramp = smoothstep(0.0, 0.5, HalfLambert);
117 
118                 //DarkRamp平滑用 LambertStep
119                 float LambertStep = smoothstep(0.423, 0.465, HalfLambert);
120 
121                 //BaseColor
122                 float4 BaseTexColor = tex2D(_BaseTex, i.uv);
123                 //return float4(BaseTexColor.rgb, 1);
124                 
125                 //MatcapUV
126                 float3 ViewDir_CameraSpace = mul(unity_WorldToCamera, i.worldNormal);
127                 ViewDir_CameraSpace = ViewDir_CameraSpace * 0.5 + 0.5;
128                 float2 MatcapUV = ViewDir_CameraSpace.xy;
129                 
130                 //ToonColor SkinColor
131                 float3 ToonColor = tex2D(_ToonTex, MatcapUV);
132                 float3 SkinColor = tex2D(_SkinTex, MatcapUV);
133 
134                 //MatcapColor
135                 float3 MatcapColor;
136                 if (_IsSkin == 1) MatcapColor = SkinColor;
137                 else MatcapColor = ToonColor;
138 
139                 //BaseColor
140                 float3 BaseColor = lerp(BaseTexColor, BaseTexColor * MatcapColor, _MatcapFac);
141                 BaseColor = lerp(BaseColor, BaseColor * _AmbientColor, _AmbientFac);
142                 float3 SphereColor = tex2D(_SphereTex, MatcapUV);
143                 BaseColor = lerp(BaseColor, BaseColor * SphereColor, _SphereTexFac);
144 
145                 //ILM
146                 float4 ILM_RGBA = tex2D(_ILM_Tex, i.uv);
147                 float ILM_R = ILM_RGBA.x;
148                 float ILM_G = ILM_RGBA.y;
149                 float ILM_B = ILM_RGBA.z;
150                 float ILM_A = ILM_RGBA.w;
151                 
152                 //NightRamp_V
153                 float ILM_Alpha_0 = 0.15;
154                 float ILM_Alpha_1 = 0.40;
155                 float ILM_Alpha_2 = 0.60;
156                 float ILM_Alpha_3 = 0.85;
157                 float ILM_Alpha_4 = 1.0;
158                 float ILM_Value_0 = 1.0;
159                 float ILM_Value_1 = 4.0;
160                 float ILM_Value_2 = 3.0;
161                 float ILM_Value_3 = 5.0;
162                 float ILM_Value_4 = 2.0;
163                 ILM_Value_0 = 0.55 - ILM_Value_0 / 10;
164                 ILM_Value_1 = 0.55 - ILM_Value_1 / 10;
165                 ILM_Value_2 = 0.55 - ILM_Value_2 / 10;
166                 ILM_Value_3 = 0.55 - ILM_Value_3 / 10;
167                 ILM_Value_4 = 0.55 - ILM_Value_4 / 10;
168                 float NightRamp_V = lerp(ILM_Value_4, ILM_Value_3, step(ILM_A, ILM_Alpha_3));
169                 NightRamp_V = lerp(NightRamp_V, ILM_Value_2, step(ILM_A, ILM_Alpha_2));
170                 NightRamp_V = lerp(NightRamp_V, ILM_Value_1, step(ILM_A, ILM_Alpha_1));
171                 NightRamp_V = lerp(NightRamp_V, ILM_Value_0, step(ILM_A, ILM_Alpha_0));
172                 
173                 //DayRamp_V
174                 float DayRamp_V = NightRamp_V + 0.5;
175 
176                 //IsDay
177                 float IsDay = (LightDir.y + 1) / 2;
178                 
179                 //RampColor
180                 //采样Ramp时,HalfLmabert * AO可以得到带 AO的 RampColor
181                 float3 DayRampColor = tex2D(_RampTex, float2(HalfLambert_Ramp, DayRamp_V));
182                 float3 DayDarkRampColor = tex2D(_RampTex, float2(0.003, DayRamp_V));
183                 float3 NightRampColor = tex2D(_RampTex, float2(HalfLambert_Ramp, NightRamp_V));
184                 float3 NightDarkRampColor = tex2D(_RampTex, float2(0.003, NightRamp_V));
185                 float3 RampColor = lerp(NightRampColor, DayRampColor, IsDay);
186                 float3 DarkRampColor = lerp(NightDarkRampColor, DayDarkRampColor, IsDay);
187                 
188                 //Diffuse
189                 //使用 LambertStep平滑
190                 float3 Diffuse = lerp(BaseColor * RampColor * _ShadowColor, BaseColor, LambertStep);
191                 Diffuse = lerp(BaseColor * DarkRampColor * _ShadowColor, Diffuse, 1);
192                 //使用 ILM_G增加AO,同时AO使用Ramp图最左侧颜色
193                 //ILM_G范围为 0-0.5,大于等于0.5的部分为非AO部分,故映射为 0-1
194                 Diffuse = lerp(BaseColor * DarkRampColor * _ShadowColor, Diffuse, ILM_G * 2);
195 
196 
197                 //BlinnPhong
198                 float3 HalfDir = normalize(ViewDir + LightDir);
199                 float NOH = dot(i.worldNormal, HalfDir);
200                 float BlinnPhong = step(0, NOL) * pow(max(0, NOH), _Gloss);
201 
202                 //IsMetallic
203                 float IsMetallic = step(0.95, ILM_R);
204                 float ILM_Hair_R = tex2D(_ILM_R_TMP, i.uv).r;
205                 
206                 //Specular
207                 float NonMetallic_Specular = step(1.04 - BlinnPhong, ILM_B) * ILM_R * _KsNonMetallic;
208                 float3 HairMetallic_Specular = 0;
209                 if (_IsHair == 1) HairMetallic_Specular = ILM_B * LambertStep * BaseColor * ILM_Hair_R * _KsHairMetallic;
210                 float3 KsMetallic_Specular = BlinnPhong * ILM_B * (LambertStep * 0.8 + 0.2) * BaseColor * _KsMetallic;
211                 float3 Specular = lerp(NonMetallic_Specular + HairMetallic_Specular, KsMetallic_Specular, IsMetallic);
212 
213                 //Metallic
214                 float3 Metallic = lerp(0, tex2D(_MetalTex, MatcapUV).r * BaseColor, IsMetallic);
215 
216                 //Albedo
217                 float3 Albedo = Diffuse + Specular + Metallic;
218 
219                 float rimOffset = 13;
220                 float rimThreshold = 0.08;
221                 float rimStrength = 0.6;
222                 float rimMax = 0.3;
223 
224                 float rawDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.scrPos);
225                 
226                 float linearDepth = LinearEyeDepth(rawDepth);
227                 float Offset = lerp(-1, 1, step(0, viewNormal.x)) * rimOffset / _ScreenParams.x;
228                 //float Offset = lerp(-1, 1, step(0, viewNormal.x)) * rimOffset / _ScreenParams.x / max(1, pow(linearDepth, 0.5));
229                 float4 screenOffset = float4(Offset, 0, 0, 0);
230                 float offsetDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.scrPos + screenOffset);
231                 float offsetLinearDepth = LinearEyeDepth(offsetDepth);
232 
233                 float rim = saturate(offsetLinearDepth - linearDepth);
234                 rim = step(rimThreshold, rim) * clamp(rim * rimStrength, 0, rimMax);
235 
236                 float fresnelPower = 6;
237                 float fresnelClamp = 0.8;
238                 float fresnel = 1 - saturate(NOV);
239                 fresnel = pow(fresnel, fresnelPower);
240                 fresnel = fresnel * fresnelClamp + (1 - fresnelClamp);
241 
242                 Albedo = 1 - (1 - rim * fresnel * _RimColor * BaseTexColor * _RimFac) * (1 - Albedo);
243                 
244                 float4 FinalColor = float4(Albedo, BaseTexColor.a);
245                 return FinalColor;
246             }
247 
248             ENDCG
249         }
250         
251         Pass
252         {
253             Name "DrawOutline"
254             Tags {"RenderPipeline" = "UniversalPipeline" "RenderType" = "Opaque"}
255             Cull Front
256             
257             CGPROGRAM
258 
259             #pragma vertex vert
260             #pragma fragment frag
261             #pragma multi_compile_fog
262             #include "UnityCG.cginc"
263 
264             sampler2D _BaseTex;
265             sampler2D _ILM_Tex;
266             sampler2D _RampTex;
267             sampler2D _Eye_Mask_Tex;
268 
269             float4 _OutlineShadowColor;
270             float _OutlineOffset;
271             float _IsEye;323             
324             struct a2v
325             {
326                 float4 vertex : POSITION;
327                 float3 normal : NORMAL;
328                 float4 tangent : TANGENT;
329                 float2 uv : TEXCOORD0;
330                 float4 uv7 : TEXCOORD7;
331             };
332 
333             struct v2f
334             {
335                 float4 position : SV_POSITION;
336                 float2 uv : TEXCOORD0;
337             };
338 
339             v2f vert(a2v v)
340             {
341                 v2f o;
342 
343                 /*float3 worldPos = mul(UNITY_MATRIX_M, v.vertex).xyz;
344                 float3 worldNormal = UnityObjectToWorldNormal(v.normal);
345                 float3 worldTangent = UnityObjectToWorldDir(v.tangent);
346                 float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
347                 float3 ViewPos = mul((float3x3)UNITY_MATRIX_IT_MV, v.vertex);
348 
349                 float3x3 tbn = float3x3(worldTangent, worldBinormal, worldNormal);
350                 float3 normalWS = mul(v.normal.rgb, tbn);
351                 
352                 float EyeMask = 1;
353                 if (_IsEye == 1) EyeMask = tex2Dlod(_Eye_Mask_Tex, float4(v.uv, 0, 0)).a;
354                 _OutlineOffset = lerp(0, _OutlineOffset, EyeMask);
355                 
356                 float3 positionWS = TransformPositionWSToOutlinePositionWS(
357                     worldPos, ViewPos.z, normalWS);
358                 
359                 o.position = UnityObjectToClipPos(positionWS);
360                 o.uv = v.uv;*/
361 
362                 float4 pos = UnityObjectToClipPos(v.vertex);
363                 //float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.uv7.xyz);
364                 float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.tangent.xyz);
365                 float3 ndcNormal = normalize(TransformViewToProjection(viewNormal.xyz)) * pos.w;//将法线变换到NDC空间
366                 float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));//将近裁剪面右上角的位置的顶点变换到观察空间
367                 float aspect = abs(nearUpperRight.y / nearUpperRight.x);//求得屏幕宽高比
368                 ndcNormal.x *= aspect;
369                 pos.xy += 0.001 * clamp(_OutlineOffset * ndcNormal.xy, -50, 50);
370                 
371                 o.position = pos;
372                 o.uv = v.uv;
373                 
374                 return o;
375             }
376             float4 frag(v2f i) : SV_Target
377             {
378 
379                 //Context
380                 float3 LightDir = normalize(_WorldSpaceLightPos0);
381                 
382                 //BaseColor
383                 float4 BaseTexColor = tex2D(_BaseTex, i.uv);
384                 
385                 float4 ILM_RGBA = tex2D(_ILM_Tex, i.uv);
386                 float ILM_A = ILM_RGBA.w;
387                 
388                 //NightRamp_V
389                 float ILM_Alpha_0 = 0.15;
390                 float ILM_Alpha_1 = 0.40;
391                 float ILM_Alpha_2 = 0.60;
392                 float ILM_Alpha_3 = 0.85;
393                 float ILM_Alpha_4 = 1.0;
394                 float ILM_Value_0 = 1.0;
395                 float ILM_Value_1 = 4.0;
396                 float ILM_Value_2 = 3.0;
397                 float ILM_Value_3 = 5.0;
398                 float ILM_Value_4 = 2.0;
399                 ILM_Value_0 = 0.55 - ILM_Value_0 / 10;
400                 ILM_Value_1 = 0.55 - ILM_Value_1 / 10;
401                 ILM_Value_2 = 0.55 - ILM_Value_2 / 10;
402                 ILM_Value_3 = 0.55 - ILM_Value_3 / 10;
403                 ILM_Value_4 = 0.55 - ILM_Value_4 / 10;
404                 float NightRamp_V = lerp(ILM_Value_4, ILM_Value_3, step(ILM_A, ILM_Alpha_3));
405                 NightRamp_V = lerp(NightRamp_V, ILM_Value_2, step(ILM_A, ILM_Alpha_2));
406                 NightRamp_V = lerp(NightRamp_V, ILM_Value_1, step(ILM_A, ILM_Alpha_1));
407                 NightRamp_V = lerp(NightRamp_V, ILM_Value_0, step(ILM_A, ILM_Alpha_0));
408                 
409                 //DayRamp_V
410                 float DayRamp_V = NightRamp_V + 0.5;
411 
412                 //IsDay
413                 float IsDay = (LightDir.y + 1) / 2;
414                 
415                 //RampColor
416                 float3 DayDarkRampColor = tex2D(_RampTex, float2(0.003, DayRamp_V));
417                 float3 NightDarkRampColor = tex2D(_RampTex, float2(0.003, NightRamp_V));
418                 float3 DarkRampColor = lerp(NightDarkRampColor, DayDarkRampColor, IsDay);
419                 
420 
421                 return float4(BaseTexColor * DarkRampColor * _OutlineShadowColor, BaseTexColor.a);
422             }
423             ENDCG
424         }
425     }
426     FallBack "Diffuse"
427 }

 

posted on 2023-08-05 16:30  Relolihentai  阅读(585)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3