| 
 又是一个post-process后期效果,god ray 上帝之光,说起上帝之光就是咱们再看太阳时太阳周围一圈的针状光芒 
先放组效果,本文的场景资源均来自浅墨大神,效果为本文shader效果 
 
       本文的代码是来自unity圣典中某大神的分享,博主做了小小的改进 链接 
然后就来做下讲解,共有两个shader,一个负责制造ray,一个负责和原屏幕图像混合,于原屏幕图像混合很简单,就是单纯的把两个图像的颜色叠加,控制一下ray的权重, 
接下来我们着重讲解一下,制造ray的shader 
是一个fragement shader 
共有4个外部变量 
_ScreenLightPos屏幕上光线的位置,这个需要在c#脚本中计算并传出,稍后会讲解 
_Density密度 
_Decay衰减 
_Exposure曝光,用来控制亮度,大家都知道,在相机中,曝光时间越长图像越亮 
 
先看vertex shader 
      [mw_shl_code=applescript,true]v2f vert(v2in v) 
        { 
                v2f o; 
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 
 
                half2 texCoord = v.texcoord; 
                half2 deltaTexCoord = texCoord - _ScreenLightPos.xy; 
                deltaTexCoord *= 1.0f / 8 * _Density; 
 
                texCoord -= deltaTexCoord; 
                o.uv0 = texCoord; 
                texCoord -= deltaTexCoord; 
                o.uv1 = texCoord; 
                texCoord -= deltaTexCoord; 
                o.uv2 = texCoord; 
                texCoord -= deltaTexCoord; 
                o.uv3 = texCoord; 
                texCoord -= deltaTexCoord; 
                o.uv4 = texCoord; 
                texCoord -= deltaTexCoord; 
                o.uv5 = texCoord; 
                texCoord -= deltaTexCoord; 
                o.uv6 = texCoord; 
                texCoord -= deltaTexCoord; 
                o.uv7 = texCoord; 
                return o; 
        }[/mw_shl_code]v.texcoord为当前点的坐标 
 
deltaTexCoord为当前点对光源点的反向向量,长度为两点间距离 
 
密度越大deltaTexCoord越大,不超过8,deltaTexCoord始终是个分数 
第一个采样点为此处本来位置 
采样点渐渐接进光源处 
_Density越大采样点间距越大 
从0到7,点的位置从光源处越来越近,离此处点越来越远 
看看我们的v2f结构体,存了多少坐标点 [mw_shl_code=applescript,true]struct v2f { 
                float4 pos : POSITION; 
                float2 uv0 : TEXCOORD0; 
                float2 uv1 : TEXCOORD1; 
                float2 uv2 : TEXCOORD2; 
                float2 uv3 : TEXCOORD3; 
                float2 uv4 : TEXCOORD4; 
                float2 uv5 : TEXCOORD5; 
                float2 uv6 : TEXCOORD6; 
                float2 uv7 : TEXCOORD7; 
        };[/mw_shl_code] 
 传入值的结构体v2in  
 [mw_shl_code=applescript,true]struct v2in { 
                float4 vertex : POSITION; 
                float2 texcoord : TEXCOORD0; 
        };[/mw_shl_code] 
  
我们就得到了当前点到光源点的一条直线中的八个点的坐标,为fragement shader取色混色用 
当然本步骤也可在fragement shader中完成,但效率没有vertex shader好,因为不用每个像素都取样,只是每个顶点取样就好 
 
再看fragement shader 
[mw_shl_code=applescript,true]  half4 frag(v2f i) : COLOR 
    { 
        half illuminationDecay = 1.0f; 
 
 
        half4 color = tex2D(_MainTex, i.uv0)*illuminationDecay; 
        illuminationDecay *= _Decay; 
        color += tex2D(_MainTex, i.uv1)*illuminationDecay; 
        illuminationDecay *= _Decay; 
        color += tex2D(_MainTex, i.uv2)*illuminationDecay; 
        illuminationDecay *= _Decay; 
        color += tex2D(_MainTex, i.uv3)*illuminationDecay; 
        illuminationDecay *= _Decay; 
        color += tex2D(_MainTex, i.uv4)*illuminationDecay; 
        illuminationDecay *= _Decay; 
        color += tex2D(_MainTex, i.uv5)*illuminationDecay; 
        illuminationDecay *= _Decay; 
        color += tex2D(_MainTex, i.uv6)*illuminationDecay; 
        illuminationDecay *= _Decay; 
        color += tex2D(_MainTex, i.uv7)*illuminationDecay; 
 
        color /= 8; 
 
        return half4(color.xyz * _Exposure, 1); 
 
    }[/mw_shl_code] 
 
    
illuminationDecay光照衰减,_Decay是我们外部可控衰减 
 _Exposure增加亮度 
 调整比重离此处像素点越远也就是离光源越近越衰减,可能有人会问,为什么会这样?因为我们还是要保留大部分为此处点的颜色,如果其他像素权重过大,则会造成此处点颜色不准确,甚至不好的模糊效果。 
然后就是混色,基本上的原理就是从光源处打出无数条射线,嗯,可以这么理解。 
 
Ray我们就制造好了,接下来我们需要把光线ray与原屏幕图像混合,这一步就比较简单了,只给出源代码,各位自己意会。 
[mw_shl_code=applescript,true]Shader "Custom/god ray 2 blend" { 
                Properties{ 
                _MainTex("Base (RGB)", 2D) = "" {} 
                _GodRayTex ("God (RGB)", 2D) = ""{} 
                _Alpha("_Alpha", Float) = 0.5 
        } 
 
 
 
                // Shader code pasted into all further CGPROGRAM blocks 
                CGINCLUDE 
 
#include "UnityCG.cginc" 
 
                struct v2in { 
                        float4 vertex : POSITION; 
                        float2 texcoord : TEXCOORD0; 
                }; 
 
                struct v2f { 
                        float4 pos : POSITION; 
                        float2 uv : TEXCOORD0; 
                }; 
 
                sampler2D _MainTex; 
 
                sampler2D _GodRayTex; 
 
                uniform float _Alpha; 
 
                v2f vert(v2in v) 
                { 
                        v2f o; 
                        o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 
                        o.uv = v.texcoord; 
                        return o; 
                } 
 
 
 
                half4 frag(v2f i) : COLOR 
                { 
                        half4 color = tex2D(_MainTex, i.uv) + tex2D(_GodRayTex, i.uv)*_Alpha; 
                        //half4 color = tex2D(_MainTex, i.uv); 
 
                        return color; 
                } 
 
                ENDCG 
 
                        Subshader{ 
 
                        Tags{ "Queue" = "Transparent" } 
 
                        Pass{ 
                                ZWrite Off 
 
                                BindChannels 
                                { 
                                Bind "Vertex", vertex 
                                Bind "texcoord", texcoord0 
                                Bind "texcoord1", texcoord1 
                        } 
 
                                Fog{ Mode off } 
                                CGPROGRAM 
#pragma fragmentoption ARB_precision_hint_fastest  
#pragma vertex vert 
#pragma fragment frag 
                                        ENDCG 
                        } 
 
                } 
 
                Fallback off 
 
        } // shader 
[/mw_shl_code] 
  
 
然后就是最后一步,也是十分重要的一步就是通过脚本把它弄到屏幕上, 
此处的要点就是要求出光源在屏幕中的位置, 
Camera类中有这么一个函数可以把世界坐标转换为屏幕坐标 
Camera.WorldToScreenPoint(position) 
官网介绍如下 
Transforms position from world space into screen space. 
把position从世界坐标转换为屏幕坐标 
Screenspace is defined in pixels. The bottom-left of the screen is (0,0); the right-top is (pixelWidth,pixelHeight). The z position is in world units from the camera. 
左下角是屏幕坐标系的原点,右上角是屏幕的最大范围,超出这个范围的光源我们都不进行god ray渲染了,以此作为判断,否则就会进行错误渲染,屏幕超出光照范围了仍在闪烁。 
 
我们把光源的transport传入脚本,然后检验光源的position 
另 外还有重要一点就是判断光源在相机前面还是在后面,如果只判断是否在屏幕内的话,相机转到光源后面也会被渲染god ray,解决方法在此,WorldToScreenPoint返回的z值为世界空间内光源与相机的距离,为矢量,所以我们就能用z值正负来判断前后了,为 正则光源在相机前可渲染god ray,为负则光源在相机后不可渲染god ray 
if (lightScreenPos.z > 0 && lightScreenPos.x > 0 && lightScreenPos.x < camera.pixelWidth  && lightScreenPos.y >0 && lightScreenPos.y < camera.pixelHeight) 
  
其实就这么渲染也可以,但是效果并不好,god ray变成了“god point”,原因刚才分析的,shader的原理是取点到光源的八个点,那渲染的结果也就是出现了好多点,层次很分明,就是因为之混乱和了那8次,解决方式就是多次渲染,点多了,就变成线了 
我们要想使效果更好一点就要多次渲染 
建立两个renderTexure tempRtA和tempRtB用来互相传值 
 
                Graphics.Blit(sourceTexture, tempRtA, material); 
第一次过滤结果存在tempRtA 
传到下一次渲染做_MainTex 
                Graphics.Blit(tempRtA, tempRtB, material); 
再传出tempRtB到第三次渲染,再传出tempRtA。。。 
                Graphics.Blit(tempRtB, tempRtA, material); 
                Graphics.Blit(tempRtA, tempRtB, material); 
                Graphics.Blit(tempRtB, tempRtA, material); 
最后做混合,把ray texture传到blend shader作为GodRayTex。然后得到最终结果 
                materialBlend.SetTexture("_GodRayTex", tempRtA); 
                Graphics.Blit(sourceTexture, destTexture, materialBlend, 0);
 代码如下: [mw_shl_code=applescript,true]using UnityEngine; 
using System.Collections; 
[ExecuteInEditMode] 
public class godRay2 : MonoBehaviour 
{ 
    public Transform lightpos; 
    public Shader curShader; 
    public Shader curShaderblend; 
    private Material curMaterial; 
    private Material curMateriaBlend; 
    public Vector4 ScreenLightPos = new Vector4(0, 0, 0, 0); 
    public float Density = 0.01f; 
    public float Decay = 0.5f; 
    public float Exposure = 0.5f; 
    public float Alpha = 1; 
    public RenderTexture tempRtA = null; 
    public RenderTexture tempRtB = null; 
 
    private Vector3 lightScreenPos; 
    #region Properties 
    Material material 
    { 
        get 
        { 
            if (curMaterial == null) 
            { 
                curMaterial = new Material(curShader); 
                curMaterial.hideFlags = HideFlags.HideAndDontSave; 
            } 
            return curMaterial; 
        } 
    } 
    Material materialBlend 
    { 
        get 
        { 
            if (curMateriaBlend == null) 
            { 
                curMateriaBlend = new Material(curShaderblend); 
                curMateriaBlend.hideFlags = HideFlags.HideAndDontSave; 
            } 
            return curMateriaBlend; 
        } 
    } 
    #endregion 
 
    void Start() 
    { 
        if (!SystemInfo.supportsImageEffects) 
        { 
            enabled = false; 
            return; 
        } 
 
        if (!curShader && !curShader.isSupported) 
        { 
            enabled = false; 
        } 
    } 
 
    void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture) 
    { 
 
        if (curShader != null) 
        { 
            lightScreenPos = Camera.main.WorldToScreenPoint(lightpos.position); 
 
            if (lightScreenPos.z > 0 && lightScreenPos.x > 0 && lightScreenPos.x < camera.pixelWidth  && lightScreenPos.y > 0 && lightScreenPos.y < camera.pixelHeight) 
            { 
                material.SetVector("ScreenLightPos", new Vector4(lightScreenPos.x / camera.pixelWidth, lightScreenPos.y / camera.pixelHeight, 0, 0)); 
                //   material.SetVector("ScreenLightPos", ScreenLightPos); 
                material.SetFloat("Density", Density); 
                material.SetFloat("Decay", Decay); 
                material.SetFloat("Exposure", Exposure); 
                materialBlend.SetFloat("Alpha", Alpha); 
                CreateBuffers(); 
                Graphics.Blit(sourceTexture, tempRtA, material); 
                Graphics.Blit(tempRtA, tempRtB, material); 
                Graphics.Blit(tempRtB, tempRtA, material); 
                Graphics.Blit(tempRtA, tempRtB, material); 
                Graphics.Blit(tempRtB, tempRtA, material); 
 
                materialBlend.SetTexture("_GodRayTex", tempRtA); 
                Graphics.Blit(sourceTexture, destTexture, materialBlend, 0); 
                //   Graphics.Blit(tempRtA, destTexture, material, 0); 
            } 
            else 
            { 
                Graphics.Blit(sourceTexture, destTexture); 
            } 
        } 
        else 
        { 
            Graphics.Blit(sourceTexture, destTexture); 
        } 
 
    } 
 
    void CreateBuffers() 
    { 
        if (!tempRtA) 
        { 
            tempRtA = new RenderTexture(Screen.width / 4, Screen.height / 4, 0); 
            tempRtA.hideFlags = HideFlags.DontSave; 
        } 
 
        if (!tempRtB) 
        { 
            tempRtB = new RenderTexture(Screen.width / 4, Screen.height / 4, 0); 
            tempRtB.hideFlags = HideFlags.DontSave; 
        } 
    } 
    void OnDisable() 
    { 
        if (curMaterial) 
        { 
            DestroyImmediate(curMaterial); 
        } 
    } 
}[/mw_shl_code] 
 
 
  本shader有几个缺点,在比较暗的场景不要使用,因为光源处不亮,所以效果不好,Ray的质量不高,从例子就可以看出来,Ray很不清晰,此处可以和Unity ImageEffect的Sun shafts作比较 最后放上两组效果 
  
林中闪耀的光芒  
 |