Unity3D教程:如何实现水墨渲染效果

发表于2016-04-28
评论25 3.14w浏览

       在一些中国风的游戏项目中,需要做水墨风格,那么那种水墨渲染的效果要如何才能实现,下面就给大家介绍下在Unity3D中水墨渲染效果的实现方法,一起来看看吧。


前言

本文旨在与大家一起探讨学习新知识,如有疏漏或者谬误,请大家不吝指出。

以下部分着色器代码参考了网上和一些书籍里的着色器技术。


二、概述

首先让我们来看一下我们要实现的最终效果:



三、原理

那么在这里,我是通过类似于卡通着色的技术来实现水墨的渲染效果。先考虑卡通着色技术,我们先实现描边的功能,描边的方法有很多种,各有优缺点,很难找出一个完美的适用任何情景的技术,在这里我们通过沿法线放大模型,绘制一层背影图,然后再在其上叠加未放大的正常缩放的模型来实现描边功能。

在水墨风格中,光线和阴影的关系并不严格遵守物理规则,甚至可以说明暗浓淡的着色完全跟绘画者的构图有关系,绘制者想要表现某种层次感,可能说近处的物体着墨更多,更浓,细节更突出,而远处则寥寥几笔带过(当然这些是我的个人理解^^)。所以我在这里并未选用Lambert或者Phong光照模型。使用来计算物体表面的光暗颜色,其中N表示顶点的法线量,V是指顶点指向视点的方向向量。完成计算,得到diffuse后,通过预定义的亮度表(一张纹理图)来从中取出对应颜色值。

在完成了上述两个步骤后,我们把输入的漫反射贴图转换成黑白色,通过使用公式diff=r*0.33+g*0.33+b*0.33来完成计算。当然你可以修改其中的权重来达到不同的效果。上述公式通过点乘来简化


四、实现

首先用一个Pass来实现描边效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Pass{
    Tags{"RenderType"="Opaque"}
    cull front
    lighting off
    ZWrite on
    CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        float _outline;
        struct appdata
        {
            float4 vertex:POSITION;
            float3 normal:NORMAL;
        };
        struct v2f
        {
            float4 pos:POSITION;
        };
        v2f vert(appdata v)
        {
            v2f o;
            float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
            float3 normal = mul(UNITY_MATRIX_IT_MV, v.normal);
            normal.z = -0.5f;
            float depth = -pos.z/pos.w*0.01f;
            _outline = clamp(depth, 0, _outline);
            pos = pos + float4(normalize(normal), 0)*_outline;
            o.pos = mul(UNITY_MATRIX_P, pos);
            return o;
        }
        float4 frag(v2f i):COLOR
        {
            return float4(0,0,0,1);
        }
    ENDCG
}


有以下几点需要注意:

1、我们是在相机空间进行缩放顶点的操作,那么在将法线normal转换到相机空间时,需要使用UNITY_MATRIX_IT_MV矩阵,而不是UNITY_MATRIX_MV矩阵。因为法线通过UNITY_MATRIX_MV矩阵转换到相机空间后不会再与顶点的切向量垂直了,更详细的推导过程请大家自行查看相关书籍。

2、_outline是一个可变参数,用于调节描边的粗细,这里加入了一个深度值来用于防止_outline太大而视点太靠近模型而出现的不理想渲染结果。

 然后我们考虑使用第二个Pass实现水墨渲染效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Pass {
    Tags { "RenderType"="Opaque" }
    cull back
    lighting on
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #include "UnityCG.cginc"
    sampler2D _MainTex;
    sampler2D _black;
    sampler2D _gray;
    sampler2D _depth;
    float4 _Color;            
    float _ink;                          
    struct appdata {
        float4 vertex : POSITION;
        fixed3 normal : NORMAL;
        half2 texcoord : TEXCOORD0;
    };
    struct v2f {
        float4 vertex : POSITION;
        half2 uv : TEXCOORD0;
        float vdotn : TEXCOORD1;
        float2 depth: TEXCOORD2;
    };
    v2f vert (appdata v)
    {
        v2f o;
        o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
        o.uv = v.texcoord;
        float3 viewDir = normalize( mul(_World2Object, float4(_WorldSpaceCameraPos.xyz, 1)).xyz - v.vertex);
        o.vdotn = dot(normalize(viewDir),v.normal);
        o.depth = o.vertex.zw;
        return o;
    }
    fixed4 frag (v2f i) : COLOR
    {
        fixed4 diff = tex2D(_MainTex, i.uv)*_Color;
        diff.xyz = dot(diff.xyz, float3(0.33f, 0.33f, 0.33f));
        float f = pow(i.vdotn,_ink);
        fixed4 inkCol = fixed4(1,1,1,1);
        float depth = Linear01Depth(i.depth.x/i.depth.y);
        float depthCol = tex2D(_depth, float2(depth, 0.5f));
        if(f<0.25)
        {
            float2 findColor = float2(f*2.5f, i.uv.x*0.5f+i.uv.y*0.5f);
            //float2 findColor = float2(f,0.5f);
            inkCol = tex2D(_black, findColor);
        }
        else
        {
            float2 findColor = float2(f,0.5f);
            //if(depthCol < 1)
            inkCol = tex2D(_gray, findColor);
        }
        inkCol *= depthCol;
        return diff*inkCol;
    }
    ENDCG
}


以上代码在优化方面可能有待改进,我们来关注几点要点:

1、_black贴图映射的颜色值更黑,_gray贴图映射的颜色值较淡,之所以用两张,而不是在一张中完成映射操作,是因为这两个映射时计算UV的方式不一样,这会导致不同的效果,_black贴图的映射方式取出的颜色值变化更匀称。

2、这里我们仍然使用到了深度值,这里是想实现一种效果:随着物体越来越远,直到其超出某种范围后,我们就将其颜色退化为偏白色,以表示远处的风景,使之随着深度的变化有层次感。Linear01Depth函数是将经过透视投影变换的Z值还原到[0, 1]区间,0表示近裁剪面,1表示远裁剪面。

    最后给出完成的示例包:

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论