/************************************************************
 * Height-dependent blend mapping terrain shader			*
 * by Joel Sjöqvist ("Rudolpho")							*
 *															*
 * Included with the foliage example project for the		*
 * Ziggurat (DX11 plugin) project.							*
 ************************************************************/

// This is a constant buffer that will be set by the engine for each rendering pass
cbuffer MatrixBuffer : register(b0) {
	// Precomputed on a per-frame basis
	float4x4 View;
	float4x4 Projection;
	float4x4 ViewProj;			
};

// This is a separate constant buffer provided from our DBPro program.
// It is only set once at startup (but its contents can be changed at runtime if desired).
cbuffer TerrainDataBuffer : register(b1) {
	float	MaxHeight;
}

// Textures are accessed as an array. There are four textures expected by this shader; stage 0 will be projected onto the
// parts of the terrain with the lowest altitude, then stage 2, 3 and finally 4 at the highest peaks.
Texture2D DiffuseMap[4];

// One sampler is good enough for this application; you may however provide up to 16 so that you can have as many as you can have textures.
// Since there is only a single sampler, we won't need to access it as an array (like the textures are). As a matter of fact, you can reference 
// each individual resource as its own variable instance too, but if you do you will have to tell the shader to which register it is bound
// (the first texture would be in register t0, the second in register t1 and so on).
sampler DiffuseSampler;

// Transform data is supplied by the Ziggurat engine using a special StructuredBuffer bound to register t24
struct sInstanceTransform {
	float4x4 World;
	float4x4 WorldViewProjection;
};

StructuredBuffer<sInstanceTransform> Transform : register(t24);

// Input from the program to the vertex shader (corresponds to the used vertex layout of the rendered mesh)
struct vs_in {
	float3		pos			: POSITION;
	float2		texcoord	: TEXCOORD;
	// We need the instance id to index into the proper Transform element.
	// Specifying the below semantic will cause the input assembler to provide us with this value.
	uint		iId			: SV_INSTANCEID;
};

// Output from the vertex shader stage
struct vs_out {
	float4 pos		: SV_POSITION;
	float2 texcoord : TEXCOORD;
	float  pHeight	: PERCENTAGE;
};


/**
 * The vertex shader is basically a so called «pass through shader», meaning it forwards its inputs to the next shader stage (in this case the pixel shader).
 * Certain calculations are done, such as converting the input object space vertex positions into screen space by multiplying it with the
 * world, view and projection matrices.
 * The pHeight member of the returned vs_out instance is set to a percentage of the elevation of the current vertex.
 * This value will be interpolated between vertices in the pixel shader and is used to determine which textures to blend between (and how much) for each pixel.
 **/
vs_out VS(vs_in IN) {
	vs_out OUT;

	OUT.pos			= mul(float4(IN.pos, 1.0), Transform[IN.iId].WorldViewProjection);
	OUT.texcoord	= IN.texcoord;
	OUT.pHeight		= IN.pos.y / MaxHeight;

    return OUT;
}

/**
 * The pixel shader blends between the four texture stages depending on how far from the terrain mesh's zero height
 * a certain pixel is located. The texture section's heights are just arbitrarily chosen and tweaked by trial and error
 * to get a decent look to the terrain, in case the texture selection looks unexpected.
 * On the whole there's nothing weird going on here, this could all be done with standard DBPro and shader model 2/3 as well.
 **/
float4 PS(vs_out IN) : SV_TARGET {
	// Blend textures based on height from the zero plane (the terrain is assumed to have its lowest height value at 0 and no negative heights)
	// Sample textures...
	float4 col[4];
	col[0] = DiffuseMap[0].Sample(DiffuseSampler, IN.texcoord);
	col[1] = float4(DiffuseMap[1].Sample(DiffuseSampler, IN.texcoord).xyz * 2.8, 1.0);	// This texture is a bit dark so it doesn't blend in very well with the rest unless we brighten it
	col[2] = DiffuseMap[2].Sample(DiffuseSampler, IN.texcoord);
	col[3] = float4(DiffuseMap[3].Sample(DiffuseSampler, IN.texcoord).xyz * 0.75, 1.0);	// Likewise, this one's a bit too light so we darken it
	
	// ... And blend the two adjacent ones chosen by this pixel's height together through linear interpolation
	float p = IN.pHeight;
	if(p > 0.6)
		return lerp(col[2], col[3], saturate((IN.pHeight - 0.6) * 8.0));
	else if(p > 0.4)
		return lerp(col[1], col[2], (IN.pHeight - 0.4) * 5);
	else
		return saturate(lerp(col[0], col[1], saturate(IN.pHeight * 14)));
}
