【Unity 插件推薦】水墨風渲染大師,輕鬆實現潑墨與暈染特效!

目錄
【Unity 插件推薦】水墨風渲染大師,輕鬆實現潑墨與暈染特效!

 

前言

 

你是否曾想在 Unity 中輕鬆實現中國水墨風格的畫面渲染?傳統的渲染技術難以模擬水墨的【流動感】、【暈染效果】、【筆觸質感】,這讓許多開發者感到頭疼!不過今天要介紹的 ChinesePainting 水墨渲染大師正是為了解決這個問題而生!

本篇文章將帶你深入了解 ChinesePainting 的強大功能,包括【筆觸模擬】、【流動渲染】、【Shader 調整】,並結合實際範例,讓你快速上手這款插件,如果你正在尋找一款能夠完美呈現水墨風格的插件,那麼千萬不要錯過這次的介紹!

你是否曾想在 Unity 中輕鬆實現中國水墨畫風格的畫面渲染?傳統的渲染技術難以模擬水墨的【流動感】、【暈染效果】、【筆觸質感】,這讓許多開發者感到頭疼!不過今天要介紹的 ChinesePainting 水墨渲染大師 正是為了解決這個問題而生!  本篇文章將帶你深入了解 ChinesePainting 的強大功能,包括【筆觸模擬】、【流動渲染】、【Shader 調整】,並結合實際範例,讓你快速上手這款插件,如果你正在尋找一款能夠完美呈現水墨畫風格的插件,那麼千萬不要錯過這次的介紹!

 

 

Unity ChinesePainting 水墨風插件介紹

 

ChinesePainting 是一款專為 Unity 設計的 水墨風渲染插件,旨在將中國傳統水墨畫風格融入現代遊戲開發中,透過自訂 Shader 來實現【潑墨】、【暈染】、【筆觸流動】等中國傳統水墨畫效果,開發者可以在虛擬世界中重現中國畫的獨特韻味,為遊戲增添濃厚的東方藝術氛圍。

ChinesePainting 插件示意圖

 

功能特色

  • 輪廓線渲染
    透過擴展模型的法線,實現類似毛筆筆觸的輪廓線效果,增強物體的邊緣表現力。
  • 內部著色
    採用光照模型結合漸變貼圖(ramp texture),控制顏色的層次變化,模擬水墨畫中濃淡相間的效果。
  • 多重 Pass 渲染
    利用【噪聲貼圖】和【筆觸紋理】,對模型表面進行擾動處理,增加隨機性,使渲染效果更接近手繪風格。

 

適用場景

  • 武侠冒险游戏
    在以古代武俠為背景的遊戲中,水墨風格能營造出濃厚的江湖氛圍,增強玩家的代入感。
  • 中國歷史類遊戲
    描繪中國【歷史事件】或【傳說】的遊戲中,水墨風格可呈現獨特的文化質感,使玩家更深入地體驗歷史情境。
  • 仙俠類遊戲
    在以【神話】或【仙俠】為題材的遊戲中,水墨風格能夠展現出【夢幻】且【神秘】的世界,提升遊戲的藝術表現力。
【Unity 插件推薦】ChinesePainting 水墨渲染大師,輕鬆實現潑墨與暈染特效!

 

 

Unity ChinesePainting 水墨風插件導讀

 

中國水墨畫的渲染效果是很久很久以前就有的方法,基本想法就是分成兩個部分,輪廓線渲染和內部渲染。輪廓線通常是渲染成毛筆筆觸的感覺,內部則是透過普通的光照方程式再加上 ramp 貼圖控制一下漸變紋理,最後用一些模糊處理。這也是基本的卡通渲染方法。

而使用 Unity 進行卡通渲染的基本思想,作者馮樂樂已經在《Unity Shader 入門精要》裡解釋得非常完整,我就不添亂了,上鏈接(樂樂姐的卡通渲染)。同時本文也參考了知乎上兩位大佬的 Unity 實作方法(Unity 中如何進行水墨風 3D 渲染 &Unity Shader】 水墨風格渲染:如何優雅的畫一隻猴子)。

《Unity Shader 入門精要》
《Unity Shader 入門精要》

 

 

Unity ChinesePainting 水墨風輪廓線 shader

 

樂樂姐已經介紹很詳細輪廓線的渲染方法了,所以選擇她在書中說的「過程式集合輪廓線渲染方法」。簡言之,單獨用一個 pass 將模型沿法線擴張一點,然後渲染成輪廓線顏色,然後再用一個 pass 正常渲染內部著色,遮擋住前面的部分,留下來顯示出來的部分就是輪廓線啦。

主要部分的程式碼如下:

Properties 

    {

        [Header(OutLine)]

        // Stroke Color

        _StrokeColor ("Stroke Color", Color) = (0,0,0,1)

        // Noise Map

        _OutlineNoise ("Outline Noise Map", 2D) = "white" {}

        // First Outline Width

        _Outline ("Outline Width", Range(0, 1)) = 0.1

        // Second Outline Width

        _OutsideNoiseWidth ("Outside Noise Width", Range(1, 2)) = 1.3

        _MaxOutlineZOffset ("Max Outline Z Offset", Range(0,1)) = 0.5

    }

   SubShader 

    {

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

        // the first outline pass

        Pass 

        {

               // 主要在vertex shader內進行計算 省略部分基本參數設置

            v2f vert (a2v v) 

            {

                // fetch Perlin noise map here to map the vertex

                // add some bias by the normal direction

                float4 burn = tex2Dlod(_OutlineNoise, v.vertex);

                v2f o = (v2f)0;

                float3 scaledir = mul((float3x3)UNITY_MATRIX_MV, normalize(v.normal.xyz));

                scaledir += 0.5;

                scaledir.z = 0.01;

                scaledir = normalize(scaledir);

                // camera space

                float4 position_cs = mul(UNITY_MATRIX_MV, v.vertex);

                position_cs /= position_cs.w;

                float3 viewDir = normalize(position_cs.xyz);

                float3 offset_pos_cs = position_cs.xyz + viewDir * _MaxOutlineZOffset;

                // y = cos(fov/2)

                float linewidth = -position_cs.z / (unity_CameraProjection[1].y);

                linewidth = sqrt(linewidth);

                position_cs.xy = offset_pos_cs.xy + scaledir.xy * linewidth * burn.x * _Outline ;

                position_cs.z = offset_pos_cs.z;

                o.pos = mul(UNITY_MATRIX_P, position_cs);

                return o;

            }

               // fragment shader只是輸出了一個顏色 不贅述

        }

}

 

其中基本需要設定的參數都很簡單明了。而基本的想法也是依照樂樂姐書中所說,在視角空間下,將頂點沿著法線擴張。而針對水墨畫風格渲染,其實就是做了一個最簡單的 noise 幹擾,在這裡使用 noise 紋理圖片(_OutlineNoise)進行採樣,這樣又個好處就是隨機出來的輪廓不會隨著視角的改變而改變。

_OutlineNoise
_OutlineNoise

 

其中稍微有點改變的是,增加了一個 linewidth 的操作,因為 unity_CameraProjection[1].y 其實就是 cos(FOV/2),所以這個操作的根本目的是為了確保輪廓線隨著 FOV 的變換也是成一定比例,同時也不會隨著鏡頭離物體的遠近距離而變換。

對比圖片如下:

沒有加入linewidth
沒有加入 linewidth
新增 linewidth
新增 linewidth

 

最後一個小 trick 是,再增加了一個 pass 進行完全相同的操作,只是寬度再稍微增加一點,然後在 fragment shader 裡根據 noise 再進行一下剔除。這也是在屬性裡面,之前沒有用到的 _OutsideNoiseWidth,來控制第二個 pass 的輪廓線的寬度,理論上它要大於 1,比第一個 pass 稍微寬一些。

簡要的程式碼如下:

// 在vertex shader內 只需要稍微改變一點

position_cs.xy = offset_pos_cs.xy + scaledir.xy * linewidth * burn.y * _Outline * _OutsideNoiseWidth ;

// 在fragment shader內 也稍微根據noise突變做了下剔除

fixed4 frag(v2f i) : SV_Target 

{

    //clip randome outline here

    fixed4 c = _StrokeColor;

    fixed3 burn = tex2D(_OutlineNoise, i.uv).rgb;

    if (burn.x > 0.5)

        discard;

    return c;

}

 

對比圖片如下:

只有一個 pass 渲染輪廓線
只有一個 pass 渲染輪廓線
用兩個 pass 渲染輪廓線
用兩個 pass 渲染輪廓線

 

 

Unity ChinesePainting 水墨風內部渲染

 

而內部著色的基本思想和 Unity 卡通渲染的一致,使用最基本的光照方程,再映射到一張 ramp 圖上進行採樣,最後形成的就是階梯狀的顏色過渡。

這裡用的 ramp 圖如下:

這裡用的 ramp 圖如下:

 

同時,與其餘的水墨渲染方法有所區別的是,相對把筆觸紋理的圖和最終顏色值疊加融合起來,直接將紋理筆觸作為一個 noise 貼圖,擾動 uv 的值之後再進行一次高斯模糊,效果感覺也不錯。

這裡是用了一張筆觸紋理和一個 noise 貼圖混合的一起擾動 uv。

筆觸紋理圖
筆觸紋理圖

 

所以最後內部著色的內部渲染部分的步驟就是,先計算半蘭伯特漫反射係數,然後用筆觸紋理和 noise 紋理稍微擾動一下,最後再採樣 ramp 紋理的時候進行高斯模糊。

程式碼如下:

Shader "ChinesePainting/MountainShader"

{

   Properties

   {

       [Header(OutLine)]

       //...省略上述已介紹過的

       [Header(Interior)]

       _Ramp ("Ramp Texture", 2D) = "white" {}

       // Stroke Map

       _StrokeTex ("Stroke Tex", 2D) = "white" {}

       _InteriorNoise ("Interior Noise Map", 2D) = "white" {}

       // Interior Noise Level

       _InteriorNoiseLevel ("Interior Noise Level", Range(0, 1)) = 0.15

       // Guassian Blur

       radius ("Guassian Blur Radius", Range(0,60)) = 30

              resolution ("Resolution", float) = 800  

              hstep("HorizontalStep", Range(0,1)) = 0.5

              vstep("VerticalStep", Range(0,1)) = 0.5  

   }

      SubShader

   {

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

       // the first outline pass

       // 省略

       // the second outline pass for random part, a little bit wider than last one

           // 省略

       // the interior pass

              Pass

       {

           // 之前的vertex shader部分沒有特殊操作  省略

           float4 frag(v2f i) : SV_Target

           {

               fixed3 worldNormal = normalize(i.worldNormal);

               fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

               // Noise

               // For the bias of the coordiante

               float4 burn = tex2D(_InteriorNoise, i.uv);

               //a little bit disturbance

               fixed diff =  dot(worldNormal, worldLightDir);

               diff = (diff * 0.5 + 0.5);

               float2 k = tex2D(_StrokeTex, i.uv).xy;

               float2 cuv = float2(diff, diff) + k * burn.xy * _InteriorNoiseLevel;

               // This iniminate the bias of the uv movement

               if (cuv.x > 0.95)

               {

                   cuv.x = 0.95;

                   cuv.y = 1;

               }

               if (cuv.y >  0.95)                {

                   cuv.x = 0.95;

                   cuv.y = 1;

               }

               cuv = clamp(cuv, 0, 1);

               // Guassian Blur

               float4 sum = float4(0.0, 0.0, 0.0, 0.0);

                              float2 tc = cuv;

                              // blur radius in pixels

                              float blur = radius/resolution/4;    

                              sum += tex2D(_Ramp, float2(tc.x - 4.0*blur*hstep, tc.y - 4.0*blur*vstep)) * 0.0162162162;

                              sum += tex2D(_Ramp, float2(tc.x - 3.0*blur*hstep, tc.y - 3.0*blur*vstep)) * 0.0540540541;

                              sum += tex2D(_Ramp, float2(tc.x - 2.0*blur*hstep, tc.y - 2.0*blur*vstep)) * 0.1216216216;

                              sum += tex2D(_Ramp, float2(tc.x - 1.0*blur*hstep, tc.y - 1.0*blur*vstep)) * 0.1945945946;

                              sum += tex2D(_Ramp, float2(tc.x, tc.y)) * 0.2270270270;

                              sum += tex2D(_Ramp, float2(tc.x + 1.0*blur*hstep, tc.y + 1.0*blur*vstep)) * 0.1945945946;

                              sum += tex2D(_Ramp, float2(tc.x + 2.0*blur*hstep, tc.y + 2.0*blur*vstep)) * 0.1216216216;

                              sum += tex2D(_Ramp, float2(tc.x + 3.0*blur*hstep, tc.y + 3.0*blur*vstep)) * 0.0540540541;

                              sum += tex2D(_Ramp, float2(tc.x + 4.0*blur*hstep, tc.y + 4.0*blur*vstep)) * 0.0162162162;

               return float4(sum.rgb, 1.0);

           }

           ENDCG

       }

   }

   FallBack “Diffuse”

}

 

其最終的效果如下:

其最終的效果如下:
其最終的效果如下:
其最終的效果如下:

 

同時可以在網路上搜尋一些不同的毛筆筆觸紋理,也會有不同的效果。 For 範例:

同時可以在網路上搜尋一些不同的毛筆筆觸紋理,也會有不同的效果。
同時可以在網路上搜尋一些不同的毛筆筆觸紋理,也會有不同的效果。

最後,本文只是一個非常簡單的 Unity 水墨渲染,如果有錯誤,希望大家指正,謝謝~

 

 

Unity ChinesePainting 水墨渲染大師相關網站 & 插件下載點

 

【ChinesePainting】

GitHub 下載連結:ChinesePainting

————————————————

以上內容改編節錄自:知乎 作者:无聊

 

更多好用插件:【Unity 好用插件推薦】持續更新,一起讓遊戲開發事半功倍!

 

 

 

本文原創(或整理)於亞洲電玩通,未經作者與本站同意不得隨意引用、轉載、改編或截錄。

特約作家簡介

X
A
Y
B
JamXu的頭像
JamXu
十年遊戲研發
二十年遊戲台主
三十年遊戲玩家

經中華網龍遊戲企劃進入遊戲圈,然後被雷打到去學程式前後端又學了點設計帶帶幾個研發團隊,見證了 3D 渲染技術及遊戲引擎互相進步,也見證了研發代理的更迭與博弈遊戲的興起,再毅然研究起 SEO 網路行銷社群廣告投放,深信自研自賣才是最大贏家,期望能為台灣研發重回輝煌時光貢獻一點力量。如果你也有遊戲夢,歡迎交流認識。


支持贊助 / DONATE

 

亞洲電玩通只是很小的力量,但仍希望為復甦台灣遊戲研發貢獻一點動能,如果您喜歡亞洲電玩通的文章,或是覺得它們對您有幫助,歡迎給予一些支持鼓勵,不論是按讚追蹤或是贊助,讓亞洲電玩通持續產出,感謝。

亞洲電玩通AsiaGameMaster - Steam 遊戲鑑賞家
亞洲電玩通AsiaGameMaster - FB 粉絲專頁
亞洲電玩通AsiaGameMaster - IG 粉絲專頁
亞洲電玩通AsiaGameMaster - Twitter
亞洲電玩通AsiaGameMaster - Yoytube 粉絲專頁
亞洲電玩通AsiaGameMaster - Tiktok

BTC

亞洲電玩通AsiaGameMaster - BTC 鏈贊助地址

352Bw8r46rfXv6jno8qt9Bc3xx6ptTcPze

 

ETH

亞洲電玩通AsiaGameMaster - ETH 鏈贊助地址

0x795442E321a953363a442C76d39f3fbf9b6bC666

 

TRON

亞洲電玩通AsiaGameMaster - TRON 鏈贊助地址

TCNcVmin18LbnXfdWZsY5pzcFvYe1MoD6f

延伸閱讀