Last active
January 1, 2025 21:33
-
-
Save runevision/0bde31108e140bb019bfc197236b5177 to your computer and use it in GitHub Desktop.
Water Foam Particle Shader
This file contains hidden or 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
UNITY_DECLARE_SHADOWMAP(_SunCascadedShadowMap); | |
float4 _SunCascadedShadowMap_TexelSize; | |
#define GET_CASCADE_WEIGHTS(wpos, z) getCascadeWeights_splitSpheres(wpos) | |
#define GET_SHADOW_FADE(wpos, z) getShadowFade_SplitSpheres(wpos) | |
#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord(wpos,cascadeWeights) | |
/** | |
* Gets the cascade weights based on the world position of the fragment and the poisitions of the split spheres for each cascade. | |
* Returns a float4 with only one component set that corresponds to the appropriate cascade. | |
*/ | |
inline fixed4 getCascadeWeights_splitSpheres(float3 wpos) | |
{ | |
float3 fromCenter0 = wpos.xyz - unity_ShadowSplitSpheres[0].xyz; | |
float3 fromCenter1 = wpos.xyz - unity_ShadowSplitSpheres[1].xyz; | |
float3 fromCenter2 = wpos.xyz - unity_ShadowSplitSpheres[2].xyz; | |
float3 fromCenter3 = wpos.xyz - unity_ShadowSplitSpheres[3].xyz; | |
float4 distances2 = float4(dot(fromCenter0,fromCenter0), dot(fromCenter1,fromCenter1), dot(fromCenter2,fromCenter2), dot(fromCenter3,fromCenter3)); | |
fixed4 weights = float4(distances2 < unity_ShadowSplitSqRadii); | |
weights.yzw = saturate(weights.yzw - weights.xyz); | |
return weights; | |
} | |
/** | |
* Returns the shadow fade based on the world position of the fragment, and the distance from the shadow fade center | |
*/ | |
inline float getShadowFade_SplitSpheres( float3 wpos ) | |
{ | |
float sphereDist = distance(wpos.xyz, unity_ShadowFadeCenterAndType.xyz); | |
half shadowFade = saturate(sphereDist * _LightShadowData.z + _LightShadowData.w); | |
return shadowFade; | |
} | |
/** | |
* Returns the shadowmap coordinates for the given fragment based on the world position and z-depth. | |
* These coordinates belong to the shadowmap atlas that contains the maps for all cascades. | |
*/ | |
inline float4 getShadowCoord( float4 wpos, fixed4 cascadeWeights ) | |
{ | |
float3 sc0 = mul (unity_WorldToShadow[0], wpos).xyz; | |
float3 sc1 = mul (unity_WorldToShadow[1], wpos).xyz; | |
float3 sc2 = mul (unity_WorldToShadow[2], wpos).xyz; | |
float3 sc3 = mul (unity_WorldToShadow[3], wpos).xyz; | |
return float4(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3], 1); | |
} | |
/** | |
* Combines the different components of a shadow coordinate and returns the final coordinate. | |
*/ | |
inline float3 combineShadowcoordComponents (float2 baseUV, float2 deltaUV, float depth, float2 receiverPlaneDepthBias) | |
{ | |
float3 uv = float3( baseUV + deltaUV, depth ); | |
uv.z += dot (deltaUV, receiverPlaneDepthBias); // apply the depth bias | |
return uv; | |
} | |
/** | |
* PCF shadowmap filtering based on a 3x3 kernel (optimized with 4 taps) | |
* | |
* Algorithm: http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/ | |
* Implementation example: http://mynameismjp.wordpress.com/2013/09/10/shadow-maps/ | |
*/ | |
half sampleShadowmap_PCF3x3 (float4 coord, float2 receiverPlaneDepthBias) | |
{ | |
const float2 offset = float2(0.5,0.5); | |
float2 uv = (coord.xy * _SunCascadedShadowMap_TexelSize.zw) + offset; | |
float2 base_uv = (floor(uv) - offset) * _SunCascadedShadowMap_TexelSize.xy; | |
float2 st = frac(uv); | |
float2 uw = float2( 3-2*st.x, 1+2*st.x ); | |
float2 u = float2( (2-st.x) / uw.x - 1, (st.x)/uw.y + 1 ); | |
u *= _SunCascadedShadowMap_TexelSize.x; | |
float2 vw = float2( 3-2*st.y, 1+2*st.y ); | |
float2 v = float2( (2-st.y) / vw.x - 1, (st.y)/vw.y + 1); | |
v *= _SunCascadedShadowMap_TexelSize.y; | |
half shadow; | |
half sum = 0; | |
sum += uw[0] * vw[0] * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u[0], v[0]), coord.z, receiverPlaneDepthBias) ); | |
sum += uw[1] * vw[0] * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u[1], v[0]), coord.z, receiverPlaneDepthBias) ); | |
sum += uw[0] * vw[1] * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u[0], v[1]), coord.z, receiverPlaneDepthBias) ); | |
sum += uw[1] * vw[1] * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u[1], v[1]), coord.z, receiverPlaneDepthBias) ); | |
shadow = sum / 16.0f; | |
shadow = lerp (_LightShadowData.r, 1.0f, shadow); | |
return shadow; | |
} | |
/** | |
* PCF shadowmap filtering based on a 5x5 kernel (optimized with 9 taps) | |
* | |
* Algorithm: http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/ | |
* Implementation example: http://mynameismjp.wordpress.com/2013/09/10/shadow-maps/ | |
*/ | |
half sampleShadowmap_PCF5x5 (float4 coord, float2 receiverPlaneDepthBias) | |
{ | |
const float2 offset = float2(0.5,0.5); | |
float2 uv = (coord.xy * _SunCascadedShadowMap_TexelSize.zw) + offset; | |
float2 base_uv = (floor(uv) - offset) * _SunCascadedShadowMap_TexelSize.xy; | |
float2 st = frac(uv); | |
float3 uw = float3( 4-3*st.x, 7, 1+3*st.x ); | |
float3 u = float3( (3-2*st.x) / uw.x - 2, (3+st.x)/uw.y, st.x/uw.z + 2 ); | |
u *= _SunCascadedShadowMap_TexelSize.x; | |
float3 vw = float3( 4-3*st.y, 7, 1+3*st.y ); | |
float3 v = float3( (3-2*st.y) / vw.x - 2, (3+st.y)/vw.y, st.y/vw.z + 2 ); | |
v *= _SunCascadedShadowMap_TexelSize.y; | |
half shadow; | |
half sum = 0.0f; | |
half3 accum = uw * vw.x; | |
sum += accum.x * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.x,v.x), coord.z, receiverPlaneDepthBias) ); | |
sum += accum.y * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.y,v.x), coord.z, receiverPlaneDepthBias) ); | |
sum += accum.z * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.z,v.x), coord.z, receiverPlaneDepthBias) ); | |
accum = uw * vw.y; | |
sum += accum.x * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.x,v.y), coord.z, receiverPlaneDepthBias) ); | |
sum += accum.y * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.y,v.y), coord.z, receiverPlaneDepthBias) ); | |
sum += accum.z * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.z,v.y), coord.z, receiverPlaneDepthBias) ); | |
accum = uw * vw.z; | |
sum += accum.x * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.x,v.z), coord.z, receiverPlaneDepthBias) ); | |
sum += accum.y * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.y,v.z), coord.z, receiverPlaneDepthBias) ); | |
sum += accum.z * UNITY_SAMPLE_SHADOW( _SunCascadedShadowMap, combineShadowcoordComponents( base_uv, float2(u.z,v.z), coord.z, receiverPlaneDepthBias) ); | |
shadow = sum / 144.0f; | |
shadow = lerp (_LightShadowData.r, 1.0f, shadow); | |
return shadow; | |
} | |
/** | |
* Samples the shadowmap at the given coordinates. | |
*/ | |
half unity_sampleShadowmap( float4 coord ) | |
{ | |
half shadow = UNITY_SAMPLE_SHADOW(_SunCascadedShadowMap,coord); | |
shadow = lerp(_LightShadowData.r, 1.0, shadow); | |
return shadow; | |
} | |
/////////////////// | |
/** | |
* Gets the shadows attenuations at world positions. | |
*/ | |
half GetSunShadowsAttenuation(float3 worldPositions, float screenDepth) | |
{ | |
fixed4 cascadeWeights = GET_CASCADE_WEIGHTS(worldPositions.xyz, screenDepth); | |
return unity_sampleShadowmap(GET_SHADOW_COORDINATES(float4(worldPositions, 1), cascadeWeights)); | |
} | |
half GetSunShadowsAttenuation_PCF3x3(float3 worldPositions, float screenDepth, float receiverPlaneDepthBias) | |
{ | |
fixed4 cascadeWeights = GET_CASCADE_WEIGHTS(worldPositions.xyz, screenDepth); | |
return sampleShadowmap_PCF3x3(GET_SHADOW_COORDINATES(float4(worldPositions, 1), cascadeWeights), receiverPlaneDepthBias); | |
} | |
half GetSunShadowsAttenuation_PCF5x5(float3 worldPositions, float screenDepth, float receiverPlaneDepthBias) | |
{ | |
fixed4 cascadeWeights = GET_CASCADE_WEIGHTS(worldPositions.xyz, screenDepth); | |
return sampleShadowmap_PCF5x5(GET_SHADOW_COORDINATES(float4(worldPositions, 1), cascadeWeights), receiverPlaneDepthBias); | |
} |
This file contains hidden or 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/WaterFoamParticle" { | |
Properties { | |
_BumpSphereScale ("Sphere Normal Scale", Float) = 1.0 | |
_BumpNoiseScale ("Noise Normal Scale", Float) = 1.0 | |
_ShadowOffsetMult ("Shadow Offset Mult", Range(0, 1)) = 0.5 | |
_MainTex ("Noise", 2D) = "white" {} | |
_EdgeToNoiseFreq ("Edge To Noise Freq", Float) = 5.0 | |
_NoiseStrength ("Noise Strength", Range(0, 1)) = 1.0 | |
_Radius ("Radius", Range(0, 1)) = 1.0 | |
_Sharpness ("Sharpness", Range(1, 20)) = 1.0 | |
_Scroll ("Scroll", Range(0, 1)) = 1.0 | |
//[Header(Colors)] | |
//_FoamTint ("Foam Tint", Color) = (1,1,1,1) | |
[Header(Lighting Hacks)] | |
_FoamIndirectLightMultiplier ("Foam Ambient Multiplier", Range(0, 20)) = 7 | |
[Header(Material Properties)] | |
_Glossiness ("Smoothness", Range(0,1)) = 0.5 | |
_Metallic ("Metallic", Range(0,1)) = 0.0 | |
} | |
SubShader { | |
Tags { "RenderType"="Opaque" } | |
LOD 200 | |
CGPROGRAM | |
#pragma surface surf Water vertex:vert alpha:premul | |
#pragma target 3.0 | |
#define _ALPHAPREMULTIPLY_ON 1 | |
#include "Lighting.cginc" | |
#include "Shadows.cginc" | |
#include "UnityPBSLighting.cginc" | |
// Textures | |
sampler2D _MainTex; | |
// Colors | |
fixed3 _FoamTint; | |
// Values | |
half _BumpSphereScale, _BumpNoiseScale; | |
half _NoiseStrength; | |
half _ShadowOffsetMult; | |
half _EdgeToNoiseFreq; | |
half _Radius; | |
half _Sharpness; | |
half _FoamIndirectLightMultiplier; | |
float _Scroll; | |
fixed _Glossiness; | |
fixed _Metallic; | |
struct appdata_t { | |
float4 vertex : POSITION; | |
float4 tangent : TANGENT; | |
float3 normal : NORMAL; | |
float4 texcoord : TEXCOORD0; | |
float4 texcoord1 : TEXCOORD1; | |
float4 texcoord2 : TEXCOORD2; | |
float4 texcoord3 : TEXCOORD3; | |
fixed4 color : COLOR; | |
}; | |
struct Input { | |
float2 uv_MainTex; | |
float2 rawUV; | |
float4 color : COLOR; | |
float4 screenPos; | |
float3 worldPos; | |
float3 worldNormal; INTERNAL_DATA | |
half size; | |
}; | |
struct SurfaceOutputWater { | |
fixed3 Albedo; // base (diffuse or specular) color | |
float3 Normal; // tangent space normal, if written | |
half3 Emission; | |
half Metallic; // 0=non-metal, 1=metal | |
// Smoothness is the user facing name, it should be perceptual smoothness but user should not have to deal with it. | |
// Everywhere in the code you meet smoothness it is perceptual smoothness | |
half Smoothness; // 0=rough, 1=smooth | |
half Occlusion; // occlusion (default 1) | |
fixed Alpha; // alpha for transparencies | |
// Added: | |
fixed ShadowAttenuation; | |
}; | |
void vert (inout appdata_t v, out Input o) { | |
UNITY_INITIALIZE_OUTPUT(Input,o); | |
o.rawUV = v.texcoord.xy; | |
o.size = v.texcoord.z; | |
} | |
inline half4 LightingWater (SurfaceOutputWater s, float3 viewDir, UnityGI gi) { | |
s.Normal = normalize(s.Normal); | |
// shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha) | |
// this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha | |
s.Albedo *= s.Alpha; | |
// Apply shadow to light contribution | |
gi.light.color *= s.ShadowAttenuation; | |
// Boost indirect lighting in shadow | |
gi.indirect.diffuse *= _FoamIndirectLightMultiplier; | |
// Remove specular/reflection from foam | |
gi.indirect.specular = 0; | |
half4 c = UNITY_BRDF_PBS (s.Albedo, 0, 0, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect); | |
c.a = s.Alpha; | |
return c; | |
} | |
inline void LightingWater_GI (SurfaceOutputWater s, UnityGIInput data, inout UnityGI gi) { | |
#if defined(UNITY_PASS_DEFERRED) && UNITY_ENABLE_REFLECTION_BUFFERS | |
gi = UnityGlobalIllumination(data, s.Occlusion, s.Normal); | |
#else | |
Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.Smoothness, data.worldViewDir, s.Normal, lerp(unity_ColorSpaceDielectricSpec.rgb, s.Albedo, s.Metallic)); | |
gi = UnityGlobalIllumination(data, s.Occlusion, s.Normal, g); | |
#endif | |
} | |
float3 GetNormalFromPattern (float pattern, float scale, float2 uv) { | |
float4 xyzw = float4(ddx(uv), ddy(uv)); | |
float determinant = 1 / (xyzw.x * xyzw.w - xyzw.y * xyzw.z); | |
float4 inverse = float4(xyzw.w, -xyzw.y, -xyzw.z, xyzw.x) * determinant; | |
float2 deriv = float2(-ddx(pattern), -ddy(pattern)); | |
float2 transformed = float2(dot(deriv, inverse.xy), dot(deriv, inverse.zw)); | |
return normalize(float3(transformed, 1.0 / scale)); | |
} | |
void surf (Input IN, inout SurfaceOutputWater o) { | |
fixed2 dirFromCenter = IN.rawUV - 0.5; | |
float radial = 1 - 2 * length(dirFromCenter); | |
float2 noiseUV = IN.uv_MainTex + IN.color.xy; | |
noiseUV += frac(float2(_Time.z, _Time.w) * _Scroll); | |
float noise = tex2D(_MainTex, noiseUV).a - 0.5; | |
// Shape | |
float val = lerp (radial - 0.5 + _Radius, 0.5 + (noise) * _EdgeToNoiseFreq, _NoiseStrength); | |
val = lerp(0.5, val, _Sharpness); | |
// Reduce based on particle alpha | |
half sizeOverLifetime = IN.color.a; | |
half maxPossibleValue = lerp(0.5 + _Radius, 0.5 + 0.4 * _EdgeToNoiseFreq, _NoiseStrength); | |
maxPossibleValue = lerp(0.5, maxPossibleValue, _Sharpness); | |
val -= (1 - sizeOverLifetime) * maxPossibleValue; | |
// Restrict to circular area and smoothen | |
val *= max(radial, smoothstep(0, 1, radial * _Sharpness * 0.5)); | |
val = sqrt(val); | |
val = smoothstep(0, 1, val); | |
// Calculate normal | |
float heightForNormal = _BumpSphereScale / length(float3(dirFromCenter, 1)) | |
+ noise * _EdgeToNoiseFreq * _BumpNoiseScale; | |
float3 normal = GetNormalFromPattern(heightForNormal, 1, IN.rawUV); | |
// World space variables | |
fixed offset = (1 - val) * IN.size * _ShadowOffsetMult; | |
float3 positionWS = IN.worldPos; | |
float3 viewDirWS = normalize(UnityWorldSpaceViewDir(positionWS)); | |
float3 normalRawWS = WorldNormalVector (IN, float3(0, 0, 1)); | |
float headOn = 0.1 + 0.9 * saturate(dot(viewDirWS, normalRawWS)); | |
// Shadow | |
float3 shadowOffset = -viewDirWS; | |
float shadowAttenuation = | |
GetSunShadowsAttenuation_PCF5x5(positionWS + shadowOffset * offset, IN.screenPos.z, 0).x; | |
// Output | |
o.Albedo = 0.5; | |
o.Alpha = val; | |
o.Normal = normal; | |
//o.Fade = val; | |
o.ShadowAttenuation = shadowAttenuation; | |
o.Metallic = _Metallic; | |
o.Smoothness = _Glossiness; | |
} | |
ENDCG | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment