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

本系列主要参考《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个材质赋给球体和平面。

最后,你会得到类似于下面的效果:




实现


由于我们之前的代码已经完成了建立一个基本的画面特效所需的核心操作,现在我们只要完成亮度、饱和度和对比度计算的代码即可。

首先,我们来完成Shader代码。
  1. 添加亮度、饱和度和对比度对应的新的Properties,我们要保留_MainTex属性,这是因为在创建画面特效时,该属性就是脚本传递给我们的render texture。
    [plain]  view plain  copy
     print ?
    1. Properties {  
    2.     _MainTex ("Base (RGB)", 2D) = "white" {}  
    3.     _BrightnessAmount ("Brightness Amount", Range(0.0, 2.0)) = 1.0  
    4.     _SaturationAmount ("Saturation Amount", Range(0.0, 1.0)) = 1.0  
    5.     _ContrastAmount ("Contrast Amount", Range(0.0, 1.0)) = 1.0  
    6. }  

  2. 和以前一样,在CGPROGRAM中建立和上述Properties之间的联系,创建对应的变量:
    [plain]  view plain  copy
     print ?
    1. SubShader {  
    2.     Pass {  
    3.         CGPROGRAM  
    4.         #pragma vertex vert_img  
    5.         #pragma fragment frag  
    6.           
    7.         #include "UnityCG.cginc"  
    8.           
    9.         uniform sampler2D _MainTex;  
    10.         fixed _BrightnessAmount;  
    11.         fixed _SaturationAmount;  
    12.         fixed _ContrastAmoun  

  3. 现在,我们需要创建操作来进行真正的亮度、饱和度和对比度的计算。在frag函数上面创建下面的函数:
    [plain]  view plain  copy
     print ?
    1. float3 ContrastSaturationBrightness (float3 color, float brt, float sat, float con) {  
    2.     // Increase or decrease these values to  
    3.     // adjust r, g and b color channels separately  
    4.     float avgLumR = 0.5;  
    5.     float avgLumG = 0.5;  
    6.     float avgLumB = 0.5;  
    7.       
    8.     // Luminance coefficients for getting luminance from the image  
    9.     float3 LuminanceCoeff = float3 (0.2125, 0.7154, 0.0721);  
    10.       
    11.     // Operation for brightmess  
    12.     float3 avgLumin = float3 (avgLumR, avgLumG, avgLumB);  
    13.     float3 brtColor = color * brt;  
    14.     float intensityf = dot (brtColor, LuminanceCoeff);  
    15.     float3 intensity = float3 (intensityf, intensityf, intensityf);  
    16.       
    17.     // Operation for saturation  
    18.     float3 satColor = lerp (intensity, brtColor, sat);  
    19.       
    20.     // Operation for contrast  
    21.     float3 conColor = lerp (avgLumin, satColor, con);  
    22.       
    23.     return conColor;  
    24. }  

    解释:这个函数的第一个参数是当前的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中的每个像素,然后再重新输出,返回给脚本:
    [plain]  view plain  copy
     print ?
    1. fixed4 frag(v2f_img i) : COLOR {  
    2.     //Get the colors from the RenderTexture and the uv's  
    3.     //from the v2f_img struct  
    4.     fixed4 renderTex = tex2D(_MainTex, i.uv);  
    5.       
    6.     //Apply the brightness, saturation, contrast operations  
    7.     renderTex.rgb = ContrastSaturationBrightness (renderTex.rgb, _BrightnessAmount, _SaturationAmount, _ContrastAmount);  
    8.       
    9.     return renderTex;  
    10. }  


下面是编写脚本。我们需要在脚本中添加新的代码,以便可以向Shader发送合适的数据信息:
  1. 首先添加合适的变量来控制亮度、饱和度和对比度:
    [plain]  view plain  copy
     print ?
    1. #region Variables  
    2. public Shader curShader;  
    3. public float brightnessAmount = 1.0f;  
    4. public float saturationAmount = 1.0f;  
    5. public float contrastAmount = 1.0f;  
    6.   
    7. private Material curMaterial;  
    8. #endregion  

  2. 然后,在OnRenderImage函数中,将新的值传递给Shader:
    [plain]  view plain  copy
     print ?
    1. void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){  
    2.     if (curShader != null) {  
    3.         material.SetFloat("_BrightnessAmount", brightnessAmount);  
    4.         material.SetFloat("_SaturationAmount", saturationAmount);  
    5.         material.SetFloat("_ContrastAmount", contrastAmount);  
    6.           
    7.         Graphics.Blit(sourceTexture, destTexture, material);  
    8.     } else {  
    9.         Graphics.Blit(sourceTexture, destTexture);  
    10.     }  
    11. }  

  3. 最后,我们只需要在函数中,确保各变量的范围即可。这些范围可以根据需要任意设置:
    [plain]  view plain  copy
     print ?
    1. // Update is called once per frame  
    2. void Update () {  
    3.     brightnessAmount = Mathf.Clamp(brightnessAmount, 0.0f, 2.0f);  
    4.     saturationAmount = Mathf.Clamp(saturationAmount, 0.0f, 2.0f);  
    5.     contrastAmount = Mathf.Clamp(contrastAmount, 0.0f, 3.0f);  
    6. }  

完成后,返回Unity。我们这时可以在面板中更改亮度、饱和度和对比度了。下面的图显示了它的结果:
  



解释


类似这样的屏幕特效对于得到高质量的游戏画面是非常重要的,它们允许你调整游戏的最终画面而不需要去编辑场景中所有的材质。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值