这个方法来自于圣典中一个大神的分享,链接:http://www.ceeger.com/forum/read.php?tid=12137#read_222385
上次ShadowGun中的做法是用一个可以伸缩的mesh来模拟,这次的则是用屏幕特效来模拟。
外加其实unity自己也提供了一个现成的,可以直接用的效果,圣典的说明链接:
http://www.ceeger.com/Components/script-SunShafts.html
pro版通过导入ImageEffect包,在里面找到 sun shafts 的.cs文件,然后跟其它屏幕特效一样,把它挂在摄像机上就可以使用了。
这种方法跟unity提供的那个应该是类似的。
首先这个实现最重要的是3个shader,分别是 Blend.shader GodRay.shader GodRayOptimize.shader
Blend:这是做RenderTexture混合用的shader
GodRay:第1种实现方法,主要逻辑在 fragment 里
GodRayOptimize:思路和第1个一样,在此原理基础上,把 uv 的计算挪到 vertex 里去做,提高了效率
思路:
这里的方法是,根据光源的方向和要发出 gay ray 的物体产生一个方向向量。
如:
half2 texCoord = i.uv;
half2 deltaTexCoord = texCoord - ScreenLightPos.xy;
deltaTexCoord *= 1.0f / NUM_SAMPLES * Density;
当前像素根据当前其在 RenderTexture 的 uv,沿着向量往光源方向有间隔的取样8次,每次取到的颜色都比上一次要衰减一次,最后取8次取样结果的平均值为当前像素的颜色。
如:
half4 color = tex2D(_MainTex, i.uv);
half illuminationDecay = 1.0f;
for (int i = 0; i < NUM_SAMPLES; i++)
{
texCoord -= deltaTexCoord;
half4 sample = tex2D(_MainTex, texCoord);
sample *= illuminationDecay;
color += sample;
illuminationDecay *= Decay;
}
color /= NUM_SAMPLES;
return half4( color.xyz * Exposure, 1);
实际效果像下面一样:
cube是要产生gay ray的物体,Sphere是光线的起点
产生了重影的地方,就是沿着光源的方向采样8次后可以取到颜色的像素所产生的。因为每次采样的结果是会进行逐步衰减的,所以它们会有深浅之分。
没有重影的像素,实际上是因为它们沿向量采样8次,结果采样不到任何颜色所造成的。
但是我们应该注意到了,现在这不是我们想要的结果,因为这是重影,并不是gay ray,那要怎么办呢?
这个时候,我们只需要反复多来几次这种采样,衰减的过程就可以了。
因为我们是在第一次采样的结果基础上继续进行采样,衰减的,所以之前没有颜色的像素,这个时候也有颜色了。
代码如:
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 对 RenderTexture 进行了好几次的过滤。
外加缩小使用的 RenderTexture 的尺寸,也可以使重影的间隔变得不那么明显。
代码如:
void CreateBuffers()
{
int rt_width = Screen.width; // Screen.width / 4;
int rt_height = Screen.height; // Screen.height / 4;
if (!tempRtA)
{
tempRtA = new RenderTexture(rt_width, rt_height, 0);
tempRtA.hideFlags = HideFlags.DontSave;
}
if (!tempRtB)
{
tempRtB = new RenderTexture(rt_width, rt_height, 0);
tempRtB.hideFlags = HideFlags.DontSave;
}
}
效果如图:
我们可以看到径向光散射效果
更明显了。
外加作为光源的物体一定要比别的物体亮,差距越明显,效果越好,这在现实生活中也是一样的道理。
更好的表现需要对发光体和参数进行更进一步的调整,这就需要使用者自己去体验了。
GodRayOptimize 跟 GodRay 的原理是相同的,只是实现方式上略有区别而已。
下面是.shader和.cs文件的具体代码
Blend:
Shader "Z_TestShader/god ray 2 blend"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "" {}
_GodRayTex ("God (RGB)", 2D) = ""{}
_Alpha("_Alpha", Float) = 0.5
}
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;
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
}
GodRay:
Shader "Z_TestShader/GodRay"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "" {}
// 光线的位置
ScreenLightPos ("ScreenLightPos", Vector) = (0,0,0,0)
// 密度(从效果上讲,不要超过8这个采样数)
Density ("Density", Float) = 0.01
// 衰减
Decay ("Decay", Float) = 0.5
// 曝光
Exposure ("Exposure", Float) = 0.5
}
CGINCLUDE
#include "UnityCG.cginc"
// 采样数
#define NUM_SAMPLES 8
struct v2in
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
uniform float4 ScreenLightPos;
uniform float Density;
uniform float Decay;
uniform float Exposure;
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
{
half2 texCoord = i.uv;
half2 deltaTexCoord = texCoord - ScreenLightPos.xy;
deltaTexCoord *= 1.0f / NUM_SAMPLES * Density;
half4 color = tex2D(_MainTex, i.uv);
half illuminationDecay = 1.0f;
for (int i = 0; i < NUM_SAMPLES; i++)
{
texCoord -= deltaTexCoord;
half4 sample = tex2D(_MainTex, texCoord);
sample *= illuminationDecay;
color += sample;
illuminationDecay *= Decay;
}
color /= NUM_SAMPLES;
return half4( color.xyz * Exposure, 1);
}
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
}
GodRayOptimize:
Shader "Z_TestShader/GodRayOptimize"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "" {}
ScreenLightPos ("ScreenLightPos", Vector) = (0,0,0,0)
Density ("Density", Float) = 0.01
Decay ("Decay", Float) = 0.5
Exposure ("Exposure", Float) = 0.5
}
CGINCLUDE
#include "UnityCG.cginc"
struct v2in
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
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;
};
sampler2D _MainTex;
uniform float4 ScreenLightPos;
uniform float Density;
uniform float Decay;
uniform float Exposure;
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;
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;
}
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);
}
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
}
cs:
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class GodRayEffect : MonoBehaviour
{
public Transform lightpos;
public Shader curShader;
public Shader curShaderblend;
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 Material m_material;
private Material m_materiaBlend;
Material material
{
get
{
if (m_material == null)
{
m_material = new Material(curShader);
m_material.hideFlags = HideFlags.HideAndDontSave;
}
return m_material;
}
}
Material materialBlend
{
get
{
if (m_materiaBlend == null)
{
m_materiaBlend = new Material(curShaderblend);
m_materiaBlend.hideFlags = HideFlags.HideAndDontSave;
}
return m_materiaBlend;
}
}
void Start()
{
if (!SystemInfo.supportsImageEffects)
{
enabled = false;
return;
}
if (!curShader && !curShader.isSupported)
{
enabled = false;
}
}
void OnDisable()
{
if (m_material != null)
{
DestroyImmediate(m_material);
}
if (m_materiaBlend != null)
{
DestroyImmediate(m_materiaBlend);
}
}
void CreateBuffers()
{
int rt_width = Screen.width; // Screen.width / 4;
int rt_height = Screen.height; // Screen.height / 4;
if (!tempRtA)
{
tempRtA = new RenderTexture(rt_width, rt_height, 0);
tempRtA.hideFlags = HideFlags.DontSave;
}
if (!tempRtB)
{
tempRtB = new RenderTexture(rt_width, rt_height, 0);
tempRtB.hideFlags = HideFlags.DontSave;
}
}
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
{
if (curShader != null)
{
Vector3 lightScreenPos = Camera.main.WorldToScreenPoint(lightpos.position);
if (lightScreenPos.z > 0 && lightScreenPos.x > 0 && lightScreenPos.x < GetComponent<Camera>().pixelWidth && lightScreenPos.y > 0 && lightScreenPos.y < GetComponent<Camera>().pixelHeight)
{
Vector4 screenLightPos = new Vector4(lightScreenPos.x / GetComponent<Camera>().pixelWidth, lightScreenPos.y / GetComponent<Camera>().pixelHeight, 0, 0);
material.SetVector("ScreenLightPos", screenLightPos);
Debug.Log(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);
}
else
{
Graphics.Blit(sourceTexture, destTexture);
}
}
else
{
Graphics.Blit(sourceTexture, destTexture);
}
}
}