uniform column_major float4x4 g_lightVpMatrixs[MAX_CASCADE_SPLITS];
uniform float3 g_lightDir;
uniform float g_normalOffsetScale[MAX_CASCADE_SPLITS];
#if FOG_TYPE > 0
uniform float4 g_fogParams;
uniform float3 g_fogColor;
uniform bool g_isSkyBox;
#endif

#if NUM_ENABLED_FREE_LIGHT > 0
uniform float4 g_freeLightColorArray[NUM_ENABLED_FREE_LIGHT];
uniform float4 g_freeLightPosArray[NUM_ENABLED_FREE_LIGHT];
uniform bool g_freeLightSwitchArray[NUM_ENABLED_FREE_LIGHT];
#endif

struct VS_INPUT_MainLighting
{
	float3 Position : POSITION;
	float3 Normal   : NORMAL;
	float4 Diffuse : COLOR;
	float2 UV : TEXCOORD0;
	float4 BoneBlendData[MAX_BONE_BIND_COUNT / 2] : TEXCOORD1;
};

struct VS_OUTPUT_MainLighting
{
	float4 Position : POSITION;
	float4 Diffuse : COLOR;
	float3 Normal : TEXCOORD3;
	float2 UV : TEXCOORD0;
	float Depth : TEXCOORD1;
	float4 WorldPos : TEXCOORD2;
#if ENABLE_LINEAR_ZBUFFER	
	float LogZ : TEXCOORD5;
#endif
};

struct PS_OUTPUT_MainLighting
{
	float4 Color0 : COLOR0;
#if ENABLE_LINEAR_ZBUFFER	
	float oDepth : DEPTH;
#endif
};

uniform float4 g_shadowmapDebugColors[MAX_CASCADE_SPLITS];
#if PCF_ROW > 0
uniform float4 g_pcfTexOffsets[PCF_ROW * PCF_ROW];
#endif
uniform float4 g_lightColor;
uniform float4 g_ambientColor;
uniform float g_shadowStrength;
uniform bool g_affectedByLight;
uniform float4 g_cascadeSplits;
uniform float2 g_shadowmapCascadeUVOffsets[4];


texture ShadowmapTexture : register(t1);

sampler2D ShadowmapSampler = 
sampler_state{
	texture = <ShadowmapTexture>;
	MinFilter = POINT;
	MagFilter = POINT;
	MipFilter = POINT;   
	AddressU  = Clamp;
	AddressV  = Clamp;
};

texture RampTexture : register(t3);

sampler2D RampSampler = 
sampler_state{
	texture = <RampTexture>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	MipFilter = NONE;
	AddressU  = Clamp;
	AddressV  = Clamp;
};

VS_OUTPUT_MainLighting VS_MainLighting(VS_INPUT_MainLighting In)
{
	VS_OUTPUT_MainLighting Out;
	
	float4 pos, normal;
	if (g_processSkinning)
	{		
		SkinningResult skinningResult = SkinningWithNormal(float4(In.Position, 1.0f), float4(In.Normal, 0.0f), In.BoneBlendData);
		pos = skinningResult.pos;
		normal = skinningResult.normal;
	}
	else
	{
		pos = float4(In.Position ,1);
		normal = float4(In.Normal, 0);
	}
	
	float2 uv = In.UV + g_textureAnimationUVOffset;
	pos.xyz = FluidAnimate(pos.xyz, g_fluidAnimationParams.x, g_fluidAnimationParams.y, g_fluidAnimationParams.z, g_fluidAnimationParams.w);
	
	Out.Position = mul( pos, g_wvpMatrix);
#if ENABLE_LINEAR_ZBUFFER
	Out.LogZ = 1.0f + Out.Position.w;
#endif
	
	
	Out.Depth = mul(pos, g_wvMatrix).z;	
	Out.WorldPos = mul(pos, g_wMatrix);
	
	Out.Diffuse = In.Diffuse;
	if (g_modelAlphaReplacing >= 0.0f)
	{
		Out.Diffuse.a = g_modelAlphaReplacing;
	}
	
	
	Out.UV = uv;
	Out.Normal = mul((float3)normal, (float3x3) g_wMatrix) ;
	return Out;
}

bool IsInShadow(float lightProjected_depth, float shadowmap_depth)
{
	float depthInShadowmap = shadowmap_depth;
	float depthInScreen = lightProjected_depth;
	return depthInShadowmap < depthInScreen;
}

float CalcShadowFactor(float3 shadowPos)
{
	float2 shadowPosInBigTex = shadowPos.xy;
	float z = shadowPos.z;
	float factor = 0.0;

#if PCF_ROW > 0	
	for(int i = 0; i < PCF_ROW * PCF_ROW; i++)
	{
		float2 uv = shadowPosInBigTex.xy + g_pcfTexOffsets[i].xy ;
		float depth = tex2D(ShadowmapSampler, uv ).r;
		if (depth < z)
			factor += 0.0f;
		else
			factor += 1.0f;
	}
	factor = factor / (PCF_ROW * PCF_ROW);
#else
	float2 uv = shadowPosInBigTex.xy;
	float depth = tex2D(ShadowmapSampler, uv ).r;
	if (depth < z)
		factor = 0.0f;
	else
		factor = 1.0f;
#endif
	
	factor = g_shadowStrength + factor * (1.0f - g_shadowStrength);
	float result = factor;
	return result;
}

bool IsInShadowmap(float2 shadowPos, int cascadeIndex)
{
	const int boarderPixelCount = 2;
	const float invActualSize = 1.0f / (SHADOWMAP_SIZE - boarderPixelCount);
	if (cascadeIndex == 0)
	{
		return shadowPos.x >= 0.0f + invActualSize && shadowPos.x < 0.5f - invActualSize && shadowPos.y >= 0.0f + invActualSize && shadowPos.y < 0.5f - invActualSize;
	}
	else if (cascadeIndex == 1)
	{
		return shadowPos.x >= 0.5f + invActualSize && shadowPos.x < 1.0f - invActualSize && shadowPos.y >= 0.0f + invActualSize && shadowPos.y < 0.5f - invActualSize;
	}
	else if (cascadeIndex == 2)
	{
		return shadowPos.x >= 0.0f + invActualSize && shadowPos.x < 0.5f - invActualSize && shadowPos.y >= 0.5 + invActualSize && shadowPos.y < 1.0f - invActualSize;
	}
	else if (cascadeIndex == 3)
	{
		return shadowPos.x >= 0.5f + invActualSize && shadowPos.x < 1.0f - invActualSize && shadowPos.y >= 0.5 + invActualSize && shadowPos.y < 1.0f - invActualSize;
	}
	else
	{
		return false;
	}
}

int ChooseCascade(float pixelDepth)
{
	float4 depthComparision = (float4(pixelDepth, pixelDepth, pixelDepth, pixelDepth) > g_cascadeSplits);
	const int CURRENT_CASCADES_COUNT = MAX_CASCADE_SPLITS;
	float fcascadeIndex = dot(float4(
	CURRENT_CASCADES_COUNT > 0,
	CURRENT_CASCADES_COUNT > 1,
	CURRENT_CASCADES_COUNT > 2,
	CURRENT_CASCADES_COUNT > 3
	), depthComparision);
	return fcascadeIndex;
}

texture g_texTest : register(t0);
sampler2D g_texSamplerTest = sampler_state
{
	Texture = <g_texTest>;
	MinFilter = POINT;
	MagFilter = POINT;
	MipFilter = POINT;   
	AddressU  = Wrap;
	AddressV  = Wrap;
	
};

float GetDirLightDiffuse(float3 normal)
{
    float3 lightDir = g_lightDir;
	#ifdef TRANSLUCENT
		return abs(dot(normal, -lightDir));
	#else
		return saturate(dot(normal, -lightDir));
	#endif
}
#if NUM_ENABLED_FREE_LIGHT > 0
float GetFreeLightDiffuse(float3 normal, float3 worldPos, int lightIndex, out float3 lightDir)
{
	float4 lightPos = g_freeLightPosArray[lightIndex];
	float3 lightVec = (worldPos - lightPos.xyz) * lightPos.w;
	float lightDist = length(lightVec);
	lightDir = lightVec / lightDist;
	#ifdef TRANSLUCENT
		return abs(dot(normal, -lightDir)) * tex2D(RampSampler, float2(lightDist, 0.0)).r;
	#else
		return saturate(dot(normal, -lightDir)) * tex2D(RampSampler, float2(lightDist, 0.0)).r;
	#endif
}
float3 GetFreeLightAmbient(float3 worldPos, int lightIndex)
{
	float4 lightColor = g_freeLightColorArray[lightIndex];
	float4 lightPos = g_freeLightPosArray[lightIndex];
	float3 lightVec = (worldPos - lightPos.xyz) * lightPos.w;
	float lightDist = length(lightVec);
	return tex2D(RampSampler, float2(lightDist, 0.0)).r * lightColor.rgb;
}
#endif
float4 GetShadowPos(float4 projWorldPos, float3 normal, int cascadeIndex)
{
	float cosAngle = saturate(dot(normal, -g_lightDir));
	float4 shadowPos = mul(float4(projWorldPos.xyz + cosAngle * g_normalOffsetScale[cascadeIndex] * normal, 1.0f), g_lightVpMatrixs[cascadeIndex]);
	return shadowPos;
}

float BlendCascade(float pixelDepth, float shadowFactor, float4 projWorldPos, float3 normal, int cascadeIndex)
{
	// Sample the next cascade, and blend between the two results to
	// smooth the transition
	const float BlendThreshold = 0.1f;
	float nextSplit = g_cascadeSplits[cascadeIndex];
	float splitSize = cascadeIndex == 0 ? nextSplit : nextSplit - g_cascadeSplits[cascadeIndex - 1];
	float splitDist = (nextSplit - pixelDepth) / splitSize;

	if(splitDist <= BlendThreshold && cascadeIndex != MAX_CASCADE_SPLITS - 1)
	{
		float4 nextShadowPos = GetShadowPos(projWorldPos, normal, cascadeIndex + 1);
		if (IsInShadowmap(nextShadowPos, cascadeIndex + 1))
		{
			//nextShadowPos /= nextShadowPos.w;
			float3 nextShadowFactor = CalcShadowFactor(nextShadowPos);
			float lerpAmt = smoothstep(0.0f, BlendThreshold, splitDist);
			float3 shadowFactorBlend = lerp(nextShadowFactor, shadowFactor, lerpAmt);
			shadowFactor = shadowFactorBlend;
		}
	}
	
	return shadowFactor;
}
// 雾公式
// https://docs.microsoft.com/en-us/windows/win32/direct3d9/fog-formulas
#if FOG_TYPE
float GetFogFactor(float depth)
{
    return saturate((g_fogParams.x - depth/g_farClip) * g_fogParams.y);
}
float GetHeightFogFactor(float depth, float height)
{
    float fogFactor = GetFogFactor(depth);
    float heightFogFactor = (height - g_fogParams.z) * g_fogParams.w;
    heightFogFactor = 1.0 - saturate(exp(-(heightFogFactor * heightFogFactor)));
    return min(heightFogFactor, fogFactor);
}
#endif

float3 GetFog(float3 color, float3 fogColor, float fogFactor)
{
    return lerp(fogColor, color, fogFactor);
}

PS_OUTPUT_MainLighting PS_MainLighting(VS_OUTPUT_MainLighting In)
{
#if ENABLE_LINEAR_ZBUFFER
	const float Fcoef = 2.0 / log2(g_farClip + 1.0);
	float linearDepth = log2(In.LogZ) * (0.5f * Fcoef);
#endif
	
	// 归一化法线
	In.Normal = normalize(In.Normal);
	// 混合模型颜色
	float4 modelColor;
	float4 texColor = tex2D(g_texSampler, In.UV);
	if (!g_haveModelTex)
	{
		modelColor = In.Diffuse;		
	}
	else
	{
		modelColor = texColor * In.Diffuse;
		#if ENABLE_HOLLOW_EFFECT
		{
			if (texColor.a < g_hollowThreshold)
			{
				discard;
			}
		}
		#endif
	}
	
	// 计算光照强度
	float lightIntensity = saturate(dot(In.Normal, -g_lightDir));
		
	float shadowFactor = 1.0f;
	int cascadeIndex = -1;
#if SHADOW_FEATURE_ENABLED
	if (g_shadowsEnabled)
	{
		// 根据像素在世界空间中的位置，挑选对应的阴影贴图
		float currentPixelDepth = -In.Depth - 0;
		cascadeIndex = ChooseCascade(currentPixelDepth);
		// 计算阴影系数，消耗大量FPS
		if (cascadeIndex < MAX_CASCADE_SPLITS)
		{
			float4 shadowPos = GetShadowPos(In.WorldPos, In.Normal, cascadeIndex);
			// shadowPos /= shadowPos.w;
			
			shadowFactor = CalcShadowFactor(shadowPos);
			
			// 混合级联阴影贴图边界，消耗不少FPS
			if (ENABLE_BLEND_ACROSS_CASCADE)
			{
				shadowFactor = BlendCascade(currentPixelDepth, shadowFactor, In.WorldPos, In.Normal, cascadeIndex);
			}		
		}
		else
		{
			// 超出有效阴影距离，不画阴影
			shadowFactor = 1.0f;
		}
	}
#endif
	
	// 合成最终颜色
#if DISABLE_MODELCOLOR
	modelColor = float4(1,1,1,1);
#endif
	float diff = GetDirLightDiffuse(In.Normal);
	diff *= shadowFactor;
	float3 finalColor = float3(0,0,0);
	if (g_affectedByLight)
	{
		if (g_haveModelTex)
		{
			finalColor += diff * g_lightColor.rgb * modelColor.rgb;
			#if NUM_ENABLED_FREE_LIGHT > 0
			for(int lightIndex = 0; lightIndex < NUM_ENABLED_FREE_LIGHT; lightIndex++)
			{
				if (!g_freeLightSwitchArray[lightIndex])
				{
					continue;
				}
				
				float3 lightDir;
				diff = GetFreeLightDiffuse(In.Normal, In.WorldPos.xyz, lightIndex, lightDir);
				float4 freeLightColor = g_freeLightColorArray[lightIndex];
				float3 lightAmbient = GetFreeLightAmbient(In.WorldPos.xyz, lightIndex);
				finalColor += lightAmbient * modelColor.rgb + diff * freeLightColor.rgb * modelColor.rgb;
			}
			#endif
			finalColor += g_ambientColor.rgb * modelColor.rgb;
		}
		else
		{
			// 武器拖影
			finalColor = modelColor.rgb;
		}
	}
	else
	{
		finalColor = shadowFactor * modelColor.rgb;
	}
	
	// 雾化效果
	#if FOG_TYPE
	#if FOG_TYPE == 1
	float fogFactor = GetFogFactor(-In.Depth);
	#elif FOG_TYPE == 2
	float fogFactor = GetHeightFogFactor(-In.Depth, In.WorldPos.z);
	#endif
	float4 color;
	if (finalColor.r != 0 || finalColor.g != 0 || finalColor.b != 0)
	{
		if (!g_isSkyBox)
		{
			color = float4(GetFog(finalColor, g_fogColor, fogFactor), modelColor.a);
		}
		else
		{
			color = float4(GetFog(finalColor, finalColor+g_fogColor, fogFactor), modelColor.a);
		}
	}
	else
	{
		color = float4(finalColor, modelColor.a);
	}

	#else
	float4 color = float4(finalColor, modelColor.a);	
	#endif
	
	
#if SHOW_DEBUG_CASCADED_SHADOWMAP
	if (cascadeIndex >= 0 && cascadeIndex < MAX_CASCADE_SPLITS)
	{
		color = color * g_shadowmapDebugColors[max(cascadeIndex, 0)];	
	}
#endif

	PS_OUTPUT_MainLighting output;
	output.Color0 = color;
		
#if ENABLE_LINEAR_ZBUFFER
	output.oDepth = linearDepth;
#endif
	return output;

}

technique MainLighting
{
	pass p0 
	{
		VertexShader = compile vs_3_0 VS_MainLighting();
		PixelShader = compile ps_3_0 PS_MainLighting();
	}
}

uniform float4 g_colorForGodRaysPass;

void VS_DrawGlowObject(
	float3 iPosition : POSITION,
	float3 iNormal   : NORMAL,
	float4 iDiffuse : COLOR,
	float2 iUV : TEXCOORD0,
	out float4 oPosition : POSITION,
	out float4 oDiffuse : COLOR,
	out float3 oNormal : TEXCOORD3,
	out float2 oUV : TEXCOORD0,
	out float oDepth : TEXCOORD1,
	out float4 oWorldPos : TEXCOORD2
#if ENABLE_LINEAR_ZBUFFER	
	,out float oLogZ : TEXCOORD4
#endif
)
{
	float4 pos, normal;
	pos = float4(iPosition ,1);
	normal = float4(iNormal, 0);
		
	oPosition = mul(pos, g_wvpMatrix);
#if ENABLE_LINEAR_ZBUFFER
	oLogZ = 1.0f + oPosition.w;
#endif
	
	oDepth = mul(pos, g_wvMatrix).z;	
	oWorldPos = mul(pos, g_wMatrix);
	oDiffuse = iDiffuse;
	oUV = iUV;
	oNormal = mul((float3)normal, (float3x3) g_wMatrix) ;
	
#if ENABLE_LINEAR_ZBUFFER	
	// 避免z-frighting
	oLogZ -= 1.0f;
#endif
}

void PS_DrawGlowObject(
	float4 iDiffuse : COLOR,
	float3 iNormal : TEXCOORD3,
	float2 iUV : TEXCOORD0,
	float iDepth : TEXCOORD1,
	float4 iWorldPos : TEXCOORD2
#if ENABLE_LINEAR_ZBUFFER	
	,float iLogZ : TEXCOORD4
#endif
	,uniform bool ball,

	out float4 oColor : COLOR0
#if ENABLE_LINEAR_ZBUFFER	
	,out float oDepth : DEPTH
#endif
)
{
	if (ball)
	{
		oColor = float4(g_colorForGodRaysPass.rgb, 1.0);
		oColor.a = 1.0f;
	}
	else
	{
		float4 texColor = tex2D(g_texSampler, iUV);
		float4 modelColor = texColor * iDiffuse;
		float3 finalColor = g_colorForGodRaysPass * modelColor;
		oColor = float4(finalColor, 1.0f);	
	}
	
#if ENABLE_LINEAR_ZBUFFER
	const float Fcoef = 2.0 / log2(g_farClip + 1.0);
	float linearDepth = log2(iLogZ) * (0.5f * Fcoef);
#endif
#if ENABLE_LINEAR_ZBUFFER
	oDepth = linearDepth;
#endif
}

technique DrawGlowObject
{
	pass p0
	{
		SrcBlend = ONE;
		DestBlend = ONE;
		BlendOp = ADD;
		#if ENABLE_LINEAR_ZBUFFER == 0
		DepthBias = -0.00001f; // 避免z-frighting
		#endif
		VertexShader = compile vs_3_0 VS_DrawGlowObject();
		PixelShader = compile ps_3_0 PS_DrawGlowObject(true);
	}
	pass p1
	{
		SrcBlend = ONE;
		DestBlend = ONE;
		BlendOp = ADD;
		#if ENABLE_LINEAR_ZBUFFER == 0
		DepthBias = -0.00001f; // 避免z-frighting
		#endif
		VertexShader = compile vs_3_0 VS_DrawGlowObject();
		PixelShader = compile ps_3_0 PS_DrawGlowObject(false);
	}
}

