Inking是为了能够能够从背景中更加清晰地勾画出网格附加在蒙皮网格上的模型特效,一般采用比较细的灰黑色的线条勾勒出网格的轮廓。
1.Rim-Light(边缘光)
背景:边缘光即对应当前视角方向,对物体上位于边缘的地方额外施加一个光的效果。
原理:通过N(法线方向)和V(视线方向)的夹角来判断判断一个点是否在物体的边缘。当V(视线方向)与N(法线方向)垂直时,这个法线对应的面就与视线方向平行,说明当前这个点对于当前视角来说就处在边缘;而视线方向与法线方向一致时,这个法线对应的面就垂直于视线方向,说明当前是直视这个面。所以,我们就可以根据dot(N,V)来获得视线方向与法线方向的余弦值,通过这个值来区分该像素是否处在边缘,进而判断是否需要增加以及增加边缘光的强弱。
公式:最终颜色 = (漫反射系数 x 纹理颜色 x RGB颜色)+自发光颜色
FinalColor=(Diffuse x Texture x RGBColor)+Emissive
Code:
Shader "Nina/Shader/Inking/Rim Light"
{
//属性
Properties
{
//主颜色
_MainColor("Main Color", Color) = (0.5,0.5,0.5,1)
//主纹理
_MainTexture("Main Texture", 2D) = "white" {}
//边缘发光颜色
_RimColor("Rim Color", Color) = (0.5,0.5,0.5,1)
//边缘发光强度
_RimPower("Rim Power", Range(0.0, 36)) = 0.1
//边缘发光强度系数
_RimIntensity("Rim Intensity", Range(0.0, 100)) = 3
}
//子着色器
SubShader
{
//渲染类型:不透明
Tags{"RenderType" = "Opaque"}
//通道
Pass
{
//通道名称
Name "ForwardBase"
//光照模式:ForwardBase
Tags{"LightMode" = "ForwardBase"}
//开启CG着色器编程语言段
CGPROGRAM
//声明:顶点和片段着色函数名称
#pragma vertex vert
#pragma fragment frag
//引入头文件:UnityCG AutoLight
#include "UnityCG.cginc"
#include "AutoLight.cginc"
//指定Shader Model:3.0
#pragma target 3.0
//变量
//系统光照颜色
uniform float4 _LightColor0;
//主颜色
uniform float4 _MainColor;
//主纹理
uniform sampler2D _MainTexture;
//主纹理_ST(sample texture)
uniform float4 _MainTexture_ST;
//边缘光颜色
uniform float4 _RimColor;
//边缘光强度
uniform float _RimPower;
//边缘光强度系数
uniform float _RimIntensity;
//顶点输入结构体
struct VertexInput
{
//顶点位置
float4 vertex : POSITION;
//法线向量坐标
float3 normal : NORMAL;
//一级纹理坐标
float4 texcoord : TEXCOORD0;
};
//顶点输出结构体
struct VertexOutput
{
//像素位置
float4 pos : SV_POSITION;
//一级纹理坐标
float2 uv : TEXCOORD0;
//法线向量坐标
float3 normal : NORMAL;
//世界空间中的坐标位置
float4 posWorld : TEXCOORD1;
//创建光源坐标,用于内置的光照
LIGHTING_COORDS(3,4)
};
//顶点着色函数
VertexOutput vert(VertexInput v)
{
//声明一个顶点输出结构对象
VertexOutput o;
//将输入纹理坐标赋值给输出纹理坐标,通过TRANSFORM_TEX宏转化纹理坐标,
//主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTexture);
//获取顶点在世界空间中的法线向量坐标
o.normal = mul(float4(v.normal,0), unity_WorldToObject).xyz;
//获得顶点在世界空间中的位置坐标
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
//获取像素位置
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//返回此输出结构对象
return o;
}
//片段着色函数
fixed4 frag(VertexOutput i) : COLOR
{
//方向参数(V N L),归一化,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出
//视角方向
float3 ViewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
//法线方向
float3 Normalection = normalize(i.normal);
//光照方向
float3 LightDirection = normalize(_WorldSpaceLightPos0.xyz);
//计算光照的衰减
//衰减值
float Attenuation = LIGHT_ATTENUATION(i);
//衰减后颜色值
float3 AttenColor = Attenuation * _LightColor0.xyz;
//计算漫反射
float NdotL = dot(Normalection, LightDirection);
//环境光,unity自身的diffuse也是带了环境光
fixed3 Ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _MainColor.xyz;
//兰伯特(或者半兰伯特)*材质diffuse颜色*衰减后光颜色值+环境光
float3 Diffuse = max(0.0, NdotL) * AttenColor + Ambient;
//准备自发光参数
//计算边缘强度,视线方向与法线方向的夹角,
half Rim = 1.0 - max(0, dot(i.normal, ViewDirection));
//计算出边缘自发光强度
float3 Emissive = _RimColor.rgb * pow(Rim,_RimPower) *_RimIntensity;
//计在最终颜色中加入自发光颜色
//最终颜色 = (漫反射系数 x 纹理颜色 x rgb颜色)+自发光颜色
float3 finalColor = Diffuse * (tex2D(_MainTexture, i.uv).rgb * _MainColor.rgb) + Emissive;
//返回最终颜色
return fixed4(finalColor,1);
}
ENDCG
}
}
//后备着色器为普通漫反射
FallBack "Diffuse"
}
2.Fresnel(菲尼尔)
背景: 菲涅耳公式用来描述光在不同折射率的介质之间的行为。菲涅尔公式是光学中的重要公式,用它能解释反射光的强度、折射光的强度、相位与入射光的强度的关系
反射公式: fresnel = fresnel基础值 + fresnel缩放量*pow( 1 - dot( N, V ), 5 )
原理: 类似于Rim Lighting, 使用视线方向(V)和点法线方向(N)的点积来判断边缘, 并将边缘高亮化.
优点: 效率高; 不需要单独的Pass就可以实现; 几乎所有的平滑的边缘都会得到高亮效果; 甚至对透明和半透明物体也有效.
缺点: 无法控制Inking线条的粗细, 这是因为Fresnel方法是针对于模型法线和摄像机视线的, 从而导致其仅与每个表面的法线方向有关, 而与表面的深度信息无关.
Code:
3.Mesh Doubling (复制网格)
背景: 类似于卡通Toon特效,重新绘制一个将所有表面都沿着法线方向延展过的模型,。两次渲染,第一次渲染背面,剔除正面,然后将正面剪裁掉。把模型顶点沿法线方向扩伸一定距离(用来表现描边的粗细);第二次渲染,渲染正面,剔除背面。
公式:
其中, L表示偏移向量; W表示轮廓线条粗细; D是物体和摄像机间的距离. V是标准化后的顶点坐标, 表示方向; N是顶点向量; f是插值参数.
优点: 效率高; 平台适应性好; 可以控制描边的线条粗细.。
缺点: 线条并不连续, 在平滑表面的表现虽然很好, 但是在锐利的表面上经常会出现断层; 只能绘制最外层轮廓, 而不对内部结构做任何处理。
Code:
Shader "Nina/Shader/Inking/Mesh Doubling"
{
//属性
Properties
{
//主纹理
_MainTexture("Base (RGB)", 2D) = "white" { }
//边缘发光颜色
_RimColor("Rim Color", Color) = (0, 0, 0, 1)
//边缘发光宽度
_RimWidth("Rim width", Range(0.0, 1.0)) = .005
}
//子着色器
SubShader
{
//1通道
Pass
{
//剔除正面
Cull front
//开启CG着色器编程语言段
CGPROGRAM
//声明:顶点和片段着色函数名称
#pragma vertex vert
#pragma fragment frag
//引入头文件:UnityCG
#include "UnityCG.cginc"
//指定Shader Model:3.0
#pragma target 3.0
//变量
//边缘发光颜色
uniform float4 _RimColor;
//边缘发光宽度
uniform float _RimWidth;
//顶点输入结构体
struct VertexInput
{
//顶点位置
float4 vertex : POSITION;
//法线向量坐标
float3 normal : NORMAL;
};
//顶点输出结构体
struct VertexOutput
{
//像素位置
float4 pos : POSITION;
};
//顶点着色函数
VertexOutput vert(VertexInput v)
{
//声明一个顶点输出结构对象
VertexOutput o;
//设置顶点偏移
v.vertex.xyz += v.normal * _RimWidth;
//获取像素位置
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//返回此输出结构对象
return o;
}
//片段着色函数
half4 frag(VertexOutput i) : COLOR
{
//返回边缘颜色
return _RimColor;
}
ENDCG
}
//通道2
Pass
{
SetTexture[_MainTexture]
{
Combine Primary * Texture
}
}
/*或者通道2
Pass
{
//开启CG着色器编程语言段
CGPROGRAM
//引入头文件:UnityCG
#include "UnityCG.cginc"
//声明:顶点和片段着色函数名称
#pragma vertex vert_img
#pragma fragment frag
//变量
uniform sampler2D _MainTex;
//片段着色函数
float4 frag(v2f_img i) : COLOR
{
//获取颜色
float4 col = tex2D(_MainTex, i.uv);
//返回最终颜色
return col;
}
ENDCG
}
*/
}
}