博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
unity3d shader之God Ray上帝之光
阅读量:6237 次
发布时间:2019-06-22

本文共 8918 字,大约阅读时间需要 29 分钟。

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

增加了前篇HDR和Bloom。效果大增:

本文的代码是来自unity圣典中某大神的分享,博主做了小小的改进
然后就来做下解说,共同拥有两个shader,一个负责制造ray,一个负责和原屏幕图像混合,于原屏幕图像混合非常easy。就是单纯的把两个图像的颜色叠加。控制一下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 http://blog.csdn.net/wolf96
你可能感兴趣的文章
如何解决Android开发学习过程中缺乏后端接口的问题「Android,资源向」
查看>>
关闭wps自动升级
查看>>
【设计模式】面向对象六大原则
查看>>
Web 通信 之 长连接、长轮询(long polling)
查看>>
Git教程摘录
查看>>
JQuery基本知识框架思维导图(上)
查看>>
Java 数据类型
查看>>
iView 发布微信小程序 UI 组件库 iView Weapp
查看>>
运维Caron 的一条心理的os
查看>>
Android - Fragment(二)加载Fragment
查看>>
JVM笔记6-垃圾回收器
查看>>
Java并发编程笔记1-竞争条件&初识原子类&可重入锁
查看>>
工厂+单例模式
查看>>
火爆的在线知识付费,能否缓解你的知识焦虑
查看>>
阿里云ACM英文版上线,论“全局配置”在电商国际化微服务平台建设中的妙用
查看>>
getComputedStyle方法获取元素CSS值
查看>>
关于图片的Base64编码
查看>>
Android加载Gif和ImageView的通用解决方案:android-gif-drawable(1)
查看>>
WPF TextBox/TextBlock 文本超出显示时,文本靠右显示
查看>>
C++的函数对象优于函数指针地方
查看>>