Skip to content

Instantly share code, notes, and snippets.

@hadashiA
Created December 14, 2019 08:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hadashiA/4e37d2f42dd376d50f57ebd6311121eb to your computer and use it in GitHub Desktop.
Save hadashiA/4e37d2f42dd376d50f57ebd6311121eb to your computer and use it in GitHub Desktop.
Shader "Hidden/Heipu/Metaball"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_ColorRamp ("Color Ramp", 2D) = "white" {}
_Threshold ("Threshold", float) = 0.04
_LineLength ("Line Length", float) = 0.5
_Intensity ("Intensity", float) = 1
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
HLSLINCLUDE
// Required to compile gles 2.0 with standard srp library
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x
#include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
uniform TEXTURE2D(_MainTex);
uniform TEXTURE2D(_MetaballSource);
uniform SAMPLER(sampler_MainTex);
uniform float4 _MainTex_TexelSize;
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
};
half3 Sample(float2 uv)
{
return SAMPLE_TEXTURE2D(TEXTURE2D_ARGS(_MainTex, sampler_MainTex), uv);
}
half3 SampleBox(float2 uv, float delta)
{
float4 offset = _MainTex_TexelSize.xyxy * float2(-delta, delta).xxyy;
half3 s =
Sample(uv + offset.xy) + Sample(uv + offset.zy) +
Sample(uv + offset.xw) + Sample(uv + offset.zw);
return s * 0.25f;
}
Varyings PassVertex(Attributes input)
{
Varyings output = (Varyings)0;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = vertexInput.positionCS;
output.uv = input.uv;
return output;
}
ENDHLSL
Pass
{
Name "DownSampling"
HLSLPROGRAM
#pragma vertex PassVertex
#pragma fragment DownSamplingPassFragment
half4 DownSamplingPassFragment(Varyings input) : SV_Target
{
return half4(SampleBox(input.uv, 1), 1);
}
ENDHLSL
}
Pass
{
Name "UpSampling"
Blend [_SrcBlend] [_DstBlend]
HLSLPROGRAM
#pragma vertex PassVertex
#pragma fragment UpSamplingPassFragment
half4 UpSamplingPassFragment(Varyings input) : SV_Target
{
return half4(SampleBox(input.uv, 0.5), 1);
}
ENDHLSL
}
Pass
{
Name "ApplyBloom"
HLSLPROGRAM
#pragma vertex PassVertex
#pragma fragment BloomPassFragment
uniform float _Intensity;
half4 BloomPassFragment(Varyings input) : SV_Target
{
half4 c = SAMPLE_TEXTURE2D(TEXTURE2D_ARGS(_MainTex, sampler_MainTex), input.uv);
c.rgb += _Intensity * SampleBox(input.uv, 0.5);
return c;
}
ENDHLSL
}
Pass
{
Name "ApplyMetaball"
HLSLPROGRAM
#pragma vertex PassVertex
#pragma fragment BloomPassFragment
uniform TEXTURE2D(_ColorRamp);
uniform float _Threshold;
uniform float _LineLength;
half4 BloomPassFragment(Varyings input) : SV_Target
{
half4 c = SAMPLE_TEXTURE2D(TEXTURE2D_ARGS(_MainTex, sampler_MainTex), input.uv);
c.rgb += SampleBox(input.uv, 0.5);
half d = c.r;
clip(d - _Threshold);
// TODO:
c = lerp(c, half4(1, 1, 1, 1), step(_Threshold, d));
half4 ramp = SAMPLE_TEXTURE2D(TEXTURE2D_ARGS(_ColorRamp, sampler_MainTex), float2(d * 0.1 + _Time.y * 0.1, 0.1));
// c = lerp(half4(1, 1, 1, 1), c, smoothstep(_Threshold - d, _Threshold - d + 0.02, 0));
// c = lerp(ramp, c, smoothstep(_Threshold - d, _Threshold - d + 0.02, 0));
// c = lerp(ramp, c, smoothstep(_Threshold - d, _Threshold - d + _LineLength, 0));
c = smoothstep(_Threshold - d, _Threshold - d + _LineLength, 0);
return c;
}
ENDHLSL
}
}
}
public sealed class MetaballPass : ScriptableRenderPass
{
public RenderTargetIdentifier SourceIdentifier;
int BlurryIterations => temporatyTargetHandles.Length;
readonly string profilerTag;
readonly IEnumerable<IDrawable> targets;
readonly Material metaballMaterial;
readonly RenderTargetHandle metaballSourceHandle;
readonly RenderTargetHandle[] temporatyTargetHandles;
readonly int downSamplingPass;
readonly int upSamplingPass;
readonly int applyBloomPass;
readonly int applyMetaballPass;
readonly MaterialPropertyBlock metaballSourceProps = new MaterialPropertyBlock();
public MetaballPass(
string profilerTag,
RenderPassEvent renderPassEvent,
IEnumerable<IDrawable> targets,
Material metaballMaterial,
int blurryIterations)
{
this.profilerTag = profilerTag;
this.renderPassEvent = renderPassEvent;
this.targets = targets;
this.metaballMaterial = metaballMaterial;
metaballSourceHandle.Init("_MetaballSource");
temporatyTargetHandles = new RenderTargetHandle[blurryIterations];
for (var i = 0; i < blurryIterations; i++)
{
temporatyTargetHandles[i].Init($"_MetaballTemp{i}");
}
downSamplingPass = metaballMaterial.FindPass("DownSampling");
upSamplingPass = metaballMaterial.FindPass("UpSampling");
applyBloomPass = metaballMaterial.FindPass("ApplyBloom");
applyMetaballPass = metaballMaterial.FindPass("ApplyMetaball");
metaballSourceProps.SetInt("_StencilComp", (int)CompareFunction.Always);
}
// This method is called before executing the render pass.
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
// When empty this render pass will render to the active camera render target.
// You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.
// The render pipeline will ensure target setup and clearing happens in an performance manner.
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
}
// Here you can implement the rendering logic.
// Use <c>ScriptableRenderContext</c> to issue drawing commands or execute command buffers
// https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html
// You don't have to call ScriptableRenderContext.submit, the render pipeline will call it at specific points in the pipeline.
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (targets.IsEmpty())
return;
var cam = renderingData.cameraData.camera;
var cmd = CommandBufferPool.Get(profilerTag);
using (new ProfilingSample(cmd, profilerTag))
{
var targetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
targetDescriptor.depthBufferBits = 0;
cmd.GetTemporaryRT(metaballSourceHandle.id, targetDescriptor, FilterMode.Bilinear);
cmd.SetRenderTarget(metaballSourceHandle.id);
cmd.ClearRenderTarget(true, true, Color.black, 1f);
foreach (var x in targets)
{
var pass = x.Material.FindPass("MetaballSource");
if (x.Transform is RectTransform rectTransform)
{
var screenPos = RectTransformUtility.WorldToScreenPoint(cam, rectTransform.position);
RectTransformUtility.ScreenPointToWorldPointInRectangle(rectTransform, screenPos, cam, out var worldPoint);
var matrix = Matrix4x4.TRS(worldPoint, Quaternion.identity, rectTransform.lossyScale);
cmd.DrawMesh(x.Mesh, matrix, x.Material, 0, pass, metaballSourceProps);
}
else
{
cmd.DrawMesh(x.Mesh, x.Transform.localToWorldMatrix, x.Material, 0, pass, metaballSourceProps);
}
}
// Blurring
// Down sampling
var currentSource = metaballSourceHandle;
var currentDestination = temporatyTargetHandles[0];
targetDescriptor.width /= 2;
targetDescriptor.height /= 2;
cmd.GetTemporaryRT(currentDestination.id, targetDescriptor, FilterMode.Bilinear);
cmd.Blit(currentSource.id, currentDestination.id, metaballMaterial, downSamplingPass);
cmd.ReleaseTemporaryRT(currentSource.id);
for (var i = 1; i < BlurryIterations; i++)
{
currentSource = currentDestination;
currentDestination = temporatyTargetHandles[i];
targetDescriptor.width /= 2;
targetDescriptor.height /= 2;
cmd.GetTemporaryRT(currentDestination.id, targetDescriptor, FilterMode.Bilinear);
cmd.Blit(currentSource.id, currentDestination.id, metaballMaterial, downSamplingPass);
}
// Up sampling
for (var i = BlurryIterations - 2; i >= 0; i--)
{
currentSource = currentDestination;
currentDestination = temporatyTargetHandles[i];
cmd.Blit(currentSource.id, currentDestination.id, metaballMaterial, upSamplingPass);
cmd.ReleaseTemporaryRT(currentSource.id);
}
// cmd.SetGlobalTexture("_MetaballSource", currentDestination.Identifier());
cmd.Blit(currentDestination.id, SourceIdentifier, metaballMaterial, applyMetaballPass);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
/// Cleanup any allocated resources that were created during the execution of this render pass.
public override void FrameCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(metaballSourceHandle.id);
foreach (var handle in temporatyTargetHandles)
{
cmd.ReleaseTemporaryRT(handle.id);
}
}
}
public sealed class MetaballRendererFeature : ScriptableRendererFeature
{
[System.Serializable]
public sealed class MetaballSettings
{
public string ProfilerTag = "MetaballRenderFeature";
public RenderPassEvent Event = RenderPassEvent.BeforeRenderingTransparents;
public Shader MetaballShader;
public BlendMode SrcBlend = BlendMode.SrcAlpha;
public BlendMode DstBlend = BlendMode.OneMinusSrcAlpha;
public Texture2D RampTexture;
[Range(1, 16)]
public int BlurryIterations = 1;
[Range(0.001f, 1f)]
public float Threshold = 0.04f;
[Range(0f, 1f)]
public float LineLength = 0.5f;
}
static readonly HashSet<IDrawable> Targets = new HashSet<IDrawable>();
public static void AddTarget(IDrawable item) => Targets.Add(item);
public static void RemoveTarget(IDrawable item) => Targets.Remove(item);
[SerializeField]
MetaballSettings settings = new MetaballSettings();
MetaballPass metaballPass;
public override void Create()
{
var metaballMaterial = CoreUtils.CreateEngineMaterial(settings.MetaballShader);
metaballMaterial.SetTexture("_ColorRamp", settings.RampTexture);
metaballMaterial.SetFloat("_Threshold", settings.Threshold);
metaballMaterial.SetFloat("_LineLength", settings.LineLength);
metaballMaterial.SetInt("_SrcBlend", (int)settings.SrcBlend);
metaballMaterial.SetInt("_DstBlend", (int)settings.DstBlend);
metaballPass = new MetaballPass(
settings.ProfilerTag,
settings.Event,
Targets,
metaballMaterial,
settings.BlurryIterations);
}
// Here you can inject one or multiple render passes in the renderer.
// This method is called when setting up the renderer once per-camera.
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
metaballPass.SourceIdentifier = renderer.cameraColorTarget;
renderer.EnqueuePass(metaballPass);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment