[Unity] 关于unity shader 屏幕后处理的问题

查看:788 |回复:4 | 2021-2-23 10:05:49

您需要 登录 才可以下载或查看,没有账号?注册

x
本帖最后由 源数之力 于 2021-3-2 14:42 编辑

有个关于shader的问题想请教下,我弄了一个屏幕后处理的特效,在onRenderimage里调用graphics.bilt(src,dest,mat)为整个屏幕内物体描边,mat是带上描边shader的材质球 。现在想为另外一个单独物体进行描边(就是想让这个物体单独运行一次描边shader),我查询了好久,好像graphics.drawmesh可以达到这种效果,但是不知道具体操作,求指教
2021-2-23 10:05:49  
 赞 赞 0

使用道具 登录

4个回答,把该问题分享到群,邀请大神一起回答。
2#
我们先进行一版简单的拉伸效果实验。


  • fixed4 frag(v2f_img i) : SV_Target



  •         {



  •                 //计算uv到中间点的向量(向外扩,反过来就是向里缩)



  •                 float2 dv = float2(0.5, 0.5) - i.uv;



  •                 //归一化



  •                 float2 dv1 = normalize(dv);



  •                 //计算每个像素uv的偏移值



  •                 float2 offset = dv1  * _distanceFactor;



  •                 //像素采样时偏移offset



  •                 float2 uv = offset + i.uv;



  •                 return tex2D(_MainTex, uv);       



  •         }


效果如下图所示,有一种恐怖的赶脚:

如果我们修改一个地方,将shader中的dv1反向,就会得到一种收缩的效果,如果以后做这种类似的效果,可以考虑用这个实现哈:




2.波纹式拉伸
第一步实现了整体拉伸,但是这并不是我们想要的波纹效果,我们如果给一个线性的输入,通过什么样的函数能够得到类似波纹效果的输出呢?
对了,就是传说中的三角函数,我们通过一个sin值,可以把线性的输入变化成波形的输出,这样就可以模拟了水波纹的效果。知道了用什么函数,函数的输入和输出分别是什么,就是偶们下一步要考虑的问题了。我们上一步中是通过像素采样时uv坐标增加一个偏移值来达到拉伸的效果,我们就可以让这个偏移值作为这个三角函数的输出,这样,有的地方拉伸的少,有的地方拉伸的多,这样就形成了不同的拉伸效果,也就形成了一个波纹的感觉。那么,输入就很明显了,输入就是距离中心位置的绝对距离。上代码:



  • fixed4 frag(v2f_img i) : SV_Target



  •         {



  •                 //计算uv到中间点的向量(向外扩,反过来就是向里缩)



  •                 float2 dv = float2(0.5, 0.5) - i.uv;



  •                 //计算像素点距中点的距离



  •                 float dis = sqrt(dv.x * dv.x + dv.y * dv.y);



  •                 //用sin函数计算出波形的偏移值factor



  •                 //dis在这里都是小于1的,所以我们需要乘以一个比较大的数,比如60,这样就有多个波峰波谷



  •                 //sin函数是(-1,1)的值域,我们希望偏移值很小,所以这里我们缩小100倍,据说乘法比较快,so...



  •                 float sinFactor = sin(dis * _distanceFactor) * _totalFactor * 0.01;



  •                 //归一化



  •                 float2 dv1 = normalize(dv);



  •                 //计算每个像素uv的偏移值



  •                 float2 offset = dv1  * sinFactor;



  •                 //像素采样时偏移offset



  •                 float2 uv = offset + i.uv;



  •                 return tex2D(_MainTex, uv);       



  •         }


结果如下:


3.让波纹动起来有了波纹效果,我们下一步的操作是让波纹动起来,说道动起来,最简单的办法就是随着时间调整某个参数。这个值我们可以从外界传递进来,也可以使用shader中内置的_Time变量。附上一张Unity官方的变量说明,一些常用的shader中特殊内置变量:



我们直接在sin函数的输入中增加一个Time变量,这样,波纹就不是固定不变的效果,而是逐渐向外扩展的效果了。



  • fixed4 frag(v2f_img i) : SV_Target



  •         {



  •                 //计算uv到中间点的向量(向外扩,反过来就是向里缩)



  •                 float2 dv = float2(0.5, 0.5) - i.uv;



  •                 //计算像素点距中点的距离



  •                 float dis = sqrt(dv.x * dv.x + dv.y * dv.y);



  •                 //用sin函数计算出波形的偏移值factor



  •                 //dis在这里都是小于1的,所以我们需要乘以一个比较大的数,比如60,这样就有多个波峰波谷



  •                 //sin函数是(-1,1)的值域,我们希望偏移值很小,所以这里我们缩小100倍,据说乘法比较快,so...



  •                 float sinFactor = sin(dis * _distanceFactor + _Time.y * _timeFactor) * _totalFactor * 0.01;



  •                 //归一化



  •                 float2 dv1 = normalize(dv);



  •                 //计算每个像素uv的偏移值



  •                 float2 offset = dv1  * sinFactor;



  •                 //像素采样时偏移offset



  •                 float2 uv = offset + i.uv;



  •                 return tex2D(_MainTex, uv);       



  •         }




好了,这次来一张gif,(折腾了半天,终于用Fraps+迅雷看看播放器做了个GIF图片):



4.怎么把波形变成圆形
首先,我们注意到这里波纹是全屏幕的,会按照屏幕的分辨率进行变化,波纹不是真正的圆形,而是一个椭圆,作为一个强迫症,真是很蛋疼,所以,要处理一下这个问题,其实也很简单,我们在计算distance的时候,按照屏幕的长宽比将dis进行一下缩放就可以了,也就是在我们计算dis之前增加下面一步操作:


  •         //按照屏幕长宽比进行缩放



  •                 dv = dv * float2(_ScreenParams.x / _ScreenParams.y, 1);


这下,我们的波纹就成了一个真正的圆形,强迫症终于不难受了....




5.让波形从中间从小到大扩散出去
我们已经可以模拟波形效果了,剩下的问题是,我们想像黑魂那种,让波形从画面的中间从小到大,扩散出去,只播放一次,而不是像我们现在这种无限循环的鬼畜。不过这个操作我们直接在shader里面就不好实现了,需要用外面的脚本来进行配合,一种思路是,从外界传递进来一个值,这个值随着时间逐渐增大,只有在距离这个值小于波纹宽度的部分才进行波纹操作,这样就可以模拟波纹从中间产生,然后一点一点向外界扩散的效果了。该部分代码直接在最终的代码中给出,此处只贴上一张原理图:



三.代码实现
上面已经详述了原理以及各种问题,这一部分不多说,直接上代码。

shader部分:


  • Shader "Custom/WaterWave Effect"



  • {



  •         Properties



  •         {



  •                 _MainTex ("Base (RGB)", 2D) = "white" {}



  •         }







  •         CGINCLUDE



  •         #include "UnityCG.cginc"



  •         uniform sampler2D _MainTex;



  •         uniform float _distanceFactor;



  •         uniform float _timeFactor;



  •         uniform float _totalFactor;



  •         uniform float _waveWidth;



  •         uniform float _curWaveDis;







  •         fixed4 frag(v2f_img i) : SV_Target



  •         {



  •                 //计算uv到中间点的向量(向外扩,反过来就是向里缩)



  •                 float2 dv = float2(0.5, 0.5) - i.uv;



  •                 //按照屏幕长宽比进行缩放



  •                 dv = dv * float2(_ScreenParams.x / _ScreenParams.y, 1);



  •                 //计算像素点距中点的距离



  •                 float dis = sqrt(dv.x * dv.x + dv.y * dv.y);



  •                 //用sin函数计算出波形的偏移值factor



  •                 //dis在这里都是小于1的,所以我们需要乘以一个比较大的数,比如60,这样就有多个波峰波谷



  •                 //sin函数是(-1,1)的值域,我们希望偏移值很小,所以这里我们缩小100倍,据说乘法比较快,so...



  •                 float sinFactor = sin(dis * _distanceFactor + _Time.y * _timeFactor) * _totalFactor * 0.01;



  •                 //距离当前波纹运动点的距离,如果小于waveWidth才予以保留,否则已经出了波纹范围,factor通过clamp设置为0



  •                 float discardFactor = clamp(_waveWidth - abs(_curWaveDis - dis), 0, 1);



  •                 //归一化



  •                 float2 dv1 = normalize(dv);



  •                 //计算每个像素uv的偏移值



  •                 float2 offset = dv1  * sinFactor * discardFactor;



  •                 //像素采样时偏移offset



  •                 float2 uv = offset + i.uv;



  •                 return tex2D(_MainTex, uv);       



  •         }







  •         ENDCG







  •         SubShader



  •         {



  •                 Pass



  •                 {



  •                         ZTest Always



  •                         Cull Off



  •                         ZWrite Off



  •                         Fog { Mode off }







  •                         CGPROGRAM



  •                         #pragma vertex vert_img



  •                         #pragma fragment frag



  •                         #pragma fragmentoption ARB_precision_hint_fastest



  •                         ENDCG



  •                 }



  •         }



  •         Fallback off



  • }




c#部分:


  • using UnityEngine;







  • public class WaterWaveEffect : PostEffectBase {











  •     //距离系数



  •     public float distanceFactor = 60.0f;



  •     //时间系数



  •     public float timeFactor = -30.0f;



  •     //sin函数结果系数



  •     public float totalFactor = 1.0f;







  •     //波纹宽度



  •     public float waveWidth = 0.3f;



  •     //波纹扩散的速度



  •     public float waveSpeed = 0.3f;







  •     private float waveStartTime;







  •     void OnRenderImage (RenderTexture source, RenderTexture destination)



  •     {



  •         //计算波纹移动的距离,根据enable到目前的时间*速度求解



  •         float curWaveDistance = (Time.time - waveStartTime) * waveSpeed;



  •         //设置一系列参数



  •         _Material.SetFloat("_distanceFactor", distanceFactor);



  •         _Material.SetFloat("_timeFactor", timeFactor);



  •         _Material.SetFloat("_totalFactor", totalFactor);



  •         _Material.SetFloat("_waveWidth", waveWidth);



  •         _Material.SetFloat("_curWaveDis", curWaveDistance);







  •                 Graphics.Blit (source, destination, _Material);



  •         }







  •     void OnEnable()



  •     {



  •         //设置startTime



  •         waveStartTime = Time.time;



  •     }



  • }




注:PsotEffectBase为后处理基类,在 之前的文章中有完整实现,此处不予贴出。


最终结果如下面的gif所示:




四.点击屏幕触发水波纹效果

今天突然想到个好玩的,反正晚上闲着,就加了个点击屏幕触发水波纹的效果。我们可以很容易的知道点击屏幕后触点的坐标,然后把这个坐标转化到(0,1)空间,就可以很容易地对应到屏幕这张RenderTexture上的纹理坐标了,然后我们就可以把上面基于屏幕纹理中心点改成我们点击屏幕后的坐标,这样就实现了点击屏幕触发水波纹的效果。

shader部分:


  • Shader "Custom/WaterWave Effect"



  • {



  •         Properties



  •         {



  •                 _MainTex ("Base (RGB)", 2D) = "white" {}



  •         }







  •         CGINCLUDE



  •         #include "UnityCG.cginc"



  •         uniform sampler2D _MainTex;



  •         float4 _MainTex_TexelSize;



  •         uniform float _distanceFactor;



  •         uniform float _timeFactor;



  •         uniform float _totalFactor;



  •         uniform float _waveWidth;



  •         uniform float _curWaveDis;



  •         uniform float4 _startPos;







  •         fixed4 frag(v2f_img i) : SV_Target



  •         {



  •                 //DX下纹理坐标反向问题



  •                 #if UNITY_UV_STARTS_AT_TOP



  •                 if (_MainTex_TexelSize.y < 0)



  •                         _startPos.y = 1 - _startPos.y;



  •                 #endif



  •                 //计算uv到中间点的向量(向外扩,反过来就是向里缩)



  •                 float2 dv = _startPos.xy - i.uv;



  •                 //按照屏幕长宽比进行缩放



  •                 dv = dv * float2(_ScreenParams.x / _ScreenParams.y, 1);



  •                 //计算像素点距中点的距离



  •                 float dis = sqrt(dv.x * dv.x + dv.y * dv.y);



  •                 //用sin函数计算出波形的偏移值factor



  •                 //dis在这里都是小于1的,所以我们需要乘以一个比较大的数,比如60,这样就有多个波峰波谷



  •                 //sin函数是(-1,1)的值域,我们希望偏移值很小,所以这里我们缩小100倍,据说乘法比较快,so...



  •                 float sinFactor = sin(dis * _distanceFactor + _Time.y * _timeFactor) * _totalFactor * 0.01;



  •                 //距离当前波纹运动点的距离,如果小于waveWidth才予以保留,否则已经出了波纹范围,factor通过clamp设置为0



  •                 float discardFactor = clamp(_waveWidth - abs(_curWaveDis - dis), 0, 1) / _waveWidth;



  •                 //归一化



  •                 float2 dv1 = normalize(dv);



  •                 //计算每个像素uv的偏移值



  •                 float2 offset = dv1  * sinFactor * discardFactor;



  •                 //像素采样时偏移offset



  •                 float2 uv = offset + i.uv;



  •                 return tex2D(_MainTex, uv);       



  •         }







  •         ENDCG







  •         SubShader



  •         {



  •                 Pass



  •                 {



  •                         ZTest Always



  •                         Cull Off



  •                         ZWrite Off



  •                         Fog { Mode off }







  •                         CGPROGRAM



  •                         #pragma vertex vert_img



  •                         #pragma fragment frag



  •                         #pragma fragmentoption ARB_precision_hint_fastest



  •                         ENDCG



  •                 }



  •         }



  •         Fallback off



  • }



C#部分:


  • using UnityEngine;







  • public class WaterWaveEffect : PostEffectBase {











  •     //距离系数



  •     public float distanceFactor = 60.0f;



  •     //时间系数



  •     public float timeFactor = -30.0f;



  •     //sin函数结果系数



  •     public float totalFactor = 1.0f;







  •     //波纹宽度



  •     public float waveWidth = 0.3f;



  •     //波纹扩散的速度



  •     public float waveSpeed = 0.3f;







  •     private float waveStartTime;



  •     private Vector4 startPos = new Vector4(0.5f, 0.5f, 0, 0);











  •     void OnRenderImage (RenderTexture source, RenderTexture destination)



  •     {



  •         //计算波纹移动的距离,根据enable到目前的时间*速度求解



  •         float curWaveDistance = (Time.time - waveStartTime) * waveSpeed;



  •         //设置一系列参数



  •         _Material.SetFloat("_distanceFactor", distanceFactor);



  •         _Material.SetFloat("_timeFactor", timeFactor);



  •         _Material.SetFloat("_totalFactor", totalFactor);



  •         _Material.SetFloat("_waveWidth", waveWidth);



  •         _Material.SetFloat("_curWaveDis", curWaveDistance);



  •         _Material.SetVector("_startPos", startPos);



  •                 Graphics.Blit (source, destination, _Material);



  •         }







  •     void Update()



  •     {



  •         if (Input.GetMouseButton(0))



  •         {



  •             Vector2 mousePos = Input.mousePosition;



  •             //将mousePos转化为(0,1)区间



  •             startPos = new Vector4(mousePos.x / Screen.width, mousePos.y / Screen.height, 0, 0);



  •             waveStartTime = Time.time;



  •         }







  •     }



  • }



结果:



回复 收起回复
2021-2-23 10:24:28   回复
 赞 赞 1

使用道具 登录

3#
楼上的回答太专业了吧
回复 收起回复
2021-8-28 07:43:43   回复
 赞 赞 2

使用道具 登录

4#
屏幕后处理实际为利用shader处理最终呈现在相机中的图片。但是shader不能直接与c#数据传递,所以创建material来沟通。
1 创建shader
shader中的_MainTex为默认相机最终图片所以必须。然后就可以调整整张图片的风格渲染。
2 检查这个平台是不是支持图片效果。

protected bool CheckSupport() {
        if (SystemInfo.supportsImageEffects == false) {
            Debug.LogWarning("This platform does not support image effects.");
            return false;
        }

        return true;
    }
1

3 检查一步骤中的shader是不是被各个平台支持,并且创建材质

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;
        }
    }
1

4 在c#脚本中处理图片

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);
        }
    }

OnRenderImage函数会在所有不透明和透明的Pass执行完毕后被调用(可选)。参数src对应源纹理,dest为目标渲染纹理。通过material处理shader中的src纹理变为dest纹理。处理过程为material.SetFloat(“_Contrast”, contrast);等等。新纹理赋予旧纹理为Graphics.Blit(src, dest, material);处理的纹理为shader中的_MatinTex。
5源码
PostEffectsBase.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

    protected bool CheckSupport() {
        if (SystemInfo.supportsImageEffects == false) {
            Debug.LogWarning("This platform does not support image effects.");
            return false;
        }

        return true;
    }

    protected void NotSupported() {
        enabled = false;
    }

    protected void CheckResources() {
        bool isSupported = CheckSupport();

        if (isSupported == false) {
            NotSupported();
        }
    }

    protected void Start() {
        CheckResources();
    }

    // Called when need to create the material used by this effect
    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;
        }
    }
}

BrightnessSaturationAndContrast.cs

using UnityEngine;
using System.Collections;

public class BrightnessSaturationAndContrast : PostEffectsBase {

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

    [Range(0.0f, 3.0f)]
    public float brightness = 1.0f;

    [Range(0.0f, 3.0f)]
    public float saturation = 1.0f;

    [Range(0.0f, 3.0f)]
    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);
        }
    }
}

Brightness.shader

Shader "Custom/Brightness Saturation And Contrast" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Brightness ("Brightness", Float) = 1
        _Saturation("Saturation", Float) = 1
        _Contrast("Contrast", Float) = 1
    }
    SubShader {
        Pass {  
            ZTest Always Cull Off ZWrite Off

            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  

            #include "UnityCG.cginc"  

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

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

            v2f vert(appdata_img 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);  

                // Apply brightness
                fixed3 finalColor = renderTex.rgb * _Brightness;

                // Apply 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);

                // Apply contrast
                fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
                finalColor = lerp(avgColor, finalColor, _Contrast);

                return fixed4(finalColor, renderTex.a);  
            }  

            ENDCG
        }  
    }

    Fallback Off
}
————————————————
版权声明:本文为CSDN博主「gd_2015」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wdsdsdsds/article/details/79374162
回复 收起回复
2021-8-28 13:16:52   回复
 赞 赞 1

使用道具 登录

5#
元素是个好家园,加油啊
回复 收起回复
2022-11-3 17:59:05   回复
 赞 赞 1

使用道具 登录

CG 游戏行业专业问题

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表