|
本文为《Unity Shader入门精要》第十四章《非真实感渲染》的第二节内容《素描风格的渲染》。
本文相关代码,详见:
原书代码,详见原作者github:
<hr/>1. 概念原理
微软研究院的Praun等人在2001年的SIGGRAPH上发表了一篇非常注明的论文,论文中使用了提前生成的素描纹理来实现实时的素描风格渲染,这些纹理组成了色调艺术映射(Tonal Art Map,TAM)。
上图中,从左到右纹理中的笔触逐渐增多,用于模拟不同光照下的漫反射效果,从上到下对应了每张纹理的多级渐远纹理(mipmaps)。这些纹理的生成并不是简单的对上一层纹理进行降采样,而是需要保持笔触之间的间隔,以便真实模拟素描的效果。
接下来我们将实现简化版的论文中剔除的算法,不考虑Mipmap的生成,直接使用6张素描纹理进行渲染。
- 首先,顶点着色阶段计算逐顶点的光照;
- 然后,根据光照结果决定6张纹理的混合权重,传递给片元着色器;
- 最后,根据权重来混合6张纹理的采样结果。
2. 案例:素描风格的渲染
2.1 效果预览
本案例最终实现效果如下:
2.2 准备工作
完成如下的准备工作:
- 新建名为Scene_14_2的场景,并去掉天空盒;
- 拖拽TeddyBear模型到场景,为了得到更好的效果,往场景中拖入一张纸张图像作为背景,模型和纸张可从文章开头的工程中找;
- 新建名为HatchingMat的材质,并赋给上一步的模型;
- 新建名为Chapter14-Hatching的Unity Shader,并赋给上一步创建的材质;
- 保存场景。
2.3 编写Shader:Chapter14-Hatching
编写代码如下:
Shader &#34;Unity Shaders Book/Chapter 14/Hatching&#34;
{
Properties
{
_Color (&#34;Color Tint&#34;, Color) = (1.0, 1.0, 1.0, 1.0)
_TileFactor (&#34;Tile Factor&#34;, Float) = 1
_Outline (&#34;Outline&#34;, Range(0, 1)) = 0.1
_Hatch0 (&#34;Hatch 0&#34;, 2D) = &#34;white&#34; {}
_Hatch1 (&#34;Hatch 1&#34;, 2D) = &#34;white&#34; {}
_Hatch2 (&#34;Hatch 2&#34;, 2D) = &#34;white&#34; {}
_Hatch3 (&#34;Hatch 3&#34;, 2D) = &#34;white&#34; {}
_Hatch4 (&#34;Hatch 4&#34;, 2D) = &#34;white&#34; {}
_Hatch5 (&#34;Hatch 5&#34;, 2D) = &#34;white&#34; {}
}
SubShader
{
Tags
{
&#34;RenderType&#34; = &#34;Opaque&#34;
&#34;Queue&#34; = &#34;Geometry&#34;
}
// 使用上一节定义的轮廓线Pass(Pass名字要全部转为大写)
UsePass &#34;Unity Shaders Book/Chapter 14/Toon Shading/OUTLINE&#34;
Pass
{
Tags
{
// 为了正确获取各个光照变量,设置Pass标签
&#34;LightMode&#34; = &#34;ForwardBase&#34;
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 为了正确获取各个光照,设置相关的编译指令
#pragma multi_compile_fwdbase
#include &#34;UnityCG.cginc&#34;
#include &#34;AutoLight.cginc&#34;
#include &#34;Lighting.cginc&#34;
// 叠加的颜色
fixed4 _Color;
// 控制纹理的平铺系数,值越大,素描线条越密
float _TileFactor;
// 控制轮廓线粗细
float _Outline;
// 素描渲染时使用的不同密度的纹理(密度依次增大)
sampler2D _Hatch0;
sampler2D _Hatch1;
sampler2D _Hatch2;
sampler2D _Hatch3;
sampler2D _Hatch4;
sampler2D _Hatch5;
struct a2v
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
// 素描纹理的权重,每个分量1个权重,所以只需要2个变量存执即可
fixed3 hatchWeights0 : TEXCOORD1;
fixed3 hatchWeights1 : TEXCOORD2;
float3 worldPos : TEXCOORD3;
// 声明阴影采样坐标
SHADOW_COORDS(4)
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// _TileFactor控制采样纹理的屏幕系数
o.uv = v.texcoord.xy * _TileFactor;
// 归一化的世界空间光照方向
fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
// 世界空间的法线方向
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
// 法线和光照方向的点乘
fixed diff = max(0, dot(worldLightDir, worldNormal));
// 初始化权重
o.hatchWeights0 = fixed3(0.0, 0.0, 0.0);
o.hatchWeights1 = fixed3(0.0, 0.0, 0.0);
// 根据点乘结果(范围在[0, 7]),将其范围划分为7个子区间
float hatchFactor = diff * 7.0;
// 通过判定hatchFactor所处的子区间计算对应的纹理混合权重
if (hatchFactor > 6.0)
{
// 纯白色,保持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.0 - o.hatchWeights0.x;
}
else if (hatchFactor > 3.0)
{
o.hatchWeights0.y = hatchFactor - 3.0;
o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
}
else if (hatchFactor > 2.0)
{
o.hatchWeights0.z = hatchFactor - 2.0;
o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
}
else if (hatchFactor > 1.0)
{
o.hatchWeights1.x = hatchFactor - 1.0;
o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
}
else
{
o.hatchWeights1.y = hatchFactor;
o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
}
// 计算阴影纹理的采样坐标
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag (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;
// 白色部分占比
fixed whiteScale = 1.0 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z
- i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z;
// 白色部分颜色值(没有素描先的地方显示白色)
fixed4 whiteColor = fixed4(1.0, 1.0, 1.0, 1.0) * whiteScale;
fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3
+ hatchTex4 + hatchTex5 + whiteColor;
// 光照衰减
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
}
ENDCG
}
}
Fallback &#34;Diffuse&#34;
}
2.4 配置材质面板
配置好如下材质属性,即可得到最终效果:
素描纹理可从文章开头的工程中找到。
3. 扩展阅读
非真实感渲染相关的扩展阅读材料:
- 国际讨论会NPAR(Non-Photorealistic Animation And Rendering)上的非真实感渲染的论文
- 浙江大学的耿卫东教授编纂的书籍《艺术化绘制的图形学原理与方法》(The Algorithms and Principles of Non-photorealistic Grahpics)
- 【Unity Assets】Toon Shader Free:https://assetstore.unity.com/packages/vfx/shaders/toon-shader-free-21288
以上是本次笔记的所有内容,下一篇笔记我们将开启新的篇章《使用噪声》。
<hr/>
写在最后
本文内容会同步发在笔者的公众号上,欢迎大家关注交流!
公众号:程序员叨叨叨(ID:i_coder) |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|