• Unity3d shader之卡通着色Toon Shading


    卡通着色的目的是为了让被着色物体显得过渡的不那么好,明暗交界线很明显,等等卡通风格的一系列特征,

    也叫Non-photorealisticrendering非真实渲染

    重点要做到两点:

    1.    描边

    2.    着色

    另:本片中cg函数均用绿色标明,想了解函数作用和函数内部构成请看这篇文章 NVIDIA CG语言 函数之所有数学类函数(Mathematical Functions)

    就从最初的描边开始

    首先声明变量
    _Outline挤出描边的粗细
    _Factor挤出多远

    Properties {
    		_Color("Main Color",color)=(1,1,1,1)
    		_Outline("Thick of Outline",range(0,0.1))=0.02
    		_Factor("Factor",range(0,1))=0.5
    	}




    我们的挤出操作在第一个pass中进行
    Cull Front 裁剪了物体的前面(对着相机的),把背面挤出
    ZWrite On 像素的深度写入深度缓冲,如果关闭的话,物体与物体交叠处将不会被描边,因为此处无z值后渲染的物体会把此处挤出的描边“盖住”

    在处理顶点的函数vert中把点挤出

    dir=normalize(v.vertex.xyz);
    建立一个float3方向变量dir
    把该点的位置作为距离几何中心的方向的单位向量

    float3 dir2=v.normal;
    建立一个float3方向变量dir
    dir2为法线方向

    D=dot(dir,dir2);
    D为计算该点位置朝向和法线方向的点积,通过正负值可以确定是指向还是背离几何中心的,正为背离,负为指向

    dir=dir*sign(D);
    乘上正负值,真正的方向值

    dir=dir*_Factor+dir2*(1-_Factor);

    把该点位置朝向与法线方向按外部变量_Factor的比重混合,来控制挤出多远

    v.vertex.xyz+=dir*_Outline;
    把物体背面的点向外挤出

    		v2f vert (appdata_full v) {
    			v2f o;
    			float3 dir=normalize(v.vertex.xyz);
    			float3 dir2=v.normal;
    			float D=dot(dir,dir2);
    			dir=dir*sign(D);
    			dir=dir*_Factor+dir2*(1-_Factor);
    			v.vertex.xyz+=dir*_Outline;
    			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    			return o;
    		}



    顶点函数结束,
    接下来为描边上色
    在frag函数中

    挤出轮廓的颜色,此处颜色随意

    效果如下:

    清楚地描出了轮廓,可以在材质中改变_Outline的值来改变粗细

    描边shader如下:

    Shader "Tut/Shader/Toon/miaobian" {
    Properties {
    		_Color("Main Color",color)=(1,1,1,1)
    		_Outline("Thick of Outline",range(0,0.1))=0.02
    		_Factor("Factor",range(0,1))=0.5
    	}
    	SubShader {
    		pass{
    		Tags{"LightMode"="Always"}
    		Cull Front
    		ZWrite On
    		CGPROGRAM
    		#pragma vertex vert
    		#pragma fragment frag
    		#include "UnityCG.cginc"
    		float _Outline;
    		float _Factor;
    		float4 _Color;
    		struct v2f {
    			float4 pos:SV_POSITION;
    		};
    
    		v2f vert (appdata_full v) {
    			v2f o;
    			float3 dir=normalize(v.vertex.xyz);
    			float3 dir2=v.normal;
    			float D=dot(dir,dir2);
    			dir=dir*sign(D);
    			dir=dir*_Factor+dir2*(1-_Factor);
    			v.vertex.xyz+=dir*_Outline;
    			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    			return o;
    		}
    		float4 frag(v2f i):COLOR
    		{
    			float4 c = _Color / 5;
    			return c;
    		}
    		ENDCG
    		}
    		pass{
    		Tags{"LightMode"="ForwardBase"}
    		Cull Back
    		CGPROGRAM
    		#pragma vertex vert
    		#pragma fragment frag
    		#include "UnityCG.cginc"
    
    		float4 _LightColor0;
    		float4 _Color;
    		float _Steps;
    		float _ToonEffect;
    
    		struct v2f {
    			float4 pos:SV_POSITION;
    			float3 lightDir:TEXCOORD0;
    			float3 viewDir:TEXCOORD1;
    			float3 normal:TEXCOORD2;
    		};
    
    		v2f vert (appdata_full v) {
    			v2f o;
    			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    			o.normal=v.normal;
    			o.lightDir=ObjSpaceLightDir(v.vertex);
    			o.viewDir=ObjSpaceViewDir(v.vertex);
    
    			return o;
    		}
    		float4 frag(v2f i):COLOR
    		{
    			float4 c=1;
    			float3 N=normalize(i.normal);
    			float3 viewDir=normalize(i.viewDir);
    			float3 lightDir=normalize(i.lightDir);
    			float diff=max(0,dot(N,i.lightDir));
    			diff=(diff+1)/2;
    			diff=smoothstep(0,1,diff);
    			c=_Color*_LightColor0*(diff);
    			return c;
    		}
    		ENDCG
    		}//
    	} 
    }
    



     

    开始卡通着色旅程

    描边之后就是重头戏着色了

    简单的举几个例子

    说明一下卡通着色

    把diffuse漫反射颜色变成了很明显的几个色阶(本例为四个)

    普通的diffuse漫反射龙,没有色阶层,颜色过渡的很好,没有卡通的感觉

    普通的漫反射材质

    上图的可爱的大河酱就是由二阶颜色着色而成,再加上边缘黑色的描边,这个是真正的卡通,不是渲染出来的= =

    《深渊传说》是传说系列的早期作品之一,用的也是卡通渲染


    感觉大部分都是卡通式纹理贴图在出力

    《仙乐传说》的战斗结束画面

    有明显的明暗交界线(两个色阶),并随摄像头(view direction)的变化而变化,人物有明显的描边处理,卡通着色起了很大作用

    《无尽传说2》,是传说系列比较近的作品,画面明显比前做好了许多,但万变不离其宗,还是用的卡通着色,(= =没玩过这作)

    人物有着明显的描边处理

    另外我感觉泛光的效果很好啊,应该是bloom或者是hdr之类的,跑题了= =

    开始动手操刀卡通着色

    第一个pass就是上面的描边pass


    对漫反射的卡通着色在第二个pass中


    先声明变量
    _Color物体的颜色
    _Outline挤出描边的粗细
    _Factor挤出多远
    _ToonEffect卡通化程度(二次元与三次元的交界线)
    _Steps色阶层数

    	Properties {
    		_Color("Main Color",color)=(1,1,1,1)//物体的颜色
    		_Outline("Thick of Outline",range(0,0.1))=0.02//挤出描边的粗细
    		_Factor("Factor",range(0,1))=0.5//挤出多远
    		_ToonEffect("Toon Effect",range(0,1))=0.5//卡通化程度(二次元与三次元的交界线)
    		_Steps("Steps of toon",range(0,9))=3//色阶层数
    	}



    卡通着色主要在着色函数frag中进行
    float diff=max(0,dot(N,i.lightDir));
    求出正常的漫反射颜色


    diff=(diff+1)/2;
    做亮化处理


    diff=smoothstep(0,1,diff);
    使颜色平滑的在[0,1]范围之内


    float toon=floor(diff*_Steps)/_Steps;
    把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示


    diff=lerp(diff,toon,_ToonEffect);
    根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重


    c=_Color*_LightColor0*(diff);
    把最终颜色混合

    第二个pass结束,

    		float4 frag(v2f i):COLOR
    		{
    			float4 c=1;
    			float3 N=normalize(i.normal);
    			float3 viewDir=normalize(i.viewDir);
    			float3 lightDir=normalize(i.lightDir);
    			float diff=max(0,dot(N,i.lightDir));//求出正常的漫反射颜色
    			diff=(diff+1)/2;//做亮化处理
    			diff=smoothstep(0,1,diff);//使颜色平滑的在[0,1]范围之内
    			float toon=floor(diff*_Steps)/_Steps;//把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示
    			diff=lerp(diff,toon,_ToonEffect);//根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重
    
    			c=_Color*_LightColor0*(diff);//把最终颜色混合
    			return c;
    		}




    第三个pass就是在第二个pass的基础之上加上离散化的高光

    建立float变量dist为求出距离光源的距离float

    float atten=1/(dist);
    根据距光源的距离求出衰减;

    漫反射部分与第二个pass相同;

    half3 h = normalize (lightDir + viewDir);

    求出半角向量

    float nh = max (0, dot (N, h));
    float spec = pow (nh, 32.0);

    求出正常情况下的高光强度

    float toonSpec=floor(spec*atten*2)/ 2;
    把高光也离散化

    spec=lerp(spec,toonSpec,_ToonEffect);
    调节卡通与现实高光的比重

    		float4 frag(v2f i):COLOR
    		{
    			float4 c=1;
    			float3 N=normalize(i.normal);
    			float3 viewDir=normalize(i.viewDir);
    			float dist=length(i.lightDir);//求出距离光源的距离
    			float3 lightDir=normalize(i.lightDir);
    			float diff=max(0,dot(N,i.lightDir));
    			diff=(diff+1)/2;
    			diff=smoothstep(0,1,diff);
    			float atten=1/(dist);//根据距光源的距离求出衰减
    			float toon=floor(diff*atten*_Steps)/_Steps;
    			diff=lerp(diff,toon,_ToonEffect);
    
    			half3 h = normalize (lightDir + viewDir);//求出半角向量
    			float nh = max (0, dot (N, h));
    			float spec = pow (nh, 32.0);//求出高光强度
    			float toonSpec=floor(spec*atten*2)/ 2;//把高光也离散化
    			spec=lerp(spec,toonSpec,_ToonEffect);//调节卡通与现实高光的比重
    
    			
    			c=_Color*_LightColor0*(diff+spec);//求出最终颜色
    			return c;
    		}



    就可以得到这种卡通效果:

    shader如下:

    Shader "Tut/Shader/Toon/toon" {
    	Properties {
    		_Color("Main Color",color)=(1,1,1,1)//物体的颜色
    		_Outline("Thick of Outline",range(0,0.1))=0.02//挤出描边的粗细
    		_Factor("Factor",range(0,1))=0.5//挤出多远
    		_ToonEffect("Toon Effect",range(0,1))=0.5//卡通化程度(二次元与三次元的交界线)
    		_Steps("Steps of toon",range(0,9))=3//色阶层数
    	}
    	SubShader {
    		pass{//处理光照前的pass渲染
    		Tags{"LightMode"="Always"}
    		Cull Front
    		ZWrite On
    		CGPROGRAM
    		#pragma vertex vert
    		#pragma fragment frag
    		#include "UnityCG.cginc"
    		float _Outline;
    		float _Factor;
    		struct v2f {
    			float4 pos:SV_POSITION;
    		};
    
    		v2f vert (appdata_full v) {
    			v2f o;
    			float3 dir=normalize(v.vertex.xyz);
    			float3 dir2=v.normal;
    			float D=dot(dir,dir2);
    			dir=dir*sign(D);
    			dir=dir*_Factor+dir2*(1-_Factor);
    			v.vertex.xyz+=dir*_Outline;
    			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    			return o;
    		}
    		float4 frag(v2f i):COLOR
    		{
    			float4 c=0;
    			return c;
    		}
    		ENDCG
    		}//end of pass
    		pass{//平行光的的pass渲染
    		Tags{"LightMode"="ForwardBase"}
    		Cull Back
    		CGPROGRAM
    		#pragma vertex vert
    		#pragma fragment frag
    		#include "UnityCG.cginc"
    
    		float4 _LightColor0;
    		float4 _Color;
    		float _Steps;
    		float _ToonEffect;
    
    		struct v2f {
    			float4 pos:SV_POSITION;
    			float3 lightDir:TEXCOORD0;
    			float3 viewDir:TEXCOORD1;
    			float3 normal:TEXCOORD2;
    		};
    
    		v2f vert (appdata_full v) {
    			v2f o;
    			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);//切换到世界坐标
    			o.normal=v.normal;
    			o.lightDir=ObjSpaceLightDir(v.vertex);
    			o.viewDir=ObjSpaceViewDir(v.vertex);
    
    			return o;
    		}
    		float4 frag(v2f i):COLOR
    		{
    			float4 c=1;
    			float3 N=normalize(i.normal);
    			float3 viewDir=normalize(i.viewDir);
    			float3 lightDir=normalize(i.lightDir);
    			float diff=max(0,dot(N,i.lightDir));//求出正常的漫反射颜色
    			diff=(diff+1)/2;//做亮化处理
    			diff=smoothstep(0,1,diff);//使颜色平滑的在[0,1]范围之内
    			float toon=floor(diff*_Steps)/_Steps;//把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示
    			diff=lerp(diff,toon,_ToonEffect);//根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重
    
    			c=_Color*_LightColor0*(diff);//把最终颜色混合
    			return c;
    		}
    		ENDCG
    		}//
    		pass{//附加点光源的pass渲染
    		Tags{"LightMode"="ForwardAdd"}
    		Blend One One
    		Cull Back
    		ZWrite Off
    		CGPROGRAM
    		#pragma vertex vert
    		#pragma fragment frag
    		#include "UnityCG.cginc"
    
    		float4 _LightColor0;
    		float4 _Color;
    		float _Steps;
    		float _ToonEffect;
    
    		struct v2f {
    			float4 pos:SV_POSITION;
    			float3 lightDir:TEXCOORD0;
    			float3 viewDir:TEXCOORD1;
    			float3 normal:TEXCOORD2;
    		};
    
    		v2f vert (appdata_full v) {
    			v2f o;
    			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    			o.normal=v.normal;
    			o.viewDir=ObjSpaceViewDir(v.vertex);
    			o.lightDir=_WorldSpaceLightPos0-v.vertex;
    
    			return o;
    		}
    		float4 frag(v2f i):COLOR
    		{
    			float4 c=1;
    			float3 N=normalize(i.normal);
    			float3 viewDir=normalize(i.viewDir);
    			float dist=length(i.lightDir);//求出距离光源的距离
    			float3 lightDir=normalize(i.lightDir);
    			float diff=max(0,dot(N,i.lightDir));
    			diff=(diff+1)/2;
    			diff=smoothstep(0,1,diff);
    			float atten=1/(dist);//根据距光源的距离求出衰减
    			float toon=floor(diff*atten*_Steps)/_Steps;
    			diff=lerp(diff,toon,_ToonEffect);
    
    			half3 h = normalize (lightDir + viewDir);//求出半角向量
    			float nh = max (0, dot (N, h));
    			float spec = pow (nh, 32.0);//求出高光强度
    			float toonSpec=floor(spec*atten*2)/ 2;//把高光也离散化
    			spec=lerp(spec,toonSpec,_ToonEffect);//调节卡通与现实高光的比重
    
    			
    			c=_Color*_LightColor0*(diff+spec);//求出最终颜色
    			return c;
    		}
    		ENDCG
    		}//
    	} 
    }
    



    这样已经能做出很好的卡通效果了,各位可以在此基础上研制加强版

    加强版1:

    加上了边缘光Rim

    漂亮的水蓝色星球(= =;纹理)

    就是在第二个pass中加上rim值

    添加的外部变量;
            
    _RimPower边缘光亮度程度
            
    _ToonRimStep边缘光色阶数


    在frag函数中
    float rim = 1.0 - saturate(dot(N, normalize (viewDir)));
    求出正常的边缘光程度

    rim = rim+1;
    使之加亮
                
    rim = pow(rim, _RimPower);
    外部变量_RimPower控制边缘光亮度大小
                
    float toonRim = floor(rim * _ToonRimStep) / _ToonRimStep;
    再对边缘光进行离散化
                
    rim = lerp(rim, toonRim, _ToonEffect);
    调节卡通与现实的比重
                
    c=_Color*_LightColor0*(diff) * rim * mc*2;

    进行最终颜色混合

    		float4 frag(v2f i):COLOR
    		{
    			half4 mc = tex2D (_MainTex, i.uv_MainTex);
    			float4 c=1;
    			float3 N=normalize(i.normal);
    			float3 viewDir=normalize(i.viewDir);
    			float3 lightDir=normalize(i.lightDir);
    			float diff=max(0,dot(N,i.lightDir));
    			diff=(diff+1)/2;
    			diff=smoothstep(0,1,diff);
    			float toon=floor(diff*_Steps)/_Steps;
    			diff=lerp(diff,toon,_ToonEffect);
    			float rim = 1.0 - saturate(dot(N, normalize (viewDir)));//求出正常的边缘光程度
    			rim = rim+1;//使之加亮
    			rim = pow(rim, _RimPower);//外部变量_RimPower控制边缘光亮度大小
    			float toonRim = floor(rim * _ToonRimStep) / _ToonRimStep;//再对边缘光进行离散化
    			rim = lerp(rim, toonRim, _ToonEffect);//调节卡通与现实的比重
    			c=_Color*_LightColor0*(diff) * rim * mc*2;//进行最终颜色混合
    			return c;
    		}



    活在三次元世界的3d布料和活在二次元世界的3d布料

    加强版2:

    带纹理贴图版

    加强版3:

    带纹理贴图Rim版

    带纹理贴图Rim版变种

    不建议把纹理贴图也离散化,效果实在是不好,= =;

    接下来就有待各位看官们继续开发了,有什么更好的效果一定要告诉我

                                                                                                                     -----------------------by wolf96

  • 相关阅读:
    C++指针笔记
    破解入门【OllyDebug爆破程序】
    c++类的定义《一》
    数组
    while循环语句的使用
    MS10-046漏洞测试
    For循环语句的使用
    C++Builder编写计算器
    C++自定义函数
    SQLyog简介
  • 原文地址:https://www.cnblogs.com/zhanlang96/p/4241727.html
Copyright © 2020-2023  润新知