素描风格渲染
素描风格渲染(Hatching Style Rendering),是一种非真实感渲染(NPR),主要目的是使3D模型看起来像 手绘素描的视觉效果。这种风格的渲染常用于游戏、动画和电影中,用来创造一种独特的艺术风格
1、基本原理
用漫反射系数决定采样权重,在多张具有不同密度和方向的素描纹理中进行采样,并将采样结果进行叠加得到最终效果
关键点:
- 多张具有不同密度和方向的素描纹理
美术需要提供多张素描纹理,我们之后会根据不同位置的光照强度决定从哪种纹理中进行采样
- 漫反射系数决定采样权重
通过兰伯特光照模型中的
max(0, 标准化后物体表面法线向量· 标准化后光源方向向量)* (素描纹理数 + 1)
将漫反射光照强度 0~1 扩充到 0~N ,如果是6张素描纹理,那么就是 0 ~ 7
根据不同顶点的不同光照,决定在哪一张纹理中进行采样的权重更大,该权重决定最后的颜色叠加
6~7:不在素描纹理中采样;
5~6:第1张素描纹理中采样;
4~5:第1、2素描张纹理中采样;
3~4:第2、3张纹理中采样;
2~3:第3、4张纹理中采样;
1~2:第4、5张纹理中采样;
0~1:第5、6张纹理中采样;
- 采样结果进行叠加
根据之前的权重计算,越亮的地方、越趋近于白色,或使用的素描纹理中线条更少更稀疏
而越暗的地方使用的素描纹理中线条更密集。
因此只需要使用之前的权重值和纹理采样结果相乘,最后将纹理颜色进行叠加即可
2、实现
Version 1:
Shader "ShaderProj/20/Sketch"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_TileFactor ("TileFactor", Float) = 1
_Sketch0 ("Sketch0", 2D) = ""{}
_Sketch1 ("Sketch1", 2D) = ""{}
_Sketch2 ("Sketch2", 2D) = ""{}
_Sketch3 ("Sketch3", 2D) = ""{}
_Sketch4 ("Sketch4", 2D) = ""{}
_Sketch5 ("Sketch5", 2D) = ""{}
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// x,y,z 分别代表第 1,2,3张素描纹理的权重
fixed3 sketchWeight0 : TEXCOORD1;
// x,y,z 分别代表第 4,5,6张素描纹理的权重
fixed3 sketchWeight1 : TEXCOORD2;
};
fixed4 _Color;
float _TileFactor;
sampler2D _Sketch0;
sampler2D _Sketch1;
sampler2D _Sketch2;
sampler2D _Sketch3;
sampler2D _Sketch4;
sampler2D _Sketch5;
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// uv 坐标平铺缩放,值越大,细节越多
o.uv = v.texcoord.xy * _TileFactor;
fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
fixed diffFac = max(0, dot(worldLightDir, worldNormal)) * 7.0;
o.sketchWeight0 = fixed3(0, 0, 0);
o.sketchWeight1 = fixed3(0, 0, 0);
if (diffFac > 6.0){}
// 最亮的部分,不从纹理中采样
else if(diffFac > 5.0) // 从第1张图中采样
{
o.sketchWeight0.x = diffFac - 5.0;
}
else if(diffFac > 4.0) // 从第2张图中采样
{
o.sketchWeight0.y = diffFac - 4.0;
}
else if (diffFac > 3.0) // 从第3张图中采样
{
o.sketchWeight0.z = diffFac - 3.0;
}
else if (diffFac > 2.0) // 从第4张图中采样
{
o.sketchWeight1.x = diffFac - 2.0;
}
else if (diffFac > 1.0) // 从第5张图中采样
{
o.sketchWeight1.y = diffFac - 1.0;
}
else // 从第6张图中采样
{
o.sketchWeight1.z = diffFac;
}
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 sketchColor0 = tex2D(_Sketch0, i.uv) * i.sketchWeight0.x;
fixed4 sketchColor1 = tex2D(_Sketch1, i.uv) * i.sketchWeight0.y;
fixed4 sketchColor2 = tex2D(_Sketch2, i.uv) * i.sketchWeight0.z;
fixed4 sketchColor3 = tex2D(_Sketch3, i.uv) * i.sketchWeight1.x;
fixed4 sketchColor4 = tex2D(_Sketch4, i.uv) * i.sketchWeight1.y;
fixed4 sketchColor5 = tex2D(_Sketch5, i.uv) * i.sketchWeight1.z;
// 最亮的部分(白色)
fixed4 whiteColor = fixed4(1,1,1,1) * (1 - i.sketchWeight0.x - i.sketchWeight0.y - i.sketchWeight0.z - i.sketchWeight1.x - i.sketchWeight1.y - i.sketchWeight1.z);
fixed4 sketchColor = whiteColor + sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 + sketchColor5;
return fixed4(sketchColor.rgb, 1);
}
ENDCG
}
}
}
现在是有问题的,可以看到材质球最暗的部分显示的是白色,同时有黑色的部分会有白色的条纹,这分别是因为:
- 当跑到 【o.sketchWeight1.z = diffFac】 的分支时,值是很小的,趋近于0 ,因此最终计算的 whiteColor 基本为白色
- 当跑到【o.sketchWeight0.x = diffFac - 5.0】的分支时,如果 diffFac 趋近于 5,那么权重也几近于 0,因此在该分支的边缘部分会变成白色
为了解决这个问题,可以每个分支用两张纹理进行采样,采样权重用【1- weight】,这样就可以在计算 whiteColor 的时候减少白色的填充(除了最亮的地方,因为本来就是白色)
Shader "ShaderProj/20/Sketch"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_TileFactor ("TileFactor", Float) = 1
_Sketch0 ("Sketch0", 2D) = ""{}
_Sketch1 ("Sketch1", 2D) = ""{}
_Sketch2 ("Sketch2", 2D) = ""{}
_Sketch3 ("Sketch3", 2D) = ""{}
_Sketch4 ("Sketch4", 2D) = ""{}
_Sketch5 ("Sketch5", 2D) = ""{}
_OutLineColor ("OutLineColor", Color) = (1,1,1,1)
_OutLineWidth ("OutLineWidth", Range(0,0.1)) = 0.04
}
SubShader
{
Tags { "RenderType"="Opaque" }
UsePass "ShaderProj/19/Kartoon/OUTLINE"
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
// x,y,z 分别代表第 1,2,3张素描纹理的权重
fixed3 sketchWeight0 : TEXCOORD1;
// x,y,z 分别代表第 4,5,6张素描纹理的权重
fixed3 sketchWeight1 : TEXCOORD2;
float3 worldPos: TEXCOORD3;
SHADOW_COORDS(4)
};
fixed4 _Color;
float _TileFactor;
sampler2D _Sketch0;
sampler2D _Sketch1;
sampler2D _Sketch2;
sampler2D _Sketch3;
sampler2D _Sketch4;
sampler2D _Sketch5;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// uv 坐标平铺缩放,值越大,细节越多
o.uv = v.texcoord.xy * _TileFactor;
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
fixed diffFac = max(0, dot(worldLightDir, worldNormal)) * 7.0;
o.sketchWeight0 = fixed3(0, 0, 0);
o.sketchWeight1 = fixed3(0, 0, 0);
if (diffFac > 6.0){}
// 最亮的部分,不从纹理中采样
else if(diffFac > 5.0) // 从第1张图中采样
{
o.sketchWeight0.x = diffFac - 5.0;
}
else if(diffFac > 4.0) // 从第1, 2张图中采样
{
o.sketchWeight0.x = diffFac - 4.0;
o.sketchWeight0.y = 1- o.sketchWeight0.x;
}
else if (diffFac > 3.0) // 从第2, 3张图中采样
{
o.sketchWeight0.y = diffFac - 3.0;
o.sketchWeight0.z = 1 - o.sketchWeight0.y;
}
else if (diffFac > 2.0) // 从第3, 4张图中采样
{
o.sketchWeight0.z = diffFac - 2.0;
o.sketchWeight1.x = 1 - o.sketchWeight0.z;
}
else if (diffFac > 1.0) // 从第4, 5张图中采样
{
o.sketchWeight1.x = diffFac - 1.0;
o.sketchWeight1.y = 1 - o.sketchWeight1.x;
}
else // 从第5, 6张图中采样
{
o.sketchWeight1.y = diffFac;
o.sketchWeight1.z = 1 - o.sketchWeight1.y;
}
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 sketchColor0 = tex2D(_Sketch0, i.uv) * i.sketchWeight0.x;
fixed4 sketchColor1 = tex2D(_Sketch1, i.uv) * i.sketchWeight0.y;
fixed4 sketchColor2 = tex2D(_Sketch2, i.uv) * i.sketchWeight0.z;
fixed4 sketchColor3 = tex2D(_Sketch3, i.uv) * i.sketchWeight1.x;
fixed4 sketchColor4 = tex2D(_Sketch4, i.uv) * i.sketchWeight1.y;
fixed4 sketchColor5 = tex2D(_Sketch5, i.uv) * i.sketchWeight1.z;
// 最亮的部分(白色)
fixed4 whiteColor = fixed4(1,1,1,1) * (1 - i.sketchWeight0.x - i.sketchWeight0.y - i.sketchWeight0.z - i.sketchWeight1.x - i.sketchWeight1.y - i.sketchWeight1.z);
fixed4 sketchColor = whiteColor + sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 + sketchColor5;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(sketchColor.rgb * atten * _Color.rgb, 1);
}
ENDCG
}
}
Fallback "Diffuse"
}