当前位置:   article > 正文

UnityShader实例05:Toon(卡通)材质_unity mktoon

unity mktoon

卡通材质

卡通着色也叫Non-photorealisticrendering非真实渲染,通常一些3D游戏用来做一些卡通风格的游戏,一般来说特点主要有两点,一是描边,二是风格化着色,表现为明暗渐变过渡为非线性梯度着色。如下图所示:火影忍者和无主之地


先做描边

关于描边网上有不少资料,这里就其中比较常用的Rim lighting和法线外拓来实现;

1.Rim lighting

原理在之前XRAY材质有提到过,通过视线和物体法线夹角的点积来获得轮廓。只不过这里我需要做一个线条比较硬一点的描边,因此需要对获取到的轮廓做个取整:

i.color=floor(i.color*2);
另外外轮廓的颜色需要做lerp处理而不是乘法叠加,否则黑色叠上去是看不到的;
col=lerp(col,i.color*_OutlineColor,i.color);



VF版本代码01:

  1. Shader "PengLu/Toon/OutlineOnePassVF" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4. _OutlineColor("OutlineColor",Color) = (0,1,1,1)
  5. _RimPower ("Rim Power", Range(0.1,8.0)) = 2
  6. }
  7. SubShader {
  8. Tags { "Queue"="Geometry" "RenderType"="Opaque" }
  9. LOD 200
  10. Pass
  11. {
  12. CGPROGRAM
  13. #pragma vertex vert
  14. #pragma fragment frag
  15. #include "UnityCG.cginc"
  16. float4 _OutlineColor;
  17. float _RimPower;
  18. sampler2D _MainTex;
  19. float4 _MainTex_ST;
  20. struct appdata_t {
  21. float4 vertex : POSITION;
  22. float2 texcoord : TEXCOORD0;
  23. float4 color:COLOR;
  24. float4 normal:NORMAL;
  25. };
  26. struct v2f {
  27. float4 pos : SV_POSITION;
  28. float2 texcoord : TEXCOORD0;
  29. float4 color:COLOR;
  30. } ;
  31. v2f vert (appdata_t v)
  32. {
  33. v2f o;
  34. o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  35. o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
  36. float3 viewDir = normalize(ObjSpaceViewDir(v.vertex));
  37. float rim = 1 - saturate(dot(viewDir,v.normal ));
  38. o.color = pow(rim,_RimPower);
  39. return o;
  40. }
  41. float4 frag (v2f i) : COLOR
  42. {
  43. fixed4 col = tex2D(_MainTex, i.texcoord);
  44. i.color=floor(i.color*2);
  45. col=lerp(col,i.color*_OutlineColor,i.color);
  46. return col;
  47. }
  48. ENDCG
  49. }
  50. }
  51. FallBack "Diffuse"
  52. }
VF版本代码01效果:



总结

Rim Lighting实现简单,只需一个Pass对渲染效率增加负担极小。而且边缘可根据需要做出渐变。但是这种方法 只适用于法线较均匀过度的模型。而不适用于棱角分明的低面物体,如立方体模型。


2.顶点外拓

主要方法是在shader里面用两个pass渲染物体两次,第一个pass顶点外拓,用来打底作为描边,第二个pass正常渲染物体。下面这个例子做了一些改良:分别从顶点和法线方向挤出,然后做判断进行插值。

第一个pass作如下处理:

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


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

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


float3 dir2=v.normal;
dir2为法线方向

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

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

dir=dir*_Factor+dir2*(1-_Factor);
把该点位置朝向与法线方向按外部变量_Factor的比重混合,来控制挤出多远

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

  1. v2f vert (appdata_full v) {
  2. v2f o;
  3. float3 dir=normalize(v.vertex.xyz);
  4. float3 dir2=v.normal;
  5. float D=dot(dir,dir2);
  6. dir=dir*sign(D);
  7. // dir=dir*_Factor+dir2*(1-_Factor);
  8. dir=lerp(dir,dir2,_Factor);
  9. v.vertex.xyz+=dir*_Outline;
  10. o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
  11. return o;
  12. }

接下来在frag里面给描边上色

  1. float4 frag(v2f i):COLOR
  2. {
  3. float4 c = _OutlineColor;
  4. return c;
  5. }

第二个pass正常渲染不做修改

VF版本代码02:

  1. //one directional light
  2. Shader "PengLu/Toon/ToonOneLightVF" {
  3. Properties {
  4. _Color("Main Color",color)=(1,1,1,1)
  5. _OutlineColor("Outline Color",color)=(0.1,0.1,0.2,1)
  6. _MainTex ("Base (RGB)", 2D) = "white" {}
  7. _Outline("Thick of Outline",range(0,0.1))=0.01
  8. _Factor("Factor",range(0,1))=0.5
  9. _ToonEffect("Toon Effect",range(0,1))=0.5
  10. _Steps("Steps of toon",range(0,9))=3
  11. }
  12. SubShader {
  13. pass{
  14. Tags{"LightMode"="Always"}
  15. Cull Front
  16. ZWrite On
  17. CGPROGRAM
  18. #pragma vertex vert
  19. #pragma fragment frag
  20. #include "UnityCG.cginc"
  21. float _Outline;
  22. float _Factor;
  23. float4 _Color,_OutlineColor;
  24. struct v2f {
  25. float4 pos:SV_POSITION;
  26. };
  27. v2f vert (appdata_full v) {
  28. v2f o;
  29. float3 dir=normalize(v.vertex.xyz);
  30. float3 dir2=v.normal;
  31. float D=dot(dir,dir2);
  32. dir=dir*sign(D);
  33. // dir=dir*_Factor+dir2*(1-_Factor);
  34. dir=lerp(dir,dir2,_Factor);
  35. v.vertex.xyz+=dir*_Outline;
  36. o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
  37. return o;
  38. }
  39. float4 frag(v2f i):COLOR
  40. {
  41. float4 c = _OutlineColor;
  42. return c;
  43. }
  44. ENDCG
  45. }
  46. pass{
  47. Tags{"LightMode"="ForwardBase"}
  48. Cull Back
  49. CGPROGRAM
  50. #pragma vertex vert
  51. #pragma fragment frag
  52. #include "UnityCG.cginc"
  53. float4 _LightColor0;
  54. float4 _Color;
  55. float _Steps;
  56. float _ToonEffect;
  57. sampler2D _MainTex;
  58. float4 _MainTex_ST;
  59. struct v2f {
  60. float4 pos:SV_POSITION;
  61. float3 lightDir:TEXCOORD0;
  62. float3 viewDir:TEXCOORD1;
  63. float3 normal:TEXCOORD2;
  64. float2 texcoord:TEXCOORD3;
  65. };
  66. v2f vert (appdata_full v) {
  67. v2f o;
  68. o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
  69. o.normal=v.normal;
  70. o.lightDir=ObjSpaceLightDir(v.vertex);
  71. o.viewDir=ObjSpaceViewDir(v.vertex);
  72. o.texcoord=TRANSFORM_TEX(v.texcoord, _MainTex);
  73. return o;
  74. }
  75. float4 frag(v2f i):COLOR
  76. {
  77. float4 c=1;
  78. float3 N=normalize(i.normal);
  79. float diff=max(0,dot(N,i.lightDir));
  80. diff=(diff+1)/2;
  81. diff=smoothstep(0,1,diff);
  82. float toon=floor(diff*_Steps)/_Steps;
  83. diff=lerp(diff,toon,_ToonEffect);
  84. c=_Color*_LightColor0*(diff);
  85. c*=tex2D(_MainTex, i.texcoord);
  86. return c;
  87. }
  88. ENDCG
  89. }
  90. }
  91. }

VF版本代码02效果


总结

顶点外拓实现的效果比较好,线条粗细比较均匀,适用范围比较广,本例子做了些改良之后,硬边位置的描边接缝也减少了;但是由于使用了两个pass, draw call消耗是正常的两倍消耗了。

开始着色

描边之后就是着色了,卡通着色最主要的特点就是亮部到暗部的过度不像是普通着色那样平滑渐变,而是梯度渐变以区别于写实风格。下面也介绍两种卡通着色的方法,一种是通过floor函数取整离散着色;一种是ramp贴图采样着色。下面分别介绍这两种方法的实现,为方便考虑下面的例子只处理一个直射光。

1.floor函数去整离散着色

卡通着色主要是frag函数里面处理

要做梯度着色,需要有渐变,因此需要先实现漫反射颜色

  1. float3 N=normalize(i.normal);
  2. 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,调节卡通与写实的比重


VF版本代码03:

  1. Shader "PengLu/Toon/ToonOnePassVF" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4. _OutlineColor("OutlineColor",Color) = (0,1,1,1)
  5. _RimPower ("Rim Power", Range(0.1,8.0)) = 2
  6. _ToonEffect("Toon Effect",range(0,1))=0.5
  7. _Steps("Steps of toon",range(0,9))=3
  8. }
  9. SubShader {
  10. Tags { "Queue"="Geometry" "RenderType"="Opaque" }
  11. LOD 200
  12. Pass
  13. {
  14. Tags{"LightMode"="ForwardBase"}
  15. Cull Back
  16. CGPROGRAM
  17. #pragma vertex vert
  18. #pragma fragment frag
  19. #include "UnityCG.cginc"
  20. float4 _OutlineColor;
  21. float _RimPower;
  22. float4 _LightColor0;
  23. float _Steps;
  24. float _ToonEffect;
  25. sampler2D _MainTex;
  26. float4 _MainTex_ST;
  27. struct v2f {
  28. float4 pos : SV_POSITION;
  29. float3 lightDir:TEXCOORD0;
  30. float3 viewDir:TEXCOORD1;
  31. float3 normal:TEXCOORD2;
  32. float2 texcoord:TEXCOORD3;
  33. float4 color:COLOR;
  34. } ;
  35. v2f vert (appdata_full v)
  36. {
  37. v2f o;
  38. o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  39. o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
  40. o.normal=v.normal;
  41. o.lightDir=normalize(ObjSpaceLightDir(v.vertex));
  42. o.viewDir=normalize(ObjSpaceViewDir(v.vertex));
  43. float rim = 1 - saturate(dot(o.viewDir,v.normal ));
  44. o.color = pow(rim,_RimPower);
  45. return o;
  46. }
  47. float4 frag (v2f i) : COLOR
  48. {
  49. fixed4 col = tex2D(_MainTex, i.texcoord);
  50. // i.color=smoothstep(0,1,i.color*2);
  51. i.color=floor(i.color*2);
  52. col=lerp(col,i.color*_OutlineColor,i.color);
  53. float3 N=normalize(i.normal);
  54. float diff=max(0,dot(N,i.lightDir));
  55. diff=(diff+1)/2;
  56. diff=smoothstep(0,1,diff);
  57. float toon=floor(diff*_Steps)/_Steps;
  58. diff=lerp(diff,toon,_ToonEffect);
  59. col*=_LightColor0*(diff);
  60. return col;
  61. }
  62. ENDCG
  63. }
  64. }
  65. FallBack "Diffuse"
  66. }
VF版本代码03效果:


2.RampMap 采样着色

RampMap采样着色是通过映射一张一维纹理(如下图)来实现卡通化着色,相对floor函数,RampMap映射方式比较容易控制卡通效果,美术师只需修改贴图就能修改卡通渐变的过渡等效果。


RampMap映射的代码基本和floor函数方式相同,只有离散方式不同,关键代码如下:

float toon=tex2D(_ToonMap,float2(diff,0.5)).r;
将diff作为uv坐标在贴图上u方向查询获得对应的颜色。

VF版本代码04:

  1. //one directional light
  2. Shader "PengLu/Toon/ToonRampMapVF" {
  3. Properties {
  4. _Color("Main Color",color)=(1,1,1,1)
  5. _OutlineColor("Outline Color",color)=(0.1,0.1,0.2,1)
  6. _MainTex ("BaseTex", 2D) = "white" {}
  7. _ToonMap("Ramp Map",2D)="white"{}
  8. _Outline("Thick of Outline",range(0,0.1))=0.02
  9. _Factor("Factor",range(0,1))=0.5
  10. _ToonEffect("Toon Effect",range(0,1))=0.5
  11. }
  12. SubShader {
  13. pass{
  14. Tags{"LightMode"="Always"}
  15. Cull Front
  16. ZWrite On
  17. CGPROGRAM
  18. #pragma vertex vert
  19. #pragma fragment frag
  20. #include "UnityCG.cginc"
  21. float _Outline;
  22. float _Factor;
  23. float4 _OutlineColor;
  24. struct v2f {
  25. float4 pos:SV_POSITION;
  26. };
  27. v2f vert (appdata_full v) {
  28. v2f o;
  29. float3 dir=normalize(v.vertex.xyz);
  30. float3 dir2=v.normal;
  31. float D=dot(dir,dir2);
  32. dir=dir*sign(D);
  33. dir=dir*_Factor+dir2*(1-_Factor);
  34. v.vertex.xyz+=dir*_Outline;
  35. o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
  36. return o;
  37. }
  38. float4 frag(v2f i):COLOR
  39. {
  40. float4 c=_OutlineColor;
  41. return c;
  42. }
  43. ENDCG
  44. }//end of pass
  45. pass{
  46. Tags{"LightMode"="ForwardBase"}
  47. Cull Back
  48. CGPROGRAM
  49. #pragma vertex vert
  50. #pragma fragment frag
  51. #include "UnityCG.cginc"
  52. sampler2D _ToonMap;
  53. sampler2D _MainTex;
  54. float4 _MainTex_ST;
  55. float4 _LightColor0;
  56. float4 _Color;
  57. float _ToonEffect;
  58. struct v2f {
  59. float4 pos:SV_POSITION;
  60. float3 lightDir:TEXCOORD0;
  61. float3 normal:TEXCOORD1;
  62. float2 texcoord:TEXCOORD2;
  63. };
  64. v2f vert (appdata_full v) {
  65. v2f o;
  66. o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
  67. o.normal=v.normal;
  68. o.lightDir=ObjSpaceLightDir(v.vertex);
  69. o.texcoord=TRANSFORM_TEX(v.texcoord, _MainTex);
  70. return o;
  71. }
  72. float4 frag(v2f i):COLOR
  73. {
  74. float4 c=1;
  75. float3 N=normalize(i.normal);
  76. float3 lightDir=normalize(i.lightDir);
  77. float diff=max(0,dot(N,lightDir));
  78. diff=(diff+1)/2;
  79. diff=smoothstep(0,1,diff);
  80. float toon=tex2D(_ToonMap,float2(diff,0.5)).r;
  81. diff=lerp(diff,toon,_ToonEffect);
  82. c=_Color*_LightColor0*(diff);
  83. c*=tex2D(_MainTex,i.texcoord);
  84. return c;
  85. }
  86. ENDCG
  87. }
  88. }
  89. }

VF版本代码04效果:


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/107721
推荐阅读
相关标签
  

闽ICP备14008679号