Cartoon CG:卡通渲染(开篇)

      今天专门为卡通渲染开一个分类,希望未来在卡通渲染上达到一定高度,因为卡通渲染风格才是我最喜欢的。

      以前买过很多游戏机玩过很多游戏,当然pc网游也玩过不少,发现自己就是喜欢玩卡通风格的游戏,比如塞尔达传说从GBAsp的缩小帽/三角力量玩到wiiu的荒野之息、马里奥3D、路易鬼屋、星之卡比、黄金太阳等,还有pc上的跑跑卡丁车、dnf等,都是我玩过很长时间的游戏,当然其他很多3A大作也玩过,就是不喜欢,比如荒野大镖客、使命召唤、战地、孤岛危机、底特律变人,唯一喜欢的真实感渲染就是ps4的神秘海域4了,我针对我个人的情况查阅过资料,我是一个患有恐怖谷综合症的玩家(不清楚的百度恐怖谷效应),所以玩一些真实感渲染的游戏会心悸恐慌,跟喝了高浓度咖啡一样。

      ps:我一般只看真实感渲染很好的游戏直播,跟看电影一样,而自己只玩卡通渲染游戏。

      言归正传,说起卡通渲染,属于非真实感渲染的领域之一,学名NPR(Non Photorealistic Rendering),主要是为了渲染出一些特殊的画质风格,比如卡通、素描、油画等创作性艺术风格,这些风格可以让我创造特立独行的游戏、影视等作品。比如二次元动漫游戏、废土朋克、水墨油画等。当然NPR覆盖的范围广大无比。

      卡通渲染的核心要素是两个:

      1.具有非真实艺术性的光线算法渲染风格

      2.能起到模型凸显效果的描边算法

      而以上两个核心要素,就囊括万千了,我们就来研究学习下。

      这里我做一个怀旧黑白渐变卡通风格的shader渲染,代码如下:

      

Shader "Custom/CartoonShader"
{
    Properties
    {
		//渐变风格
		_GradientWeight("Gradient Weight",Range(-1,1)) = 0.5
		_BrightColor("Bright Color",Color) = (1,1,1,1)
		_DarkColor("Dark Color",Color) = (0,0,0,1)
		//描边
		_Extsn("Extsn Value", Range(0,0.001)) = 0.001
		_OutColor("Outline Color", Color) = (0,0,0,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

		//正常渲染
		//优先逐顶点光照
		//测试逐片段光照
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
			#include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
				float4 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
				float4 color : TEXCOORD1;
            };

			float _GradientWeight;
			float4 _BrightColor;
			float4 _DarkColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
				//计算顶点上的光照diffuse权重信息
				float3 worldNormal = UnityObjectToWorldNormal(v.normal.xyz);
				float3 worldp2s = normalize(WorldSpaceLightDir(v.vertex));
				float diffuse = dot(worldNormal, worldp2s);
				if (diffuse > _GradientWeight){
					o.color = _BrightColor;
				}
				else {
					o.color = _DarkColor;
				}
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				fixed4 col = i.color;
                return col;
            }
            ENDCG
        }
		//扩展描边
		Pass
		{
			Cull front
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float4 normal : NORMAL;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
			};

			float _Extsn;
			float4 _OutColor;

			v2f vert(appdata v)
			{
				v2f o;
				v.vertex += v.normal * _Extsn;
				o.vertex = UnityObjectToClipPos(v.vertex);
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				fixed4 col = _OutColor;
				return col;
			}
			ENDCG
		}
    }
}

      效果如下:

    

    效果原理如下:黑白渐变渲染pass在顶点函数中模拟diffuse的世界空间中单位法向量和单位顶点朝向光源方向向量点积得到-1到1的权重值,然后根据权重值去赋值渐变色,描边pass则扩展法向量。

    可以看得出来因为是逐顶点计算,所以在法向量与顶点到光源向量因旋转产生变化时,渐变非平滑很生硬,早期在游戏硬件设备不强的时候,使用的可编程管线都是逐顶点计算。同时因为顶点颜色之间会自动插值,所以产生渐变色,当然也可以在顶点函数中手动计算lerp插值color。

    如果我们可以把计算挪到片段函数中,如下:

  

fixed4 frag (v2f i) : SV_Target
            {
				fixed4 col;
				float diffuse = dot(i.worldNormal, i.worldP2S);
				if (diffuse > _GradientWeight) {
					col = _BrightColor;
				}
				else {
					col = _DarkColor;
				}
                return col;
            }

    效果如下:

    

   可以看得出,因为是逐片段计算了,所以没有了自动颜色插值,如果只是单纯的大于小于等于判断就造成了无渐变的黑白两色。

   以上两种做法是基于线性计算,因为diffuse = dot(normal,p2s)在函数图象上就是随着单位向量normal与p2s的夹角增长,diffuse从1到-1,所以达成的效果如上。

   后面我来介绍一个非线性增长的光线计算,可以达到其他渲染效果,当然下次再聊。

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值