【更新中】【Unity/UE】基础仿原神渲染

发布时间 2023-08-05 16:34:33作者: Relolihentai

前言

【本文持续更新中】

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

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

废话不多说,先看效果。

Unity

 UE

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

 

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

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

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

贴图、模型什么的网上一找就能找到,实在找不到就评论区或者私信问我吧,本随笔默认已经准备好所有模型贴图。

概念

  1. Matcap
  2. ILM
  3. Ramp
  4. Rim
  5. fresnel

实现流程

 

源码

  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;
272 
273             float GetCameraFOV()
274             {
275                 //https://answers.unity.com/questions/770838/how-can-i-extract-the-fov-information-from-the-pro.html
276                 float t = unity_CameraProjection._m11;
277                 float Rad2Deg = 180 / 3.1415;
278                 float fov = atan(1.0f / t) * 2.0 * Rad2Deg;
279                 return fov;
280             }
281             float ApplyOutlineDistanceFadeOut(float inputMulFix)
282             {
283                 //make outline "fadeout" if character is too small in camera's view
284                 return saturate(inputMulFix);
285             }
286             float GetOutlineCameraFovAndDistanceFixMultiplier(float positionVS_Z)
287             {
288                 float cameraMulFix;
289                 if(unity_OrthoParams.w == 0)
290                 {
291                     ////////////////////////////////
292                     // Perspective camera case
293                     ////////////////////////////////
294 
295                     // keep outline similar width on screen accoss all camera distance       
296                     cameraMulFix = abs(positionVS_Z);
297 
298                     // can replace to a tonemap function if a smooth stop is needed
299                     cameraMulFix = ApplyOutlineDistanceFadeOut(cameraMulFix);
300 
301                     // keep outline similar width on screen accoss all camera fov
302                     cameraMulFix *= GetCameraFOV();       
303                 }
304                 else
305                 {
306                     ////////////////////////////////
307                     // Orthographic camera case
308                     ////////////////////////////////
309                     float orthoSize = abs(unity_OrthoParams.y);
310                     orthoSize = ApplyOutlineDistanceFadeOut(orthoSize);
311                     cameraMulFix = orthoSize * 50; // 50 is a magic number to match perspective camera's outline width
312                 }
313 
314                 return cameraMulFix * 0.00005; // mul a const to make return result = default normal expand amount WS
315             }
316 
317             float3 TransformPositionWSToOutlinePositionWS(float3 positionWS, float positionVS_Z, float3 normalWS)
318             {
319                 //you can replace it to your own method! Here we will write a simple world space method for tutorial reason, it is not the best method!
320                 float outlineExpandAmount = _OutlineOffset * GetOutlineCameraFovAndDistanceFixMultiplier(positionVS_Z);
321                 return positionWS + normalWS * outlineExpandAmount;
322             }
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 }