2019-10-30

Octahedral Impostors Octahedral Impostors - Nao_uの日記 を含むブックマーク はてなブックマーク - Octahedral Impostors - Nao_uの日記 Octahedral Impostors - Nao_uの日記 のブックマークコメント

//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 );
}