shader 非真实感渲染(Non-Photorealistic Rendering)
卡通风格渲染
渲染轮廓线
方法
-
基于观察角度和表面法线的轮廓线渲染 ,视角方向和表面法线点乘结果来得到轮廓线。这种方法简单迅速可以在一个 pass 中就得到渲染结果,但时局限性很大,很多模型得不到满意的描边效果
-
过程式几何轮廓线渲染,使用两个 pass,一个渲染正面,一个渲染背面,并让轮廓可见。优点快速有效,适用于大部分表面平滑模型,不适合类似于正方体这样平整模型
-
基于图像处理的轮廓线渲染 ,例如使用 sobel 算子的边缘检测,缺点是无法检测深度和法线变化很小的轮廓线,例如桌上的一张纸
-
基于轮廓边检测的轮廓线渲染,上面的方法无法控制轮廓线的渲染风格。用于精确检测轮廓边并控制渲染风格
$$
\left( \vec{n}_0 \cdot \vec{v} > 0 \right) \neq \left( \vec{n}_1 \cdot \vec{v} > 0 \right)
$$n0 n1 分别表示这条边相邻两个三角面皮的法线,v 是视角到该变任意顶点的方向
顶点扩张
在视角空间下把顶点沿法线向往外扩张一段距离,以此来让背部轮廓线可见。但是如果直接对顶点进行扩张,对于一些内凹模型,就会发生背面面片遮挡正面面片的情况
为了尽量避免这种情况,在扩张背面顶点之前,先对顶点法线的 z 分量进行一定处理,使他们等于一个定值,然后把法线归一化后进行扩张。
viewNormal.z=-0.5;
viewNormal = normalize(viewNormal);
viewPos = viewPos +viewNormal*_Outline;
添加高光
卡通风格渲染中的高光往往是一块分界明显的纯色区域,对于卡通渲染需要的高光反射光照模型,我们会 normalDir 和 halfdir 的点乘结果和一个阈值比较,若小于则高光反射系数 返回 0,否则返回 1。
$$
\mathtt{c}{specular}=(\mathtt{c}\cdot\mathtt{m}{specular})max(0,\vec n\cdot\vec h)^{m{gloss}}
$$
实现
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);
//扩展前指定 z 的值
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" }
//背面已经在上一pass着色
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 diff = dot(worldNormal, worldLightDir);
diff = (diff * 0.5 + 0.5) * atten;
fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;
fixed spec = dot(worldNormal, worldHalfDir);
//邻像素之间近似导数值
fixed w = fwidth(spec) * 2.0;
fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"

浙公网安备 33010602011771号