Unity Shader怎么实现卡通素描风格渲染

本篇内容介绍了“Unity Shader怎么实现卡通素描风格渲染”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

卡通风格渲染
卡通风格渲染的游戏画面通常物体颜色分界明显,具有黑色的线条描边。卡通渲染的实现有多种方法,基于色调的着色技术是其中之一,实现过程中通过使用漫反射系数对一维纹理进行采样,控制漫反射色调。之前通过一张渐变纹理来控制漫反射颜色实现过卡通风格的渲染效果。卡通风格的高光效果往往是一块分界明显的色块,而物体边缘通常会有描边。本节中将通过基于模型的方式进行描边,而不是之前的屏幕后处理的方式。

轮廓线渲染
轮廓线的渲染是实时渲染中应用非常广泛的一种效果。目前常用的5种绘制模型轮廓线的方法:

  • 基于观察角度和表面法线的轮廓线渲染
    使用视角方向和表面法线的点乘结果得到轮廓线信息。简单快速,一个Pass可以得到结果,局限性较大,不能得到比较满意的描边效果。

  • 正背面渲染
    使用两个Pass,一个渲染背面,另一个渲染正面面片。快速有效,适用于大多数表面平滑的模型。

  • 基于图像处理的轮廓线
    之前屏幕后处理以及利用深度纹理就是采用的这种方式。可以用于任何模型,但深度和法线变化很小的轮廓无法检测,比如紧贴的薄平面。

  • 基于轮廓边缘的轮廓线检测
    通过计算得到精确的轮廓边,然后直接渲染,渲染出独特的风格。检测一条边是否为轮廓边,只需检测和这条边相邻的三角面片是否满足:

(N0*V>0)!=(N1*V>0)

N0和N1分别是相邻面片的法向,这种方式由于是单帧提取轮廓,当帧数较低时,会出现帧与帧之间的跳跃性。

  • 最后一种是以上的综合渲染方法
    首先找到精确的轮廓边,将模型和轮廓渲染到纹理,再通过图形处理的方式识别轮廓线,在图像空间下进行风格化渲染。

下面使用正背面渲染的方式进行轮廓线的勾勒,之前的正背面渲染中,是直接将顶点在裁剪空间中向裁剪空间下的法线方向进行偏移。这里使用观察空间,在观察空间下对顶点进行观察空间下的法向偏移,区别在于观察空间是一个线性空间,尽管之前的效果也基本达到要求,但线性空间下的处理的结果会更加连贯。为了防止一些内凹的模型在使用正面剔除后发生背面遮挡正面的情况,先对顶点法线的z分量进行定值处理,再将法线归一化后进行扩张。这样可以使扩张后背面更加扁平化,降低遮挡正面面片的可能性。即:

viewNormal.z=-0.5;
viewNormal=normalize(viewNormalize);
viewPos=viewPos+viewNormal*_Outline;

卡通风格的高光通常表现为在模型上是一块块分界明显的色块。为了得到这种效果不再使用之前的高光计算模型。之前Blinn-Phong时,使用法线方向点乘视角和光照方向和的一半,再与_Gloss参数进行指数操作得到系数:

float spec=pow(max(0,dot(normal,halfDir)),_Gloss);

对于卡通风格的高光反射光照模型,同样需要计算normal和halfDir的点乘结果,然后直接与一个阈值相比较,大于该阈返回1,小于该阈值返回0,以形成分界明显的色块界限:

float spec=dot(normal,halfDir);
spec=step(threshold,spec);

CG的step函数实现和阈值比较返回0,1结果,第一个为参考值,第二个参数大于第一个参数,返回1,否则返回0。
这种直接0,1的取值方式会在高光的边缘区域形成锯齿,因为由0,1突变。为了得到高光边缘叫平滑的效果,可以在边界处的小块区域内进行平滑处理。

float spec=dot(normal,halfDir);
spec=lerp(0,1,smoothstep(-w,w,spec-threshold));

使用CG的smoothstep函数,w是一个较小的值,当spec-threshold小于-w时,返回0,大于w时,返回1,否则在0,1之间进行插值。这样的效果是在[-w,w]区间,即高光反射边缘,进行0到1的平滑过渡,防止出现锯齿。w的值可以使用CG的fwidth函数得到邻域像素之间的近似导数(像素之间的变化程度)值。

代码实例:

Shader "Custom/Chapter14_ToonShading" {
Properties{
	_MainTex("MainTex",2D)="white"{}
	_Color("Color",Color)=(1,1,1,1)
	_RampTex("Ramp",2D)="white"{}
	_Outline("Outline",Range(0,1))=0.1
	_OutlineColor("OutlineColor",Color)=(0,0,0,1)
	_Specular("SpecularColor",Color)=(1,1,1,1)
	_SpecularScale("Specular Scale",Range(0,0.1))=0.01
}
SubShader{
	Pass{
		NAME "OUTLINE"
		Cull Front
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		fixed _Outline;
		fixed4 _OutlineColor;

		struct a2v{
			float4 vertex:POSITION;
			float3 normal:NORMAL;
		};
		struct v2f{
			float4 pos:SV_POSITION;
		};

		v2f vert(a2v v){
			v2f o;
			float4 pos=mul(UNITY_MATRIX_MV,v.vertex);
			float3 normal=mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
			normal.z=-0.5;
			pos=pos+float4(normalize(normal),0)*_Outline;

			o.pos=mul(UNITY_MATRIX_P,pos);
			return o;
		}

		fixed4 frag(v2f i):SV_Target{
			return fixed4(_OutlineColor.rgb,1);
		}
		ENDCG
	}

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

		sampler2D _MainTex;
		float4 _MainTex_ST;
		fixed4 _Color;
		sampler2D _RampTex;
		fixed4 _Specular;
		fixed _SpecularScale;

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

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

		v2f vert(a2v v){
			v2f o;
			o.pos=UnityObjectToClipPos(v.vertex);
			o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
			o.worldNormal=mul(v.normal,(float3x3)unity_WorldToObject);
			o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;

			TRANSFER_SHADOW(o);

			return o;
		}

		fixed4 frag(v2f i):SV_Target{
			fixed3 worldNormal=normalize(i.worldNormal);
			fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
			fixed3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
			fixed3 worldHalf=normalize(worldLightDir+worldViewDir);

			fixed4 c=tex2D(_MainTex,i.uv);
			fixed3 albedo=c.rgb*_Color.rgb;

			fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;

			UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
			fixed diff=dot(worldNormal,worldLightDir);
			diff=(diff*0.5+0.5)*atten;

			fixed3 diffuse=_LightColor0.rgb*albedo*tex2D(_RampTex,float2(diff,diff)).rgb;

			fixed spec=dot(worldNormal,worldHalf);
			fixed w=fwidth(spec)*2.0;
			fixed3 specular=_Specular.rgb*lerp(0,1,smoothstep(-w,w,spec+_SpecularScale-1))*step(0.0001,_SpecularScale);
			//最后添加的step(0.0001,_SpecularScale);是为了控制当Specular为0时,不出现高光效果
			
			return fixed4(ambient+diffuse+specular,1.0);
		}
		ENDCG
	}
}
FallBack "Diffuse"
//这里的回调需要注意包含能够处理阴影的特殊Pass
}

实例效果:

Unity Shader怎么实现卡通素描风格渲染

素描风格渲染
素描风格的渲染在非真实渲染中应用也比较流行。目前实时的素描风格渲染是通过使用提前生成的素描纹理来实现的。

Unity Shader怎么实现卡通素描风格渲染

这些纹理组成色调艺术映射,纹理从左到右笔触逐渐增多,用于模拟不同光照效果下的漫反射效果,从上到下对应每张纹理的多级渐远纹理。
下面的过程不考虑多级渐远纹理的生成,直接使用6张纹理进行渲染。首先在顶点着色器计算逐顶点光照,根据光照结果决定纹理的混合权重,然后传递给片元着色器,片元着色器根据权重混合6张纹理的采样结果。

实例代码:

Shader "Custom/Chapter14_Hatching" {
Properties{
	_Color("Color",Color)=(1,1,1,1)
	_TileFactor("Tile Factor",Float)=1
	_Outline("Outline",Range(0,1))=0.1
	_Hatch0("Hatch 0",2D)="white"{}
	_Hatch2("Hatch 1",2D)="white"{}
	_Hatch3("Hatch 2",2D)="white"{}
	_Hatch4("Hatch 3",2D)="white"{}
	_Hatch5("Hatch 4",2D)="white"{}
	_Hatch6("Hatch 5",2D)="white"{}

	//TileFactor为纹理的平铺系数,值越大,素描线条越密集
}
SubShader{
	Tags{"RenderType"="Opaque" "Queue"="Geometry"}
	UsePass "Custom/Chapter14_ToonShading/OUTLINE"  
	//素描风格往往也需要绘制轮廓线,使用之前的渲染轮廓Pass
	Pass{
		Tags{"LightMode"="ForwardBase"}
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

			fixed4 _Color;
			float _TileFactor;
			fixed _Outline;
			sampler2D _Hatch0;
			float4 _Hatch0_ST;
			sampler2D _Hatch2;
			float4 _Hatch2_ST;
			sampler2D _Hatch3;
			float4 _Hatch3_ST;
			sampler2D _Hatch4;
			float4 _Hatch4_ST;
			sampler2D _Hatch5;
			float4 _Hatch5_ST;
			sampler2D _Hatch6;
			float4 _Hatch6_ST;

			struct a2v{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
				half4 texcoord:TEXCOORD0;
			};

			struct v2f{
				float4 pos:SV_POSITION;
				float2 uv:TEXCOORD0;
				fixed3 hatchWeight0:TEXCOORD1;
				fixed3 hatchWeight1:TEXCOORD2;
				float3 worldPos:TEXCOORD3;

				SHADOW_COORDS(4)

				//6个权重值分别存储在2个float3类型变量中
			};

			v2f vert(a2v v){
				v2f o;
				o.pos=UnityObjectToClipPos(v.vertex);
				o.uv=v.texcoord.xy*_TileFactor;
				//_TileFactor用来控制素描线条的密集程度(TEX的WrapMode为Repeat)
				
				float3 worldLightDir=normalize(WorldSpaceLightDir(v.vertex));
				float3 worldNormal=UnityObjectToWorldNormal(v.normal);
				float3 diff=max(0,dot(worldLightDir,worldNormal));
				//这里的关键便是通过计算漫反射系数来区分采样权重,并将权重与不同密集程度的TEX相对应

				o.hatchWeight0=fixed3(0,0,0);
				o.hatchWeight1=fixed3(0,0,0);

				//使用世界空间下的光照方向和法线方向得到漫反射系数
				//初始化权重值,*7分为7个区间,并根据hatchFactor的值,为权重赋值
				float hatchFactor=diff*7;
				if(hatchFactor>6){
					//不做任何赋值,保持纯白
				}
				else if(hatchFactor>5.0){
					o.hatchWeight0.x=hatchFactor-5.0;
				}
				else if(hatchFactor>4.0){
					o.hatchWeight0.x=hatchFactor-4.0;
					o.hatchWeight0.y=1.0-o.hatchWeight0.x;
				}
				else if(hatchFactor>3.0){
					o.hatchWeight0.y=hatchFactor-3.0;
					o.hatchWeight0.z=1.0-o.hatchWeight0.y;
				}
				else if(hatchFactor>2.0){
					o.hatchWeight1.x=hatchFactor-2.0;
				}
				else if(hatchFactor>1.0){
					o.hatchWeight1.x=hatchFactor-1.0;
					o.hatchWeight1.y=1.0-o.hatchWeight1.x;
				}
				else{
					o.hatchWeight1.y=hatchFactor;
					o.hatchWeight1.z=1.0-o.hatchWeight1.y;
				}

				o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;

				TRANSFER_SHADOW(o)

				return o;
			}
			fixed4 frag(v2f i):SV_Target{
				fixed4 hatchTex0=tex2D(_Hatch0,i.uv)*i.hatchWeight0.x;
				fixed4 hatchTex1=tex2D(_Hatch2,i.uv)*i.hatchWeight0.y;
				fixed4 hatchTex2=tex2D(_Hatch3,i.uv)*i.hatchWeight0.z;
				fixed4 hatchTex3=tex2D(_Hatch4,i.uv)*i.hatchWeight1.x;
				fixed4 hatchTex4=tex2D(_Hatch5,i.uv)*i.hatchWeight1.y;
				fixed4 hatchTex5=tex2D(_Hatch6,i.uv)*i.hatchWeight1.z;
				//得到6张素描纹理采样结果,并乘以对应的权重
				fixed4 whiteColor=fixed4(1,1,1,1)*(1.0-i.hatchWeight0.x-i.hatchWeight0.y-i.hatchWeight0.z-i.hatchWeight1.x-i.hatchWeight1.y-i.hatchWeight1.z);
				fixed4 hatchColor=hatchTex0+hatchTex1+hatchTex2+hatchTex3+hatchTex4+hatchTex5+whiteColor;
				//计算纯白的占比程度,素描风格中会有留白,并且高光部分也是白色
				UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

				return fixed4(hatchColor.rgb*_Color.rgb*atten,1.0);
				//混合各个颜色,并与衰减和模型颜色相乘得到最终颜色
			}
		ENDCG
	}
}
FallBack "Diffsue"
}

实例效果:

Unity Shader怎么实现卡通素描风格渲染

“Unity Shader怎么实现卡通素描风格渲染”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程之家网站,小编将为大家输出更多高质量的实用文章!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍了Unity游戏开发中外观模式是什么意思,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章主要介绍Unity中地面检测方案的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.普通射线在角色坐标(一般是脚底)...
这篇文章主要介绍了Unity游戏开发中如何消除不想要的黄色警告,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带...
这篇文章主要介绍了Unity中有多少种渲染队列,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解
这篇文章主要介绍Unity中如何实现Texture,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!了解Texture2D 如上图,Texture2D是一张
小编给大家分享一下Unity中DOTS要实现的特点有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让...
这篇文章给大家分享的是有关unity中如何实现UGUI遮罩流光特效的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。下面是核心shader:Sh...
这篇文章主要为大家展示了“Unity中如何实现3D坐标转换UGUI坐标”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下...
这篇文章主要介绍了Unity游戏开发中设计模式的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章主要介绍了Unity中如何实现仿真丝袜渲染,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了...
这篇文章给大家分享的是有关Unity插件OVRLipSync有什么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。项目需要接入对话口型动...
这篇文章主要介绍了Unity性能优化之DrawCall的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章给大家分享的是有关Unity给力插件之Final IK怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。这插件有什么用:一般游...
这篇文章给大家分享的是有关Unity中如何内嵌网页插件UniWebView的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、常见Unity中内...
小编给大家分享一下Unity如何做流体物理的几个轮子,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让...
小编给大家分享一下Unity中Lod和Occlusion Culling的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收...
这篇文章将为大家详细讲解有关Unity中LineRenderer与TrailRenderer有什么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获...
这篇文章主要介绍了Unity中coroutine问题的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起...
这篇文章将为大家详细讲解有关unity中spine怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。骨骼动画首先我们来看到...
这篇文章主要为大家展示了“Unity Shader后处理中如何实现简单均值模糊”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学...