Verantwortlich für die Initialisierung der für das Geometry Instancing benötigten Texture-Buffer-Objekte sind die Set_NumSunsMax()-Methode der CGalaxyGraphics-Klasse sowie die Set_NumNebulaObjectsMax()-Methode der C3DNebulaSystem-Klasse. Bitte haben Sie dafür Verständnis, dass wir im Rahmen dieses Artikels unser Augenmerk lediglich auf die Darstellung der Sterne richten werden:
void CGalaxyGraphics::Set_NumSunsMax(long numSunsMax)
{
SAFE_DELETE_ARRAY(InstanceWorldViewProjectionMatrix)
SAFE_DELETE_ARRAY(WorldSpacePosition)
SAFE_DELETE_ARRAY(SunScale)
SAFE_DELETE_ARRAY(SunTextureID)
SAFE_DELETE_ARRAY(CameraSpacePosition)
SAFE_DELETE_ARRAY(IDOfSunsNearTheCamera)
SAFE_DELETE(InstanceDataArray)
SAFE_DELETE(SunInstancesTBO)
IDOfSunNearTheCamera = 0;
MaxLocalGroupDistanceSq = 0.0f;
NumOfSunsInsideLocalGroup = 0;
NumSunsMax = numSunsMax;
NumSunsDefined = 0;
NumSunsVisible = 0;
InstanceWorldViewProjectionMatrix = new D3DXMATRIXA16[NumSunsMax];
WorldSpacePosition = new D3DXVECTOR3[NumSunsMax];
SunScale = new float[NumSunsMax];
SunTextureID = new float[NumSunsMax];
CameraSpacePosition = new D3DXVECTOR3[NumSunsMax];
IDOfSunsNearTheCamera = new long[NumSunsMax];
SunInstancesTBO = new CTextureBufferObject;
// Pro Sonne werden 16 float-Parameter im Texture Buffer
// gespeichert:
SunInstancesTBO->Init_TextureBuffer(4*NumSunsMax, 4);
InstanceDataArray = new float[16*NumSunsMax];
}
{
SAFE_DELETE_ARRAY(InstanceWorldViewProjectionMatrix)
SAFE_DELETE_ARRAY(WorldSpacePosition)
SAFE_DELETE_ARRAY(SunScale)
SAFE_DELETE_ARRAY(SunTextureID)
SAFE_DELETE_ARRAY(CameraSpacePosition)
SAFE_DELETE_ARRAY(IDOfSunsNearTheCamera)
SAFE_DELETE(InstanceDataArray)
SAFE_DELETE(SunInstancesTBO)
IDOfSunNearTheCamera = 0;
MaxLocalGroupDistanceSq = 0.0f;
NumOfSunsInsideLocalGroup = 0;
NumSunsMax = numSunsMax;
NumSunsDefined = 0;
NumSunsVisible = 0;
InstanceWorldViewProjectionMatrix = new D3DXMATRIXA16[NumSunsMax];
WorldSpacePosition = new D3DXVECTOR3[NumSunsMax];
SunScale = new float[NumSunsMax];
SunTextureID = new float[NumSunsMax];
CameraSpacePosition = new D3DXVECTOR3[NumSunsMax];
IDOfSunsNearTheCamera = new long[NumSunsMax];
SunInstancesTBO = new CTextureBufferObject;
// Pro Sonne werden 16 float-Parameter im Texture Buffer
// gespeichert:
SunInstancesTBO->Init_TextureBuffer(4*NumSunsMax, 4);
InstanceDataArray = new float[16*NumSunsMax];
}
Bei Programmstart werden zunächst alle Sterne bzw. Nebelwolken im Weltraum positioniert:
void CGalaxyGraphics::Init_Sun(D3DXVECTOR3* pWorldSpacePosition,
float sunScale, long sunTextureID)
{
if(NumSunsDefined >= NumSunsMax)
return;
WorldSpacePosition[NumSunsDefined] = *pWorldSpacePosition;
SunScale[NumSunsDefined] = sunScale;
SunTextureID[NumSunsDefined] = sunTextureID;
NumSunsDefined++;
}
Die Update_InstanceWorldViewProjectionMatrices()-Methoden der CGalaxyGraphics-Klasse sind für die Überprüfung der Sichtbarkeit der einzelnen Sterne sowie für die Berechnung der für die korrekte Darstellung benötigten World-View-Projection-Matrizen verantwortlich:
void CGalaxyGraphics::Update_InstanceWorldViewProjectionMatrices(
long IdOfCenteredSun, float CenteredSunScaleMultiplier,
D3DXVECTOR3* pCameraPosition, D3DXVECTOR3* pCameraViewDirection,
D3DXMATRIXA16* pViewProjectionMatrix, float maxVisibilityDistanceSq)
{
// Diese Methode kommt immer dann zum Einsatz, wenn sich der Spieler
// (und mit ihm die Kamera) innerhalb eines Sonnensystems (IdOfCenteredSun)
// befindet! Alle weiteren Sterne liegen im Hintergrund.
float angle = 0.0f;
IDOfSunNearTheCamera = 0;
NumOfSunsInsideLocalGroup = 0;
float minDistanceSq = 100000000000000.0f;
float DistanceSq;
NumSunsVisible = 0;
float modifiedScale;
for(long i = 0; i < NumSunsDefined; i++)
{
if(IdOfCenteredSun == i)
// Position des Zentralgestirns relativ zur Kamera im Sonnensystem,
// in dem sich der Spieler momentan befindet:
CameraSpacePosition[i] = WorldSpacePosition[i] - *pCameraPosition;
else
// Der Abstand aller Hintergrundsterne ist unabhängig von der
// aktuellen Position der Kamera:
CameraSpacePosition[i] = WorldSpacePosition[i] –
WorldSpacePosition[IdOfCenteredSun];
// Sichtbarkeitstest:
DistanceSq = D3DXVec3LengthSq(&CameraSpacePosition[i]);
if(DistanceSq > maxVisibilityDistanceSq)
continue;
if(D3DXVec3Dot(&CameraSpacePosition[i], pCameraViewDirection) < 0.0f)
continue;
// Sichtbarkeitstest abgeschlossen
// Speichern der Indizes aller Sterne in der näheren Umgebung
// (wichtig sowohl für die Anzeige der Sternennamen als auch für
// die Darstellung von Lens-Flare-Effekten!).
if(DistanceSq < MaxLocalGroupDistanceSq)
{
IDOfSunsNearTheCamera[NumOfSunsInsideLocalGroup] = i;
NumOfSunsInsideLocalGroup++;
}
if(DistanceSq < minDistanceSq)
{
minDistanceSq = DistanceSq;
IDOfSunNearTheCamera = i;
}
// Berechnung der World-View-Projection-Matrizen:
if(IdOfCenteredSun == i)
{
modifiedScale = CenteredSunScaleMultiplier*SunScale[i];
Positioning_2DObject_rotated(
&InstanceWorldViewProjectionMatrix[NumSunsVisible],
&CameraSpacePosition[i], modifiedScale, angle);
}
else
Positioning_2DObject_rotated(
&InstanceWorldViewProjectionMatrix[NumSunsVisible],
&CameraSpacePosition[i], SunScale[i], angle);
InstanceWorldViewProjectionMatrix[NumSunsVisible] =
InstanceWorldViewProjectionMatrix[NumSunsVisible]*
(*pViewProjectionMatrix);
// Null-Element zum Speichern des Textur-Index zweckentfremden:
InstanceWorldViewProjectionMatrix[NumSunsVisible]._14 = SunTextureID[i];
NumSunsVisible++;
}
}
Im zweiten Schritt müssen die für die Darstellung der Instanzen benötigten Daten – die Indizes der zu verwendenden Texturen sowie die Elemente der zuvor berechneten World-View-Projection-Matrizen – mithilfe der Update_SunInstancesTBO()-Methode in den Texture Buffer kopiert werden:
void CGalaxyGraphics::Update_SunInstancesTBO(void)
{
Build_ShaderMatrix4X4Array(InstanceDataArray,
InstanceWorldViewProjectionMatrix, NumSunsVisible);
// Pro Instance/Matrix 64 Byte (4x4=16 Elemente mit je 32 Bit bzw. 4 Byte):
SunInstancesTBO->Update_Buffer(InstanceDataArray, 0, 64*NumSunsVisible);
}
Im letzten Schritt können die sichtbaren Sterne schließlich unter Verwendung der beiden CGalaxyGraphics-Methoden Render_Galaxy() sowie Render_Galaxy_AsBackground() gerendert werden:
void CGalaxyGraphics::Render_Galaxy_AsBackground(void)
{
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_COLOR);
BillboardBackgroundShader->Use_Shader();
BillboardBackgroundShader->Set_TextureBuffer(0,
SunInstancesTBO->Texture, "SunInstancesTextureBuffer");
BillboardBackgroundShader->Set_TextureArray(1,
pSunTextureArray, g_MipMappingSamplerID, "SunTextureArray");
glBindBuffer( GL_ARRAY_BUFFER, VertexBufferId);
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);
BillboardBackgroundShader->Set_VertexAttributes();
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL,
NumSunsVisible);
BillboardBackgroundShader->Stop_Using_Shader();
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glCullFace(GL_BACK);
}
Der Zugriff auf einen Texture Buffer innerhalb eines Shader-Programms sowie die Verwendung von Texture Arrays beim Geometry Instancing wurden bereits in den vorangegangenen Beiträgen thematisiert. Für die Sternen- und Nebeldarstellung werden nun beide Techniken wie folgt miteinander kombiniert:
Vertex Shader:
uniform samplerBuffer SunInstancesTextureBuffer;
void main()
{
int Index = 4*gl_InstanceID;
mat4 matWorldViewProjection = mat4(texelFetch(SunInstancesTextureBuffer,
Index++),
texelFetch(SunInstancesTextureBuffer,
Index++),
texelFetch(SunInstancesTextureBuffer,
Index++),
texelFetch(SunInstancesTextureBuffer,
Index));
// Texture-ID ist im Matrixelement matWorldViewProjection[0][3]
// gespeichert
gs_TexCoord[0] = vec4(gs_MultiTexCoord0.xy, matWorldViewProjection[0][3],
1.0);
// Texture-ID löschen, damit die Transformation der Vertexdaten mithilfe
// matWorldViewProjection-Matrix wieder korrekt funktioniert:
// matWorldViewProjection-Matrix wieder korrekt funktioniert:
matWorldViewProjection[0][3] = 0.0;
gl_Position = matWorldViewProjection*gs_Vertex;
}
Fragment Shader:
uniform sampler2DArray SunTextureArray;
void main()
{
// Hinweise:
// gs_TexCoord[0].z: Index der zu verwendenden Textur (Texture-ID)
// gs_TexCoord[0].xy: die normalen Texturkoordinaten
vec4 SunColor = texture(SunTextureArray, gs_TexCoord[0].xyz);
if(SunColor.a < 0.01)
discard;
gs_FragColor = SunColor;
}