Abstract:

Este articulo presenta una nueva técnica para renderizar terreno utilizando múltiples materiales (o texturas). Para ello hace uso de Geometry Shaders, Texture Arrays y Vertex Shader Textures. El objetivo es reducir el espacio ocupado por las texturas de splatting y a la vez incrementar la cantidad de materiales que pueden ser usados a la vez. La implementación del paper asume un material por vértice, pero puede ser extendido para usar varios. Otro punto fuerte es que es ideal para complementar el uso de Geometry Clipmaps, donde no se usan chunks prearmados para renderizar el terreno y donde es difícil saber por draw call que materiales se tienen que usar.

Introduction:

La idea original de texture splatting es simple, usar una textura para mesclar otras texturas o materiales. Por cada capa de material se tiene una textura con la mascara que indica donde se aplica. Esta técnica puede ser usada por vértice también, por lo cual se remueve la necesidad de una textura de splatting y simplemente se usan los colores interpolados por vértice como mascara.

Sin embargo el concepto original tiene varios problemas cuando se pretender usar con terrenos muy grandes usando Geometry Clipmaps. Por un lado esta el problema de espacio, ya que cada material tiene su mascara, y que cuantos mas materiales se usan mas partes de la mascara de splatting queda sin usar. A la vez usando Geometry Clipmaps, es difícil estimar por DrawCall que materiales hay que usar.

The Technique:

La idea básica Indexed texture splatting, es utilizar una textura de indices, que combinado con un Texture Array que contiene todos las materiales (en la practica usamos varios uno para Albedo, otro para NormalMap y otro para Specular), para que cada píxel utilicé solo la textura que le corresponde. De esa forma no importa si una draw call necesita 1 o 30 texturas, cada píxel accede solo a la que necesita. En la practica esto se extiende a tres materiales por píxel, ya que asumimos un material por vértice y cada píxel puede estar mezclando hasta tres materiales, uno por cada vértice del triangulo al que pertenece.

Nota: Asumimos un material por vértice.

Example:

Supongamos que tenemos el siguiente parche de terreno, donde cada vértice tiene un numero que nos indica el material a usar:

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

Como vemos hay triángulos que usan un solo material (A usa solo el 1), otros dos (B, C, D, F) y otros tres (E, G, H). La idea es que cada píxel en un triangulo acceda solo a las texturas que le corresponden.

NOTA: La implementación actual accede a las tres texturas, una por cada vértice del triangulo, con el fin de simplificar la explicación, pero usando Dynamic Branching se puede optar por acceder solo a las necesarias. También simplificamos el hecho de que todas las texturas se acceden con los mismos uv, pero estos también pueden varias por material.

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:
  • Se lee la altura desde el HeightMap (acá se aplica todo el proceso tradicional de usar Geometry Clipmaps)
  • Se lee la textura de indices de materiales (cada vértice tiene su 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
  • Lee los tres vértices de la primitiva
  • Copia la información de cada vértice a los otros vértices, y a la vez genera la mascara de pesos

[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: Accede las texturas de los tres materiales usando en indice pasado por vértice y luego usa los pesos para mesclar las.

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

Nota: Usar nointerpolation en el interpolador de indices, para evitar errores de precisión en ciertos gpus

Conclusions:

Como podemos ver esta técnica tiene mucho potencial, ya que mantiene constantes el uso de memoria y la cantidad de accesos a textura por primitiva, no importa cuantos materiales se usen en cada llamada de dibujo. Ademas es fácil de integrar con 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 6

Comments

No comments yet.