• unity3d shader之God Ray上帝之光


    又是一个post-process后期效果,god ray 上帝之光,说起上帝之光就是咱们再看太阳时太阳周围一圈的针状光芒
    先放组效果,本文的场景资源均来自浅墨大神,效果为本文shader效果


     

     

    加入了前篇HDR和Bloom,效果大增:链接



     

    本文的代码是来自unity圣典中某大神的分享,博主做了小小的改进 链接
    然后就来做下讲解,共有两个shader,一个负责制造ray,一个负责和原屏幕图像混合,于原屏幕图像混合很简单,就是单纯的把两个图像的颜色叠加,控制一下ray的权重,
    接下来我们着重讲解一下,制造ray的shader
    是一个fragement shader
    共有4个外部变量
    _ScreenLightPos屏幕上光线的位置,这个需要在c#脚本中计算并传出,稍后会讲解
    _Density密度
    _Decay衰减
    _Exposure曝光,用来控制亮度,大家都知道,在相机中,曝光时间越长图像越亮

    先看vertex shader

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


    v.texcoord为当前点的坐标

    deltaTexCoord为当前点对光源点的反向向量,长度为两点间距离

    密度越大deltaTexCoord越大,不超过8,deltaTexCoord始终是个分数
    第一个采样点为此处本来位置
    采样点渐渐接进光源处
    _Density越大采样点间距越大
    从0到7,点的位置从光源处越来越近,离此处点越来越远
    看看我们的v2f结构体,存了多少坐标点

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

    传入值的结构体v2in

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



    我们就得到了当前点到光源点的一条直线中的八个点的坐标,为fragement shader取色混色用
    当然本步骤也可在fragement shader中完成,但效率没有vertex shader好,因为不用每个像素都取样,只是每个顶点取样就好

    再看fragement shader


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

    illuminationDecay光照衰减,_Decay是我们外部可控衰减

    _Exposure增加亮度

    调整比重离此处像素点越远也就是离光源越近越衰减,可能有人会问,为什么会这样?因为我们还是要保留大部分为此处点的颜色,如果其他像素权重过大,则会造成此处点颜色不准确,甚至不好的模糊效果。
    然后就是混色,基本上的原理就是从光源处打出无数条射线,嗯,可以这么理解。

    Ray我们就制造好了,接下来我们需要把光线ray与原屏幕图像混合,这一步就比较简单了,只给出源代码,各位自己意会。

    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




    然后就是最后一步,也是十分重要的一步就是通过脚本把它弄到屏幕上,
    此处的要点就是要求出光源在屏幕中的位置,
    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);

    代码如下:

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



     

    本shader有几个缺点,在比较暗的场景不要使用,因为光源处不亮,所以效果不好,Ray的质量不高,从例子就可以看出来,Ray很不清晰,此处可以和Unity ImageEffect的Sun shafts作比较

    最后放上两组效果

    林中闪耀的光芒

     


                                                      ------ by  wolf96


  • 相关阅读:
    webservice的简单介绍
    如何在page_load方法判断是服务器端控件引发的page_load方法
    android架构概述
    页面传递数组参数
    XML与DataTable/DataSet互转
    jquery调用asp.net 页面后台方法
    asp.net缓存机制
    jQuery操作radio,checkbox,select
    jquery选择器
    android开发环境的搭建过程
  • 原文地址:https://www.cnblogs.com/zhanlang96/p/4336982.html
Copyright © 2020-2023  润新知