uniform sampler2DArray NebulaTextureArray;
[...]
void main()
{
// Hinweise:
// gs_TexCoord[0].z: Index der zu verwendenden Textur
// gs_TexCoord[0].xy: die normalen Texturkoordinaten
vec4 NebulaColor = texture(NebulaTextureArray, gs_TexCoord[0].xyz);
[...]
Für die Übergabe eines Texture-Arrays an einen Fragment Shader verwenden wir in unseren Programmbeispielen die CGLSLShader-Framework-Methode Set_TextureArray():
Shader->Set_TextureArray(0, pNebulaTextureArray, g_MipMappingSamplerID,
"NebulaTextureArray");
"NebulaTextureArray");
void CGLSLShader::Set_TextureArray(long Stage, CTexture* pTexture,
GLuint SamplerID, char* pParameter)
GLuint SamplerID, char* pParameter)
{
// Textur bereits gesetzt:
if(g_StageTexture[Stage] == pTexture)
return;
if(pTexture)
{
if(Stage > TextureStagesUsed)
TextureStagesUsed = Stage;
glActiveTexture(GL_TEXTURE0+Stage);
glBindTexture(GL_TEXTURE_2D_ARRAY, pTexture->TextureID);
if(g_IdOfUsedSamplerObject[Stage] != SamplerID)
{
g_IdOfUsedSamplerObject[Stage] = SamplerID;
glBindSampler(Stage, SamplerID);
}
glUniform1i(glGetUniformLocation(ShaderProgram, pParameter), Stage);
}
g_StageTexture[Stage] = pTexture;
}
Bei der Initialisierung eines neuen Texture-Arrays bietet sich die folgende Vorgehensweise an:
Zunächst werden die Bildinformationen sämtlicher Texturen nacheinander mithilfe von SDL_Image in den RAM-Speicher kopiert. In diesem Zusammenhang verhindern die beiden Helper-Funktionen Enable_TextureArrayGeneration() sowie Disable_TextureArrayGeneration(), dass diese Daten nicht auch automatisch in den Texturspeicher der Grafikkarte übertragen werden.
Als konkretes Beispiel betrachten wir die Erzeugung eines Texture-Arrays, welches im Rahmen des Graphics-And-Physics-Framework-Programmbeispiels Procedural Galaxy Rendering für die volumetrische Nebeldarstellung benötigt wird:
void C3DNebulaSystem::Set_NewTexture(char* pTextureFile)
{
if(NumTexturesUsed >= NumTexturesMax)
return;
// Texturdaten werden zunächst in ein Daten-Array im RAM-Speicher
// kopiert (ppNebulaTexture beansprucht keinerlei Texturspeicher!):
Enable_TextureArrayGeneration();
ppNebulaTexture[NumTexturesUsed] = Load_Texture_And_Get_ResourceID(
&NebulaTextureResourceID[NumTexturesUsed],
pTextureFile, TRUE);
NumTexturesUsed++;
Disable_TextureArrayGeneration();
}
void Enable_TextureArrayGeneration(void)
{
// Texturdaten sollen nicht in den Grafikspeicher übertragen werden
// Dies geschieht erst dann, wenn das Texture Array erzeugt wird!!
g_Enable_TextureArrayGeneration = true;
}
void Disable_TextureArrayGeneration(void)
{
g_Enable_TextureArrayGeneration = false;
}
Nachdem alle benötigten Texturen mithilfe der C3DNebulaSystem-Methode Set_NewTexture() in den RAM-Speicher eingelesen wurden, wird das eigentliche Texture-Array nun auf folgende Weise erzeugt:
- Anlegen eines leeren Texture-Array-Objekts (pNebulaTextureArray).
- Anlegen eines temporären Arrays (pTextureArray), in dem die Speicheradressen der einzelnen Textur-Ressourcen (ppNebulaTexture) abgelegt werden.
- Kopieren der Grafikdaten aus dem RAM- in den Grafikspeicher des Texture-Arrays unter Zuhilfenahme des temporären pTextureArray-Arrays mithilfe der CTexture-Methode Build_TextureArray().
- Löschen des temporären Arrays sowie Freigabe der nun nicht mehr benötigten ppNebulaTexture-Ressourcen.
void C3DNebulaSystem::Build_TextureArray(void)
{
if(NumTexturesUsed < 1)
return;
// einzigartige Bezeichnung für das Texture Array generieren:
long UniqueID = GetTickCount();
char strBuffer[100];
sprintf(strBuffer,"3DNebulaTextureArray_%d", UniqueID);
// leeres Texture-Array-Objekt anlegen:
pNebulaTextureArray = Get_Texture_And_ResourceID(
&NebulaTextureArrayResourceID, strBuffer);
CTexture** pTextureArray = new CTexture*[NumTexturesUsed];
long i;
for(i = 0; i < NumTexturesUsed; i++)
pTextureArray[i] = ppNebulaTexture[i];
// alle Grafikdaten in das Texture-Array-Objekt kopieren:
pNebulaTextureArray->Build_TextureArray(pTextureArray, NumTexturesUsed);
SAFE_DELETE_ARRAY(pTextureArray)
if(NumTexturesUsed > 0)
{
for(i = 0; i < NumTexturesUsed; i++)
Release_Texture(&ppNebulaTexture[i]);
}
}
Um den entscheidenen Schritt – das Kopieren der Texturdaten aus dem RAM- in den Grafikspeicher – besser nachvollziehen zu können, betrachten wir uns die Build_TextureArray()-Methode unserer CTexture-Klasse im Folgenden einmal etwas genauer:
void CTexture::Build_TextureArray(CTexture** ppTextureList,
long NumTextures)
{
if(TextureObject_Initialized == true)
return;
if(TextureID > 0)
glDeleteTextures(1, &TextureID);
// leeres Texture-Array-Objekt im Grafikspeicher anlegen:
glGenTextures(1, &TextureID);
glBindTexture( GL_TEXTURE_2D_ARRAY, TextureID);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, ppTextureList[0]->Width,
ppTextureList[0]->Height, NumTextures, 0, GL_BGRA,
GL_UNSIGNED_BYTE, NULL);
long i;
for(i = 0; i < NumTextures; i++)
{
// Texturdaten nacheinander in den Grafikspeicher des Texture-Arrays
// Texturdaten nacheinander in den Grafikspeicher des Texture-Arrays
// kopieren:
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i,
ppTextureList[i]->Width, ppTextureList[i]->Height, 1,
GL_BGRA, GL_UNSIGNED_BYTE,
ppTextureList[i]->TextureDataRAM);
}
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
for(i = 0; i < NumTextures; i++)
ppTextureList[i]->Delete_Texture();
RenderToTexture = FALSE;
GenerateMipMaps = TRUE;
TextureObject_Initialized = true;
}