【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度

时间:2021-10-13 10:48:51

本系列主要參考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同一时候会加上一点个人理解或拓展。

这里是本书全部的插图。

这里是本书所需的代码和资源(当然你也能够从官网下载)。

========================================== 切割线 ==========================================

写在前面

通过上一篇我们建立了一个简化的屏幕特效系统,在这一篇,我们開始学习创建更复杂的像素操作来实现一些在当代游戏中常见的屏幕特效。

使用屏幕特效来调整游戏的总体色彩是非常重要的,这给了设计师控制游戏最后全貌的能力。

比方,通过颜色调整条来调整游戏终于画面的红蓝绿三色的密度,或者通过加入一定的色调使整个屏幕看起来像是老电影那样的效果。

这篇文章里,我们将会很多其它地学习关于调整图像颜色方面的内容。也就是,亮度、饱和度和对照度。学习这些调整颜色的方法能够让我们对屏幕特效有一个非常好的理解。

实现

这一篇非常多代码是建立在上一篇的基础上,所以非常多代码不用写啦~

  1. 创建一个新的脚本,命名为BSC_ImageEffect;
  2. 创建一个新的Shader。命名为BSC_Effect。
  3. 上一篇中的C#代码拷贝到第一步中创建的脚本中,我们仅仅须要关注亮度、饱和度和对照度效果的运算。
  4. 上一篇中的Shader代码拷贝到第二步中创建的Shader中。
  5. 把新的脚本加入到Camera上,并使用新的Shader给脚本中的Cur Shader赋值。
  6. 创建一个新的场景,命名为BSC_Effect。加入一个平行光,三个球体。以及一个平面。

    创建4个新材质,使用自带的Specular Shader就可以,随意选择四种颜色为它们赋值,这能够使我们更好地检验我们的画面特效。最后。把4个材质赋给球体和平面。

最后,你会得到相似于以下的效果:
【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度

实现

由于我们之前的代码已经完毕了建立一个主要的画面特效所需的核心操作,如今我们仅仅要完毕亮度、饱和度和对照度计算的代码就可以。
首先,我们来完毕Shader代码。
  1. 加入亮度、饱和度和对照度相应的新的Properties,我们要保留_MainTex属性,这是由于在创建画面特效时。该属性就是脚本传递给我们的render texture。
    	Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _BrightnessAmount ("Brightness Amount", Range(0.0, 2.0)) = 1.0
    _SaturationAmount ("Saturation Amount", Range(0.0, 1.0)) = 1.0
    _ContrastAmount ("Contrast Amount", Range(0.0, 1.0)) = 1.0
    }
  2. 和曾经一样。在CGPROGRAM中建立和上述Properties之间的联系,创建相应的变量:
    	SubShader {
    Pass {
    CGPROGRAM
    #pragma vertex vert_img
    #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex;
    fixed _BrightnessAmount;
    fixed _SaturationAmount;
    fixed _ContrastAmoun
  3. 如今,我们须要创建操作来进行真正的亮度、饱和度和对照度的计算。在frag函数上面创建以下的函数:
    			float3 ContrastSaturationBrightness (float3 color, float brt, float sat, float con) {
    // Increase or decrease these values to
    // adjust r, g and b color channels separately
    float avgLumR = 0.5;
    float avgLumG = 0.5;
    float avgLumB = 0.5; // Luminance coefficients for getting luminance from the image
    float3 LuminanceCoeff = float3 (0.2125, 0.7154, 0.0721); // Operation for brightmess
    float3 avgLumin = float3 (avgLumR, avgLumG, avgLumB);
    float3 brtColor = color * brt;
    float intensityf = dot (brtColor, LuminanceCoeff);
    float3 intensity = float3 (intensityf, intensityf, intensityf); // Operation for saturation
    float3 satColor = lerp (intensity, brtColor, sat); // Operation for contrast
    float3 conColor = lerp (avgLumin, satColor, con); return conColor;
    }

    解释:这个函数的第一个參数是当前的render texture的某个像素。其它參数则是用于总体调整颜色,这些变量会通过后面的脚本传递给Shader。

    函数一開始。声明了一些常量。它们被用于定义一个最主要的颜色值,以便和改动后的进行比較。

    书上讲这个函数的实现的地方比較含糊不清,看不懂。我按自己的理解解释一下。这事实上是一个叠加的过程:先计算当前亮度值下的像素,再在此基础上计算当前饱和度值下的像素。最后再在此基础上计算当前对照度下的像素,并输出:
    —— 当前亮度值下的像素是通过使用render texture上原始的像素乘以亮度值实现的,这非常好理解,brt越大(能够大于1),图像越偏向白色,也就越亮。

    —— 计算当前饱和度值下的像素。须要使用lerp函数。当中,lerp的右边界值就是上一步得到的像素值,而左边界值是当前亮度下饱和度最低的像素值。计算方法是在当前亮度的基础上,点乘LuminanceCoeff常量系数。这些系数是基于CIE颜色匹配函数的,也是业界公认的标准。

    最后,sat參数在两者之间进行线性插值。sat越大(能够大于1),brtColor的份重越大。

    —— 计算当前对照度下的像素是相似的过程。相同使用lerp函数。当中。lerp的右边界是上一步得到的像素值,左边界值是对照度最低的像素值。这里使用了常量avgLumin。最后,con參数在两者之间进行线性插值。

    con越大(能够大于1),satColor的份重越大。

  4. 最后。我们须要略微改动下frag函数。使得它能够逐像素处理render texture中的每一个像素。然后再又一次输出,返回给脚本:
    			fixed4 frag(v2f_img i) : COLOR {
    //Get the colors from the RenderTexture and the uv's
    //from the v2f_img struct
    fixed4 renderTex = tex2D(_MainTex, i.uv); //Apply the brightness, saturation, contrast operations
    renderTex.rgb = ContrastSaturationBrightness (renderTex.rgb, _BrightnessAmount, _SaturationAmount, _ContrastAmount); return renderTex;
    }
以下是编写脚本。我们须要在脚本中加入新的代码,以便能够向Shader发送合适的数据信息:
  1. 首先加入合适的变量来控制亮度、饱和度和对照度:
    	#region Variables
    public Shader curShader;
    public float brightnessAmount = 1.0f;
    public float saturationAmount = 1.0f;
    public float contrastAmount = 1.0f; private Material curMaterial;
    #endregion
  2. 然后,在OnRenderImage函数中。将新的值传递给Shader:
    	void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){
    if (curShader != null) {
    material.SetFloat("_BrightnessAmount", brightnessAmount);
    material.SetFloat("_SaturationAmount", saturationAmount);
    material.SetFloat("_ContrastAmount", contrastAmount); Graphics.Blit(sourceTexture, destTexture, material);
    } else {
    Graphics.Blit(sourceTexture, destTexture);
    }
    }
  3. 最后。我们仅仅须要在函数中,确保各变量的范围就可以。这些范围能够依据须要随意设置:
    	// Update is called once per frame
    void Update () {
    brightnessAmount = Mathf.Clamp(brightnessAmount, 0.0f, 2.0f);
    saturationAmount = Mathf.Clamp(saturationAmount, 0.0f, 2.0f);
    contrastAmount = Mathf.Clamp(contrastAmount, 0.0f, 3.0f);
    }
完毕后,返回Unity。我们这时能够在面板中更改亮度、饱和度和对照度了。

以下的图显示了它的结果:

【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度  【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2FuZHljYXQxOTky/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

解释

相似这种屏幕特效对于得到高质量的游戏画面是非常重要的。它们同意你调整游戏的终于画面而不须要去编辑场景中全部的材质。