uniform column_major float4x4 g_lightWvpMatrixs[MAX_CASCADE_SPLITS];
uniform float3 g_lightDir;
uniform float g_normalOffsetScale[MAX_CASCADE_SPLITS];


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

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

struct PS_OUTPUT_Lighting
{
	float4 Color0 : COLOR0;
#if ENABLE_GODRAYS
	float4 Color1 : COLOR1;
#endif
#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];
uniform float4 g_colorForGodRaysPass;


texture ShadowmapTexture : register(t1);

sampler2D ShadowmapSampler : register(s2)= 
sampler_state{
	texture = <ShadowmapTexture>;
	MinFilter = POINT;
	MagFilter = POINT;
	MipFilter = POINT;   
	AddressU  = Clamp;
	AddressV  = Clamp;
};

VS_OUTPUT_Lighting VS_Lighting(VS_INPUT_Lighting In)
{
	VS_OUTPUT_Lighting 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.InterpolatedPosition = pos;
	
	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 - 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 GetDiffuse(float3 normal)
{
    float3 lightDir = g_lightDir;
	#ifdef TRANSLUCENT
		return abs(dot(normal, -lightDir));
	#else
		return saturate(dot(normal, -lightDir));
	#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_lightWvpMatrixs[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;
}

PS_OUTPUT_Lighting PS_Lighting(VS_OUTPUT_Lighting 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 (g_shadowsEnabled)
	{
		// 根据像素在世界空间中的位置，挑选对应的阴影贴图
		float currentPixelDepth = -In.Depth - 0;
		cascadeIndex = ChooseCascade(currentPixelDepth);
		// 计算阴影系数，消耗大量FPS
		if (cascadeIndex < MAX_CASCADE_SPLITS)
		{
			float4 shadowPos = GetShadowPos(In.InterpolatedPosition, In.Normal, cascadeIndex);
			// shadowPos /= shadowPos.w;
			
			shadowFactor = CalcShadowFactor(shadowPos);
			
			// 混合级联阴影贴图边界，消耗不少FPS
			if (ENABLE_BLEND_ACROSS_CASCADE)
			{
				shadowFactor = BlendCascade(currentPixelDepth, shadowFactor, In.InterpolatedPosition, In.Normal, cascadeIndex);
			}		
		}
		else
		{
			// 超出有效阴影距离，不画阴影
			shadowFactor = 1.0f;
		}
	}
	
	// 合成最终颜色
#if DISABLE_MODELCOLOR
	modelColor = float4(1,1,1,1);
#endif
	float diff = GetDiffuse(In.Normal);
	diff *= shadowFactor;
	float3 finalColor = diff * g_lightColor.rgb * modelColor.rgb;
	if (g_affectedByLight)
	{
		if (g_haveModelTex)
		{
			finalColor += g_ambientColor.rgb * modelColor.rgb;
		}
		else
		{
			finalColor = modelColor.rgb;
		}
	}
	else
	{
		finalColor = shadowFactor * modelColor.rgb;
	}
	float4 color = float4(finalColor, modelColor.a);
	
#if SHOW_DEBUG_CASCADED_SHADOWMAP
	if (cascadeIndex >= 0 && cascadeIndex < MAX_CASCADE_SPLITS)
	{
		color = color * g_shadowmapDebugColors[max(cascadeIndex, 0)];	
	}
#endif

	PS_OUTPUT_Lighting output;
	output.Color0 = color;
	
#if ENABLE_GODRAYS
	output.Color1 = g_colorForGodRaysPass;
#endif
	
#if ENABLE_LINEAR_ZBUFFER
	output.oDepth = linearDepth;
#endif
	return output;

}

technique Lighting
{
	pass p0 
	{
		VertexShader = compile vs_3_0 VS_Lighting();
		PixelShader = compile ps_3_0 PS_Lighting();
	}
}

