《Twin Souls: Path of the Shadows》是一款游戏性很强,且具有优质渲染效果的端游,本文我们一起来看看如何使用Unity的渲染管道来实现像《Twin Souls: Path of the Shadows》一样的渲染的效果。

101017bff6lefzvqqftdpr.jpg
上图中,从左至右分别是Diffuse着色器,Cel Shading和有三个阈的Cel Shading。 我们认为一个像素接收的光量是光方向和表面法线之间的点积(NdotL)。如果将此值取整为0或者1,或者定义不同的“等级”或“阈”,将导致不同的渲染样式。在最简单的渲染中,如果点积大于零,则将像素设置为点亮,如果点积小于零,则将像素设置为阴影。

实现方法
此效果可以根据您的渲染路径(Rendering Path)以不同的方式实现。渲染路径(Rendering Path)可以在相机的检视面板(Inspector)中设置。下面我们介绍一下正向渲染和延迟渲染。

正向渲染
如果您的场景不使用多光源,正向渲染(Forward Rendering)是一个不错的选择。因为在这种模式下,渲染引擎会遍历所有光源的每个顶点和像素,可能会带来较大负担。 然而,如果您的场景只有几个或甚至一个光源,这个解决方案是非常轻量级的。具体实施的时候,您可以创建一个新的表面着色器(surface shader)。

您可以创建一个新的表面着色器(surface shader)如下:

[C#] 纯文本查看 复制代码Shader Custom/CelShadingForward { Properties { _Color(Color, Color) = (1, 1, 1, 1) _MainTex(Albedo (RGB), 2D) = white {} } SubShader { Tags { RenderType = Opaque } LOD 200 CGPROGRAM #pragma surface surf CelShadingForward #pragma target 3.0 half4 LightingCelShadingForward(SurfaceOutput s, half3 lightDir, half atten) { half NdotL = dot(s.Normal, lightDir); NdotL = 1 + clamp(floor(NdotL), -1, 0); //把NdotL设置为0(小于0)或设置为1(大于0) half4 c; c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten * 2); c.a = s.Alpha; return c; } sampler2D _MainTex; fixed4 _Color; struct Input { float2 uv_MainTex; }; void surf(Input IN, inout SurfaceOutput o) { // Albedo comes from a texture tinted by color //反照率来自一个有色纹理 fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack Diffuse }

您通过下面的代码,让边缘平滑。
[C#] 纯文本查看 复制代码NdotL = smoothstep(0, 0.025f, NdotL);

延迟渲染
延迟渲染(Deferred Rendering)的优点是它在多光源的场景中可以表现出很好的性能。当使用延迟渲染(Deferred Rendering)时,引擎会遍历场景中的每个光源,并将其与场景中的几何信息一起存储在缓冲区中。要改变延迟渲染(Deferred Rendering)管道,我们需要从Unity网站上下载内置着色器包。

接下来,对下载好的文件进行解压缩,在“DefaultResourcesExtra”文件夹中找到“Internal-DeferredShading.shader”,并将此文件复制到项目中的Resources文件夹。因为自从最初的解决方案发布以来,Unity进行了很多更新,所以我们还需要做以下步骤:
在解压缩的文件夹中找到UnityStandardBRDF.cginc和UnityDeprecated.cginc,并将它们移动到项目中的Resources文件夹。
将UnityDeprecated.cginc重命名为UnityDeprecatedEx.cginc,并使用以下代码替换整个代码,以便添加CustomLambertTerm 。

[C#] 纯文本查看 复制代码inline half CustomDotClamped ( half3 a, half3 b ) { #if (SHADER_TARGET < 30) return saturate(dot(a,b)); #else return max(0.0h, dot(a,b)); #endif } inline half CustomLambertTerm ( half3 normal, half3 lightDir ) { return smoothstep(0.0,0.05f, CustomDotClamped (normal, lightDir)); }

将UnityStandardBRDF.cginc重命名为UnityStandardBRDFCustom.cginc,删除其中的整个代码,并用BRDF_1_Unity_PBS方法的自定义实现替换它。它被重命名为BRDF_CUSTOM_Unity_PBS且使用我们的CustomLambertTerm。

UnityStandardBRDFCustom.cginc应该如下所示:

[C#] 纯文本查看 复制代码#include UnityCG.cginc #include UnityStandardConfig.cginc #include UnityLightingCommon.cginc #include UnityDeprecatedEx.cginc half4 BRDF_CUSTOM_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness, half3 normal, half3 viewDir, UnityLight light, UnityIndirect gi) { half perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness); half3 halfDir = Unity_SafeNormalize (light.dir + viewDir); #define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0 #if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV half shiftAmount = dot(normal, viewDir); normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal; half nv = saturate(dot(normal, viewDir)); #else half nv = abs(dot(normal, viewDir)); #endif //渲染 half nl = CustomLambertTerm(normal, light.dir); half nh = saturate(dot(normal, halfDir)); half lv = saturate(dot(light.dir, viewDir)); half lh = saturate(dot(light.dir, halfDir)); // Diffuse term half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl; half roughness = PerceptualRoughnessToRoughness(perceptualRoughness); #if UNITY_BRDF_GGX half V = SmithJointGGXVisibilityTerm (nl, nv, roughness); half D = GGXTerm (nh, roughness); #else // Legacy half V = SmithBeckmannVisibilityTerm (nl, nv, roughness); half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness)); #endif half specularTerm = V*D * UNITY_PI; # ifdef UNITY_COLORSPACE_GAMMA specularTerm = sqrt(max(1e-4h, specularTerm)); # endif specularTerm = max(0, specularTerm * nl); #if defined(_SPECULARHIGHLIGHTS_OFF) specularTerm = 0.0; #endif half surfaceReduction; # ifdef UNITY_COLORSPACE_GAMMA surfaceReduction = 1.0-0.28*roughness*perceptualRoughness; # else surfaceReduction = 1.0 / (roughness*roughness + 1.0); # endif specularTerm *= any(specColor) ? 1.0 : 0.0; half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) + specularTerm * light.color * FresnelTerm (specColor, lh) + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); return half4(color, 1); }

在导入的Internal-DeferredShading.shader中,使用UnityStandardBRDFCustom.cginc中定义的自定义宏重写UNITY_BRDF_PBS函数宏:

[C#] 纯文本查看 复制代码#define UNITY_BRDF_PBS BRDF_CUSTOM_Unity_PBS

在着色器中包含UnityStandardBRDF.cginc和UnityStandardBRDFCustom.cginc:

[C#] 纯文本查看 复制代码#include UnityStandardBRDF.cginc #include UnityStandardBRDFCustom.cginc

由于UNITY_BRDF_PBS函数完全被覆盖,因此可以从Internal-DeferredShading.shader中删除UnityPBSLighting.cginc。
在着色器中添加以下行:

[C#] 纯文本查看 复制代码if (light.ndotl <= 0.0) light.ndotl = 0; else light.ndotl = 1;

最后,当所有这些步骤完成后,我们的Internal-DeferredShading.shader应该像这样:

[C#] 纯文本查看 复制代码Shader Hidden/Internal-DeferredShading { Properties { _LightTexture0 (, any) = {} _LightTextureB0 (, 2D) = {} _ShadowMapTexture (, any) = {} _SrcBlend (, Float) = 1 _DstBlend (, Float) = 1 } SubShader { // Pass 1: Lighting pass // LDR case - Lighting encoded into a subtractive ARGB8 buffer // HDR case - Lighting additively blended into floating point buffer Pass { ZWrite Off Blend [_SrcBlend] [_DstBlend] CGPROGRAM #define UNITY_BRDF_PBS BRDF_CUSTOM_Unity_PBS #pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON #pragma target 3.0 #pragma vertex vert_deferred #pragma fragment frag #pragma multi_compile_lightpass #pragma multi_compile ___ UNITY_HDR_ON #pragma exclude_renderers nomrt #include UnityCG.cginc #include UnityDeferredLibrary.cginc //#include UnityPBSLighting.cginc #include UnityStandardUtils.cginc #include UnityGBuffer.cginc #include UnityStandardBRDF.cginc #include UnityStandardBRDFCustom.cginc sampler2D _CameraGBufferTexture0; sampler2D _CameraGBufferTexture1; sampler2D _CameraGBufferTexture2; half4 CalculateLight (unity_v2f_deferred i) { float3 wpos; float2 uv; float atten, fadeDist; UnityLight light; UNITY_INITIALIZE_OUTPUT(UnityLight, light); UnityDeferredCalculateLightParams (i, wpos, uv, light.dir, atten, fadeDist); light.color = _LightColor.rgb * atten; // unpack Gbuffer //解压缩Gbuffer half4 gbuffer0 = tex2D (_CameraGBufferTexture0, uv); half4 gbuffer1 = tex2D (_CameraGBufferTexture1, uv); half4 gbuffer2 = tex2D (_CameraGBufferTexture2, uv); UnityStandardData data = UnityStandardDataFromGbuffer(gbuffer0, gbuffer1, gbuffer2); data.diffuseColor.rgb = gbuffer0.rgb; float3 eyeVec = normalize(wpos-_WorldSpaceCameraPos); half oneMinusReflectivity = 1 - SpecularStrength(data.specularColor.rgb); UnityIndirect ind; UNITY_INITIALIZE_OUTPUT(UnityIndirect, ind); ind.diffuse = 0; ind.specular = 0; if (light.ndotl <= 0.0) light.ndotl = 0; else light.ndotl = 1; half4 res = UNITY_BRDF_PBS (data.diffuseColor, data.specularColor, oneMinusReflectivity, data.smoothness, data.normalWorld, -eyeVec, light, ind); return res; } #ifdef UNITY_HDR_ON half4 #else fixed4 #endif frag (unity_v2f_deferred i) : SV_Target { half4 c = CalculateLight(i); #ifdef UNITY_HDR_ON return c; #else return exp2(-c); #endif } ENDCG } // Pass 2: Final decode pass. // Used only with HDR off, to decode the logarithmic buffer into the main RT //通过2:最终解码通过 //关闭HDR,向主RT中解码对数缓冲区 Pass { ZTest Always Cull Off ZWrite Off Stencil { ref [_StencilNonBackground] readmask [_StencilNonBackground] // Normally just comp would be sufficient, but theres a bug and only front face stencil state is set (case 583207) //通常只是排版就足够了,但是有一个错误,只有正面模板状态被设置(583207例) compback equal compfront equal } CGPROGRAM #pragma target 3.0 #pragma vertex vert #pragma fragment frag #pragma exclude_renderers nomrt #include UnityCG.cginc sampler2D _LightBuffer; struct v2f { float4 vertex : SV_POSITION; float2 texcoord : TEXCOORD0; }; v2f vert (float4 vertex : POSITION, float2 texcoord : TEXCOORD0) { v2f o; o.vertex = UnityObjectToClipPos(vertex); o.texcoord = texcoord.xy; #ifdef UNITY_SINGLE_PASS_STEREO o.texcoord = TransformStereoScreenSpaceTex(o.texcoord, 1.0f); #endif return o; } fixed4 frag (v2f i) : SV_Target { return -log2(tex2D(_LightBuffer, i.texcoord)); } ENDCG } } Fallback Off }


最后,当所有这些步骤完成后,我们的Internal-DeferredShading.shader就大功告成了!然后,我们可以用新创建的着色器替换Deffered着色器:到Edit - > ProjectSettings - > Graphics。在Deferred中,选择我们要修改的文件。

101017s1y1y7gaz9mwyww9.jpg
之后,确保您的相机正在使用所需的渲染路径(延迟的Deferred), 现在您应该注意标准着色器(Standard Shader)已经应用了渲染,如下图所示。

101017otzepie8oai084m0.jpg

边缘
许多游戏使用渲染对物体(Object)勾画轮廓,这样场景会有漫画一般的视觉效果。这种效果可以用许多不同的方式实现。 轮廓可以是纹理的一部分,着色器可以检查表面法线和视角之间的点积,或者可以在后处理中完成。最简单的解决方案是在您的相机中添加Edge Detection效果:
点击Assets->Import Package->Effects,导入Standard Assets Effects包 在层级结构(Hierarchy)中选择您的摄像机,然后在检视面板(Inspector)中点击 Add Component->Image Effects->Edge Detection->Edge Detection

突出边缘的效果如下图所示。

101017tnh073n6415hjh5l.jpg
最终效果
在本文中,我们借助对《Twin Souls: The Path of Shadows》进行渲染的解决方案,更改Unity的渲染管道以实现风格 。最终,几乎没有使用其他添加,我们实现了类似的独特风格,如下图所示。

101017l8q3yyyt3whhpbbj.jpg
总结
非真实渲染技术非常有用,因为它不会替换整个照明模型,只是将现有的模型表面显示为点亮或阴影。它可以与图像效果完美融合,也可以在没有额外性能开销的情况下允许您使用完整的PBR容量。如果您想了解更多有关的技术文章,请访问Unity官方平台。

101919w3dxu3akddyoffcd.png

更多Unity技术文章
让神奇的Unity动画插件激活您的游戏
使用Unity进行增强现实中的光照和阴影的渲染
在Unity中如何让3D模型呈现2D效果
Unity实时渲染电影短片《Adam》完整版发布
Unity实时渲染动画电影《Gift》

近期活动

活动一:2017 Unity技术路演 — 华南站将于3月6日开启,仅剩3天!报名从速。详情请关注2017 Unity技术路演正式启动,3月华南区报名开启!

活动二:Unite 2017 Shanghai将于5月11 - 13日在上海国际会议中心举行!不到5折的超值早鸟票现已开售!赞助商招募已经开始!详情请关注“再一次问候” - Unite 2017 Shanghai 正式开启。

101942p3d00px1f4rc3z54.png
Unity, 非真实感渲染锐亚教育

锐亚教育 锐亚科技 unity unity教程