【UnityShader】字符画风格屏幕后处理

原理

在UnityShader中实现字符画,实际工作就是把原图像分成矩阵块,分析每个每个块内的图像,并替换为字符。

图像的分析方法最简单的就是灰度值,在字符密度较大时能以很简单的方式达到效果。更准确的方法是对块内像素与准备好的字符取样图像素对比,得出最相近的字符,由于这个方法效率较低,更适合生成静态图片。

文中实现了一个根据灰度判断的方法,和一个采取了及其简单的形状判断与直接映射查找字符的方法(上面黄色背景图片的边缘)。

替换字符的方法是根据原图像小块所采用的字符,对一张准备好的字符图采样。

C#后处理脚本

首先是后处理脚本基类

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {

	protected void CheckResources() {
		bool isSupported = CheckSupport();
		
		if (isSupported == false) {
			NotSupported();
		}
	}

	protected bool CheckSupport() {
		if (SystemInfo.supportsImageEffects == false) 
			return false;

		return true;
	}


	protected void NotSupported() {
		enabled = false;
	}
	
	protected void Start() {
		CheckResources();
	}

	protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) {
		if (shader == null) {
			return null;
		}
		
		if (shader.isSupported && material && material.shader == shader)
			return material;
		
		if (!shader.isSupported) {
			return null;
		}
		else {
                    material = new Material(shader)
                    {
                        hideFlags = HideFlags.DontSave
                    };
                    if (material)
			return material;
		    else 
			return null;
		}
	}
}

接着是字符画需要的后处理脚本,这里可以设置字符像素尺寸、字符颜色等信息,这里的图像已经通过字符像素尺寸进行了降采样处理,输入到shader中的是一张马赛克图。

using UnityEngine;
using System.Collections;


[ExecuteInEditMode]
public class ASCIIart : PostEffectsBase {


    public Shader ASCIIartShader;
    private Material ASCIIartMaterial = null;


    public Material material {  
	get {
		ASCIIartMaterial = CheckShaderAndCreateMaterial(ASCIIartShader, ASCIIartMaterial);
		return ASCIIartMaterial;
	    }  
    }

    // 字符正方形边长
    [Range(1,100)]
    public int texelPerChar;
    //伽马校正
    public float gamaMutipler = 1;
    //背景色
    public Color bgColor;
    //字符色
    public Color charColor;

    protected new void Start()
    {
        base.Start();
        material.SetColor("_BGColor", bgColor);
        material.SetColor("_CharColor", charColor);
    }
    void OnRenderImage (RenderTexture src, RenderTexture dest) {
	if (material != null) {
            material.SetFloat("_TexelPerChar", texelPerChar);
            material.SetFloat("_GamaMutipler", gamaMutipler);


            int rtW = src.width/ texelPerChar;
	    int rtH = src.height/ texelPerChar;

            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
            buffer0.filterMode = FilterMode.Point;

            Graphics.Blit(src, buffer0);
            Graphics.Blit(buffer0, src);
            RenderTexture.ReleaseTemporary(buffer0);

            Graphics.Blit(src, dest, material, 0);
        }
        else {
	    Graphics.Blit(src, dest);
	}
    }
}

Shader实现

字符取样方式和取样图设计有关,本文并没有设置相关变量,有需求的还要额外定义变量,文中取样图片如下:


下面是只对灰度进行处理的Shader:

Shader "Post/ASCII art"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" { }
        _CharTex ("CharTex", 2D) = "white" { }
        _BGColor ("背景色", Color) = (0.2,0.3,0.5,1)
        _CharColor ("字体色", Color) = (0,0,0,1)
    }

    SubShader 
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        sampler2D _CharTex;
        float _TexelPerChar;
        float4 _BGColor;
        float4 _CharColor;
        float _GamaMutipler;

        struct v2f
        {
            float4 pos : SV_POSITION;
            half2 uv : TEXCOORD0;
        };

        v2f ASCIIvertex(appdata_img v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            return o;
        }

        fixed4 ASCIIfrag(v2f i) : SV_Target
        {
            //每个字符占用的UV值
			float2 uvPerChar = _TexelPerChar * _MainTex_TexelSize.xy;
            //所在字符的起点UV
            float2 startUV = floor(i.uv / uvPerChar) * uvPerChar + _MainTex_TexelSize.xy;
            //所在字符的坐标比例(0-1)
            float2 oppositeUV = (i.uv - startUV)/uvPerChar;
            fixed4 mainColor = tex2D(_MainTex, startUV);
            //如果项目是非线性空间,需要1/2.2的Gama校正
	    //mainColor = pow(mainColor,_GamaMutipler);
            //计算灰度值
            fixed luminosity = dot(mainColor.rgb,fixed3(0.299,0.587,0.114));
            //计算灰度阶数
            int luminosityStep = floor(luminosity * 4* 4) - 1;
            //计算灰度图的坐标原点
            float2 charStartUV = float2(fmod(luminosityStep,4),floor(luminosityStep / 4))/4;
            float2 charUV = charStartUV + oppositeUV/_CharCount;
            float4 color = tex2D(_CharTex, charUV);
            color = lerp(_BGColor,_CharColor, 1 - color.r);
            return color;
        }
        ENDCG

        ZTest Always
        Cull Off
        ZWrite Off

        Pass
        {
            Name "ASCII art"

            CGPROGRAM
            #pragma vertex ASCIIvertex
            #pragma fragment ASCIIfrag
            ENDCG
        }
    }
    FallBack "Diffuse"
}

效果:


另外一种聊天中常见的字符画,如下图所示,比起明暗这种字符画更注重形体


为模拟这种字符画,建一张简单的贴图,由于工作量问题,这里不考虑平均灰度的影响,且仅采用2X2的采样区,共需要字符数量是2的2X2次幂(16个),如果3X3就需要512个字符,下面是用到的形状图:


下面的shader需要在后处理脚本中增加一个_LuminosityThreshold变量,用来控制灰度阈值

Shader "Post/ASCII art Gird"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" { }
        _CharTex ("CharTex", 2D) = "white" { }
        _BGColor ("背景色", Color) = (0.2,0.3,0.5,1)
        _CharColor ("字体色", Color) = (0,0,0,1)
        _LuminosityThreshold ("LuminosityThreshold", Float) = 0.5
    }

    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        sampler2D _CharTex;
        half4 _CharTex_TexelSize;
        float _TexelPerChar;
        float _LuminosityThreshold;
        float4 _BGColor;
        float4 _CharColor;
        float _GamaMutipler;

        struct v2f
        {
            float4 pos : SV_POSITION;
            half2 uv : TEXCOORD0;
        };

        v2f ASCIIvertex(appdata_img v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            return o;
        }

        fixed4 ASCIIfrag(v2f i) : SV_Target
        {
            //每1个字符的像素
	    float2 uvPerChar = _TexelPerChar * _MainTex_TexelSize.xy;
            //每2个字符的像素
	    float2 uvPer2Char = 2 * uvPerChar;
            //原图网格起点,4个网格为一组,额外偏移1像素
            float2 startUV = floor(i.uv / uvPer2Char) * uvPer2Char + _MainTex_TexelSize.xy;
            //找到原图上相对起点的坐标的比例,因为2x2为一组,要除2被字符像素长宽值,以映射到0-1的值
            float2 oppositeUV = (i.uv - startUV)/uvPer2Char;
            //计算4个灰度值
            fixed4 mainColor0 = tex2D(_MainTex, startUV);
            fixed4 mainColor1 = tex2D(_MainTex, startUV + float2(1,0) * uvPerChar);
            fixed4 mainColor2 = tex2D(_MainTex, startUV+ float2(0,1) * uvPerChar);
            fixed4 mainColor3 = tex2D(_MainTex, startUV+ float2(1,1) * uvPerChar);
            fixed luminosity0 = dot(mainColor0.rgb,fixed3(0.299,0.587,0.114));
            fixed luminosity1 = dot(mainColor1.rgb,fixed3(0.299,0.587,0.114));
            fixed luminosity2 = dot(mainColor2.rgb,fixed3(0.299,0.587,0.114));
            fixed luminosity3 = dot(mainColor3.rgb,fixed3(0.299,0.587,0.114));

            // fixed luminosity = (luminosity0 + luminosity1 + luminosity2 + luminosity3)/4;
            //由形状图排版和4个灰度阶数求灰度图上的坐标
            int x = 0,y = 0;
            if (luminosity0 > _LuminosityThreshold) y+=2;
            if (luminosity1 > _LuminosityThreshold) y+=1;
            if (luminosity2 > _LuminosityThreshold) x+=2;
            if (luminosity3 > _LuminosityThreshold) x+=1;

            //计算灰度图的坐标原点
            float2 charStartUV = float2(x,y)/4;
            float2 charUV = charStartUV + oppositeUV/4;
            // _CharColor = lerp(_CharColor,_BGColor, luminosity);
            float4 color = tex2D(_CharTex, charUV);
            color = lerp(_CharColor,_BGColor, color.r);
            return color;
        }
        ENDCG

        ZTest Always
        Cull Off
        ZWrite Off

        Pass
        {
            Name "ASCII art"

            CGPROGRAM
            #pragma vertex ASCIIvertex
            #pragma fragment ASCIIfrag
            ENDCG
        }
    }
    FallBack "Diffuse"
}

这里并没有灰度对比,但可以简单的绘制出边缘形状,效果为下方左图

在字符画绘制之前,先提取出图片边缘得到方右图




  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,Shader是用来定义物体表面的着色和渲染效果的一种程序。在Unity5.2及以上的版本中,Unity提供了四种Shader模板供我们选择。其中,Standard Surface Shader会产生一个包含了标准光照模型的表面着色器模板;Unity Shader会产生一个不包含光照(但包含雾效)的基本顶点/片元着色器;Image Effect Shader为我们实现各种屏幕后处理效果提供一个基本模板;Computer Shader则产生一种特殊的Shader文件,用于进行一些与常规渲染流水线无关的计算。一个单独的Unity Shader不能单独发挥作用,必须要和材质结合起来使用。 每一个Unity Shader文件可以包含多个SubShader语义块,但最少要有一个。当Unity需要加载一个Unity Shader时,会扫描所有的SubShader语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持的话,Unity会使用Fallback语义指定的Unity Shader。这是因为不同的显卡具有不同的能力,一些旧的显卡仅能支持一定数目的操作指令,而一些更高级的显卡可以支持更多的指令数。因此,我们希望在旧的显卡上使用计算复杂度较低的着色器,而在高级的显卡上使用计算复杂度较高的着色器,以提供更出色的面效果。 SubShader的标签是一个键值对,它的键和值都是字符串类型。这些键值对是SubShader和渲染引擎之间的沟通桥梁,用来告诉Unity的渲染引擎应该以怎样的方式和何时渲染这个对象。具体情况可以查看相关的表格。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值