■ Octahedral Impostors
//float3 UVtoOctahedron(float2 uv)
vector2 uv;
uv.x = @P.x;
uv.y = @P.y;
{
// Unpack the 0...1 range to the -1...1 unit square.
vector position = set( (uv.x - 0.5f)*2.0, (uv.y - 0.5f)*2.0,0);
// "Lift" the middle of the square to +1 z, and let it fall off linearly
// to z = 0 along the Manhattan metric diamond (absolute.x + absolute.y == 1),
// and to z = -1 at the corners where position.x and .y are both = +-1.
vector2 absolute;
absolute.x = abs(position.x);
absolute.y = abs(position.y);
position.z = 1.0f - absolute.x - absolute.y;
// "Tuck in" the corners by reflecting the xy position along the line y = 1 - x
// (in quadrant 1), and its mirrored image in the other quadrants.
if(position.z < 0) {
vector2 f2 = set(1.0f - absolute.y, 1.0f - absolute.x);
position.x = sign(position.x) * f2.x;
position.y = sign(position.y) * f2.y;
}
@P.x = position.x;
@P.y = position.y;
@P.z = position.z;
v@absolute = absolute;
}
float2 VectortoOctahedron( float3 N )
{
N /= dot( 1.0, abs(N) );
if( N.z <= 0 )
{
N.xy = ( 1 - abs(N.yx) ) * ( N.xy >= 0 ? 1.0 : -1.0 );
}
return N.xy;
}
float2 VectortoHemiOctahedron( float3 N )
{
N.xy /= dot( 1.0, abs(N) );
return float2( N.x + N.y, N.x - N.y );
}
float3 OctahedronToVector( float2 Oct )
{
float3 N = float3( Oct, 1.0 - dot( 1.0, abs(Oct) ) );
if( N.z < 0 )
{
N.xy = ( 1 - abs(N.yx) ) * ( N.xy >= 0 ? 1.0 : -1.0 );
}
return normalize(N);
}
float3 HemiOctahedronToVector( float2 Oct )
{
Oct = float2( Oct.x + Oct.y, Oct.x - Oct.y ) *0.5;
float3 N = float3( Oct, 1 - dot( 1.0, abs(Oct) ) );
return normalize(N);
}
inline void OctaImpostorVertex( inout float4 vertex, inout float3 normal, inout float4 uvsFrame1, inout float4 uvsFrame2, inout float4 uvsFrame3, inout float4 octaFrame, inout float4 viewPos )
{
// Inputs
float2 uvOffset = _AI_SizeOffset.zw;
float parallax = -_Parallax; // check sign later
float UVscale = _ImpostorSize;
float framesXY = _Frames;
float prevFrame = framesXY - 1;
float3 fractions = 1.0 / float3( framesXY, prevFrame, UVscale );
float fractionsFrame = fractions.x;
float fractionsPrevFrame = fractions.y;
float fractionsUVscale = fractions.z;
// Basic data
float3 worldOrigin = 0;
float4 perspective = float4( 0, 0, 0, 1 );
// if there is no perspective we offset world origin with a 5000 view dir vector, otherwise we use the original world position
if( UNITY_MATRIX_P[ 3 ][ 3 ] == 1 )
{
perspective = float4( 0, 0, 5000, 0 );
worldOrigin = ai_ObjectToWorld._m03_m13_m23;
}
float3 worldCameraPos = worldOrigin + mul( UNITY_MATRIX_I_V, perspective ).xyz;
float3 objectCameraPosition = mul( ai_WorldToObject, float4( worldCameraPos, 1 ) ).xyz - _Offset.xyz; //ray origin
float3 objectCameraDirection = normalize( objectCameraPosition );
// Create orthogonal vectors to define the billboard
float3 upVector = float3( 0,1,0 );
float3 objectHorizontalVector = normalize( cross( objectCameraDirection, upVector ) );
float3 objectVerticalVector = cross( objectHorizontalVector, objectCameraDirection );
// Billboard
float2 uvExpansion = vertex.xy;
float3 billboard = objectHorizontalVector * uvExpansion.x + objectVerticalVector * uvExpansion.y;
float3 localDir = billboard - objectCameraPosition; // ray direction
// Octahedron Frame
#ifdef _HEMI_ON
objectCameraDirection.y = max(0.001, objectCameraDirection.y);
float2 frameOcta = VectortoHemiOctahedron( objectCameraDirection.xzy ) * 0.5 + 0.5;
#else
float2 frameOcta = VectortoOctahedron( objectCameraDirection.xzy ) * 0.5 + 0.5;
#endif
// Setup for octahedron
float2 prevOctaFrame = frameOcta * prevFrame;
float2 baseOctaFrame = floor( prevOctaFrame );
float2 fractionOctaFrame = ( baseOctaFrame * fractionsFrame );
// Octa 1
float2 octaFrame1 = ( baseOctaFrame * fractionsPrevFrame ) * 2.0 - 1.0;
#ifdef _HEMI_ON
float3 octa1WorldY = HemiOctahedronToVector( octaFrame1 ).xzy;
#else
float3 octa1WorldY = OctahedronToVector( octaFrame1 ).xzy;
#endif
float3 octa1LocalY;
float2 uvFrame1;
RayPlaneIntersectionUV( octa1WorldY, objectCameraPosition, localDir, /*inout*/ uvFrame1, /*inout*/ octa1LocalY );
float2 uvParallax1 = octa1LocalY.xy * fractionsFrame * parallax;
uvFrame1 = ( uvFrame1 * fractionsUVscale + 0.5 ) * fractionsFrame + fractionOctaFrame;
uvsFrame1 = float4( uvParallax1, uvFrame1) - float4( 0, 0, uvOffset );
// Octa 2
float2 fractPrevOctaFrame = frac( prevOctaFrame );
float2 cornerDifference = lerp( float2( 0,1 ) , float2( 1,0 ) , saturate( ceil( ( fractPrevOctaFrame.x - fractPrevOctaFrame.y ) ) ));
float2 octaFrame2 = ( ( baseOctaFrame + cornerDifference ) * fractionsPrevFrame ) * 2.0 - 1.0;
#ifdef _HEMI_ON
float3 octa2WorldY = HemiOctahedronToVector( octaFrame2 ).xzy;
#else
float3 octa2WorldY = OctahedronToVector( octaFrame2 ).xzy;
#endif
float3 octa2LocalY;
float2 uvFrame2;
RayPlaneIntersectionUV( octa2WorldY, objectCameraPosition, localDir, /*inout*/ uvFrame2, /*inout*/ octa2LocalY );
float2 uvParallax2 = octa2LocalY.xy * fractionsFrame * parallax;
uvFrame2 = ( uvFrame2 * fractionsUVscale + 0.5 ) * fractionsFrame + ( ( cornerDifference * fractionsFrame ) + fractionOctaFrame );
uvsFrame2 = float4( uvParallax2, uvFrame2) - float4( 0, 0, uvOffset );
// Octa 3
float2 octaFrame3 = ( ( baseOctaFrame + 1 ) * fractionsPrevFrame ) * 2.0 - 1.0;
#ifdef _HEMI_ON
float3 octa3WorldY = HemiOctahedronToVector( octaFrame3 ).xzy;
#else
float3 octa3WorldY = OctahedronToVector( octaFrame3 ).xzy;
#endif
float3 octa3LocalY;
float2 uvFrame3;
RayPlaneIntersectionUV( octa3WorldY, objectCameraPosition, localDir, /*inout*/ uvFrame3, /*inout*/ octa3LocalY );
float2 uvParallax3 = octa3LocalY.xy * fractionsFrame * parallax;
uvFrame3 = ( uvFrame3 * fractionsUVscale + 0.5 ) * fractionsFrame + ( fractionOctaFrame + fractionsFrame );
uvsFrame3 = float4( uvParallax3, uvFrame3) - float4( 0, 0, uvOffset );
// maybe remove this?
octaFrame = 0;
octaFrame.xy = prevOctaFrame;
#if AI_CLIP_NEIGHBOURS_FRAMES
octaFrame.zw = fractionOctaFrame;
#endif
vertex.xyz = billboard + _Offset.xyz;
normal.xyz = objectCameraDirection;
// view pos
viewPos = 0;
#ifdef AI_RENDERPIPELINE
viewPos.xyz = TransformWorldToView( TransformObjectToWorld( vertex.xyz ) );
#else
viewPos.xyz = UnityObjectToViewPos( vertex.xyz );
#endif
#ifdef EFFECT_HUE_VARIATION
float hueVariationAmount = frac( ai_ObjectToWorld[0].w + ai_ObjectToWorld[1].w + ai_ObjectToWorld[2].w);
viewPos.w = saturate(hueVariationAmount * _HueVariation.a);
#endif
}
inline void OctaImpostorFragment( inout SurfaceOutputStandardSpecular o, out float4 clipPos, out float3 worldPos, float4 uvsFrame1, float4 uvsFrame2, float4 uvsFrame3, float4 octaFrame, float4 interpViewPos )
{
float depthBias = -1.0;
float textureBias = _TextureBias;
// Octa1
float4 parallaxSample1 = tex2Dbias( _Normals, float4( uvsFrame1.zw, 0, depthBias) );
float2 parallax1 = ( ( 0.5 - parallaxSample1.a ) * uvsFrame1.xy ) + uvsFrame1.zw;
float4 albedo1 = tex2Dbias( _Albedo, float4( parallax1, 0, textureBias) );
float4 normals1 = tex2Dbias( _Normals, float4( parallax1, 0, textureBias) );
float4 mask1 = tex2Dbias( _Emission, float4( parallax1, 0, textureBias) );
float4 spec1 = tex2Dbias( _Specular, float4( parallax1, 0, textureBias) );
// Octa2
float4 parallaxSample2 = tex2Dbias( _Normals, float4( uvsFrame2.zw, 0, depthBias) );
float2 parallax2 = ( ( 0.5 - parallaxSample2.a ) * uvsFrame2.xy ) + uvsFrame2.zw;
float4 albedo2 = tex2Dbias( _Albedo, float4( parallax2, 0, textureBias) );
float4 normals2 = tex2Dbias( _Normals, float4( parallax2, 0, textureBias) );
float4 mask2 = tex2Dbias( _Emission, float4( parallax2, 0, textureBias) );
float4 spec2 = tex2Dbias( _Specular, float4( parallax2, 0, textureBias) );
// Octa3
float4 parallaxSample3 = tex2Dbias( _Normals, float4( uvsFrame3.zw, 0, depthBias) );
float2 parallax3 = ( ( 0.5 - parallaxSample3.a ) * uvsFrame3.xy ) + uvsFrame3.zw;
float4 albedo3 = tex2Dbias( _Albedo, float4( parallax3, 0, textureBias) );
float4 normals3 = tex2Dbias( _Normals, float4( parallax3, 0, textureBias) );
float4 mask3 = tex2Dbias( _Emission, float4( parallax3, 0, textureBias) );
float4 spec3 = tex2Dbias( _Specular, float4( parallax3, 0, textureBias) );
// Weights
float2 fraction = frac( octaFrame.xy );
float2 invFraction = 1 - fraction;
float3 weights;
weights.x = min( invFraction.x, invFraction.y );
weights.y = abs( fraction.x - fraction.y );
weights.z = min( fraction.x, fraction.y );
// Blends
float4 blendedAlbedo = albedo1 * weights.x + albedo2 * weights.y + albedo3 * weights.z;
float4 blendedNormal = normals1 * weights.x + normals2 * weights.y + normals3 * weights.z;
float4 blendedMask = mask1 * weights.x + mask2 * weights.y + mask3 * weights.z;
float4 blendedSpec = spec1 * weights.x + spec2 * weights.y + spec3 * weights.z;
float3 localNormal = blendedNormal.rgb * 2.0 - 1.0;
float3 worldNormal = normalize( mul( (float3x3)ai_ObjectToWorld, localNormal ) );
float3 viewPos = interpViewPos.xyz;
float depthOffset = ( ( parallaxSample1.a * weights.x + parallaxSample2.a * weights.y + parallaxSample3.a * weights.z ) - 0.5 /** 2.0 - 1.0*/ ) /** 0.5*/ * _DepthSize * length( ai_ObjectToWorld[ 2 ].xyz );
#if !defined(AI_RENDERPIPELINE) // no SRP
#if defined(SHADOWS_DEPTH)
if( unity_LightShadowBias.y == 1.0 ) // get only the shadowcaster, this is a hack
{
viewPos.z += depthOffset * _AI_ShadowView;
viewPos.z += -_AI_ShadowBias;
}
else // else add offset normally
{
viewPos.z += depthOffset;
}
#else // else add offset normally
viewPos.z += depthOffset;
#endif
#elif defined(AI_RENDERPIPELINE) // SRP
#if ( defined(SHADERPASS) && (SHADERPASS == SHADERPASS_SHADOWS) ) || defined(UNITY_PASS_SHADOWCASTER)
viewPos.z += depthOffset * _AI_ShadowView;
viewPos.z += -_AI_ShadowBias;
#else // else add offset normally
viewPos.z += depthOffset;
#endif
#endif
worldPos = mul( UNITY_MATRIX_I_V, float4( viewPos.xyz, 1 ) ).xyz;
clipPos = mul( UNITY_MATRIX_P, float4( viewPos, 1 ) );
#if !defined(AI_RENDERPIPELINE) // no SRP
#if defined(SHADOWS_DEPTH)
clipPos = UnityApplyLinearShadowBias( clipPos );
#endif
#elif defined(AI_RENDERPIPELINE) // SRP
#if defined(UNITY_PASS_SHADOWCASTER) && !defined(SHADERPASS)
#if UNITY_REVERSED_Z
clipPos.z = min( clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE );
#else
clipPos.z = max( clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE );
#endif
#endif
#endif
clipPos.xyz /= clipPos.w;
if( UNITY_NEAR_CLIP_VALUE < 0 )
clipPos = clipPos * 0.5 + 0.5;
#ifdef EFFECT_HUE_VARIATION
half3 shiftedColor = lerp(blendedAlbedo.rgb, _HueVariation.rgb, interpViewPos.w);
half maxBase = max(blendedAlbedo.r, max(blendedAlbedo.g, blendedAlbedo.b));
half newMaxBase = max(shiftedColor.r, max(shiftedColor.g, shiftedColor.b));
maxBase /= newMaxBase;
maxBase = maxBase * 0.5f + 0.5f;
shiftedColor.rgb *= maxBase;
blendedAlbedo.rgb = saturate(shiftedColor);
#endif
#if AI_CLIP_NEIGHBOURS_FRAMES
float t = ceil( fraction.x - fraction.y );
float4 cornerDifference = float4( t, 1 - t, 1, 1 );
float2 step_1 = ( parallax1 - octaFrame.zw ) * _Frames;
float4 step23 = ( float4( parallax2, parallax3 ) - octaFrame.zwzw ) * _Frames - cornerDifference;
step_1 = step_1 * (1-step_1);
step23 = step23 * (1-step23);
float3 steps;
steps.x = step_1.x * step_1.y;
steps.y = step23.x * step23.y;
steps.z = step23.z * step23.w;
steps = step(-steps, 0);
float final = dot( steps, weights );
clip( final - 0.5 );
#endif
o.Albedo = blendedAlbedo.rgb;
o.Normal = worldNormal;
o.Emission = blendedMask.rgb;
o.Specular = blendedSpec.rgb;
o.Smoothness = blendedSpec.a;
o.Occlusion = blendedMask.a;
o.Alpha = ( blendedAlbedo.a - _ClipMask );
clip( o.Alpha );
}