(1)卡通风格渲染
基于色调的着色技术:使用漫反射系数对一张以为纹理进行采样,以控制漫反射的色调。
渲染轮廓线:
基于观察角度和表面法线的轮廓线渲染:使用视角方向和表面法线的点乘结果来得到轮廓线的信息。在一个Pass中但效果不好。
过程式几何轮廓线渲染:第一个Pass渲染背面的面片,并使用某些技术让它的轮廓可见;第二个Pass再正常渲染正面的面片。该方法适用于大不多表面光滑的模型,不适用于立方体这样的平整的模型。
基于图像处理的轮廓线渲染:之前介绍的边缘检测就是这样的方法,但是深度和法线变化很小的轮廓无法检测出来。
基于轮廓边检测的轮廓线渲染:检测一条边是否是轮廓线公式: (n0*v>0)!= (n1*v>0) n0 n1为相邻两个三角面片的法向,v是从视角到该边上人意顶点的方向。公式的本质在于检查两个相邻三角面片是否一个朝正面,一个朝背面。但是动画连贯性时会出现帧与帧之间的跳跃性。
最后就是混合上述几种渲染方法,首先找到精确的轮廓边,把模型和轮廓边渲染到纹理中,在使用图像处理的方法识别出轮廓线,并在图像空间下进行风格化渲染。
本例子中使用几何轮廓线渲染方法进行描边。第一个Pass用轮廓线颜色渲染整个背面,并在视角空间下把模型顶点沿着法线方向向外扩张一段距离,以此让背部轮廓线可见。
viewPos = viewNormal * _Outline
但是如果直接使用顶点法线进行扩展,对于一些内凹的模型,可能会发生背面面片遮挡正面面片的情况。我们可以在扩张背面顶点之前,先对顶点法线的z分量进行处理,是他们等于一个定值,然后把法线归一化后再对顶点进行扩张。使扩展后的背面更加扁平化,从而降低了遮挡正面面片的可能性。
viewNormal.z = -0.5
viewNormal = normalize(viewNormal)
viewPos = viewPos + viewNormal * _Outline
blinn-phong模型:float spec = pow(max(0,dot(normal,halfDir)),_Gloss) 对于卡通渲染中的高光反射光照模型,与其类似,但是需要进行一个阈值比较,如果小于该阈值高光反射系数为0,否则返回1.
float spec = dot(worldNormal,worldHalfDir)
spec = step(threshold,spec)
step是CG中的函数,第一个参数是参考值,第二个参数是待比较的数值,第二个参数大于第一个参数,则返回1,否则返回0.
这种方法容易产生锯齿,可以用smoothstep函数。
spec = lerp(0,1,smoothstep(-w,w,spec-threshold))
w是一个很小的值,当spec-threshold小于-w时,返回0,大于w时返回1,否则在0,1间插值。这就实现的抗锯齿。本例子中w为邻域像素之间的近似导数值,可以通过CG的fwidth函数来得到。
Shader "Unity Shaders Book/Chapter 14/Toon Shading" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Ramp ("Ramp Texture", 2D) = "white" {}//控制漫反射色调的渐变纹理
_Outline ("Outline", Range(0, 1)) = 0.1//控制轮廓线的宽度
_OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)//轮廓线颜色
_Specular ("Specular", Color) = (1, 1, 1, 1)//高光反射颜色
_SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01//高光反射时的阈值
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
NAME "OUTLINE"//描边是常用的渲染
Cull Front//只渲染背面
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _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;
}
float4 frag(v2f i) : SV_Target {
return float4(_OutlineColor.rgb, 1);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardBase" }
Cull Back//只渲染正面
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityShaderVariables.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Ramp;
fixed4 _Specular;
fixed _SpecularScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
};
struct v2f {
float4 pos : 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 = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
float4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 worldHalfDir = 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