【Unity Shader】后处理——SunShaft容积光
一、Sunshaft原理也叫God Ray,类似丁达尔效应,用Unity还原UE4容积光的效果,参考了一些插件中的实现和算法。Sunshaft原理是在PostProcessing时提取区域,在特定方向进行uv偏移做模糊处理,再进行叠加,产生太阳光向外发散的感觉。
二、SunShaft区域判定
SunShaft提取区域的判定方式有三种:
[*]直接提取屏幕中高亮的区域。
//提取亮度值
half4 main=SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,i.texcoord);
half region=step(_Range,Luminance(main.rgb));
[*]只渲染天空盒,剔除场景中物体所占的区域,并取高亮的部分。
half TransformColor(half4 skyboxValue) {
return dot(max(skyboxValue.rgb - _SunThreshold.rgb, half3(0, 0, 0)), half3(1, 1, 1)); // threshold and convert to greyscale
}
half4 frag_nodepth(v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(I);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
half4 sky = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_Skybox, i.uv.xy);
half4 tex = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv.xy);
/// consider maximum radius
half2 vec = _SunPosition.xy - i.uv.xy;
half dist = saturate(_SunPosition.w - length(vec));
half4 outColor = 0;
if (Luminance(abs(sky.rgb - tex.rgb)) < 0.2) //_DepthThreshold
{
outColor = TransformColor(tex) * dist;
}
return outColor;
}
[*]使用深度图,提取深度较大的区域。
half4 frag_depth(v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
half depthSample = SampleSceneDepth(i.screenPos.xy / i.screenPos.w);
half4 tex = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv.xy);
depthSample = Linear01Depth(depthSample,_ZBufferParams);
half2 vec = _SunPosition.xy - i.uv.xy;
half dist = saturate(_SunPosition.w - length(vec.xy));
half4 outColor = 0;
// consider shafts blockers
if (depthSample < 0.018) { //_DepthThreshold
outColor = TransformColor(tex) * dist;
}
return outColor;
}
三、特定方向的径向模糊
计算太阳的视口空间坐标:
Vector3 v = Vector3.one * 0.5f;
if (sunTransform != Vector3.zero) {
v = camera.WorldToViewportPoint(sunTransform);
}
else {
v = new Vector3(0.5f, 0.5f, 0.0f);
}得到一个坐标值,再与RenderTexture的uv值去做相减得到一个方向。
o.blurVector = (_SunPosition.xy - v.texcoord.xy) * _BlurRadius4.xy;再对提取区域做偏移模糊处理,根据当前其在 RenderTexture 的 uv,沿着向量往光源方向有间隔的取样并衰减,最后取结果的平均值为当前像素的颜色。
half4 frag_radial(Varyings_radial i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
half4 color = half4(0,0,0,0);
for (int j = 0; j < SAMPLES_INT; j++)
{
half4 tmpColor = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv.xy);
color += tmpColor;
i.uv.xy += i.blurVector;
}
return color / SAMPLES_FLOAT;
}
四、后处理
实现思路:
RenderPass:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace UnityEngine.Rendering.URP
{
internal class BlitPassSunShaftsSRP : ScriptableRenderPass
{
//SUN SHAFTS
public BlitSunShaftsSRP.BlitSettings.SunShaftsResolution resolution = BlitSunShaftsSRP.BlitSettings.SunShaftsResolution.Low;
public BlitSunShaftsSRP.BlitSettings.ShaftsScreenBlendMode screenBlendMode = BlitSunShaftsSRP.BlitSettings.ShaftsScreenBlendMode.Screen;
public Vector3 sunTransform = new Vector3(0f, 0f, 0f); // Transform sunTransform;
public int radialBlurIterations = 2;
public Color sunColor = Color.white;
public Color sunThreshold = new Color(0.87f, 0.74f, 0.65f);
public float sunShaftBlurRadius = 2.5f;
public float sunShaftIntensity = 1.15f;
public float maxRadius = 0.75f;
public bool useDepthTexture = true;
public enum RenderTarget
{
Color,
RenderTexture,
}
public Material blitMaterial = null;
public FilterMode filterMode { get; set; }
private RenderTargetIdentifier source { get; set; }
private RenderTargetHandle destination { get; set; }
RenderTargetHandle m_TemporaryColorTexture;
string m_ProfilerTag;
//SUN SHAFTS
RenderTexture lrColorB;
RenderTargetHandle lrDepthBuffer;
/// <summary>
/// Create the CopyColorPass
/// </summary>
public BlitPassSunShaftsSRP(RenderPassEvent renderPassEvent, Material blitMaterial, string tag,BlitSunShaftsSRP.BlitSettings settings)
{
this.renderPassEvent = renderPassEvent;
this.blitMaterial = blitMaterial;
m_ProfilerTag = tag;
m_TemporaryColorTexture.Init(&#34;_TemporaryColorTexture&#34;);
//SUN SHAFTS
this.resolution = settings.resolution;
this.screenBlendMode = settings.screenBlendMode;
this.sunTransform = settings.sunTransform;
this.radialBlurIterations = settings.radialBlurIterations;
this.sunColor = settings.sunColor;
this.sunThreshold = settings.sunThreshold;
this.sunShaftBlurRadius = settings.sunShaftBlurRadius;
this.sunShaftIntensity = settings.sunShaftIntensity;
this.maxRadius = settings.maxRadius;
this.useDepthTexture = settings.useDepthTexture;
//this.blend = settings.blend;
}
/// <summary>
/// Configure the pass with the source and destination to execute on.
/// </summary>
/// <param name=&#34;source&#34;>Source Render Target</param>
/// <param name=&#34;destination&#34;>Destination Render Target</param>
public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
{
this.source = source;
this.destination = destination;
}
connectSuntoSunShaftsURP connector;
/// <inheritdoc/>
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
//grab settings if script on scene camera
if (connector == null)
{
connector = renderingData.cameraData.camera.GetComponent<connectSuntoSunShaftsURP>();
if(connector == null && Camera.main != null)
{
connector = Camera.main.GetComponent<connectSuntoSunShaftsURP>();
}
}
//Debug.Log(Camera.main.GetComponent<connectSuntoSunShaftsURP>().sun.transform.position);
if (connector != null)
{
this.sunTransform = connector.sun.transform.position;
this.screenBlendMode = connector.screenBlendMode;
//public Vector3 sunTransform = new Vector3(0f, 0f, 0f);
this.radialBlurIterations = connector.radialBlurIterations;
this.sunColor = connector.sunColor;
this.sunThreshold = connector.sunThreshold;
this.sunShaftBlurRadius = connector.sunShaftBlurRadius;
this.sunShaftIntensity = connector.sunShaftIntensity;
this.maxRadius = connector.maxRadius;
this.useDepthTexture = connector.useDepthTexture;
}
CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
opaqueDesc.depthBufferBits = 0;
if (destination == UnityEngine.Rendering.Universal.RenderTargetHandle.CameraTarget)
{
cmd.GetTemporaryRT(m_TemporaryColorTexture.id, opaqueDesc, filterMode);
RenderShafts(context, renderingData, cmd, opaqueDesc);
}
}
/// <inheritdoc/>
public override void FrameCleanup(CommandBuffer cmd)
{
if (destination == UnityEngine.Rendering.Universal.RenderTargetHandle.CameraTarget)
{
cmd.ReleaseTemporaryRT(m_TemporaryColorTexture.id);
cmd.ReleaseTemporaryRT(lrDepthBuffer.id);
}
}
//SUN SHAFTS
public void RenderShafts(ScriptableRenderContext context, RenderingData renderingData, CommandBuffer cmd, RenderTextureDescriptor opaqueDesc)
{
opaqueDesc.depthBufferBits = 0;
Material material = blitMaterial;
Camera camera = Camera.main;
if (useDepthTexture)
{
camera.depthTextureMode |= DepthTextureMode.Depth;
}
int divider = 8;
if (this.resolution == BlitSunShaftsSRP.BlitSettings.SunShaftsResolution.Normal)
divider = 4;
else if (this.resolution == BlitSunShaftsSRP.BlitSettings.SunShaftsResolution.High)
divider = 2;
Vector3 v = Vector3.one * 0.5f;
if (sunTransform != Vector3.zero) {
v = camera.WorldToViewportPoint(sunTransform);
}
else {
v = new Vector3(0.5f, 0.5f, 0.0f);
}
//v0.1
int rtW = opaqueDesc.width/divider;
int rtH = opaqueDesc.height/divider;
cmd.GetTemporaryRT(lrDepthBuffer.id, opaqueDesc, filterMode);
material.SetVector(&#34;_BlurRadius4&#34;, new Vector4(1.0f, 1.0f, 0.0f, 0.0f) * sunShaftBlurRadius);
material.SetVector(&#34;_SunPosition&#34;, new Vector4(v.x, v.y, v.z, maxRadius));
material.SetVector(&#34;_SunThreshold&#34;, sunThreshold);
if (!useDepthTexture)
{
var format = camera.allowHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
RenderTexture tmpBuffer = RenderTexture.GetTemporary(rtW, rtH, 0, format);
RenderTexture.active = tmpBuffer;
GL.ClearWithSkybox(false, camera);
material.SetTexture(&#34;_Skybox&#34;, tmpBuffer);
Blit(cmd, source, lrDepthBuffer.Identifier(), material, 3);
RenderTexture.ReleaseTemporary(tmpBuffer);
}
else
{
Blit(cmd, source, lrDepthBuffer.Identifier(), material, 2);
}
Blit(cmd, source, m_TemporaryColorTexture.Identifier()); //KEEP BACKGROUND
radialBlurIterations = Mathf.Clamp(radialBlurIterations, 1, 4);
float ofs = sunShaftBlurRadius * (1.0f / 768.0f);
material.SetVector(&#34;_BlurRadius4&#34;, new Vector4(ofs, ofs, 0.0f, 0.0f));
material.SetVector(&#34;_SunPosition&#34;, new Vector4(v.x, v.y, v.z, maxRadius));
for (int it2 = 0; it2 < radialBlurIterations; it2++)
{
lrColorB = RenderTexture.GetTemporary(rtW, rtH, 0);
Blit(cmd, lrDepthBuffer.Identifier(), lrColorB, material, 1);
cmd.ReleaseTemporaryRT(lrDepthBuffer.id);
ofs = sunShaftBlurRadius * (((it2 * 2.0f + 1.0f) * 6.0f)) / 768.0f;
material.SetVector(&#34;_BlurRadius4&#34;, new Vector4(ofs, ofs, 0.0f, 0.0f));
cmd.GetTemporaryRT(lrDepthBuffer.id, opaqueDesc, filterMode);
Blit(cmd, lrColorB, lrDepthBuffer.Identifier(), material, 1);
RenderTexture.ReleaseTemporary(lrColorB);
ofs = sunShaftBlurRadius * (((it2 * 2.0f + 2.0f) * 6.0f)) / 768.0f;
material.SetVector(&#34;_BlurRadius4&#34;, new Vector4(ofs, ofs, 0.0f, 0.0f));
}
// put together:
if (v.z >= 0.0f)
{
material.SetVector(&#34;_SunColor&#34;, new Vector4(sunColor.r, sunColor.g, sunColor.b, sunColor.a) * sunShaftIntensity);
}
else
{
material.SetVector(&#34;_SunColor&#34;, Vector4.zero); // no backprojection !
}
cmd.SetGlobalTexture(&#34;_ColorBuffer&#34;, lrDepthBuffer.Identifier());
Blit(cmd, m_TemporaryColorTexture.Identifier(), source, material, (screenBlendMode == BlitSunShaftsSRP.BlitSettings.ShaftsScreenBlendMode.Screen) ? 0 : 4);
cmd.ReleaseTemporaryRT(lrDepthBuffer.id);
cmd.ReleaseTemporaryRT(m_TemporaryColorTexture.id);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
RenderTexture.ReleaseTemporary(lrColorB);
}
}
}
RenderFeature:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace UnityEngine.Rendering.URP
{
public class BlitSunShaftsSRP : ScriptableRendererFeature
{
public class BlitSettings
{
public RenderPassEvent Event = RenderPassEvent.AfterRenderingTransparents;
public Material blitMaterial = null;
public Target destination = Target.Color;
public string textureId = &#34;_BlitPassTexture&#34;;
/////SUN SHAFTS
//
// public float blend = 0.5f;
public enum SunShaftsResolution
{
Low = 0,
Normal = 1,
High = 2,
}
public enum ShaftsScreenBlendMode
{
Screen = 0,
Add = 1,
}
public SunShaftsResolution resolution = SunShaftsResolution.Low;
public ShaftsScreenBlendMode screenBlendMode = ShaftsScreenBlendMode.Screen;
public Vector3 sunTransform =new Vector3(0f, 0f, 0f); // Transform sunTransform;
public int radialBlurIterations = 2 ;
public Color sunColor = Color.white ;
public Color sunThreshold =new Color(0.87f, 0.74f, 0.65f) ;
public float sunShaftBlurRadius = 2.5f ;
public float sunShaftIntensity = 1.15f ;
public float maxRadius = 0.75f ;
public bool useDepthTexture = true ;
}
public enum Target
{
Color,
Texture
}
public BlitSettings settings = new BlitSettings();
RenderTargetHandle m_RenderTextureHandle;
BlitPassSunShaftsSRP blitPass;
public override void Create()
{
var passIndex = settings.blitMaterial != null ? settings.blitMaterial.passCount - 1 : 1;
blitPass = new BlitPassSunShaftsSRP(settings.Event, settings.blitMaterial, name, settings);//, settings.blitMaterialPassIndex
m_RenderTextureHandle.Init(settings.textureId);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
var src = renderer.cameraColorTarget;
var dest = (settings.destination == Target.Color) ? RenderTargetHandle.CameraTarget : m_RenderTextureHandle;
if (settings.blitMaterial == null)
{
Debug.LogWarningFormat(&#34;Missing Blit Material. {0} blit pass will not execute. Check for missing reference in the assigned renderer.&#34;, GetType().Name);
return;
}
blitPass.Setup(src, dest);
renderer.EnqueuePass(blitPass);
}
}
}
SunShaft Shader中的pass结构:
Subshader
{
Tags
{
&#34;RenderPipeline&#34;=&#34;UniversalPipeline&#34;
&#34;Queue&#34;=&#34;Transparent&#34;
&#34;IgnoreProjector&#34;=&#34;True&#34;
&#34;RenderType&#34;=&#34;Transparent&#34;
}
ZTest Always
Cull Off
ZWrite Off
Pass //0
{
Name &#34;Fragment Screen&#34;
HLSLPROGRAM
#pragma target 3.5
#pragma multi_compile_instancing
#pragma vertex vert
#pragma fragment fragScreen
ENDHLSL
}
Pass // 1
{
Name &#34;Fragment Radial&#34;
HLSLPROGRAM
#pragma target 3.5
#pragma multi_compile_instancing
#pragma vertex vert_radial
#pragma fragment frag_radial
ENDHLSL
}
Pass // 2
{
Name &#34;Fragment Depth&#34;
HLSLPROGRAM
#pragma target 3.5
#pragma multi_compile_instancing
#pragma vertex vert
#pragma fragment frag_depth
ENDHLSL
}
Pass // 3
{
Name &#34;Fragment No Depth&#34;
HLSLPROGRAM
#pragma target 3.5
#pragma multi_compile_instancing
#pragma vertex vert
#pragma fragment frag_nodepth
ENDHLSL
}
Pass // 4
{
Name &#34;Fragment Add&#34;
HLSLPROGRAM
#pragma target 3.5
#pragma multi_compile_instancing
#pragma vertex vert
#pragma fragment fragAdd
ENDHLSL
}
}
五、参考
页:
[1]