Shaderlab Notizen 10 屏幕像素化特效

一、Shader实现部分

Shader "Shader/PixelEffect"  
{  
    //------------------------------------【属性值】------------------------------------  
    Properties  
    {  
    //主纹理  
    _MainTex("Texture", 2D) = "white" {}  
    //封装的变量值  
    _Params("PixelNumPerRow (X) Ratio (Y)", Vector) = (80, 1, 1, 1.5)  
}  

    //------------------------------------【唯一的子着色器】------------------------------------  
    SubShader  
    {  
        //关闭剔除操作  
        Cull Off  
        //关闭深度写入模式  
        ZWrite Off  
        //设置深度测试模式:渲染所有像素.等同于关闭透明度测试(AlphaTest Off)  
        ZTest Always  

        //--------------------------------唯一的通道-------------------------------  
        Pass  
        {  
            //===========开启CG着色器语言编写模块===========  
            CGPROGRAM  

            //编译指令:告知编译器顶点和片段着色函数的名称  
            #pragma vertex vert  
            #pragma fragment frag  

            //包含头文件  
            #include "UnityCG.cginc"  

            //顶点着色器输入结构  
            struct vertexInput  
            {  
                float4 vertex : POSITION;//顶点位置  
                float2 uv : TEXCOORD0;//一级纹理坐标  
            };  

            //顶点着色器输出结构  
            struct vertexOutput  
            {  
                float4 vertex : SV_POSITION;//像素位置  
                float2 uv : TEXCOORD0;//一级纹理坐标  
            };  

            //--------------------------------【顶点着色函数】-----------------------------  
            // 输入:顶点输入结构体  
            // 输出:顶点输出结构体  
            //---------------------------------------------------------------------------------  
            //顶点着色函数  
            vertexOutput vert(vertexInput   v)  
            {  
                //【1】实例化一个输入结构体  
                vertexOutput o;  
                //【2】填充此输出结构  
                //输出的顶点位置(像素位置)为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口  
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);  
                //输入的UV纹理坐标为顶点输出的坐标  
                o.uv = v.uv;  

                //【3】返回此输出结构对象  
                return o;  
            }  

            //变量的声明  
            sampler2D _MainTex;  
            half4 _Params;  

            //进行像素化操作的自定义函数PixelateOperation  
            half4 PixelateOperation(sampler2D tex, half2 uv, half scale, half ratio)  
            {  
                //【1】计算每个像素块的尺寸  
                half PixelSize = 1.0 / scale;  
                //【2】取整计算每个像素块的坐标值,ceil函数,对输入参数向上取整  
                half coordX=PixelSize * ceil(uv.x / PixelSize);  
                half coordY = (ratio * PixelSize)* ceil(uv.y / PixelSize / ratio);  
                //【3】组合坐标值  
                half2 coord = half2(coordX,coordY);  
                //【4】返回坐标值  
                return half4(tex2D(tex, coord).xyzw);  
            }  

            //--------------------------------【片段着色函数】-----------------------------  
            // 输入:顶点输出结构体  
            // 输出:float4型的像素颜色值  
            //---------------------------------------------------------------------------------  
            fixed4 frag(vertexOutput  Input) : COLOR  
            {  
                //使用自定义的PixelateOperation函数,计算每个像素经过取整后的颜色值  
                return PixelateOperation(_MainTex, Input.uv, _Params.x, _Params.y);  
            }  

            //===========结束CG着色器语言编写模块===========  
            ENDCG  
        }  
    }  
}

屏幕像素化特效主要用一个自定义函数来实现,实现代码如下:

 //进行像素化操作的自定义函数PixelateOperation  
            half4 PixelateOperation(sampler2D tex, half2 uv, half scale, half ratio)  
            {  
                //【1】计算每个像素块的尺寸  
                half PixelSize = 1.0 / scale;  
                //【2】取整计算每个像素块的坐标值,ceil函数,对输入参数向上取整  
                half coordX=PixelSize * ceil(uv.x / PixelSize);  
                half coordY=( ratio * PixelSize ) * ceil(uv.y / PixelSize / ratio);  
                //【3】组合坐标值  
                half2 coord = half2(coordX,coordY);  
                //【4】返回坐标值  
                return half4(tex2D(tex, coord).xyzw);  
            }

ps:此自定义函数中用到了CG标准函数库中的一个库函数——ceil。ceil(x)的作用是对输入参数向上取整。例如:ceil(float(1.3)) ,返回值就为2.0。
PixelateOperation函数的首先先计算出每个像素块的尺寸,然后根据这里的向上取整函数ceil,分别表示出像素块的坐标值。X坐标值为PixelSize * ceil(uv.x / PixelSize)。而Y轴这边还引入了一个系数ratio,先在式子一开头乘以此系数,然后在ceil函数之中的分母部分除以一个ratio,以达到用此参数实现自定义像素长宽比的调整操作。
然后在片段着色器中调用此自定义的PixelateOperation函数,其返回值就作为片段函数frag的返回值即可:

fixed4 frag(vertexOutput  Input) : COLOR  
    {  
        //使用自定义的PixelateOperation函数,计算每个像素经过取整后的颜色值  
        return PixelateOperation(_MainTex, Input.uv, _Params.x, _Params.y);  
    }

二、脚本实现部分

using UnityEngine;  
using System.Collections;  

//设置在编辑模式下也执行该脚本  
[ExecuteInEditMode]  
//添加选项到菜单中  
[AddComponentMenu("Shader/PixelEffect")]  
public class PixelEffect : MonoBehaviour  
{  
    //-----------------------------变量声明部分---------------------------  
    #region Variables  

    //着色器和材质实例  
    public Shader CurShader;  
    private Material CurMaterial;  

    //三个可调节的自定义参数  
    [Range(1f, 1024f), Tooltip("屏幕每行将被均分为多少个像素块")]  
    public float PixelNumPerRow = 580.0f;  

    [Tooltip("自动计算平方像素所需的长宽比与否")]  
    public bool AutoCalulateRatio = true;  

    [Range(0f, 24f), Tooltip("此参数用于自定义长宽比")]  
    public float Ratio = 1.0f;  

    #endregion  

    //-------------------------材质的get&set----------------------------  
    #region MaterialGetAndSet  
    Material material  
    {  
        get  
        {  
            if(CurMaterial == null)  
            {  
                CurMaterial = new Material(CurShader);  
                CurMaterial.hideFlags = HideFlags.HideAndDontSave;    
            }  
            return CurMaterial;  
        }  
    }  
    #endregion  

    //-----------------------------------------【Start()函数】---------------------------------------------    
    // 说明:此函数仅在Update函数第一次被调用前被调用  
    //--------------------------------------------------------------------------------------------------------  
    void Start ()  
    {  
        //找到当前的Shader文件  
        CurShader = Shader.Find("浅墨Shader编程/Volume11/PixelEffect");  

        //判断当前设备是否支持屏幕特效  
        if(!SystemInfo.supportsImageEffects)  
        {  
            enabled = false;  
            return;  
        }  
    }  

    //-------------------------------------【OnRenderImage()函数】------------------------------------    
    // 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果  
    //--------------------------------------------------------------------------------------------------------  
    void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)  
    {  
        //着色器实例不为空,就进行参数设置  
        if(CurShader != null)  
        {  
            float pixelNumPerRow = PixelNumPerRow;  
            //给Shader中的外部变量赋值  
            material.SetVector("_Params", new Vector2(pixelNumPerRow,  
                AutoCalulateRatio ? ((float)sourceTexture.width / (float)sourceTexture.height) : Ratio ));  

            Graphics.Blit(sourceTexture, destTexture, material);  
        }  

        //着色器实例为空,直接拷贝屏幕上的效果。此情况下是没有实现屏幕特效的  
        else  
        {  
            //直接拷贝源纹理到目标渲染纹理  
            Graphics.Blit(sourceTexture, destTexture);  
        }  
    }  

    //-----------------------------------------【Update()函数】----------------------------------------  
    // 说明:此函数在每一帧中都会被调用    
    //------------------------------------------------------------------------------------------------------  
    void Update()  
    {  
        //若程序在运行,进行赋值  
        if (Application.isPlaying)  
        {  
         #if UNITY_EDITOR  
            if (Application.isPlaying != true)  
            {  
                CurShader = Shader.Find("浅墨Shader编程/Volume11/PixelEffect");  
            }  
        #endif  
        }  
    }  
    //-----------------------------------------【OnDisable()函数】---------------------------------------    
    // 说明:当对象变为不可用或非激活状态时此函数便被调用    
    //--------------------------------------------------------------------------------------------------------  
    void OnDisable ()  
    {  
        if(CurMaterial)  
        {  
            //立即销毁材质实例  
            DestroyImmediate(CurMaterial);    
        }        
    }  
}  

根据C#脚本中参数的设定,可以有每行每列的像素个数PixelNumPerRow参数、是否自动计算正方形像素所需的长宽比与否AutoCalulateRatio参数、自定义长宽比的Ratio参数可以调节。
ps:若AutoCalulateRatio参数被勾选,Shader将自动计算正方形像素所需的长宽比,这样第三个参数Ratio也就失效了。反正,若AutoCalulateRatio参数没有被勾选,就可以用Ratio参数自己定制像素的长宽比。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容