pc8888888 发表于 2022-11-14 16:51

《Unity Shader入门精要》笔记(二十八)

本文为《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 "Unity Shaders Book/Chapter 14/Hatching"
{
    Properties
    {
      _Color ("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
      _TileFactor ("Tile Factor", Float) = 1
      _Outline ("Outline", Range(0, 1)) = 0.1
      _Hatch0 ("Hatch 0", 2D) = "white" {}
      _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" {}
    }

    SubShader
    {
      Tags
      {
            "RenderType" = "Opaque"
            "Queue" = "Geometry"
      }

      // 使用上一节定义的轮廓线Pass(Pass名字要全部转为大写)
      UsePass "Unity Shaders Book/Chapter 14/Toon Shading/OUTLINE"

      Pass
      {
            Tags
            {
                // 为了正确获取各个光照变量,设置Pass标签
                "LightMode" = "ForwardBase"
            }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            // 为了正确获取各个光照,设置相关的编译指令
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"

            // 叠加的颜色
            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);

                // 根据点乘结果(范围在),将其范围划分为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 "Diffuse"
}

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)
页: [1]
查看完整版本: 《Unity Shader入门精要》笔记(二十八)