• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • Unity_Shader高級篇_13.1_Unity Shader入門精要

    13.4 再談邊緣檢測
    在12.3中,我們曾使用Sobel算子對屏幕圖像進行邊緣測試,實現描邊的效果。但是,這種直接利用顏色信息進行邊緣檢測的方法會產生很對我們不希望得到的邊緣線,如圖13.8所示。
    可以看出,物體的紋理、陰影等位置也被描上黑邊,而這往往不是我們希望看到的。在本節中,我們將學習如何在深度和法線上進行邊緣檢測,這些圖像不會受紋理和光照的影響,而僅僅保存了當前渲染物體的模型信息,通過這樣的方式檢測出來的邊緣更加可靠。最終實現效果如下圖(13.9)的效果。
    這里寫圖片描述
    本節將使用Roberts(羅伯茨)算子來進行邊緣檢測。
    Roberts算子的本質就是計算左上角和右下角的插值,乘以右上角和左下角的插值 ,作為評估邊緣的依據。在下面的實現中,我們也會按這樣的方式,取對角方向的深度或法線值,比較他們之間的差值,如果超過某個閥值(可由參數控制),就認為他們之間存在一條邊。

    實現
    (1)新建場景(Scene_13_4)。
    (2)搭建一個測試邊緣檢測的場景,同時,將Translating.cs腳本拖拽給攝像機。
    (3)新建腳本(EdgeDetectNormalsAndDepth.cs)。并拖拽到攝像機上。
    (4)新建Unity Shader (Chapter13-EdgeDetectNormalsAndDepth)

    using UnityEngine;
    using System.Collections;
    
    public class EdgeDetectNormalsAndDepth : PostEffectsBase {
    
        public Shader edgeDetectShader;
        private Material edgeDetectMaterial = null;
        public Material material {  
            get {
                edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
                return edgeDetectMaterial;
            }  
        }
    
        [Range(0.0f, 1.0f)]
        //調整邊緣線強度描邊顏色以及背景顏色的參數。同時添加了控制采樣距離以及對深度和法線進行邊緣檢測時的靈敏度參數
        public float edgesOnly = 0.0f;
        //
        public Color edgeColor = Color.black;
    
        public Color backgroundColor = Color.white;
        //用于控制對深度+法線紋理采樣時,使用的采樣距離。數值越大描邊越寬。
        public float sampleDistance = 1.0f;
        //當領域的深度值相差多少時,會被認為存在一條邊界。
        public float sensitivityDepth = 1.0f;
    
        public float sensitivityNormals = 1.0f;
    
        //設置攝像機的相應狀態:
        void OnEnable() {
            GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
        }
    
        //這個屬性的任何圖像效果都將在不透明的幾何圖形之后呈現出來在透明的幾何。
        //在默認情況下,OnRenderImage函數會在所有的不透明和透明的Pass執行完畢后被調用,
        //以便對場景中所有游戲對象都產生影響。但有時,我們希望在不透明的Pass(即渲染隊列小于等于1500的Pass
        //,內置的Background、Geometry和AlphaTest渲染隊列均在此范圍內)執行完畢后立即調用該函數,
        //而不對透明物體(渲染隊列為Transparent的Pass)產生影響,此時,我們可以在OnRenderImage函數前添加
        //ImageEffectOpaque屬性來實現這樣的目的。在本例中,我們只希望對不透明物體進行描邊,而不希望透明
        //物體也被描邊,因此需要添加該屬性。
        [ImageEffectOpaque]
    
        void OnRenderImage (RenderTexture src, RenderTexture dest) {
            if (material != null) {
                material.SetFloat("_EdgeOnly", edgesOnly);
                material.SetColor("_EdgeColor", edgeColor);
                material.SetColor("_BackgroundColor", backgroundColor);
                material.SetFloat("_SampleDistance", sampleDistance);
                material.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0.0f, 0.0f));
    
                Graphics.Blit(src, dest, material);
            } else {
                Graphics.Blit(src, dest);
            }
        }
    }
    Shader "Unity Shaders Book/Chapter 13/Edge Detection Normals And Depth" {
        Properties {
            _MainTex ("Base (RGB)", 2D) = "white" {}
            _EdgeOnly ("Edge Only", Float) = 1.0
            _EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
            _BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
            _SampleDistance ("Sample Distance", Float) = 1.0
            //_Sensitivity的xy分量分別對應了法線和深度的檢測靈敏度,zw分量則沒有實際用途。
            _Sensitivity ("Sensitivity", Vector) = (1, 1, 1, 1)
        }
        SubShader {
            CGINCLUDE
    
            #include "UnityCG.cginc"
    
            sampler2D _MainTex;
            //存儲紋素大小的變量
            half4 _MainTex_TexelSize;
            fixed _EdgeOnly;
            fixed4 _EdgeColor;
            fixed4 _BackgroundColor;
            float _SampleDistance;
            half4 _Sensitivity;
            //需要獲取的深度+法線紋理
            sampler2D _CameraDepthNormalsTexture;
    
            //定義頂點著色器:
            struct v2f {
                float4 pos : SV_POSITION;
                half2 uv[5]: TEXCOORD0;
            };
    
            v2f vert(appdata_img v) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    
                half2 uv = v.texcoord;
                o.uv[0] = uv;
    
                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                    uv.y = 1 - uv.y;
                #endif
    
                o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance;
                o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance;
                o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance;
                o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance;
    
                return o;
            }
            //在v2f結構體中定義了一個維數為5的紋理坐標數組。這個數組的第一個坐標存儲了屏幕顏色圖像的采樣紋理。
            //我們對深度紋理的采樣坐標進行了平臺差異化處理,在必要情況下對它的豎直方向進行了翻轉。
            //數組中剩余的4個坐標則存儲了使用Roberts算子時需要采樣的紋理坐標,我們還使用了
            //_SampleDistance來控制采樣距離。通過把計算采樣紋理坐標的代碼從片元著色器中轉移到頂點
            //著色器中,可以減少運算,提高性能。由于從頂點著色器到片元著色器到片元著色器的
            //插值是線性的,因此這樣的轉移并不會影響紋理坐標的計算結果。
    
            //首先對輸入參數進行處理,得到兩個采樣點的法線和深度值。值的注意的是這里我們
            //并沒有解碼得到真正的法線值,而是直接使用了xy分量。這是因為我們只需要比較兩個
            //采樣值之間的差異度,而并不需要知道他們真正的法線值。然后,我們把兩個采樣點的對應值相減并
            //取絕對值,在乘以靈敏度參數,把差異值的每個分量相加再和一個閥值比較,如果他們的和小于
            //閥值,則返回1,說明差異度不明顯,不存在一條邊界;否則返回0.最后把法線和深度的檢查結果
            //相乘,作為組合后的返回值。
            half CheckSame(half4 center, half4 sample) {
                half2 centerNormal = center.xy;
                float centerDepth = DecodeFloatRG(center.zw);
                half2 sampleNormal = sample.xy;
                float sampleDepth = DecodeFloatRG(sample.zw);
    
                // 法線上的差異
                // 不用麻煩解碼法線-這里沒有必要
                half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
                int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
                // 不同的深度
                float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;
                // 按距離對所需閾值進行縮放
                int isSameDepth = diffDepth < 0.1 * centerDepth;
    
                // return:
                // 1 - if normals and depth are similar enough
                // 0 - otherwise            
                return isSameNormal * isSameDepth ? 1.0 : 0.0;
            }
    
            fixed4 fragRobertsCrossDepthAndNormal(v2f i) : SV_Target {
                half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv[1]);
                half4 sample2 = tex2D(_CameraDepthNormalsTexture, i.uv[2]);
                half4 sample3 = tex2D(_CameraDepthNormalsTexture, i.uv[3]);
                half4 sample4 = tex2D(_CameraDepthNormalsTexture, i.uv[4]);
    
                half edge = 1.0;
    
                edge *= CheckSame(sample1, sample2);
                edge *= CheckSame(sample3, sample4);
    
                fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), edge);
                fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
    
                return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
            }
            //我們首先使用4個紋理坐標對深度+法線紋理進行采樣,再調用CheckSame函數來分別計算對
            //角線上兩個紋理值的差值。CheckSame函數的返回值要么是0,要么是1,返回0時表明這兩點
            //之間存在一條邊界,反之則返回1。
            ENDCG
    
            Pass { 
                ZTest Always Cull Off ZWrite Off
    
                CGPROGRAM      
    
                #pragma vertex vert  
                #pragma fragment fragRobertsCrossDepthAndNormal
    
                ENDCG  
            }
        } 
        FallBack Off
    }

    實現的描邊效果是基于整個屏幕空間進行的,也就是說,場景內的所有物體都會被添加描邊效果。
    如果想要對單一物體添加描邊效果。這時,我們可以使用Unity提供的Graphics.DrawMesh或Graphics.DrawMeshNow 函數把需要描邊的物體再次渲染一遍,然后再使用邊緣檢測算法計算深度或法線紋理中每個像素的梯度值,判斷它們是否小于某個閥值,如果是,就在Shader中使用clip()函數將該像素剔除掉,從而顯示出原來的物體顏色。
    13.5 擴展閱讀
    實際上我們可以在Unity中創建任何需要的緩存紋理。這可以通過使用Unity的著色器替換(Shader Replacement)功能(即調用Camera.renderWithShadr(shadr,replacementTag)函數)把整個場景再次渲染一遍來得到,而在很對時候,這實際也是Unity創建深度好法線紋理是使用的方法。
    Unity曾在2011年的SIGGRAPH(計算圖形學的頂級會議)上做了一個關于使用深度紋理實現各種特效的演講(http://blogs.unity3d.com/2011/09/08/special-effects-with-depth-talk-at-siggraph/),演講中,解釋了利用深度紋理來實現特定物體的描邊、角色護盾、相交線的高光模擬等效果。在Unity的Image Effect(http://docs.unity3d.com/Manual/comp-ImageEffects.html)包中,也能找到一些傳統的使用深度紋理實現屏幕特效的例子,例如屏幕空間的環境遮擋(SSAO)等效果。

    版權聲明:本文為qq_39710961原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/qq_39710961/article/details/79792252

    智能推薦

    Unity Shader入門精要 閱讀筆記十三

    透明效果二 對于我來說,透明效果的編寫非常重要,甚至于比一些高級的渲染效果還要重要。那么我需要實現的效果就是,給一張含透明通道的貼圖,能夠實現雙面渲染,并且雙面不會出現透明效果的錯誤。之前很多次嘗試都是錯誤的(主要是沒有正兒八經的學過shader)因此,這次學了之后好好總結一下。 一、透明度混合 從名詞中可以看出,透明度混合能夠實現透明度與其他不透明物體的混合。為了實現半透明效果我們考慮到: 1、...

    Unity Shader入門精要 --- 閱讀筆記 18.07.18

    數學基礎:代數運算(結合律/交換律),三角運算(如正弦/余弦計算),線性代數,微積分 程序員的三大浪漫:編譯原理/操作系統/圖形學 Page-03 Chapter 02 渲染流水線  01.Shader 即著色器   2.1 綜述 01.Shader 僅僅是渲染流水線中的一個環節   2.1.1 什么是流水線 01.理想情況下,如果把一個非流水線系統分成 n 個流水線...

    Unity+Shader入門精要(筆記二)

    材質和Unity Shader 常見流程: (1)創建一個材質 (2)創建一個Unity Shader,并把它賦給上一步創建的材質 (3)把材質賦給要渲染的對象 (4)在材質面板中調整Unity Shader的屬性   ShaderLab:Unity提供的編寫Unity Shader的一種說明性語言 基礎結構 Shader  "___/___": 控制材質面...

    Unity Shader入門精要 閱讀筆記十二

    透明效果一 一、基礎 要在模型中實現透明效果,可以通過兩種方式來實現:透明測試和透明混合。 透明測試:這種方式其實是通過透明度來判斷哪些地方需要去渲染,如果透明度低于設定的值就不去渲染。其實這里和遮罩有點相似。透明測試主要去判斷一張貼圖的透明通道,然后將低于該透明通道的部分取消渲染。 透明度混合:這種方式可以實現半透明效果。 二、一些關鍵詞 深度緩沖(z-buffer):深度緩沖對應深度測試中的一...

    Unity Shader入門精要 閱讀筆記十一

    遮罩紋理 遮罩紋理其實內容不是很多,說白了就是添加一張紋理來存儲權重值,以控制渲染中某些變量的值。由于一張圖中有四個通道(r,g,b,a),因此一張圖可以控制四個變量。 下面主要講一下通過一張紋理來控制高光的效果。 圖一 未添加遮罩貼圖效果 圖二 添加遮罩貼圖效果 從圖一圖二中對比可以看出,圖二添加了遮罩貼圖后高光部分顯得比較暗淡。 在shader中: 漫反射和高光部分其實使用的方法都比較傳統,漫...

    猜你喜歡

    Unity Shader入門精要 閱讀筆記十

    漸變紋理 不同漸變紋理所帶來的效果不同。 首先從分析半蘭伯特模型開始: 半蘭伯特模型在之前計算光照的時候被應用,主要是為了解決blinn模型不能計算陰影面的問題,而這個問題的表述在于在計算光照的時候,算法直接將計算結果的負數剪掉,從而在背面其實也就不計算光照。 那么在半蘭伯特模型中,采用了先縮放再平移的方式將本該在[-1,1]的受光照范圍擴展到了[0,1]。這樣的計算就能保證整個模型,包括背影都能...

    Unity Shader入門精要 閱讀筆記八

    逐頂點光照 效果圖 從效果里里可以看出,逐頂點光在邊緣處有很多鋸齒和凸起,并且在光暗分界線部分出現不平滑。這個問題是因為,在計算光照效果的時候,逐頂點光照其實是對每個頂點部分的顏色效果值進行計算,而中間部分則使用插值計算得到,所以會出現這些問題。在頂點密度較低的情況下,這個問題很明顯,而在頂點密度較高的時候,這個問題就現實較好。 逐頂點光照shader腳本: 這里最核心的是使用到了漫反射的計算。而...

    Unity Shader入門精要總結--渲染路徑

    前向渲染路徑 原理 ? 每進行一次完整的前向渲染,我們需要渲染該對象的渲染圖元,并計算兩個緩沖區的信息:一個是顏色緩沖區,一個是深度緩沖區。我們利用深度緩沖來決定一個片元是否可見,如果可見就更新顏色緩沖區中的顏色值。我們可以用下面的偽代碼來描述前向渲染路徑的大致過程: Unity中的前向渲染 ? 在Unity中,前向渲染路徑有3種處理光照的方式:逐頂點處理、逐像素處理、球諧函數處理。而決定一個光源...

    MATLAB入門級知識

      要入坑MATLAB了。   老年人的記憶力傷不起,還是記下來方便以后查閱。   主要分為三部分:數據類型、可視化、程序設計。   數據類型   MATLAB中的數據類型主要包括字符型(char)、整型(int8、int16、int32、int64、uint8、...、uint64)、浮點(single、double)、元胞型(cell)和結構體...

    Hibernate新官網下載源碼及支持jar

    訪問官網:Hibernate官網網址 還是從這進去 最新穩定版在這 打開網頁往下拉,點擊下載 幾秒后開始下載,網速慢的耐心點 歷史版本,好像歷的不那么明顯,就是前幾個版本 接下來就是源碼了 往這瞧,點擊,ok 這算是對之前一個教程的補充吧...

    精品国产乱码久久久久久蜜桃不卡