Last active
December 19, 2015 20:49
-
-
Save Jerdak/6015609 to your computer and use it in GitHub Desktop.
Unity multiple echo shader. Uses floating point -> pixel packing to support transmitting data from Unity component to the shader through a texture rather than as properties.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
[Serializable] | |
public class EchoSphere2 { | |
public enum ShaderPackingMode { Texture, Property }; | |
public ShaderPackingMode CurrentPackingMode = ShaderPackingMode.Texture; | |
public Texture2D EchoTexture; | |
public Material EchoMaterial = null; | |
public Vector3 Position; | |
public int SphereIndex = 0; | |
// Echo sphere Properties | |
public float SphereMaxRadius = 10.0f; //Final size of the echo sphere. | |
private float sphereCurrentRadius = 0.0f; //Current size of the echo sphere | |
public float FadeDelay = 0.0f; //Time to delay before triggering fade. | |
public float FadeRate = 1.0f; //Speed of the fade away | |
public float echoSpeed = 1.0f; //Speed of the sphere growth. | |
public bool is_manual = false; //Is pulse manual. if true, pulse triggered by left-mouse click | |
private bool is_animated = false; //If true, pulse is currently running. | |
public float pulse_frequency = 5.0f; | |
private float deltaTime = 0.0f; | |
private float fade = 0.0f; | |
public EchoSphere2(){} | |
// Update is called once per frame | |
public void Update () { | |
if(EchoMaterial == null)return; | |
// If manual selection is disabled, automatically trigger a pulse at the given freq. | |
deltaTime += Time.deltaTime; | |
UpdateEcho(); | |
if(CurrentPackingMode == ShaderPackingMode.Texture)UpdateTexture(); | |
if(CurrentPackingMode == ShaderPackingMode.Property)UpdateProperties(); | |
} | |
// Called to trigger an echo pulse | |
public void TriggerPulse(){ | |
deltaTime = 0.0f; | |
sphereCurrentRadius = 0.0f; | |
fade = 0.0f; | |
is_animated = true; | |
} | |
// Called to halt an echo pulse. | |
void HaltPulse(){ | |
Debug.Log("HaltPulse reached"); | |
is_animated = false; | |
} | |
void ClearPulse(){ | |
fade = 0.0f; | |
sphereCurrentRadius = 0.0f; | |
is_animated = false; | |
} | |
void UpdateProperties(){ | |
if(!is_animated)return; | |
float maxRadius = SphereMaxRadius; | |
float maxFade = SphereMaxRadius / echoSpeed; | |
Debug.Log("Updating _Position"+SphereIndex.ToString()); | |
EchoMaterial.SetVector("_Position"+SphereIndex.ToString(),Position); | |
EchoMaterial.SetFloat("_Radius"+SphereIndex.ToString(),sphereCurrentRadius); | |
EchoMaterial.SetFloat("_Fade"+SphereIndex.ToString(),fade); | |
EchoMaterial.SetFloat("_MaxRadius",maxRadius); | |
EchoMaterial.SetFloat("_MaxFade",maxFade); | |
} | |
void UpdateTexture(){ | |
if(!is_animated)return; | |
float maxRadius = SphereMaxRadius; | |
float maxFade = SphereMaxRadius / echoSpeed; | |
EchoTexture.SetPixel(SphereIndex,0,FloatPacking.ToColor(Position.x)); | |
EchoTexture.SetPixel(SphereIndex,1,FloatPacking.ToColor(Position.y)); | |
EchoTexture.SetPixel(SphereIndex,2,FloatPacking.ToColor(Position.z)); | |
EchoTexture.SetPixel(SphereIndex,3,FloatPacking.ToColor(sphereCurrentRadius)); | |
EchoTexture.SetPixel(SphereIndex,4,FloatPacking.ToColor(fade)); | |
EchoTexture.Apply(); | |
EchoMaterial.SetFloat("_MaxRadius",maxRadius); | |
EchoMaterial.SetFloat("_MaxFade",maxFade); | |
} | |
// Called to update the echo front edge | |
void UpdateEcho(){ | |
if(!is_animated)return; | |
if(sphereCurrentRadius >= SphereMaxRadius){ | |
HaltPulse(); | |
} else { | |
sphereCurrentRadius += Time.deltaTime * echoSpeed; | |
} | |
float radius = sphereCurrentRadius; | |
float maxRadius = SphereMaxRadius; | |
float maxFade = SphereMaxRadius / echoSpeed; | |
if(fade > maxFade){ | |
return; | |
} | |
if(deltaTime > FadeDelay) | |
fade += Time.deltaTime * FadeRate; | |
} | |
} | |
public class EchoSpheres : MonoBehaviour { | |
public EchoSphere2.ShaderPackingMode CurrentPackingMode = EchoSphere2.ShaderPackingMode.Texture; | |
public Texture2D EchoTexture; | |
public Material EchoMaterial = null; | |
public int SphereCount = 1; | |
public int CurrentSphere = 0; | |
// Echo sphere Properties | |
public float SphereMaxRadius = 10.0f; //Final size of the echo sphere. | |
public float FadeDelay = 0.0f; //Time to delay before triggering fade. | |
public float FadeRate = 1.0f; //Speed of the fade away | |
public float echoSpeed = 1.0f; //Speed of the sphere growth. | |
private List<EchoSphere2> Spheres = new List<EchoSphere2>(); | |
// Use this for initialization | |
void Start () { | |
CreateEchoTexture(); | |
InitializeSpheres(); | |
} | |
void InitializeSpheres(){ | |
for(int i = 0; i < SphereCount; i++){ | |
EchoSphere2 es = new EchoSphere2{ | |
EchoMaterial = EchoMaterial, | |
EchoTexture = EchoTexture, | |
echoSpeed = echoSpeed, | |
SphereMaxRadius = SphereMaxRadius, | |
FadeDelay = FadeDelay, | |
FadeRate = FadeRate, | |
SphereIndex = i, | |
CurrentPackingMode = CurrentPackingMode | |
}; | |
Spheres.Add(es); | |
} | |
} | |
/// <summary> | |
/// Create an echo texture used to hold multiple echo sources and fades. | |
/// </summary> | |
void CreateEchoTexture(){ | |
EchoTexture = new Texture2D(128,128,TextureFormat.RGBA32,false); | |
EchoTexture.filterMode = FilterMode.Point; | |
EchoTexture.Apply(); | |
EchoMaterial.SetTexture("_EchoTex",EchoTexture); | |
} | |
// Update is called once per frame | |
void Update () { | |
if(EchoMaterial == null)return; | |
foreach (EchoSphere2 es in Spheres){ | |
es.Update(); | |
} | |
UpdateRayCast(); | |
} | |
// Called to manually place echo pulse | |
void UpdateRayCast() { | |
if (Input.GetButtonDown("Fire1")){ | |
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); | |
RaycastHit hit; | |
if (Physics.Raycast(ray,out hit, 10000)) { | |
Debug.Log("Triggering pulse["+CurrentSphere.ToString()+"]"); | |
Spheres[CurrentSphere].TriggerPulse(); | |
Spheres[CurrentSphere].Position = hit.point; | |
CurrentSphere += 1; | |
if(CurrentSphere >= Spheres.Count)CurrentSphere = 0; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
using System.Collections; | |
/// <summary> | |
/// Float packing class handles | |
/// </summary> | |
/// <notes> | |
/// step,exp2,abs,floor,mod,log2 methods are included here to mimic shader | |
/// functionality. Some methods were copied from Nvidia's Cg reference | |
/// implementations, others are just wrappers around C# functions. | |
/// | |
/// In practice I don't think it matters. | |
/// | |
/// Code taken directly from: http://stackoverflow.com/questions/7059962/how-do-i-convert-a-vec4-rgba-value-to-a-float (hrehfeld's answer) | |
/// </notes> | |
/// | |
public static class FloatPacking { | |
public static float step(float edge, float x) { | |
return (x < edge)?0.0f:1.0f; | |
} | |
public static float exp2(float x){ | |
return (float)Math.Pow(2,x); | |
} | |
public static float abs(float x){ | |
return (float)Math.Abs(x); | |
} | |
public static int floor(float x){ | |
return (int)Math.Floor(x); | |
} | |
public static float mod(float x, float y){ | |
return x - y * floor(x/y); | |
} | |
public static float log2(float x){ | |
return (float)Math.Log(x,2); | |
} | |
//unpack a 32bit float from 4 8bit, [0;1] clamped floats | |
public static float FromFloat4( Vector4 _packed) | |
{ | |
Vector4 rgba = 255.0f * _packed; | |
float sign = step(-128.0f, -rgba.y) * 2.0f - 1.0f; | |
float exponent = rgba.x - 127.0f; | |
if (abs(exponent + 127.0f) < 0.001f) | |
return 0.0f; | |
float mantissa = mod(rgba.y, 128.0f) * 65536.0f + rgba.z * 256.0f + rgba.w + (0x800000); | |
return sign * exp2(exponent-23.0f) * mantissa ; | |
} | |
//pack a 32bit float into 4 8bit, [0;1] clamped floats | |
public static Vector4 ToFloat4(float f) | |
{ | |
float F = abs(f); | |
if(F == 0.0) | |
{ | |
return new Vector4(0,0,0,0); | |
} | |
float Sign = step(0.0f, -f); | |
float Exponent = floor( log2(F)); | |
float Mantissa = F/ exp2(Exponent); | |
//std::cout << " sign: " << Sign << ", exponent: " << Exponent << ", mantissa: " << Mantissa << std::endl; | |
//denormalized values if all exponent bits are zero | |
if(Mantissa < 1.0f) | |
Exponent -= 1; | |
Exponent += 127; | |
Vector4 rgba = new Vector4(0,0,0,0); | |
rgba.x = Exponent; | |
rgba.y = 128.0f * Sign + mod(floor(Mantissa * 128.0f),128.0f); | |
rgba.z = floor( mod(floor(Mantissa* exp2(23.0f - 8.0f)), exp2(8.0f))); | |
rgba.w = floor( exp2(23.0f)* mod(Mantissa, exp2(-15.0f))); | |
return (1 / 255.0f) * rgba; | |
} | |
public static Color ToColor(float f){ | |
Vector4 tmp = ToFloat4(f); | |
return new Color(tmp.x,tmp.y,tmp.z,tmp.w); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Shader "Custom/Echo/MultipleUsingProperty" { | |
Properties { | |
_MainTex ("Base (RGB)", 2D) = "white" {} | |
_EchoTex ("Echo (RGBA)", 2D) = "white" {} | |
_MainColor ("Main Color",Color) = (1.0,1.0,1.0,1.0) | |
_DistanceFade("Distance Fade",float) = 1.0 | |
_MaxRadius("MaxRadius",float) = 1.0 | |
_MaxFade("MaxFade",float) = 1.0 | |
_Position0("Position0",Vector) = (0.0,0.0,0.0) | |
_Position1("Position1",Vector) = (0.0,0.0,0.0) | |
_Position2("Position2",Vector) = (0.0,0.0,0.0) | |
_Radius0("Radius0",float) = 0.0 | |
_Radius1("Radius1",float) = 0.0 | |
_Radius2("Radius2",float) = 0.0 | |
_Fade0("Fade0",float) = 0.0 | |
_Fade1("Fade1",float) = 0.0 | |
_Fade2("Fade2",float) = 0.0 | |
} | |
SubShader { | |
Tags { "RenderType"="Opaque" } | |
LOD 200 | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf NoLighting | |
#include "UnityCG.cginc" | |
struct Input { | |
float2 uv_MainTex; | |
float3 worldPos; | |
}; | |
sampler2D _MainTex; | |
sampler2D _EchoTex; | |
float4 _MainColor; | |
float _DistanceFade; | |
float _MaxRadius; | |
float _MaxFade; | |
float3 _Position0; | |
float3 _Position1; | |
float3 _Position2; | |
float _Radius0; | |
float _Radius1; | |
float _Radius2; | |
float _Fade0; | |
float _Fade1; | |
float _Fade2; | |
// Custom light model that ignores actual lighting. | |
half4 LightingNoLighting (SurfaceOutput s, half3 lightDir, half atten) { | |
half4 c; | |
c.rgb = s.Albedo; | |
c.a = s.Alpha; | |
return c; | |
} | |
float ApplyFade(Input IN,float3 position, float radius, float infade){ | |
float size = 128.0; //hardcoded width/height of echo texture | |
float dist = distance(IN.worldPos, position); // Distance from current pixel (from its world coord) to center of echo sphere | |
if(radius >= 3*_MaxRadius || dist >= radius){ | |
return 0.0; | |
} else { | |
// If _DistanceFade = true, fading is related to vertex distance from echo origin. | |
// If false, fading is even across entire echo. | |
float c1 = (_DistanceFade>=1.0)?dist/radius:1.0; | |
// Apply fading effect. | |
c1 *= (infade<=_MaxFade)?1.0-infade/_MaxFade:0.0; //adjust by fade distance. | |
// Ignore Fade values <= 0 (meaning no fade.) | |
c1 = (infade<=0)?1.0:c1; | |
return c1; | |
} | |
} | |
// Custom surfacer that mimics an echo effect | |
void surf (Input IN, inout SurfaceOutput o) { | |
float c1; | |
// manually add more echos here. | |
c1 += ApplyFade(IN,_Position0,_Radius0,_Fade0); | |
c1 += ApplyFade(IN,_Position1,_Radius1,_Fade1); | |
c1 += ApplyFade(IN,_Position2,_Radius2,_Fade2); | |
c1 /= 3.0; | |
float c2 = 1.0 - c1; | |
o.Albedo = _MainColor.rgb * c2 + tex2D (_MainTex, IN.uv_MainTex).rgb * c1 ; | |
} | |
ENDCG | |
} | |
FallBack "Diffuse" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Shader "Custom/Echo/MultipleUsingTexture" { | |
Properties { | |
_MainTex ("Base (RGB)", 2D) = "white" {} | |
_EchoTex ("Echo (RGBA)", 2D) = "white" {} | |
_MainColor ("Main Color",Color) = (1.0,1.0,1.0,1.0) | |
_DistanceFade("Distance Fade",float) = 1.0 | |
_MaxRadius("MaxRadius",float) = 1.0 | |
_MaxFade("MaxFade",float) = 1.0 | |
} | |
SubShader { | |
Tags { "RenderType"="Opaque" } | |
LOD 200 | |
CGPROGRAM | |
// Target Shader Model 3.0 or you won't have enough arithmetic instructions for float unpack | |
#pragma target 3.0 | |
#pragma surface surf NoLighting | |
#include "UnityCG.cginc" | |
struct Input { | |
float2 uv_MainTex; | |
float3 worldPos; | |
}; | |
sampler2D _MainTex; | |
sampler2D _EchoTex; | |
float4 _MainColor; | |
float _DistanceFade; | |
float _MaxRadius; | |
float _MaxFade; | |
// Custom light model that ignores actual lighting. | |
half4 LightingNoLighting (SurfaceOutput s, half3 lightDir, half atten) { | |
half4 c; | |
c.rgb = s.Albedo; | |
c.a = s.Alpha; | |
return c; | |
} | |
//unpack float from pixel | |
float unpackFloat4( float4 _packed) | |
{ | |
float4 rgba = 255.0 * _packed; | |
float sign = step(-128.0, -rgba[1]) * 2.0 - 1.0; | |
float exponent = rgba[0] - 127.0; | |
if (abs(exponent + 127.0) < 0.001){ | |
return 0.0; | |
} else { | |
float mantissa = fmod(rgba[1], 128.0) * 65536.0 + rgba[2] * 256.0 + rgba[3] + (0x800000); | |
return sign * exp2(exponent-23.0) * mantissa ; | |
} | |
} | |
float ApplyFade(Input IN,float textureOffset){ | |
float3 position; | |
float size = 128.0; //hard coded texture width/height | |
//NOTE: UV's are [0,1] so divide by the width(or height, they should be equal) of the image. | |
//In this example the texture is 128x128. | |
position[0] = unpackFloat4(tex2D(_EchoTex,float2(textureOffset/size,0.0))); | |
position[1] = unpackFloat4(tex2D(_EchoTex,float2(textureOffset/size,1.0/size))); | |
position[2] = unpackFloat4(tex2D(_EchoTex,float2(textureOffset/size,2.0/size))); | |
float radius = unpackFloat4(tex2D(_EchoTex,float2(textureOffset/size,3.0/size))); | |
float infade = unpackFloat4(tex2D(_EchoTex,float2(textureOffset/size,4.0/size))); | |
float dist = distance(IN.worldPos, position); // Distance from current pixel (from its world coord) to center of echo sphere | |
if(radius >= 3*_MaxRadius || dist >= radius){ | |
return 0.0; | |
} else { | |
// If _DistanceFade = true, fading is related to vertex distance from echo origin. | |
// If false, fading is even across entire echo. | |
float c1 = (_DistanceFade>=1.0)?dist/radius:1.0; | |
// Apply fading effect. | |
c1 *= (infade<=_MaxFade)?1.0-infade/_MaxFade:0.0; //adjust by fade distance. | |
// Ignore Fade values <= 0 (meaning no fade.) | |
c1 = (infade<=0)?1.0:c1; | |
return c1; | |
} | |
} | |
// Custom surfacer that mimics an echo effect | |
void surf (Input IN, inout SurfaceOutput o) { | |
float c1; | |
//Unity was not pleased with my use of a for loop. Somewhere | |
//in the loop unroll Unity blew up and crashed. So echo count | |
//is hardcoded to be 3 here. | |
c1 += ApplyFade(IN,0.0); | |
c1 += ApplyFade(IN,1.0); | |
c1 += ApplyFade(IN,2.0); | |
c1 /= 3.0; | |
float c2 = 1.0 - c1; | |
o.Albedo = _MainColor.rgb * c2 + tex2D (_MainTex, IN.uv_MainTex).rgb * c1 ; | |
} | |
ENDCG | |
} | |
FallBack "Diffuse" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment