Unity Shader学习记录(九)

Unity Shader学习记录(九)

  屏幕特效是一种很常见的平面特效,不同于直接在三维渲染中做出的特效,屏幕特效仅针对已经渲染完成的屏幕显示,通俗点说就是一张和屏幕大小相当的图片。这种针对已经渲染完成的屏幕图片进行特效处理的行为被称作“屏幕后处理”。
  实际使用场景中的屏幕后处理类型繁多,不同的效果各有各的针对,能良好地使用这种后处理能极大地增加游戏内容表现力,而随之带来的则是性能消耗的上升。现代3D游戏的图形图像设置选项中都会有“后处理”相关选项,该选项直接影响游戏的视觉效果和运行性能,其重要性可见一斑。
  Unity中有一套用于实现屏幕后处理的机制,其核心是RenderTexture和Shader,因此开发者只需要按照Unity给出的这套标准实现所需的效果便可。


后处理的入口

  Untiy中,屏幕后处理基本都是针对Camera组件的,该组件负责将自己“看到”的场景进行渲染并且输出到指定目标。因此,最简单直接的后处理就是在Camera对象上挂载一个脚本,并且在脚本中重写OnRenderImage方法。
  其中OnRenderImage方法的原型如下

MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest);

  很显然,这个方法会将src经过处理后输出到dest,那么这个输出过程如何完成呢?Unity的Graphics类提供了静态方法Blit可以帮助完成这个过程,Blit方法的原型如下。

public static void Blit(Texture src, RenderTexture dest);
public static void Blit(Texture src, RenderTexture dest, Material mat, int pass = -1);
public static void Blit(Texture src, Material mat, int pass = -1);

  从这个函数的原型可以看出,Unity允许在渲染纹理过程中使用材质,这里的材质往往是Unity提供的一种稍微有些特别的材质,它所用的Shader与众不同。
  首先这个Shader从外部接受了必要的参数,包括纹理贴图;其次这个Shader跟着OnRenderImage函数的调用时间来,换言之这个Shader是在在所有的透明或者不透明的渲染Pass执行完之后再执行的,因此它的深度写入必须关闭。
  在正式进入相关Shader的编写之前,需要先确定一个脚本,它包含了屏幕后处理脚本所需的基本方法,同时也预留了接口给不同的后处理脚本使用。

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectBase : MonoBehaviour {
    // Use this for initialization
    private void Start () {
        CheckResources();
        init();
    }

    // Update is called once per frame
    private void Update () {
        execute();
    }

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

    // 检查当前系统是否支持后处理
    // 需要注意的是,新版本里SystemInfo.supportsRenderTextures已经固定为true
    protected bool CheckSupport() {
        if(!SystemInfo.supportsImageEffects || !SystemInfo.supportsRenderTextures) {
            Debug.LogWarning("This platform does not support image effects or render textures.");
            return false;
        }
        return true;
    }

    // 如果不支持,则禁用该脚本
    protected void NotSupported() {
        enabled = false;
    }

    // 检查并生成材质
    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);
            material.hideFlags = HideFlags.DontSave;
            if(material) {
                return material;
            } else {
                return null;
            }
        }
    }

    protected virtual void init() { }
    protected virtual void execute() { }
}

  如上就是一个简单的基类,它包含了后处理重要的一段代码,即根据传入的Shader来生成Material,后处理的材质并不是直接创建在Asset中的,而是脚本运行过程中自行生成的。
  有了这个基类,下面就开始解析几种常见的后处理。


亮度,饱和度和对比度

  亮度,饱和度和对比度是非常常见的图像调整参数,几乎所有的图片处理工具都会提供调整这三个数字的地方,而在Unity中想要做到这一点,使用后处理机制是再合适不过了。
  在进入代码部分之前,首先要分析这三个参数都和什么东西有关,它们是怎么起效的。
  先看亮度Brightness,这个参数的含义很直白,就是图片的明亮程度,它起效的方式也很直接,只要将置顶像素点的RGB分量等比例放大即可,实际上拿出调色板工具就能看出,无论什么颜色,只要RGB分量值按比例变大,那么颜色就会变得明亮;如果按比例缩小,则颜色变得昏暗。
  然后是饱和度,这个概念比较复杂,一般而言可以简单认为它是度量一个颜色偏离其灰度颜色的程度,饱和度越高,则该颜色距离原灰度颜色越远,也就越“鲜艳”。大部分时候饱和度是个针对视觉的概念,尤其是在计算中,经常需要依赖经验公式。
  最后的对比度,顾名思义它是图片中各个不同颜色之间的“区分程度”,这个值越高,则不同颜色之间的区分度越大,直观的表现就是暗的部分更暗,亮的部分更亮。
  知道了这三个概念都是怎么回事后,接下来着手进行代码编写。
  第一步,需要一个挂载到Camera对象上的脚本。

public class BrightSaturateAdnContrast : PostEffectBase {
    public Shader briSatConShader;
    private Material briSatConMaterial;
    public Material material {
        get {
            briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
            return briSatConMaterial;
        }
    }

    [Range(0f, 3f)]
    public float brightness = 1.0f;
    [Range(0f, 3f)]
    public float saturation = 1.0f;
    [Range(0f, 3f)]
    public float contrast = 1.0f;

    void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if(material != null) {
            material.SetFloat("_Brightness", brightness);
            material.SetFloat("_Saturation", saturation);
            material.SetFloat("_Contrast", contrast);
            Graphics.Blit(src, dest, material);
        } else {
            Graphics.Blit(src, dest);
        }
    }
}

  继承了之前编写的通用基类后事情就简单了,要求配置一个Shader,然后生成一个Material,最后在OnRenderImage方法中为材质传入参数,用Blit方法渲染图像。
  所需的Shader如下,需要注意的是,如果手动创建,在Unity中的Shader次级菜单下选择ImageEffectShader能节约一些改写的时间。

Shader "Hidden/BSCShader" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Brightness ("Brightness", Float) = 1
        _Saturation ("Saturation", Float) = 1
        _Contrast ("Contrast", Float) = 1
    }
    SubShader {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            half _Brightness;
            half _Saturation;
            half _Contrast;

            struct appdata {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };

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

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

            fixed4 frag (v2f i) : SV_Target {
                fixed4 renderTex = tex2D(_MainTex, i.uv);
                // Brightness
                fixed3 finalColor = renderTex.rgb * _Brightness;
                // Saturation
                fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
                fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
                finalColor = lerp(luminanceColor, finalColor, _Saturation);
                // Contrast
                fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
                finalColor = lerp(avgColor, finalColor, _Contrast);
                return fixed4(finalColor, renderTex.a);
            }
            ENDCG
        }
    }
}

  注意到开头的Shader路径,这是直接创建ImageEffectShader的好处之一,因为后处理Shader往往并不需要用在材质上,因此放入隐藏菜单可以避免它出现在材质设置选项中,如果不是创建的ImageEffectShader,那么开发者必须自己修改这些代码。
  SubShader中的第一行,三个设置项可以说是后处理Shader的标配,关闭剔除,关闭深度写入,开启深度测试。
  之后的重头戏在片元处理函数里,可以看到亮度处理直接将颜色的RGB通道乘以亮度参数,饱和度处理使用了灰度计算的经验公式,即通过该公式计算得到的灰度值最接近人们“认为”正确的颜色灰度。
  最后的对比度则利用了简单的lerp方法,当对比度超过1时会按照一定的插值距离进行向后插值,达到提升对比度的效果;而如果对比度小于1则会逐渐靠近一个固定的灰色。
  至此,一个简单的调整渲染图像的亮度,饱和度和对比度的工具就可以

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值