Tutorial 17 – Terrain Generation + Terrain Rendering

Tutorial 17 demonstriert die Verwendung von Terrain-Generation-Skript-Dateien bei der Terrain-Erzeugung und stellt die beim Terrain Rendering zum Einsatz kommenden Techniken vor.
Das Ziel bei der Entwicklung des Terrain-Renderers bestand darin, ein Terrain aus einer möglichst großen Anzahl kleiner Tiles aufzubauen. Kleine Tiles ermöglichen eine detaillierte Texturierung kleiner Flächen, was unter anderem von Vorteil bei der Darstellung von Straßen und Wegen ist.
Gerendert werden die einzelnen Tiles mittels Geometry Instancing, vorzugsweise unter Verwendung eines Texture-Buffer-Objekts (auf diese Weise lässt sich das komplette Terrain mit nur einem einzigen Draw Call rendern!) zum Speichern der für die Darstellung benötigten Instanzdaten (für die Rekonstruktion der Vertexpositionen des flachen Terrains im Vertex Shader müssen im Texture Buffer lediglich die Tile-Indices gespeichert werden!).
Zum Speichern der Höhenwerte und Vertexnormalen sowie der benötigten Texturinformationen (Textur-IDs und Gewichtungsfaktoren für das Texture Splatting) kommen zwei weitere Texture-Buffer-Objekte zum Einsatz.

Hinweis:
Sofern das Terrain zur Laufzeit dynamisch modifiziert werden soll (zerstörbares Terrain), müssen Sie lediglich die neuen Höhenwerte, Normalen und Texturinformationen der betroffenen Vertices in die jeweiligen Texture-Buffer-Objekte eintragen.



Erzeugen eines Terrains unter Verwendung einer Terrain-Generation-Skript-Datei (TerrainGenerationParamFile.txt):

TerrainTileSet->Generate_Terrain_BinaryDesc(
                "../Models/TerrainGenerationParamFile.txt",
                "../Models/TileSet1", "MeshTestSet1.bin",
                "RenderListsTestSet1.bin");


Beispielhafter Aufbau einer Terrain-Generation-Skript-Datei:

#Num. Tiles XDir:# 100
#Num. Tiles ZDir:# 100
#TileScale: 0.75; 0.75*8 # 7.0
#CenterX:# 0.0
#CenterZ:# 0.0
#BaseHeight:# -25.0
#Tile:# 9VerticesPerTile.txt
#Num Vertices Per TerrainTile(0=4, 1=9, 2=16, 3=25):# 1


#Num Random Height Value Regions:# 1

#Upper Left Coordinates (0.0 to 1.0):# 0.0, 0.0
#Range XDir, ZDir:# 1.0, 1.0
#RandomSmoothness(>=0):# 6
#Minimum Value:# -0.5
#Maximum Value:# 0.5


#Add Height Value:# 0.0


#Num Height Peaks:# 5

#Maximum Height Value:# 70.0
#RandomSmoothness(>=0):# 0
#Minimum Random Height Value:# -2.9
#Maximum Random Height Value:# 2.9
#Influence Radius^2 (Calculation Function 2):# 0.05
#Inv Influence Radius^2 (Calculation Function 3):# 50.0
#HeightCalculationExponent:# 0.632
#AdditionalHeightCalculationFactor:# 0.1
#Height Calculation Function (0 = constHeight, 1 = square^Exp, 2 = 1/(AdditionalCalcFactor+PeakDist^Exp)):# 1
#HeightModification(0 = Add, 1 = Substitute, 2 = Clamp To Max HeightValue, 3 = max):# 3
#Coordinates (0.0 to 1.0):# 0.35, 0.5

[…]

#Num Flat Rectagular Areas:# 1

#Height:# 1.0
#RandomSmoothness(>=0):# 4
#Minimum Random Height Value:# -0.6
#Maximum Random Height Value:# 0.0
#Upper Left Coordinates (0.0 to 1.0):# 0.3, 0.5
#Range XDir, ZDir:# 0.4, 0.4


#Num Flat Circular Areas:# 1

#Height:# 2.0
#RandomSmoothness(>=0):# 4
#Minimum Random Height Value:# -0.2
#Maximum Random Height Value:# 0.9
#Center Coordinates (0.0 to 1.0):# 0.3, 0.5
#Radius^2:# 0.025


#NumFilterRegions:# 1

#Upper Left Coordinates (0.0 to 1.0):# 0.0, 0.0
#Range XDir, ZDir:# 1.0, 1.0
#NumIterations:# 2
#FilterMatrix (smoothing):#      1.0, 2.0, 1.0,
                                 2.0, 24.0, 2.0,
                                 1.0, 2.0, 1.0


# Texturierung: #

#Num Initial Texture Settings:# 1

#Upper Left Coordinates (0.0 to 1.0):# 0.0, 0.0
#Range XDir, ZDir:# 1.0, 1.0
#TextureID_TextureColumn0: # 0
#SplattingValues(min, max):# 0.0, 0.0 
#TextureID_TextureColumn1: # 5
#SplattingValues(min, max):# 0.001, 0.1
#TextureID_TextureColumn2 alt:8:# 12
#SplattingValues(min, max):# 0.001, 0.1
#TextureID_TextureColumn3: # 11
#SplattingValues(min, max):# 0.001, 0.1

#minRandomSplattingRangeValue:# 1.0
#maxRandomSplattingRangeValue:# 13.0

#RandomSplattingRange (single Texture):# 1.0, 2.0
#RandomSplattingRange (Textures: 1, 2):# 2.0, 3.0
#RandomSplattingRange (Textures: 1, 3):# 3.0, 4.0
#RandomSplattingRange (Textures: 1, 4):# 4.0, 5.0
#RandomSplattingRange (Textures: 2, 3):# 5.0, 6.0
#RandomSplattingRange (Textures: 2, 4):# 6.0, 7.0
#RandomSplattingRange (Textures: 3, 4):# 7.0, 8.0
#RandomSplattingRange (Textures: 1, 2, 3):# 8.0, 9.0
#RandomSplattingRange (Textures: 1, 2, 4):# 9.0, 10.0
#RandomSplattingRange (Textures: 1, 3, 4):# 10.0, 11.0
#RandomSplattingRange (Textures: 2, 3, 4):# 11.0, 12.0
#RandomSplattingRange (Textures: 1, 2, 3, 4):# 12.0, 13.0


#Num Vertical Texture Areas:# 1

#min Height:# 30.0
#max Height:# 63.0
#Upper Left Coordinates (0.0 to 1.0):# 0.0, 0.0
#Range XDir, ZDir:# 1.0, 1.0
#SplattingExponent:# 0.1
#IntensityFactor:# 1.0
#TextureColumnID:# 0
#TextureID:# 0
#SplattingCalculation(top=0,bottom=1,center=2,const=3):# 2


#Num Cliff Texture Regions:# 1

#NonCliffTextureSplatting (no=0,yes=1):# 1
#Upper Left Coordinates (0.0 to 1.0):# 0.0, 0.0
#Range XDir, ZDir:# 1.0, 1.0
#Num Cliff Textures (<=3 pro Region):# 1
#TextureColumn:# 0
#TextureID:# 0
#max Normal.y (0 = max slope, 1 = flat Terrain):# 0.085175


#Num Rectagular Texture Areas:# 1

#Upper Left Coordinates (0.0 to 1.0):# 0.8, 0.2
#Range XDir, ZDir:# 0.15, 0.15
#IntensityFactor:# 0.4
#TextureColumnID:# 0
#TextureID:# 12


#Num Circular Texture Areas:# 1

#Center Coordinates (0.0 to 1.0):# 0.7, 0.21
#Radius^2:# 0.007
#SplattingDistanceFactor:# 0.1
#SplattingExponent:# 0.05
#IntensityFactor:# 1.0
#TextureColumnID:# 0
#TextureID:# 12
#SplattingCalculation(0 = distanceBased, 1 = const):# 1


Einlesen der Terraindaten, Erstellen der benötigten Texture-Array-Objekte:

Enable_TextureArrayGeneration();

TerrainTileSet->Init_TileSet("../Models/TileSet1", "MeshTestSet1.bin",
                             "TextureSet1.txt", "RenderListsTestSet1.bin",
                             1, true);
TerrainTileSet->Build_TextureArrays();

Disable_TextureArrayGeneration();


Update des Texture-Buffer-Objekts für das Geometry Instancing:

NumInstances = 0;

// Sinnvollerweise sollte an dieser Stelle ein Sichtbarkeitstest
// durchgeführt werden. Um die Sache nicht zu verkomplizieren, kopieren wir
// jedoch einfach alle Terrain-Tile-Indices in das für das
// Geometry Instancing benötigte Texture-Buffer-Objekt:

for(i = 0; i < pTerrainTileSet->RenderList[k].NumEntries; i++)
{
    j = pTerrainTileSet->RenderList[k].List[i];

    pTerrainTileInstanceArray[NumInstances] = (float)j;
    NumInstances++;
}

pTerrainTileInstancesTBO->Update_Buffer(pTerrainTileInstanceArray,
                                        4*NumInstances); //4 Byte Pro Index

pShader->Set_TextureBuffer(GL_TEXTURE4, 4,
                           pTerrainTileInstancesTBO->Texture,
                          "TerrainTileInstancesTextureBuffer");

pTerrainTileSet->InstancedTerrainTile[0].pMeshVB_IB->Render_Mesh(pShader,
                 LODStep, NumInstances);


Texture-Buffer-based Geometry Instancing:

// Position des Terrain-Tiles auf Basis des Tileindex berechnen:
int Index = int(texelFetch(TerrainTileInstancesTextureBuffer,
                           gl_InstanceID));

float row    = float(mod(Index, NumTilesPerDir));
float column = float(Index/NumTilesPerDir);

vec4 Vertex = vec4(TileScale*gs_Vertex.x, TileScale*gs_Vertex.y,
                   TileScale*gs_Vertex.z, gs_Vertex.w);

Vertex.x += TileScale+column*TileScale2-HalfTerrainRange;
Vertex.y += BaseHeight;
Vertex.z += TileScale+row*TileScale2-HalfTerrainRange;


int x = int(InvTerrainRange*(Vertex.x+HalfTerrainRange)*
            TextureBufferSizeTU_TV_Minus1);

int z = int(InvTerrainRange*(Vertex.z+HalfTerrainRange)*
            TextureBufferSizeTU_TV_Minus1);

int VertexID = clamp(z+x*TextureBufferSizeTU_TV, 0,
                     NumBufferElements_Minus1);

Vertex.x += CenterX;
Vertex.z += CenterZ;

Vertex.xyz = Vertex.xyz-CameraPos;

// Vertexnormale und Höhenwert aus Texture-Buffer auslesen:
vec4 NormalAndHeight = texelFetch(NormalAndHeightTextureBuffer, VertexID);

// Texturindices samt Splatting-Faktoren aus Texture-Buffer auslesen:
gs_TexCoord[5] = texelFetch(TextureWeightFactorsTextureBuffer, VertexID);

// die Stellen vor dem Komma entsprechen den Texturindices:
TextureIDVector = floor(gs_TexCoord[5]);

// die Stellen nach dem Komma entsprechen den (Splatting)
// Gewichtungsfaktoren:
gs_TexCoord[5] -= TextureIDVector;

Vertex.y += NormalAndHeight.w;

gl_Position = matViewProjection*Vertex;


Verwendung von Texture-Array-Objekten beim Texture Splatting im Fragment Shader:

// gs_TexCoord[5]:  Splatting-Faktoren
// TextureIDVector: Indizes der zu verwendenden Texturen

vec4 SurfaceColor_MeanValue = gs_TexCoord[5].x*texture(SurfaceTextureArray,
                              vec3(gs_TexCoord[0].st, TextureIDVector.x))+
                              gs_TexCoord[5].y*texture(SurfaceTextureArray,
                              vec3(gs_TexCoord[0].st, TextureIDVector.y))+
                              gs_TexCoord[5].z*texture(SurfaceTextureArray,
                              vec3(gs_TexCoord[0].st, TextureIDVector.z))+
                              gs_TexCoord[5].w*texture(SurfaceTextureArray,
                              vec3(gs_TexCoord[0].st, TextureIDVector.w));

vec4 NormalColor_MeanValue = gs_TexCoord[5].x*texture(NormalTextureArray,
                             vec3(gs_TexCoord[0].st, TextureIDVector.x))+
                             gs_TexCoord[5].y*texture(NormalTextureArray,
                             vec3(gs_TexCoord[0].st, TextureIDVector.y))+
                             gs_TexCoord[5].z*texture(NormalTextureArray,
                             vec3(gs_TexCoord[0].st, TextureIDVector.z))+
                             gs_TexCoord[5].w*texture(NormalTextureArray,
                             vec3(gs_TexCoord[0].st, TextureIDVector.w));



Hinweise zum Erstellen eines neuen Projekts:

  • Kopieren Sie den Ordner GraphicsAndPhysicsFrameworkImports ins Projektverzeichnis
  • Kopieren sie alle dll-Dateien sowie die Konfigurationsdatei ResolutionAndRendering.txt aus besagtem Ordner ins gleiche Verzeichnis, in dem sich auch die exe-Datei befindet (in unseren Programmbeispielen ist dies das Bin-Verzeichnis)
  • Binden Sie die folgenden Dateien in Ihr Projekt ein: GraphicsAndPhysics_Framework_Imports.h, GraphicsAndPhysics_Framework_Imports.lib, glew32.lib, glew32s.lib, glut32.lib. Die Glew- und Glut-Bibliotheken ermöglichen die Nutzung der aktuellen OpenGL-Spezifikationen unabhängig vom Framework.

GraphicsAndPhysicsFrameworkDemo17.zip