1、卡通风格渲染是什么
卡通风格渲染(Cartoon Shading),也称为非真实感渲染(NPR)或卡通渲染(Toon Shading)
主要目的是使3D模型看起来更像手绘的二维卡通或漫画风格,而不是逼真写实的3D渲染效果。
这种风格的渲染常用于游戏、动画和电影中,用来创造一种独特的艺术风格
2、卡通风格渲染 基本原理
让光的过渡效果变硬并且实现轮廓描边!
关键点:
- 如何让光的过渡效果变硬
回顾 Blinn-Phong 光照模型公式:
影响对象光照效果的部分主要是:漫反射的计算 + 高光反射的计算
因此,想要光的过渡效果变硬,只需要从这两方面去考虑即可
漫反射部分的变硬需要使用到渐变纹理:
高光反射部分的变硬需要基于它的公式修改计算规则
把 pow( max(0, dot(法线单位向量, 半角单位向量)), 幂) 直接进行简化
相当于之前平滑的值变化变得只有1和0两种情况,要不有要不没有
- 如何实现轮廓描边
这里的轮廓描边不采用全部沿发现放大然后渲染的方法,而是只将背面顶点沿法线方向偏移扩大,即:
同样两个Pass渲染对象:一个Pass渲染背面 将模型背面顶点沿法线方向偏移扩大;一个Pass渲染正面 正常渲染
这样实现的效果会让模型上有重叠的结构出现描边效果
(左图是全部顶点偏移放大,右图是背面顶点偏移放大)
注意:
- 模型背面就是法线方向和摄像机面朝向呈锐角的部分
- 模型正面就是法线方向和摄像机面朝向呈钝角的部分
3、实现
Shader "ShaderProj/19/Kartoon"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_MainColor ("MainColor", Color) = (1,1,1,1)
_BumpMap ("BumpMap", 2D) = ""{}
_BumpScale ("BumpScale", Range(0, 1)) = 1
_RampTex ("RampTex", 2D) = ""{}
_SpecularColor ("SpecularColor", Color) = (1,1,1,1)
_SpecularNum ("SpecularNum", Range(8, 256)) = 18
_SpecularThreshold( "SPpecularThreshold", Range(0, 1)) = 0.5
_OutLineColor ("OutLineColor", Color) = (1,1,1,1)
_OutLineWidth ("OutLineWidth", Range(0,0.1)) = 0.04
}
SubShader
{
Pass
{
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertex:SV_POSITION;
};
fixed4 _OutLineColor;
float _OutLineWidth;
v2f vert(appdata_base v)
{
v2f o;
v.vertex.xyz += normalize(v.normal) * _OutLineWidth;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_TARGET
{
return _OutLineColor;
}
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;
float4 uv : TEXCOORD0;
float3 lightDir : TEXCOORD1;
float3 viewDir : TEXCOORD2;
float3 worldPos : TEXCOORD3;
SHADOW_COORDS(4)
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _SpecularColor;
float _SpecularNum;
fixed _SpecularThreshold;
v2f vert (appdata_full v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 binormal = cross(normalize(v.tangent), normalize(v.normal)) * v.tangent.w;
float3x3 rotation = float3x3 (v.tangent.xyz,
binormal,
v.normal);
data.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
data.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
data.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(data)
return data;
}
fixed4 frag (v2f i) : SV_Target
{
float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _MainColor.rgb;
fixed halfLambertNum = dot(normalize(i.viewDir), normalize(i.lightDir)) * 0.5 + 0.5;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
halfLambertNum *= atten;
fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb * tex2D(_RampTex, fixed2(halfLambertNum,halfLambertNum)).rgb;
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir));
fixed3 specularColor = dot(normalize(tangentNormal), normalize(halfA));
specularColor = step(_SpecularThreshold, specularColor);
specularColor *= _SpecularColor.rgb;
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + diffuseColor + specularColor;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
Fallback "Diffuse"
}