又是一个post-process后期效果。god ray 上帝之光,说起上帝之光就是咱们再看太阳时太阳周围一圈的针状光芒

先放组效果,本文的场景资源均来自浅墨大神,效果为本文shader效果


unity3d shader之God Ray上帝之光_ide

unity3d shader之God Ray上帝之光_#pragma_02



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

unity3d shader之God Ray上帝之光_2d_03

unity3d shader之God Ray上帝之光_2d_04

unity3d shader之God Ray上帝之光_坐标转换_05


本文的代码是来自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作比較

最后放上两组效果

unity3d shader之God Ray上帝之光_2d_06


unity3d shader之God Ray上帝之光_屏幕坐标_07

林中闪耀的光芒

unity3d shader之God Ray上帝之光_ide_08

unity3d shader之God Ray上帝之光_坐标转换_09

unity3d shader之God Ray上帝之光_屏幕坐标_10

unity3d shader之God Ray上帝之光_坐标转换_11