当前位置:   article > 正文

【Shader入门精要】第十四章——卡通风格的渲染_paintbrush shader

paintbrush shader

一、卡通风格的渲染(渐变纹理漫反射+分块的高光反射处理) 

  1. Shader "MilkShader/14/ToonShading"
  2. {
  3. Properties
  4. {
  5. _Color("Color Tint", Color) = (1,1,1,1)
  6. _MainTex ("Main Tex", 2D) = "white"{}
  7. _Ramp("Ramp Texture", 2D) = "white"{} //漫反射渐变纹理
  8. _Outline("Outline", Range(0,1)) = 0.1 //轮廓线宽度
  9. _OutlineColor("Outline Color", Color) = (1,1,1,1)
  10. _Specular ("Specular", Color) = (1,1,1,1) //高光反射颜色
  11. _SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01 //阈值
  12. }
  13. SubShader
  14. {
  15. Tags { "RenderType"="Opaque" "Queue"="Geometry"}
  16. LOD 100
  17. Pass
  18. {
  19. //第一个PASS是用法线进行向外扩展,然后只渲染背面,颜色输出是边缘颜色
  20. NAME "OUTLINE"
  21. Cull Front
  22. CGPROGRAM
  23. #pragma vertex vert
  24. #pragma fragment frag
  25. #include "UnityCG.cginc"
  26. float _Outline;
  27. fixed4 _OutlineColor;
  28. struct a2v {
  29. float4 vertex : POSITION;
  30. float3 normal : NORMAL;
  31. };
  32. struct v2f {
  33. float4 pos : SV_POSITION;
  34. };
  35. v2f vert(a2v v){
  36. v2f o;
  37. //将顶点和法线转到视角空间下
  38. float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
  39. float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
  40. //设置法线的z分量,对其归一化后在将顶点沿其方向扩张
  41. //设置法线z的处理是为了尽可能避免背面扩张(现在我们渲染的是背面)后的顶点挡住正面的面片.
  42. normal.z = -0.5;
  43. //在视角空间下进行的一个顶点位置偏移(向法线方向)
  44. pos = pos + float4(normalize(normal), 0) * _Outline;
  45. //再转为裁剪空间
  46. o.pos = mul(UNITY_MATRIX_P, pos);
  47. return o;
  48. }
  49. float4 frag(v2f i) : SV_Target{
  50. //直接输出边缘颜色
  51. return float4(_OutlineColor.rgb, 1);
  52. }
  53. ENDCG
  54. }
  55. Pass{
  56. //前向渲染
  57. Tags{"LightMode"="ForwardBase"}
  58. Cull Back
  59. CGPROGRAM
  60. #include "UnityCG.cginc"
  61. #include "Lighting.cginc"//漫反射、高光反射需要的
  62. #include "AutoLight.cginc"//这是阴影三剑客需要的头文件
  63. #include "UnityShaderVariables.cginc"
  64. #pragma vertex vert
  65. #pragma fragment frag
  66. #pragma multi_compile_fwdbase
  67. sampler2D _MainTex;
  68. float4 _MainTex_ST;
  69. fixed4 _Color;
  70. sampler2D _Ramp;
  71. fixed4 _Specular;
  72. fixed _SpecularScale;
  73. struct a2v {
  74. float4 vertex : POSITION;
  75. float3 normal : NORMAL;
  76. float4 texcoord : TEXCOORD0;
  77. //float4 tangent : TANGENT; 为啥例子会有tangent
  78. };
  79. struct v2f {
  80. float4 pos : POSITION;
  81. float2 uv : TEXCOORD0;
  82. float3 worldNormal : TEXCOORD1;
  83. float3 worldPos : TEXCOORD2;
  84. SHADOW_COORDS(3)
  85. };
  86. v2f vert (a2v v) {
  87. v2f o;
  88. o.pos = UnityObjectToClipPos( v.vertex);
  89. o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
  90. o.worldNormal = UnityObjectToWorldNormal(v.normal);
  91. o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
  92. TRANSFER_SHADOW(o);
  93. return o;
  94. }
  95. float4 frag(v2f i) :SV_Target{
  96. fixed3 worldNormal = normalize(i.worldNormal);
  97. fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
  98. fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
  99. fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);
  100. fixed4 c = tex2D (_MainTex, i.uv);
  101. fixed3 albedo = c.rgb * _Color.rgb;
  102. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
  103. UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
  104. fixed diff = dot(worldNormal, worldLightDir);
  105. diff = (diff * 0.5 + 0.5) * atten; //先进行转为(0,1)范围,再乘以衰减值*阴影
  106. //这里的漫反射是用渐变纹理方式,diff作为纹理坐标进行采样,通过这种方式采样出的漫反射颜色是卡通化的
  107. fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;
  108. fixed spec = dot(worldNormal, worldHalfDir);
  109. fixed w = fwidth(spec) * 2.0;//抗锯齿处理? 这种获取w阈值绝对值的方式很懵逼
  110. //下面lerp(...)是比较复杂的,先假设_SpecularScale为0.5, spec是(0,1)的一个动态数值根据halfDir和normal而变化
  111. // spec + _SpecularScale - 1 是将 spec从(0,1)转为(-(1-_SpecularScale), _SpecularScale),现在是(-0.5, 0.5)
  112. // w实际上可以直接理解为是一个动态的阈值范围, 实际上可以直接看成固定的比如:[-0.3, 0.3]
  113. // 此时当spec + _SpecularScale - 1 小于 -0.3(-w)时就会返回0, 在-w,w之间时就会返回0~1, 大于w时会返回1
  114. // 这样这个高光反射系数就不会产生突变而是渐变效果
  115. // 书上直接说_SpecularScale是一个阈值,而我的理解是如上,不然太难理解了..
  116. // 这样我们得到的高光反射颜色是一个个块状的,而不是之前那样子的 之前是pow(dot(normal, half), x)
  117. // 这种lerp(...) 处理 如果觉得难以理解的话 简单的做法是 直接 step(阈值, dot(...)) 当点积大于阈值时会返回1,小于阈值时会返回0
  118. // 但是这种做法就是导致了突变,高光部分会产生锯齿,可以想象假设 阈值时 0.5, 那么当 (0.5, 1)时直接返回1,(0,0.5)时候返回0
  119. // 在0.49 时候还是0, 逐渐变为0.51时突变为1, 反过来也是一样,高光颜色会直接从0,突变为有色状态,锯齿就是由于突变导致的。
  120. // 上面那个阈值0.5,可看成一个分界线, 当dot()在0.5(左边)时就会返回0,在0.5的右边时返回1
  121. // 此时下面的做法就是 将这个分界线,变成了2个! 即 -w 和 w, 虽然这2个值得获取很难理解,但是就可以直接理解为就是一个很小的常数
  122. // 当dot() < -w 时,返回的是0, > w时候 返回的是1, 在(-w,w)中间时返回的是0~1的数值,所以效果就是渐变,解决了抗锯齿问题
  123. fixed3 specular = _Specular.rgb * lerp(0,1, smoothstep(-w,w,spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
  124. return fixed4(ambient + diffuse + specular, 1.0);
  125. }
  126. ENDCG
  127. }
  128. }
  129. Fallback "Diffuse"
  130. }

二、素描风格的渲染

  1. Shader "MilkShader/14/Hatching"
  2. {
  3. Properties
  4. {
  5. _Color ("Color Tint", Color) = (1,1,1,1) //控制颜色
  6. _TileFactor ("Tile Factor", Float) = 1 //平铺系数(对纹理坐标的缩放系数<x,y都有影响>)
  7. //描边使用的参数
  8. _Outline("Outline", Range(0,1)) = 0.1
  9. _OutlineColor("Outline Color", Color) = (1,1,1,1)
  10. //六张素描纹理(逐渐增多横向线条的纹理)
  11. _Hatch0 ("Hatch 0", 2D) = "white"{}
  12. _Hatch1 ("Hatch 1", 2D) = "white"{}
  13. _Hatch2 ("Hatch 2", 2D) = "white"{}
  14. _Hatch3 ("Hatch 3", 2D) = "white"{}
  15. _Hatch4 ("Hatch 4", 2D) = "white"{}
  16. _Hatch5 ("Hatch 5", 2D) = "white"{}
  17. }
  18. SubShader
  19. {
  20. Tags { "RenderType"="Opaque" "Queue"="Geometry"}
  21. UsePass "MilkShader/14/ToonShading/OUTLINE"//使用了之前的描边PASS
  22. Pass
  23. {
  24. Tags{ "LightMode" = "ForwardBase"}
  25. CGPROGRAM
  26. #pragma vertex vert
  27. #pragma fragment frag
  28. // make fog work
  29. #pragma multi_compile_fwdbase
  30. #include "UnityCG.cginc"
  31. #include "Lighting.cginc"
  32. #include "AutoLight.cginc"
  33. #include "UnityShaderVariables.cginc"
  34. fixed4 _Color;
  35. fixed _TileFactor;
  36. fixed _Outline;
  37. fixed4 _OutlineColor;
  38. sampler2D _Hatch0;
  39. sampler2D _Hatch1;
  40. sampler2D _Hatch2;
  41. sampler2D _Hatch3;
  42. sampler2D _Hatch4;
  43. sampler2D _Hatch5;
  44. struct appdata
  45. {
  46. float4 vertex : POSITION;
  47. float4 tangent : TANGENT;
  48. float2 texcoord : TEXCOORD0;
  49. float3 normal : NORMAL;
  50. };
  51. struct v2f
  52. {
  53. float4 pos : SV_POSITION;
  54. float2 uv : TEXCOORD0;
  55. fixed3 hatchWeight0 : TEXCOORD1;
  56. fixed3 hatchWeight1 : TEXCOORD2;
  57. float3 worldPos : TEXCOORD3;
  58. SHADOW_COORDS(4)
  59. };
  60. v2f vert (appdata v)
  61. {
  62. v2f o;
  63. o.pos = UnityObjectToClipPos(v.vertex);
  64. o.uv = v.texcoord.xy * _TileFactor;
  65. fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
  66. fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
  67. //法线和光源的点积
  68. fixed diff = max(0, dot(worldNormal, worldLightDir));
  69. //将diff转到[0,7]范围
  70. float hatchFactor = diff * 7.0;
  71. o.hatchWeight0 = fixed3(0,0,0);
  72. o.hatchWeight1 = fixed3(0,0,0);
  73. //分别判断每一个区间[0,1] [1,2] [2,3] [3,4] [4,5] [5,6] [6,7]再计算对应区间的素描纹理权重值
  74. //这个是固定的算法,记住是这样写就好了,如果想研究的话可以参考冯乐乐的第十四章
  75. //实际上就是不同的区间时会对其中1个或2个素描纹理权重进行赋值,以此在片元着色器进行使用它们来进行与相应素描颜色进行相乘
  76. //也就是决定在什么情况下,哪几个素描纹理产生多少影响
  77. // hatchWeight0.x 是 第一个素描纹理的权重, 以此类推 hatchWeight1.z 是最后一个素描纹理的权重
  78. // 初始化全为0是表示素描纹理完全没有任何作用
  79. if(hatchFactor > 6.0){
  80. }else if(hatchFactor > 5.0){
  81. o.hatchWeight0.x = hatchFactor - 5.0;
  82. }else if(hatchFactor > 4.0){
  83. o.hatchWeight0.x = hatchFactor - 4.0;
  84. o.hatchWeight0.y = 1 - o.hatchWeight0.x;
  85. }else if(hatchFactor > 3.0){
  86. o.hatchWeight0.y = hatchFactor - 3.0;
  87. o.hatchWeight0.z = 1 - o.hatchWeight0.y;
  88. }else if(hatchFactor > 2.0){
  89. o.hatchWeight0.z = hatchFactor - 2.0;
  90. o.hatchWeight1.x = 1 - o.hatchWeight0.z;
  91. }else if(hatchFactor > 1.0){
  92. o.hatchWeight1.x = hatchFactor - 1.0;
  93. o.hatchWeight1.y = 1 - o.hatchWeight1.x;
  94. }else{
  95. o.hatchWeight1.y = hatchFactor;
  96. o.hatchWeight1.z = 1 - o.hatchWeight1.y;
  97. }
  98. o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
  99. TRANSFER_SHADOW(o);
  100. return o;
  101. }
  102. fixed4 frag (v2f i) : SV_Target
  103. {
  104. //从不同的素描纹理采样后乘以相应的素描纹理权重值(在顶点着色器我们就已经对它们进行了一个赋值)
  105. fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeight0.x;
  106. fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeight0.y;
  107. fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeight0.z;
  108. fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeight1.x;
  109. fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeight1.y;
  110. fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeight1.z;
  111. //非素描部分的颜色是白色(如果素描的纹理权重总和越大,非素描部分越黑,否则越白)
  112. fixed4 whiteColor = fixed4(1,1,1,1) * (1 - i.hatchWeight0.x - i.hatchWeight0.y - i.hatchWeight0.z - i.hatchWeight1.x
  113. - i.hatchWeight1.y - i.hatchWeight1.z);
  114. fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;
  115. UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
  116. return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
  117. }
  118. ENDCG
  119. }
  120. }
  121. Fallback "Diffuse"
  122. }

平铺系数越大素描的线越密集,否则反之。

中秋节快乐!

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

闽ICP备14008679号