-1/1: linke obere Bildschirmecke
1/-1: rechte untere Bildschirmecke
0/0 : Bildschirmmitte
Mithilfe der beiden nachfolgenden Funktionen können Bildschirmkoordinaten und Viewportkoordinaten ineinander umgerechnet werden:
INLINE void Transform_Screen_To_Viewport_Coordinates(float* pViewportX,
float* pViewportY,
float* pScreenX,
float* pScreenY,
long &screenwidth,
long &screenheight)
{
*pViewportX = 2.0f*(*pScreenX)/screenwidth - 1.0f;
*pViewportY = 1.0f - 2.0f*(*pScreenY)/screenheight;
}
INLINE void Transform_Viewport_To_Screen_Coordinates(float* pViewportX,
float* pViewportY,
float* pScreenX,
float* pScreenY,
long &screenwidth,
long &screenheight)
{
*pScreenX = 0.5f*((*pViewportX) + 1.0f)*screenwidth;
*pScreenY = 0.5f*(1.0f - (*pViewportY))*screenheight;
}
float* pViewportY,
float* pScreenX,
float* pScreenY,
long &screenwidth,
long &screenheight)
{
*pViewportX = 2.0f*(*pScreenX)/screenwidth - 1.0f;
*pViewportY = 1.0f - 2.0f*(*pScreenY)/screenheight;
}
INLINE void Transform_Viewport_To_Screen_Coordinates(float* pViewportX,
float* pViewportY,
float* pScreenX,
float* pScreenY,
long &screenwidth,
long &screenheight)
{
*pScreenX = 0.5f*((*pViewportX) + 1.0f)*screenwidth;
*pScreenY = 0.5f*(1.0f - (*pViewportY))*screenheight;
}
Object Picking
In fast allen Spielen müssen die verschiedensten Spieleobjekte mithilfe des Mauszeigers selektiert werden. Für die Realisierung dieses als Objekt-Picking bekannten Verfahrens gibt es nun mehrere Möglichkeiten. Ein einfacher Weg besteht darin, die Bildschirmkoordinaten des Mauszeigers mit den Bildschirmkoordinaten des zu selektierenden Spieleobjekts zu vergleichen. Betrachten wir hierzu zwei Varianten:
Object Picking (einfach), bei dem der Kameraabstand des Objekts unberücksichtigt bleibt
Beim Objekt-Picking in seiner einfachsten Form bleibt der Kameraabstand des Objekts unberücksichtigt. Dies mag zwar zum Betätigen eines Buttons oder eines Schalters ausreichen, beim Selektieren eines 3D-Objekts – wie etwa in einem Strategiespiel – muss dessen Kameraabstand jedoch zwingend berücksichtigt werden. In diesem Zusammenhang muss man bedenken, dass die Größe eines Objekts auf dem Bildschirm vom Kameraabstand abhängig ist.
// Korrekturterme, damit die Genauigkeit beim Object Picking unabhängig
// von der gewählten Bildschirmauflösung ist:
g_WidthCorrection = g_screenwidth/1024.0f;
g_HeightCorrection = g_screenheight/768.0f;
. . .
if(abs(ObjectScreenPosX - g_CursorScreenX) < 15.0f*g_WidthCorrection)
{
if(abs(ObjectScreenPosY - g_CursorScreenY) < 15.0f*g_HeightCorrection)
{
ObjectSelected = true;
}
}
// von der gewählten Bildschirmauflösung ist:
g_WidthCorrection = g_screenwidth/1024.0f;
g_HeightCorrection = g_screenheight/768.0f;
. . .
if(abs(ObjectScreenPosX - g_CursorScreenX) < 15.0f*g_WidthCorrection)
{
if(abs(ObjectScreenPosY - g_CursorScreenY) < 15.0f*g_HeightCorrection)
{
ObjectSelected = true;
}
}
Object Picking mit Berücksichtigung des Kameraabstands
Die Berücksichtigung des Kameraabstands beim Objekt-Picking ist gleichbedeutend mit der Berücksichtigung der Größe des zu selektierenden Objekts auf dem Bildschirm. Hierfür benötigt man zusätzlich zu den Bildschirmkoordinaten des Objektmittelpunkts (ObjectCenterScreenPosX, ObjectCenterScreenPosY) einen zweiten Satz von Koordinaten, welche die Objektausdehnung auf dem Bildschirm beschreiben (ScreenPosBorderX, ScreenPosBorderY). Zusammengenommen ergeben beide Koordinatensätze den Picking-Bereich des Objekts. Dieser kann jetzt im zweiten Schritt mit dem Abstand des Mauszeigers vom Objektmittelpunkt verglichen werden.
Calculate_Screen_Coordinates(&ObjectCenterScreenPosX,
&ObjectCenterScreenPosY,
&ObjectCameraSpacePosition,
&ViewProjectionMatrix,
screenwidth, screenheight);
BorderPos = ObjectCameraSpacePosition +
0.25f*ObjectScaleFactor*g_CameraHorizontal;
Calculate_Screen_Coordinates(&ScreenPosBorderX, &ScreenPosBorderY,
&BorderPos,
&ViewProjectionMatrix,
screenwidth, screenheight);
// Picking-Bereich (Screen range) des zu selektierenden Objekts bestimmen:
ObjectScreenRangeX = ScreenPosBorderX - ObjectCenterScreenPosX;
ObjectScreenRangeY = ScreenPosBorderY - ObjectCenterScreenPosY;
// Verhindern, dass der Picking-Bereich mit zunehmendem Kameraabstand
// zu klein wird:
if(ObjectScreenRangeX < 25.0f)
ObjectScreenRangeX = 25.0f;
ObjectScreenRangeX *= g_WidthCorrection;
if(ObjectScreenRangeY < 25.0f)
ObjectScreenRangeY = 25.0f;
ObjectScreenRangeY *= g_HeightCorrection;
// quadratischen Picking-Bereich berechnen:
ObjectScreenRangeXSquare = ObjectScreenRangeX*ObjectScreenRangeX;
ObjectScreenRangeYSquare = ObjectScreenRangeY*ObjectScreenRangeY;
// Abstand (Bildschirmkoordinaten) von Mauszeiger und Objektmittelpunkt:
CursorDistanceX = g_CursorScreenX - ObjectCenterScreenPosX;
CursorDistanceY = g_CursorScreenY - ObjectCenterScreenPosY;
// quadratischen Maus-Objekt-Abstand berechnen:
CursorDistanceXSquare = CursorDistanceX*CursorDistanceX;
CursorDistanceYSquare = CursorDistanceY*CursorDistanceY;
// Wenn sich der quadratische Maus-Objekt-Abstand innerhalb des
// quadratischen Picking-Bereichs befindet, dann gilt das Objekt als
//selektiert:
if(CursorDistanceXSquare + CursorDistanceYSquare <
ObjectScreenRangeXSquare + ObjectScreenRangeYSquare)
{
ObjectSelected = true;
}
&ObjectCenterScreenPosY,
&ObjectCameraSpacePosition,
&ViewProjectionMatrix,
screenwidth, screenheight);
BorderPos = ObjectCameraSpacePosition +
0.25f*ObjectScaleFactor*g_CameraHorizontal;
Calculate_Screen_Coordinates(&ScreenPosBorderX, &ScreenPosBorderY,
&BorderPos,
&ViewProjectionMatrix,
screenwidth, screenheight);
// Picking-Bereich (Screen range) des zu selektierenden Objekts bestimmen:
ObjectScreenRangeX = ScreenPosBorderX - ObjectCenterScreenPosX;
ObjectScreenRangeY = ScreenPosBorderY - ObjectCenterScreenPosY;
// Verhindern, dass der Picking-Bereich mit zunehmendem Kameraabstand
// zu klein wird:
if(ObjectScreenRangeX < 25.0f)
ObjectScreenRangeX = 25.0f;
ObjectScreenRangeX *= g_WidthCorrection;
if(ObjectScreenRangeY < 25.0f)
ObjectScreenRangeY = 25.0f;
ObjectScreenRangeY *= g_HeightCorrection;
// quadratischen Picking-Bereich berechnen:
ObjectScreenRangeXSquare = ObjectScreenRangeX*ObjectScreenRangeX;
ObjectScreenRangeYSquare = ObjectScreenRangeY*ObjectScreenRangeY;
// Abstand (Bildschirmkoordinaten) von Mauszeiger und Objektmittelpunkt:
CursorDistanceX = g_CursorScreenX - ObjectCenterScreenPosX;
CursorDistanceY = g_CursorScreenY - ObjectCenterScreenPosY;
// quadratischen Maus-Objekt-Abstand berechnen:
CursorDistanceXSquare = CursorDistanceX*CursorDistanceX;
CursorDistanceYSquare = CursorDistanceY*CursorDistanceY;
// Wenn sich der quadratische Maus-Objekt-Abstand innerhalb des
// quadratischen Picking-Bereichs befindet, dann gilt das Objekt als
//selektiert:
if(CursorDistanceXSquare + CursorDistanceYSquare <
ObjectScreenRangeXSquare + ObjectScreenRangeYSquare)
{
ObjectSelected = true;
}
Die Berechnung der Bildschirmkoordinaten mithilfe der Calculate_Screen_Coordinates()-Funktion erfolgt unter Verwendung der View- und Projektionsmatrix. Beide Matrizen werden jedoch nicht einzeln übergeben sondern in Form einer kombinierten View-Projection-Matrix:
ViewProjectionMatrix = matView*matProj;
Mittels einer einfachen Matrizenmultiplikation wird die Kameraposition in den Projektionsraum (projection space) transformiert und dann im zweiten Schritt in Bildschirmkoordinaten umgerechnet.
INLINE void Calculate_Screen_Coordinates(float* pScreenX, float* pScreenY,
D3DXVECTOR3* pVec,
D3DXMATRIXA16* pViewProjectionMatrix,
long &screenwidth,
long &screenheight)
{
float tempX = pVec->x;
float tempY = pVec->y;
float tempZ = pVec->z;
float tempX2 = pViewProjectionMatrix->_11*tempX +
pViewProjectionMatrix->_21*tempY +
pViewProjectionMatrix->_31*tempZ +
pViewProjectionMatrix->_41;
float tempY2 = pViewProjectionMatrix->_12*tempX +
pViewProjectionMatrix->_22*tempY +
pViewProjectionMatrix->_32*tempZ +
pViewProjectionMatrix->_42;
float tempW2 = pViewProjectionMatrix->_14*tempX +
pViewProjectionMatrix->_24*tempY +
pViewProjectionMatrix->_34*tempZ +
pViewProjectionMatrix->_44;
float tempInvW2 = 1.0f/tempW2;
*pScreenX = (1.0f + (tempX2*tempInvW2))*0.5f*screenwidth;
*pScreenY = (1.0f - (tempY2*tempInvW2))*0.5f*screenheight;
}
D3DXVECTOR3* pVec,
D3DXMATRIXA16* pViewProjectionMatrix,
long &screenwidth,
long &screenheight)
{
float tempX = pVec->x;
float tempY = pVec->y;
float tempZ = pVec->z;
float tempX2 = pViewProjectionMatrix->_11*tempX +
pViewProjectionMatrix->_21*tempY +
pViewProjectionMatrix->_31*tempZ +
pViewProjectionMatrix->_41;
float tempY2 = pViewProjectionMatrix->_12*tempX +
pViewProjectionMatrix->_22*tempY +
pViewProjectionMatrix->_32*tempZ +
pViewProjectionMatrix->_42;
float tempW2 = pViewProjectionMatrix->_14*tempX +
pViewProjectionMatrix->_24*tempY +
pViewProjectionMatrix->_34*tempZ +
pViewProjectionMatrix->_44;
float tempInvW2 = 1.0f/tempW2;
*pScreenX = (1.0f + (tempX2*tempInvW2))*0.5f*screenwidth;
*pScreenY = (1.0f - (tempY2*tempInvW2))*0.5f*screenheight;
}
Zu guter letzt möchte ich Ihnen noch eine weitere Funktion vorgestellen. Mithilfe der Calculate_ProjectedCameraSpacePosition()-Funktion kann die Kameraposition eines Objekts in den Projektionsraum (projection space) transformiert werden. Gleiches geschieht übrigens im Vertex Shader, nur dass dort die Vertices eines 3D-Modells transformiert werden.
Mithilfe der projizierten Kameraposition lassen sich im weiteren Verlauf durch Tiefenvergleiche (Abstand zur Kamera in Blickrichtung) Sichtbarkeitstests im Pixel (Fragment) Shader durchführen. Mögliche Fragestellungen sind: Verdeckt das Raumschiff oder der Planet die Sonne? Sind die Lens Flares sichtbar?
INLINE void Calculate_ProjectedCameraSpacePosition(D3DXVECTOR3* pVecOut,
D3DXVECTOR3* pVecIn,
D3DXMATRIXA16* pViewProjectionMatrix)
{
float tempX = pVecIn->x;
float tempY = pVecIn->y;
float tempZ = pVecIn->z;
float tempX2 = pViewProjectionMatrix->_11*tempX +
pViewProjectionMatrix->_21*tempY +
pViewProjectionMatrix->_31*tempZ +
pViewProjectionMatrix->_41;
float tempY2 = pViewProjectionMatrix->_12*tempX +
pViewProjectionMatrix->_22*tempY +
pViewProjectionMatrix->_32*tempZ +
pViewProjectionMatrix->_42;
float tempZ2 = pViewProjectionMatrix->_13*tempX +
pViewProjectionMatrix->_23*tempY +
pViewProjectionMatrix->_33*tempZ +
pViewProjectionMatrix->_43;
float tempFloat = 1.0f/tempZ2;
pVecOut->x = 0.5*tempX2*tempFloat + 0.5;
pVecOut->y = 0.5*tempY2*tempFloat + 0.5;
pVecOut->z = tempZ2;
}
D3DXVECTOR3* pVecIn,
D3DXMATRIXA16* pViewProjectionMatrix)
{
float tempX = pVecIn->x;
float tempY = pVecIn->y;
float tempZ = pVecIn->z;
float tempX2 = pViewProjectionMatrix->_11*tempX +
pViewProjectionMatrix->_21*tempY +
pViewProjectionMatrix->_31*tempZ +
pViewProjectionMatrix->_41;
float tempY2 = pViewProjectionMatrix->_12*tempX +
pViewProjectionMatrix->_22*tempY +
pViewProjectionMatrix->_32*tempZ +
pViewProjectionMatrix->_42;
float tempZ2 = pViewProjectionMatrix->_13*tempX +
pViewProjectionMatrix->_23*tempY +
pViewProjectionMatrix->_33*tempZ +
pViewProjectionMatrix->_43;
float tempFloat = 1.0f/tempZ2;
pVecOut->x = 0.5*tempX2*tempFloat + 0.5;
pVecOut->y = 0.5*tempY2*tempFloat + 0.5;
pVecOut->z = tempZ2;
}