Abstract:

This article introduces a new technique for terrain shading using multiple material (or textures). It uses Geometry Shaders, Texture Arrays and Vertex Shader Textures. One of the goals is to reduce the storage space used for the splatting mask, and at the same time increment the number of materials that can be used at the same time. This implementation assumes one material per vertex, but it can be extended. Another strong point is that this technique can be used with Geometry Clipmaps, where not pre-baked chunks are used and where it's hard to tell which materials are required on each draw call.

Introduction:

The original texture splatting is simple, it uses a texture to blend textures or materials. Each material has a splatting texture mask, but it can be done per vertex too. However, the original implementation has some issues in combination with big terrains using Geometry Clipmaps. For example, storage could be a problem since each material needs a mask. It's also hard to find with materials need to be used per draw call. And lastly, using complex materials (with diffuse, specular and normal-maps) and sample all that texture to just be throw away (since usually only one, two maybe three materials are needed per pixel) is a waste of resources.

The original texture splatting is simple, use a texture to blend a textures or materials. Each material has a splatting texture mask, but it can be done per vertex too. However the original implementation has some issues in combination with big terrains using Geometry Clipmaps. For example storage could be a problem since each material need a mask. Is also hard to find with materials need to be use per draw call.

The Technique:

The basic idea behind this technique is the following: use a texture with index per vertex in combination with a texture array that contain all the materials (in practice one array per texture type, as albedo, specularmaps, normal-maps) so each pixel only has to sample the textures that it needs. In practice, if we assume one material per vertex, each pixel needs to blend up to 3 materials, if all the vertices of a triangle have different materials.

Example:

Suppose we have this terrain patch, where each vertex has the index of the material to be used.

1-------1-------2 
| A   / | C   / | 
|   /   |   /   | 
| /   B | /   D | 
1-------2-------5 
| E   / | G   / | 
|   /   |   /   | 
| /   F | /   H | 
3-------3-------4 

In this example there are triangles that only use one material (A), some two (B, C, D and F) and others use three (E, G, H). So a pixel in G needs to blend three materials, no matter how many materials are used in the patch.

Note: We assume that each material uses the same UVs. Also in the sample we access the three materials per primitive, but it can be optimized with dynamic branching to sample only the required textures .

Shaders: Where the magic happens

Input stuff:
//! Constant Buffer View Global Parameters
cbuffer PerViewParameters : register( b0 )
{
	// View and Projection Matrices (and its combination, inverses, etc)
	matrix	g_matView		: packoffset(  c0 );
	matrix	g_matProjection		: packoffset(  c4 );
	matrix	g_matViewProjection	: packoffset(  c8 );
};

// Textures

Texture2D	g_txHeightMap		: register( t0 );	// 
Texture2D	g_txIndices		: register( t1 );	// 
Texture2DArray	g_txMaterialMaps	: register( t2 );	// 
	
// Samplers
SamplerState	g_sampleLinear		: register( s0 );

// Structs

struct VSI_Position2D
{
	float2 position	: POSITION;
};

struct TerrainVertexOut
{
			float4 position	: SV_POSITION;
	linear		float3 uv	: TEXCOORD0;
	nointerpolation	float4 material	: TEXCOORD1;
	linear		float3 weights	: TEXCOORD2;
};
	
struct TerrainVertexInfo
{
	float4	positionWS;
	float	material;
};


Vertex Shaders:
  • Sample Heightmap (as in any GPU Geometry Clipmap implementation)
  • Sample the index texture (each vertex has a material)


TerrainVertexOut VS_Terrain( VSI_Position2D input)
{
	TerrainVertexOut output = (TerrainVertexOut)0;	
	
	float3	vPositionOS	= float3(input.position.x, 0, input.position.y);

	float4	vPositionWS;
	vPositionWS.x		= input.position.x;// * g_vScale + g_vPosition;
	vPositionWS.z		= input.position.y;// * g_vScale + g_vPosition;
	vPositionWS.y		= g_txHeightMap.Load(int3(vPositionWS.xz, 0)) * 64;
	vPositionWS.w		= 1;

	float  materialID	= g_txIndices.Load(int3(vPositionWS.xz, 0)) * 255;
	
	output.position		= mul(float4(vPositionWS.xyz, 1), g_matViewProjection);
	output.uv.xyz		= vPositionWS.xyz;
	output.material.x	= floor(materialID);

	return output;
}


// Geometry Shader
  • Load the three vertices of the primitive
  • Copy all info to the output vertices. Also write the vertex materials ID on all vertices and generate the weight mask

[maxvertexcount(3)]
void GS_Terrain( triangle TerrainVertexOut input[3], inout TriangleStream<TerrainVertexOut> TriStream )
{
	TerrainVertexOut vertex0 = input[0];
	TerrainVertexOut vertex1 = input[1];
	TerrainVertexOut vertex2 = input[2];

	vertex0.material.xyz = float3(vertex0.material.x, vertex1.material.x, vertex2.material.x);
	vertex1.material.xyz = float3(vertex0.material.x, vertex1.material.x, vertex2.material.x);
	vertex2.material.xyz = float3(vertex0.material.x, vertex1.material.x, vertex2.material.x);
	
	vertex0.weights.xyz	 = float3(1, 0, 0);
	vertex1.weights.xyz	 = float3(0, 1, 0);
	vertex2.weights.xyz	 = float3(0, 0, 1);

	TriStream.Append( vertex0 );
	TriStream.Append( vertex1 );
	TriStream.Append( vertex2 );
        
	TriStream.RestartStrip();
}


Pixel Shader: Read the three textures for that primitive and use the weights to blend them.

float4 PS_Terrain(TerrainVertexOut input)  : SV_Target
{   
	float2 uv		= input.uv.xz * 0.25;

	float4 clrGround0	= g_txMaterialMaps.Sample(g_sampleLinear, float3(uv, input.material.x)) * input.weights.x;
	float4 clrGround1	= g_txMaterialMaps.Sample(g_sampleLinear, float3(uv, input.material.y)) * input.weights.y;
	float4 clrGround2	= g_txMaterialMaps.Sample(g_sampleLinear, float3(uv, input.material.z)) * input.weights.z;

	return clrGround0 + clrGround1 + clrGround2;
}

Note: Use nointerpolation on the index interpolator to avoid precision errors on some hardware.

Conclusions:

As we can see, this technique has a lot of potential, it keeps constant both storage and samples per primitive, no matter how many textures are use per drawcall. Also, it is easy to integrate with Geometry ClipMaps.

References:

Terrain Texture Compositing by Blending in the Frame-Buffer - by Charles Bloom - http://www.cbloom.com/3d/techdocs/splatting.txt

Texture Splatting in Direct3D - by Nate Glasser - http://www.gamedev.net/reference/articles/article2238.asp

Terrain Rendering Using GPU-Based Geometry Clipmaps - by Arul Asirvatham & Hugues Hoppe - http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter02.html




Last edited Nov 25, 2010 at 6:41 PM by SergioJdelos, version 14

Comments

No comments yet.