unity3d shader之God Ray上帝之光
又是一个post-process后期效果。god ray 上帝之光,说起上帝之光就是咱们再看太阳时太阳周围一圈的针状光芒
先放组效果,本文的场景资源均来自浅墨大神,效果为本文shader效果
然后就来做下解说,共同拥有两个shader,一个负责制造ray,一个负责和原屏幕图像混合,于原屏幕图像混合非常easy。就是单纯的把两个图像的颜色叠加。控制一下ray的权重,
接下来我们着重解说一下。制造ray的shader
是一个fragement shader
共同拥有4个外部变量
_ScreenLightPos屏幕上光线的位置,这个须要在c#脚本中计算并传出。稍后会解说
_Density密度
_Decay衰减
_Exposure曝光,用来控制亮度,大家都知道。在相机中,曝光时间越长图像越亮
先看vertex shader
v.texcoord为当前点的坐标
deltaTexCoord为当前点对光源点的反向向量,长度为两点间距离
密度越大deltaTexCoord越大,不超过8,deltaTexCoord始终是个分数
第一个採样点为此处本来位置
採样点渐渐接进光源处
_Density越大採样点间距越大
从0到7,点的位置从光源处越来越近,离此处点越来越远
看看我们的v2f结构体。存了多少坐标点
我们就得到了当前点到光源点的一条直线中的八个点的坐标。为fragement shader取色混色用
当然本步骤也可在fragement shader中完毕,但效率没有vertex shader好,由于不用每一个像素都取样。仅仅是每一个顶点取样就好
再看fragement 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。
------ by wolf96 http://blog.csdn.net/wolf96
先放组效果,本文的场景资源均来自浅墨大神,效果为本文shader效果
然后就来做下解说,共同拥有两个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