- 卡通风格
特点:物体都被黑色的线条描边,以及分明的明暗变化等。
–卡通渲染方法
技术 | 方法 |
基于色调的着色技术 | 使用漫反射系数对一张一维纹理进行采样,以控制漫反射的色调 |
光照模型 – 高光效果 | 一块块分界明显的纯色区域 | |
物体边缘部分绘制轮廓 | 基于模型的描边方法 | 适用屏幕后处理技术对屏幕图像进行描边 |
- 绘制模型轮廓线《Real Time Rendering, third edition》
类型 | 方法 | 优点 | 局限 |
基于观察角度和表面法线的轮廓线渲染 | 轮廓线的信息 = 视角方向 · 表面法线 | 简单快速,可以在一个Pass中就得到渲染结果 | 描边效果不够好 |
过程式几何轮廓线渲染 | 使用2个pass。 1.渲染背面的面片,并使用某些技术让它的轮廓可见 2.正常渲染正面的面片。 | 快速有效,并且适用于绝大多数表面平滑的模型 | 不适应类似于立方体这样平整的模糊 |
基于图像处理的 | 利用边缘检测算子对图像进行卷积操作 | 适用任何类型的模型 | 一些深度和法线变化很小的轮廓无法被检测出来 |
基于轮廓边检测的轮廓线渲染 | 检测相邻的三角面片是否一个朝正面、一个朝背面 (n0 · v>0) 不等于(n1 · v>0) | 可以检测出精准的轮廓线 | 复杂、帧与帧之间会出现跳跃性 |
混合上述的几种渲染 | 1.找到精确的轮廓边 2.把模型和轮廓边渲染到纹理中 3.使用图像处理的方法识别出轮廓线 4在图像空间下进行风格化渲染 |
- 高光
高光反射模型 | 缺陷 | 优化 | ||
卡通 | float spec = dot(normal, halfDir); spec = step(threshold,spec); | 点乘后与一个阈值做比较 | 会在高光区域的边界造成锯齿1 高光区域的边缘不是平滑渐变的,而是从0突变到1 | 可以在边界处很小的区域内,进行平滑处理 spec = lerp(0,1,smoothstep(-w,w,spec - threshold)) |
正常 | float spec = pow(max(0, dot(normal, halfdir)), _Gloss) |
/******************************************************
* 过程式轮廓线渲染方法对模型进行轮廓描边
Pass1:1.使用轮廓线颜色渲染整个背面的面片,
2.对顶点法线的z分量进行处理,使它们等于一个定值
3.把法线归一化后视角空间下把模型顶点沿着法线向外扩张,让背部轮廓线可见
Pass2:光照模型,渲染模型的正面
* ToonShader.Shader
*****************************************************/
Shader"ToonShader"{
Properties{
_Color("Color Tint", Color)=(1,1,1,1)
_MainTex("Main Tex", 2D) = "white"{}
_Ramp("Ramp Texture", 2D) = "white"{} //控制漫反射色调的渐变纹理
_OutLine("Out Line", Range(0,1)) = 1.0 //控制轮廓线宽度
_OutLineColor("Out Line Color", Color)=(1,1,1,1)//轮廓线颜色
_Specular("Specular", Color) = (1,1,1,1) //高光反射颜色
_SpecularScale("Specular Scale", Range(0,1))=0.01 //高光反射阈值
}
SubShader{
Pass{
NAME "OUTLINE"
Cull Front //只渲染背面
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struc av2{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
half _OutLine;
float4 _OutLineColor;
struct v2f{
float4 pos:SV_POSITION;
};
v2f vert(av2 v){
v2f o;
float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
float3 normal = UnityObjectToViewPos(v.normal);
normal.z = -0.5;
pos += float4(normalize(normal), 0) * _OutLine;
o.pos = mul(UNITY_MATRIX_P, pos);
return o;
}
//描边
fixed4 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"
struct v2f{
float4 pos : SV_POSITION;
float2 uv: TEXCOORD0;
float3 worldPos:TEXCOORD1;
float3 worldNormal:TEXCOORD2;
SHADOW_COORDS(3) //阴影
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
sampler2D _Ramp;
float4 _Specular;
half _SpecularScale;
v2f vert(appdata_img 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;
}
fixed4 frag(v2f i):SV_Target{
fixed3 wNormal = normalize(i.worldNormal);
fixed3 wLightDir = normalize(UnityWordlSpaceLightDir(i.worldPos));
fixed3 wViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(wLightDir+wViewDir);
fixed4 c = tex2D(_MainTex, i.uv);
fixed3 albedo = _Color.rgb * c.rgb;
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//光照衰减
fixed diff = dot(wNormal, wLightDir);
diff = (diff * 0.5 + 0.5) * atten ;
fixed3 diffC tex2D(_Ramp, float2(diff, diff)).rgb;
//漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * diffC ;
fixed spec = dot(wNormal, halfDir);
//光照边界做抗锯齿处理
fixed w = fwidth(spec) * 2.0;
//高光反射
fixed3 specular = _Specular.rgb * lerp(0,1,smoothstep(-w,w,spec+_SpecularScale - 1)) * step(0.001, _SpecularScale);
fixed3 col = ambient + diffuse + specular
return fixed4(col, 0);
}
ENDCG
}
}
Fallback "Diffuse"
}
/// <summary>
/// 与阈值的比较
/// </summary>
/// <param name="a">参考值</param>
/// <param name="b">待比较的数值</param>
/// <return > a <= b ? 1 : 0 </return>>
int step(a, b)
/// <summary>
/// 生成0到1的平滑过渡
/// </summary>
/// <param name="a">最小值</param>
/// <param name="b">最大值</param>
/// <param name="x">输入值</param>
/// <returns>x<a ==>0; x>b ==> 1; a<x<b ==>[0,1]</returns>
float smoothstep(float a, float b, float x)
{
float t = saturate((x-a)/(b-a));
return t*t*(3.0 - (2.0*t));
}
/// <summary>
/// 邻域像素之间的近似导数值
/// </summary>
/// <param name="a">比较值</param>
/// <returns>x和y方向偏导数的绝对值的和,ddx、ddy函数获得</returns>
float fwidth(float a)
- 素描风格
- 多级渐远纹理
保持笔触之间的间隔,以便更真实地模拟素描效果
/******************************************************
* 素描风格的渲染
* 使用6张素描纹理进行渲染
* 1.vertex:计算逐顶点的光照 ==>决定6张纹理的混合权重
* 2.fragment:根据权重混合6张纹理的采样结果
* HatchingShader.Shader
*****************************************************/
Shader ""{
Properties{
_Color("Color Tint", Color)=(1,1,1,1) //控制模型颜色
_Hatch0("Hatch 0", 2D) = "white"{} //渲染时使用的6张素描纹理
_Hatch1("Hatch 1", 2D) = "white"{}
_Hatch2("Hatch 2", 2D) = "white"{}
_Hatch3("Hatch 3", 2D) ="white"{}
_Hatch4("Hatch 4", 2D) = "white"{}
_Hatch5("Hatch 5", 2D) = "white"{}
_OutLine("Out Line",Range(0,2)) = 0.1
_OutLineColor("OutLine Color", Color)=(0,0,0,0)
_TileFactor("Tile Factor", Float) = 1 //纹理的平铺系数,数值越大,模型上的素描线条越密
}
SubShader{
Tags{"RenderType" = "Opaque" "Queue"="Geometry"}
UsePass "OUTLINE" //上一个shader的名字+pass 描边
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
d#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityCG.cginc"
struct v2f{
float4 pos :SV_POSITION;
float2 uv:TEXCOORD0;
fixed3 hatchWeights0:TEXCOORD1;
fixed3 hatchWeights1:TEXCOORD2;
float3 worldPos:TEXCOORD3;
SHADOW_COORDS(4)
};
float _TileFactor;
sampler2D _Hatch0;
sampler2D _Hatch1;
sampler2D _Hatch2;
sampler2D _Hatch3;
sampler2D _Hatch4;
sampler2D _Hatch5;
fixed _OutLine;
float4 _OutLineColor;
float4 _Color;
v2f vert(appdata_base v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy * _TileFactor;
float3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
//获取漫反射系数【0,1】
fixed diff = max(0, dot(worldNormal, worldLightDir));
o.hatchWeights0 = fixed3(0,0,0);
o.hatchWeights1 = fixed3(0,0,0);
//缩放到[0,7]
float hatchFactor = diff * 7.0;
//纹理混合权重
if (hatchFactor > 6.0){
} else if (hatchFactor > 5.0) {
o.hatchWeights0.x = hatchFactor - 5.0;
} else if (hatchFactor > 4.0) {
o.hatchWeights0.x = hatchFactor - 4.0;
o.hatchWeights0.y = 1 - o.hatchWeights0.x;
} else if (hatchFactor > 3.0) {
o.hatchWeights0.y = hatchFactor - 3.0;
o.hatchWeights0.z = 1 - o.hatchWeights0.y;
} else if (hatchFactor > 2.0) {
o.hatchWeights0.z = hatchFactor - 2.0;
o.hatchWeights1.x = 1 - o.hatchWeights0.z;
} else if (hatchFactor > 1.0) {
o.hatchWeights1.x = hatchFactor - 1.0;
o.hatchWeights1.y = 1 - o.hatchWeights1.x;
} else {
o.hatchWeights1.y = hatchFactor;
o.hatchWeights1.z = 1 - o.hatchWeights1.y;
}
TRASFER_SHADOW(o);
return o;
}
fixed4 fragment(v2f i):SV_Target{
//获取每张纹理的采样颜色
fixed4 hatchTex0 = tex2D(_Hatch0, i.uv)*i.hatchWeights0.x;
fixed4 hatchTex1 = tex2D(_Hatch1, i.uv)*i.hatchWeights0.y;
fixed4 hatchTex2 = tex2D(_Hatch2, i.uv)*i.hatchWeights0.z;
fixed4 hatchTex3 = tex2D(_Hatch3, i.uv)*i.hatchWeights1.x;
fixed4 hatchTex4 = tex2D(_Hatch4, i.uv)*i.hatchWeights1.y;
fixed4 hatchTex5 = tex2D(_Hatch5, i.uv)*i.hatchWeights1.z;
//获取纯白在渲染中的贡献度
fixed4 whiteColor = fixed4(1,1,1,1) * (1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);
fixed4 hatchColor = hatchTex0 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + hatchTex1 + whiteColor;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 col = hatchColor.rgb * _Color.rgb * atten;
return fixed4(col, 1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}