寄存器分配图着色_【02】从零开始的卡通渲染-着色篇1

4e74a542131ece9bacafe3fde7a0c123.png

专栏目录

2173:【01】从零开始的卡通渲染-描边篇​zhuanlan.zhihu.com
748acf12526d45f86e378c74120d1e07.png
2173:【02】从零开始的卡通渲染-着色篇1​zhuanlan.zhihu.com
748acf12526d45f86e378c74120d1e07.png
2173:【03】从零开始的卡通渲染-着色篇2​zhuanlan.zhihu.com
748acf12526d45f86e378c74120d1e07.png
2173:【04】从零开始的卡通渲染-PBR篇​zhuanlan.zhihu.com
2ac4c049ce90046afe4eae408bee80b6.png

序言:

接上一篇的描边篇,整理成一个专栏了。在本节中,我们开始讨论卡通渲染的一些光照计算方法。

如何让角色看起来卡通

思考一下,究竟是哪些因素,让我们觉得角色是卡通的呢。我觉得可以先从下面3点入手

1.减少色阶数量

2.冷暖色调分离

3.对明暗区域的手绘控制

减少色阶数量

cbbef93b26bf5c05f30ed44669e2597a.png
减少色阶的数量,给画面卡通感

冷暖色调分离

5c7e1cdfe83d39bca3b6ac9ceddec146.png
明面和暗面分配不同冷暖的颜色,给画面卡通感

在美术上根据颜色区分为暖色调(红色,黄色)和冷色调(蓝色、紫色)。在偏真实的光照计算中,往往只计算一个明暗关系,然后由光和物体的颜色决定最终效果。而卡通渲染则会根据明暗关系,为明面和暗面分配不同色调的颜色。比如一个暖色调的明面,配合一个冷色调的暗面。将色调拉开以后,更进一步给人卡通感。相关链接 tone-based-shading

0f7fce89307d4d1931416884a72bf827.png
《GUILTY GEAR Xrd》中通过单独的贴图定义暗面色调,和明面的色调做区分。左图是未调整暗面色调,右图是调整了暗面色调的

在《GUILTY GEAR Xrd》游戏中,绘制了一张称为SSS Texture的贴图,来对暗面的色调进行调整。

对明暗区域的手绘控制

在手绘动画中为了好的画面效果,往往其明暗的分布并不是完全正确的。最明显的,角色的脖子部分通常都出现明显的阴影。经典光照计算的结果是非常“正确”的,因而缺少卡通的手绘感。需要用其他方式对光照的计算结果进行调整。

456c23f85212e9b2f766c6bb8c7789d1.png
按照正确的光照计算,角色的脖子是不会有那么明显的阴影的

下面介绍一下《GUILTY GEAR Xrd》中是如何对明暗区域进行手绘控制的

70ee8ad71669666a771dad23e0216cab.png
在《GUILTY GEAR Xrd》中,通过灯光方向,Threshold贴图,法线方向对光照计算进行手绘风格的控制

灯光方向控制:

卡通渲染的角色在部分灯光方向下,可以有最佳的画面表现。有时候这个灯光方向和场景灯光或者其他角色的灯光方向不一致。为了让每个角色都有最佳表现,最好每个角色有一盏自己的灯光方向。甚至当这个角色转向时,这个灯光也跟着角色做一定程度的转向,来让角色有一个更好的光影表现。

Threshold贴图控制:

《GUILTY GEAR Xrd》中将这张贴图称作ilmTexture。为了减少歧义,我们这里也这么称呼好了。

c9df1e73d4de97fdd0a3b005f96f4149.png
ilmTexture关闭和开启对角色阴影区域的影响

这张贴图有些类似于AO贴图,不过它是对光照计算的结果进行一些倾向性的修正。让一部分区域,比如角色脖子的部分更容易产生阴影。来达到手绘风格的阴影效果。

法线方向控制:

法线控制有两种方法,一种是直接编辑法线,达到想要的光照结果。一种是创建一个平滑的简单模型,然后将其法线传递到复杂物体上,达到优化阴影的效果。Maya自带法线传递的功能,3ds Max可以通过插件Noors Normal Thief实现法线传递的功能。

32b2cb35db0a2806d7d5db96be4b124b.png
通过直接编辑法线,达到想要的光照效果

7497898bcb0003bab70847637846db57.png
创建简单的头套模型,传递其法线到头发上,优化头发的阴影

76bb65c2ecf39e6de6a9e44c7843b25e.png
在《火影忍者 究极风暴》中,将人物的面部向外膨胀,再用膨胀后的面部法线来优化面部阴影

赛璐璐风格插画

d41f2b5bf171d508934406becd6b59f1.png
《百变小樱》动画的赛璐璐片

赛璐璐片是一种塑料卡片,在早期日本动画制作流程中的,画师会在赛璐璐材质的塑料卡片上对原画进行上色。其特点为通常只有明暗2个色阶,明暗变化的交界非常明显。现在这种风格的卡通渲染比较流行。在本篇中,也将实现偏向这种风格的卡通渲染。

厚涂风格插画

4417003c3369e201f84472517c2b6510.png
《明日方舟》塔露拉立绘

厚涂风格相较赛璐璐风格,色阶更多,明暗交界变化会柔和很多。这个风格也有它的好处,因为3D场景比较难做成赛璐璐的。如何让赛璐璐风格的角色和非赛璐璐的场景融合是也许需要考虑的。厚涂风格的角色会更容易和场景进行融合。

双色阶的渲染实现

首先我们实现一个明暗边界分明的光照效果,并支持分别设置明暗区域的颜色,设置暗面颜色为冷色调,和明面的色调做出区分。

Shader "Unlit/CelRender"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
        _MainColor("Main Color", Color) = (1,1,1)
	_ShadowColor ("Shadow Color", Color) = (0.7, 0.7, 0.8)
	_ShadowRange ("Shadow Range", Range(0, 1)) = 0.5
        _ShadowSmooth("Shadow Smooth", Range(0, 1)) = 0.2

        [Space(10)]
	_OutlineWidth ("Outline Width", Range(0.01, 2)) = 0.24
        _OutLineColor ("OutLine Color", Color) = (0.5,0.5,0.5,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        pass
        {
           Tags {"LightMode"="ForwardBase"}
			 
            Cull Back
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			
	    #include "UnityCG.cginc"
	    #include "Lighting.cginc"
            #include "AutoLight.cginc"

            sampler2D _MainTex; 
	    float4 _MainTex_ST;
            half3 _MainColor;
	    half3 _ShadowColor;
            half _ShadowRange;

            struct a2v 
	   {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
	   {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
		float3 worldPos : TEXCOORD2; 
            };


            v2f vert(a2v v)
	  {
                v2f o;
		UNITY_INITIALIZE_OUTPUT(v2f, o);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
		o.worldNormal = UnityObjectToWorldNormal(v.normal);
		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }

            half4 frag(v2f i) : SV_TARGET 
	   {
                half4 col = 1;
                half4 mainTex = tex2D(_MainTex, i.uv);
                half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
		half3 worldNormal = normalize(i.worldNormal);
                half3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
		half halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
                half3 diffuse = halfLambert > _ShadowRange ? _MainColor : _ShadowColor;
                diffuse *= mainTex;
                col.rgb = _LightColor0 * diffuse;
                return col;
            }
            ENDCG
        }

        Pass
	{
                //描边,参考上一篇
        }
    }
}

08f6a98c4a677db90df172fdbc7e8f62.png
实现明暗边界分明的光照,并且单独设置明面和暗面的颜色来区分色调

smoothstep柔化明暗边界

现在我们希望能够对明暗边界的变化做一些柔化,让风格往厚涂的风格靠一些,这样可以跟更容易地跟一些非赛璐璐风格的场景做融合。这里我们使用smoothstep函数实现这个效果。这个函数可以在根据输入数据,计算一个范围在0到1区间的平滑过渡曲线。通过这个函数的结果对明面和暗面的颜色进行插值,来实现明暗边界的软硬控制。wiki百科链接

c70731925cfb4d5ae2b579f1979e1c25.png
smoothstep(0,0.7,x)

对代码进行如下修改

          half halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
          half ramp = smoothstep(0, _ShadowSmooth, halfLambert - _ShadowRange);
          half3 diffuse = lerp(_ShadowColor, _MainColor, ramp);

67e670093671fbe8dc9c61de49c07957.png
使用smoothstep函数对明暗分界的软硬进行控制

Ramp贴图

还有一个做法是通过采样Ramp贴图来实现对色阶和明暗边界的控制。可以看成是用标准光照的结果为UV,采样一张用作颜色映射表的贴图,通过这张贴图控制光照计算的结果。制作如下图的ramp贴图,然后对代码进行修改。

	half halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
        half ramp =  tex2D(_rampTex, float2(saturate(halfLambert - _ShadowRange), 0.5)).r;
        half3 diffuse = lerp(_ShadowColor,_MainColor, ramp);

d50d5bf5ab754927a57d3ce4390fec6d.png
Ramp贴图,从左向右对应光照从0到1的范围。注意最左边光照应该最弱的部分反而设置的比较亮

49a92d50dab6e36487a857decb4bbcf0.png
使用Ramp贴图对色阶进行控制,在暗面形成了一个有点亮的反光

Ramp贴图能够更容易的定义多个色阶,不过贴图需要自己制作。贴图制作起来并不复杂,也可以通过编写编辑器工具来生成。这里的贴图是使用Toony Colors Pro插件生成的。

在制作Ramp贴图的时候,最左边光照应该最弱的部分反而设置的比较亮。这个是为了制作出暗面的反光效果。在素描上面有个明暗五调子的知识,在物体边缘的部分会有一圈反光,所以物体的边缘不会是最暗的部分。链接

2ef02793077d3411d2e3f65613e32857.png
素描关系上的三大面五大调子

在图形学上,也有对应的概念,称为菲涅耳(fresnel)现象。我觉得这体现了一个非常有趣的观点,无论是图形学使用光照模型对现实世界的物理现象进行模拟,还是画家们通过观察现实世界总结出的美术理论,最终都是殊途同归的。一个使用公式进行绘图,一个使用画笔进行绘图罢了。

在《偶像大师》系列,也使用了Ramp贴图来实现色阶和色调的控制

e1d7f811c818040d34c942ab6ff055b5.png
左上的贴图是《偶像大师1》的ramp贴图,左下是《偶像大师2》的ramp贴图

aa2ab4d1ca0591c24f2f0529bc99be8a.png
《偶像大师1》(左)和《偶像大师2》(右)的画面变化

因为本篇的篇幅有点太长了,有关边缘反光部分的讨论,放在下一篇再详细讨论。

ilmTexture贴图的实现

《GUILTY GEAR Xrd》中使用称为ilmTexture的贴图对角色明暗区域实现手绘风格的控制。其中绿通道控制漫反射的阴影阈值,红通道控制高光强度,蓝通道控制高光范围。这里跟据这个原理,完成一个最简单的实现。卡通渲染不像Lambert等光照模型有统一的公式,如果要更进一步的表现还需要根据画面需求做各种trick。比如专栏标题的展示图片还添加了阴影残留和阴影色调分离的效果,这方面就由大家自己发挥吧。

Shader "Unlit/CelRenderFull"
{
	Properties
	{
		_MainTex ("MainTex", 2D) = "white" {}
                _IlmTex ("IlmTex", 2D) = "white" {}

		[Space(20)]
		_MainColor("Main Color", Color) = (1,1,1)
		_ShadowColor ("Shadow Color", Color) = (0.7, 0.7, 0.7)
		_ShadowSmooth("Shadow Smooth", Range(0, 0.03)) = 0.002
		_ShadowRange ("Shadow Range", Range(0, 1)) = 0.6

		[Space(20)]
		_SpecularColor("Specular Color", Color) = (1,1,1)
		_SpecularRange ("Specular Range",  Range(0, 1)) = 0.9
                _SpecularMulti ("Specular Multi", Range(0, 1)) = 0.4
		_SpecularGloss("Sprecular Gloss", Range(0.001, 8)) = 4

		[Space(20)]
		_OutlineWidth ("Outline Width", Range(0, 1)) = 0.24
                _OutLineColor ("OutLine Color", Color) = (0.5,0.5,0.5,1)
	}

	SubShader
	{
		Pass
		{
			Tags { "LightMode"="ForwardBase"}

			CGPROGRAM
                        #pragma vertex vert
                        #pragma fragment frag
			#pragma multi_compile_fwdbase
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
                        #include "AutoLight.cginc"

                        sampler2D _MainTex; 
			float4 _MainTex_ST;
                        sampler2D _IlmTex; 
			float4 _IlmTex_ST;

			half3 _MainColor;
			half3 _ShadowColor;
			half _ShadowSmooth;
			half _ShadowRange;
			
			half3 _SpecularColor;
			half _SpecularRange;
        	        half _SpecularMulti;
			half _SpecularGloss;

			struct a2v
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;	
				float3 worldNormal : TEXCOORD1;
				float3 worldPos : TEXCOORD2; 
			};
			
			v2f vert (a2v v)
			{
				v2f o = (v2f)0;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				return o;
			}
			
			half4 frag (v2f i) : SV_Target
			{
				half4 col = 0;
				half4 mainTex = tex2D (_MainTex, i.uv);
				half4 ilmTex = tex2D (_IlmTex, i.uv);
				half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				half3 worldNormal = normalize(i.worldNormal);
				half3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				half3 diffuse = 0;
				half halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
				half threshold = (halfLambert + ilmTex.g) * 0.5;
				half ramp = saturate(_ShadowRange  - threshold); 
				ramp =  smoothstep(0, _ShadowSmooth, ramp);
				diffuse = lerp(_MainColor, _ShadowColor, ramp);
				diffuse *= mainTex.rgb;

				half3 specular = 0;
				half3 halfDir = normalize(worldLightDir + viewDir);
				half NdotH = max(0, dot(worldNormal, halfDir));
				half SpecularSize = pow(NdotH, _SpecularGloss);
				half specularMask = ilmTex.b;
				if (SpecularSize >= 1 - specularMask * _SpecularRange)
				{
					specular = _SpecularMulti * (ilmTex.r) * _SpecularColor;
				}

				col.rgb = (diffuse + specular) * _LightColor0.rgb;
				return col;
			}
			ENDCG
		}

                Pass
                {
                        //描边,参考上一篇
                }
	}
	FallBack Off
}

afb92efd5ef88e57aff5e19912a8295f.png
上色后的效果

总结

在本节中,介绍了卡通渲染和写实风格渲染的主要区别和一些实现方法。在下一个章节中,将会讨论卡通渲染的边缘光、后处理、以及和PBR进行融合的方向等。

分享

在写这篇文章的ramp贴图部分的时候,想起了以前在OpenGPU论坛看到的Trace大佬翻译的《偶像大师》相关文章。回去找的时候发现论坛已经没了,以前上面的很多文章也丢失了。觉得还是有点伤感的,我最早是看着OpenGPU上面的Trace翻译的科普文章,开始对渲染产生兴趣的。从这一点上来说,我非常感谢在网络上科普图形学知识的前辈们,给了我们非常好的学习途径。后来发现因为我一直有备份ppt的习惯,让我保留了这几篇文章的备份。虽然都很老了,但是觉得不应该消失在网络上面。所以我把这两篇《偶像大师》的文章上传到百度网盘里了,感兴趣的小伙伴可以去下载。 那么我们下一篇见。

百度网盘 提取码:2whi。

附:参考链接

【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,前篇(1)

【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,前篇(2)

【翻译】西川善司的「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,后篇

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值