經過一個小Trick實現shader的像素識別/統計操做

2018/12/14日補充:後來發現compute shader裏用AppendStructuredBuffer能夠解決這類問題,請看這裏:http://www.javashuo.com/article/p-ccyhllxl-ed.htmlhtml

 

1.簡介

在平常開發中會遇到諸如判斷某張圖的某顏色像素百分比佔多少的問題,因爲gpu運算並行的緣由並不能對其進行累加操做。網上一些針對此類問題git

的作法是將一張大圖分紅多個小塊逐步處理並逐步合併,保留關鍵像素的向下採樣:github

 

 

但我在思考一種更簡便的方法,因而想到在頂點shader裏作判斷檢測,在像素shader裏獲取結果這樣一個形式:ide

用一組頂點去讀單個像素,判斷失敗的頂點座標提交到屏幕外,而判斷成功的頂點座標放在屏幕內。函數

最後在CPU中獲取是否有屏幕內頂點這樣一個結果,來進行簡單的識別操做。測試

而在開啓透明以後,還能夠用透明度疊加來獲取更復雜的結果。ui

 

 

2.實踐

首先實踐結果並無想象的那麼好,由於若是純用三角面來作頂點部分的判斷未免太費效率了。spa

因此我改爲了傳入頂點判斷並生成面的方式,而且縮小了傳入圖片的像素大小。3d

Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, 1);

 

畢竟更多的運用場合是用來作刮刮卡或者擦除的識別。只須要檢測mask圖片。code

 

上代碼:

Shader "Hidden/FooShader"
{
    Properties
    {
    }
    SubShader
    {
        Blend One One

        tags
        {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }

        Pass
        {
            CGPROGRAM
            #pragma target 4.0
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                float4 color : COLOR;
                float4 vertex : SV_POSITION;
            };

            sampler2D _Image;
            float4 _ImageSize;

            v2f vert(uint vid : SV_VertexID)
            {
                v2f o = (v2f)0;

                half y = floor(vid / _ImageSize.x);
                half x = (vid - y * _ImageSize.x) / _ImageSize.x;
                y = y / _ImageSize.y;

                o.vertex = 0;

                float4 image_col = tex2Dlod(_Image, half4(x,y,0,0));

                if (all(image_col.rgb == half3(0, 0, 1)))
                //if (all(image_col.rgb == half3(0, 1, 1)))    /*error*/
                {
                    o.color = 1;
                }
                else
                {
                    o.color = 0;
                }

                return o;
            }

            [maxvertexcount(4)]
            void geom(point v2f vertElement[1], inout TriangleStream<v2f> triStream)
            {
                if (vertElement[0].color.r <= 0) return;

                float size = 10;

                float4 v1 = vertElement[0].vertex + float4(-size, -size, 0, 0);
                float4 v2 = vertElement[0].vertex + float4(-size, size, 0, 0);
                float4 v3 = vertElement[0].vertex + float4(size, -size, 0, 0);
                float4 v4 = vertElement[0].vertex + float4(size, size, 0, 0);

                v2f r = (v2f)0;

                r.vertex = mul(UNITY_MATRIX_VP, v1);
                r.color = vertElement[0].color;
                triStream.Append(r);

                r.vertex = mul(UNITY_MATRIX_VP, v2);
                r.color = vertElement[0].color;
                triStream.Append(r);

                r.vertex = mul(UNITY_MATRIX_VP, v3);
                r.color = vertElement[0].color;
                triStream.Append(r);

                r.vertex = mul(UNITY_MATRIX_VP, v4);
                r.color = vertElement[0].color;
                triStream.Append(r);
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}
FooShader.shader

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

namespace Hont
{
    public class Foo : MonoBehaviour
    {
        void Start()
        {
            var blueTex = new Texture2D(64, 64);
            for (int x = 0; x < blueTex.width; x++)
                for (int y = 0; y < blueTex.height; y++)
                    blueTex.SetPixel(x, y, Color.blue);
            blueTex.Apply();

            var mat = new Material(Shader.Find("Hidden/FooShader"));
            mat.SetTexture("_Image", blueTex);
            mat.SetVector("_ImageSize", new Vector4(blueTex.width, blueTex.height));
            mat.SetPass(0);
            var tempRT = RenderTexture.GetTemporary(16, 16, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1);
            tempRT.filterMode = FilterMode.Point;
            tempRT.autoGenerateMips = false;
            tempRT.anisoLevel = 0;
            tempRT.wrapMode = TextureWrapMode.Clamp;
            var cacheRT = RenderTexture.active;
            RenderTexture.active = tempRT;
            Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, 1);
            var tex2D = new Texture2D(16, 16, TextureFormat.ARGB32, false, false);
            tex2D.wrapMode = TextureWrapMode.Clamp;
            tex2D.anisoLevel = 0;
            tex2D.filterMode = FilterMode.Point;
            tex2D.ReadPixels(new Rect(0, 0, 16, 16), 0, 0);
            var firstPixel = tex2D.GetPixel(0, 0);
            Debug.Log("firstPixel: " + firstPixel);
            RenderTexture.active = cacheRT;
            RenderTexture.ReleaseTemporary(tempRT);
        }
    }
}
Foo.cs

 

 

跑了一下代碼以後我發現了三個問題,也是沒解決的問題,一個是計算結果有偏差

o.color = float4(0.05, 0, 0, 0);

輸出是0.05結果卻有一些出入。

特別是當返回顏色小於0.1以後,我嘗試改變圖像格式或者RT等參數依舊沒能解決

第二個問題是開啓透明後,透明圖片的疊加是有上限的,畢竟深度有限,堆疊二十多層後,後面層會丟失。

第三個問題是傳入圖片尺寸過大直接致使帶寬爆炸,以致於unity直接假死了,512x512的圖片就是26萬多的像素要處理,也就是26萬多的頂點。

第三個問題很好解決,控制圖片尺寸+讓單個頂點採樣更多像素便可。

 

對於第一個問題,目前還不須要太精確因此沒解決但也能用。第二個問題能夠用一些方法來緩解

好比在頂點shader中增長運算量,把返回值分散到rgba四個通道上去。

uint roll = (roll_width + roll_height) % 4;

if (roll == 0)
    result = float4(GAIN_VALUE, 0, 0, 0);

if (roll == 1)
    result = float4(0, GAIN_VALUE, 0, 0);

if (roll == 2)
    result = float4(0, 0, GAIN_VALUE, 0);

if (roll == 3)
    result = float4(0, 0, 0, GAIN_VALUE);

 

把更多的像素遍歷放入頂點中,這樣處理圖片的頂點數量是原大小/n:

v2f vert(uint vid : SV_VertexID)
{
    v2f o = (v2f)0;

    o.vertex = 0;

    half2 image_size = half2(GRID_SIZE_X * LOOP_IMAGE_SIZE_X, GRID_SIZE_Y * LOOP_IMAGE_SIZE_Y);

    half y = floor(vid / LOOP_IMAGE_SIZE_X);
    half x = (vid - y * LOOP_IMAGE_SIZE_X) / LOOP_IMAGE_SIZE_X;
    y = y / LOOP_IMAGE_SIZE_Y;
    //將vid轉化爲x,y座標

    for (half rx = 0; rx < GRID_SIZE_X; rx++)
    {
        for (half ry = 0; ry < GRID_SIZE_Y; ry++)
        {
            half xx = x + rx;
            half yy = y + ry;

            float4 r = Statistics_sample(_Image, _Rec_Color, half4(xx, yy, 0, 0), image_size);

            o.color += r;
        }
    }
    //一個頂點處理多個像素

    return o;
}

 

 

3.測試結果

最終達到了一個比較不錯的結果,我把相關函數封裝成了一個類。

 

我寫了一個塗抹效果demo來測試一下,它經過識別白色像素的數量來判斷是否爲所有塗完:

 

 

工程文件我丟在了github上: https://github.com/hont127/Image-Rec-Base-unity-shader-

經過這個小Trick其實能夠在像素裏返回更多的信息,簡單的場合這麼仍是比較方便的,固然一些複雜的狀況分塊或者配合computer shader來作其實更合適。

相關文章
相關標籤/搜索