Professional Documents
Culture Documents
Za neke primene, nije nam briga o dužini vektora jer želimo da koristimo vektor da predstavljaju čisti
pravac. Za takve vektore u pravcu, želimo da dužina vektora bude tačno 1. Kada napravimo vektorsku
jedinicu, kažemo da smo normalizovali vektor. Mi možemo normalizovati vektor deljenjem svake
komponente po veličini:
Točkovni proizvod je oblik vektorskog množenja koji rezultira skalarnom vrijednošću; iz tog razloga,
ponekad se naziva skalarni proizvod. Neka je u = (uk, ui, uz) i v = (vk, vi, vz), onda je tačni proizvod
definisan na sledeći način: rečima, tački proizvod je zbir proizvoda odgovarajućih komponenti. Definicija
tačnog proizvoda ne predstavlja očigledno geometrijsko značenje. Koristeći zakon kosinusa, možemo
pronaći odnos,
1.3.1 Orthogonalization
A skup vektora {v0, ..., vn-1} se naziva ortormalno ako su vektori međusobno
ortogonalni (svaki vektor u setu je ortogonalan za svaki drugi vektor u setu) i jedinicu
dužina. Ponekad imamo skup vektora koji su gotovo ortonormalni, ali ne i sasvim. A Zajednički zadatak je
ortogonalizirati skup i učiniti ga ortonormalnim. U 3D računaru grafiku koju možemo započeti sa
orthormormalnom skupom, ali zbog numeričkih preciznih problema,A set postepeno postaje ne-
ortonormalni. Mi smo uglavnom zabrinuti za 2D i 3D slučajevi ovog problema (tj. setovi koji sadrže dva i
tri vektora, respektivno). Prvo smo ispitali jednostavniji 2D slučaj. Pretpostavimo da imamo skup vektora
{v0, v1} da želimo ortogonalizovati u ortonormalni skup {v0, v1} kao što je prikazano na slici 1.11.
Počinjemo sa v0 = v0 i modifikujemo v1 da bi bilo ortogonalno v0; ovo se obavlja oduzimajući deo v1 koji
deluje u pravcu v0: Sada imamo međusobno ortogonalni skup vektora {v0, v1}; poslednji korak
konstrukcija ortormormnog seta je da normalizuje v0 i v1 da bi im napravio dužinu jedinice. 3D slučaj
prati u istom duhu kao i 2D kućište, ali sa više koraka. Pretpostavimo imamo skup vektora {v0, v1, v2}
koji želimo da ortogonalizujemo u ortormalno postavite {v0, v1, v2} kao što je prikazano na slici 1.12.
Počinjemo sa v0 = v0 i mijenjamo v1 da bismo uspeli ortogonalno do v0; ovo se radi tako što se oduzima
deo v1 koji deluje u v0.
Za Vindovs 8 i iznad, DirectKs Math je 3D matematička biblioteka za Direct3D aplikaciju koja je deo
Vindovs SDK-a. Biblioteka koristi set instrukcija SSE2 (Streaming SIMD Ektensions 2). Sa 128-bitnim
širokim SIMD (single instruction multiple data) registra, SIMD instrukcije mogu raditi na četiri 32-bitna
floats ili ints sa jednom instrukcijom. Ovo je veoma korisno za vektorske proračune; vidimo da samo
dodamo odgovarajuće komponente. Koristeći SIMD, možemo učiniti 4D dodatak vektora sa jednom
SIMD instrukcijom umesto četiri skalarna uputstva. Ako smo samo potrebne su tri koordinate za 3D rad, i
dalje možemo koristiti SIMD, ali bismo samo ignorisali četvrta koordinata; Takođe, za 2D bi ignorisali
treću i četvrtu koordinatu. Naše pokrivanje DirectKs Math biblioteke nije sveobuhvatno, a mi pokrivamo
samo one ključne delove potrebne za ovu knjigu. Za sve detalje preporučujemo online dokumentacija
[DirectKSMath]. Za čitaoce koji žele razumjeti kako SIMD vector biblioteka bi mogla biti optimalno
razvijena, i, možda, da bi stekla neki uvid zašto DirectKs Matematička biblioteka je donela neke od
odluka o dizajnu koje je uradila, preporučujemo članak Dizajniranje brze cross-platforme SIMD vektorske
biblioteke od strane [Oliveira2010]. Da biste koristili DirectKs Math biblioteku, potrebno je da uključite
<DirectKSMath.h> I za neke dodatne tipove podataka #include <DirectKSPackedVector.h>. Postoje nema
dodatnih bibliotečkih datoteka, pošto se svi kodovi implementiraju u liniji zaglavlja. The Kod
DirectKSMath.h živi u DirectKs imenskom prostoru i DirectKSPackedVector.h kod živi u DirectKs ::
PackedVector imeničkom prostoru. Pored toga, za k86 platformu koju trebate omogućiti SSE2
(Properties Properties> Properties> Configuration> C / C ++> Generation Code> Enable Enhanced Setup
Set) i za sve platforme trebali biste omogućiti model brze plutajuće tačke / fp: brzo (Project
Properties>Konfiguracijske osobine> C / C ++> Generisanje koda> Model plutajućih tačaka). Ti nije
potrebno da omogućite SSE2 za k64 platformu jer svi k64 procesori podržavaju SSE2.
1.6.1 Vektorski tipovi
U DirectKs Math, jezgro vektorski tip je KSMVECTOR, koji mapira SIMD hardverske registre. Ovo je 128-
bitni tip koji može procesirati četiri 32-bitna plovila sa jednim SIMD instrukcijama. Kada je SSE2 na
raspolaganju, on je definisan kao takav za k86 i k64 platforme: tipedef __m128 KSMVECTOR; gde je
__m128 poseban SIMD tip. Kod izvođenja kalkulacija, vektori moraju biti takve vrste da bi iskoristili
SIMD. Kao što smo već spomenuli, i dalje koristimo ovaj tip za 2D i 3D vektore kako bi iskoristili prednost
od SIMD-a, ali mi samo izvlačimo neiskorišćene komponente i ignorišemo ih. KSMVECTOR treba da bude
16-bite poravnat, a to se automatski vrši za lokalne i globalne varijable. Za članove klase podataka
preporučuje se da koristite KSMFLOAT2 (2D), KSMFLOAT3 (3D) i KSMFLOAT4 (4D); ove strukture su
definisane u nastavku:
struct XMFLOAT2
{
float x;
float y;
XMFLOAT2() {}
XMFLOAT2(float _x, float _y) : x(_x), y(_y) {}
explicit XMFLOAT2(_In_reads_(2) const float *pArray)
:
x(pArray[0]), y(pArray[1]) {}
XMFLOAT2& operator= (const XMFLOAT2& Float2)
{ x = Float2.x; y = Float2.y; return *this; }
};
struct XMFLOAT3
{
float x;
float y;
float z;
XMFLOAT3() {}
XMFLOAT3(float _x, float _y, float _z) : x(_x),
y(_y), z(_z) {}
explicit XMFLOAT3(_In_reads_(3) const float *pArray)
:
x(pArray[0]), y(pArray[1]), z(pArray[2]) {}
XMFLOAT3& operator= (const XMFLOAT3& Float3)
{ x = Float3.x; y = Float3.y; z = Float3.z; return
*this; }
};
struct XMFLOAT4
{
float x;
float y;
float z;
float w;
XMFLOAT4() {}
XMFLOAT4(float _x, float _y, float _z, float _w) :
x(_x), y(_y), z(_z), w(_w) {}
explicit XMFLOAT4(_In_reads_(4) const float *pArray)
:
x(pArray[0]), y(pArray[1]), z(pArray[2]),
w(pArray[3]) {}
XMFLOAT4& operator= (const XMFLOAT4& Float4)
{ x = Float4.x; y = Float4.y; z = Float4.z;
w = Float4.w; return *this; }
};
Međutim, ako ove vrste koristimo direktno za proračune, onda ih nećemo uzeti
prednost SIMD-a. Da bi koristili SIMD, potrebno je pretvoriti primere ovih tipova u tip KSMVECTOR-a.
Ovo se radi sa DirectKs Math funkcijama učitavanja. Nasuprot tome, DirectKs Math pruža funkcije
skladištenja koje se koriste za pretvaranje podataka iz KSMVECTOR u KSMFLOATn tipove iznad.
Da rezimiramo,
1. Koristite KSMVECTOR za lokalne ili globalne varijable.
2. Koristite KSMFLOAT2, KSMFLOAT3 i KSMFLOAT4 za članove klase podataka.
3. Koristite funkcije učitavanja da biste prešli iz KSMFLOATn u KSMVECTOR prije nego što počnete calcul.
4. Uradite proračune sa KSMVECTOR instancama.
5. Koristite funkcije skladištenja za konverziju iz KSMVECTOR na KSMFLOATn.
1.6.2 Metode za utovar i skladištenje
Koristimo sledeće metode za učitavanje podataka iz KSMFLOATn u KSMVECTOR:
// Loads XMFLOAT2 into XMVECTOR
XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2
*pSource);
// Loads XMFLOAT3 into XMVECTOR
XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3
*pSource);
// Loads XMFLOAT4 into XMVECTOR
XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4
*pSource);
Koristimo sledeće metode za čuvanje podataka od KSMVECTOR-a u KSMFLOATn:
// Loads XMVECTOR into XMFLOAT2
void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination,
FXMVECTOR V);
// Loads XMVECTOR into XMFLOAT3
void XM_CALLCONV XMStoreFloat3(XMFLOAT3 *pDestination,
FXMVECTOR V);
// Loads XMVECTOR into XMFLOAT4
void XM_CALLCONV XMStoreFloat4(XMFLOAT4 *pDestination,
FXMVECTOR V);
Ponekad samo želimo da nabavimo ili postavimo jednu komponentu KSMVECTOR-a; sledeće funkcije
gitara i podešavanja olakšavaju ovo:
float XM_CALLCONV XMVectorGetX(FXMVECTOR V);
float XM_CALLCONV XMVectorGetY(FXMVECTOR V);
float XM_CALLCONV XMVectorGetZ(FXMVECTOR V);
float XM_CALLCONV XMVectorGetW(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V, float
x);
XMVECTOR XM_CALLCONV XMVectorSetY(FXMVECTOR V, float
y);
XMVECTOR XM_CALLCONV XMVectorSetZ(FXMVECTOR V, float
z);
XMVECTOR XM_CALLCONV XMVectorSetW(FXMVECTOR V, float
w);
1.6.3 Parametar Passing
U cilju efikasnosti, vrijednosti KSMVECTOR mogu se prenijeti kao argumenti za funkcije u SSE / SSE2
registrima umjesto na stacku. Broj argumenata koji se mogu preneti ovako zavise od platforme (npr. 32-
bitni Vindovs, 64-bitni Vindovs i Vindovs RT) i kompajler. Zbog toga, da budemo nezavisni od platforme /
kompajlera, koristimo tipove FKSMVECTOR, GKSMVECTOR, HKSMVECTOR i CKSMVECTOR za prenos
KSMVECTOR parametara; oni su definisani na odgovarajući tip baziran na platformi i kompajleru. Osim
toga, annotacija pozivne konvencije KSM_CALLCONV mora biti specificirana prije naziva funkcije tako da
se koristi odgovarajuća konvencija poziva, što opet zavisi od verzije kompajlera.
Sada su pravila za prenos KSMVECTOR parametara sledeća:
1. Prva tri KSMVECTOR parametra treba da budu tipa FKSMVECTOR;
2. Četvrti KSMVECTOR treba da bude tipa GKSMVECTOR;
3. Peti i šesti KSMVECTOR parametar treba da bude tipa HKSMVECTOR;
4. Bilo koji dodatni KSMVECTOR parametri trebaju biti tipa CKSMVECTOR.
Mi ilustrujemo kako su ti tipovi definirani na 32-bitnom Vindovsu sa kompajlerom koji
podržava __fastcall pozivnu konvenciju i kompajler koji podržava novije
__vectorcall pozivna konvencija:
// 32-bit Windows __fastcall passes first 3 XMVECTOR
arguments
// via registers, the remaining on the stack.
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR& GXMVECTOR;
typedef const XMVECTOR& HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
// 32-bit Windows __vectorcall passes first 6 XMVECTOR
arguments
// via registers, the remaining on the stack.
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR GXMVECTOR;
typedef const XMVECTOR HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
Za detalje o tome kako su ovi tipovi definisani za druge platforme, pogledajte "Pozivanje
Konvencija "u okviru" Biblioteka Internals "u DirectKs Math dokumentaciji
[DirectKSMath]. Izuzetak od ovih pravila je sa metodama konstruktora. [DirectKSMath] preporučuje
korišćenje FKSMVECTOR za prva tri KSMVECTOR parametra i CKSMVECTOR za ostalo kada piše
konstruktor koji uzima KSMVECTOR parametre. Štaviše, nemojte koristiti anotaciju KSM_CALLCONV za
konstruktore Evo primera iz biblioteke DirectKSMath:
inline XMMATRIX XM_CALLCONV XMMatrixTransformation(
FXMVECTOR ScalingOrigin,
FXMVECTOR ScalingOrientationQuaternion, .
FXMVECTOR Scaling,
GXMVECTOR RotationOrigin,
HXMVECTOR RotationQuaternion,
HXMVECTOR Translation);
1.6.4.KOnstantni vektori
Konstantni XMVECTOR bi trebalo da se koristi the XMVECTORF32 type. Ovde su:
examples from the DirectX SDK’s CascadedShadowMaps11 sample:
static const XMVECTORF32 g_vHalfVector = { 0.5f, 0.5f,
0.5f, 0.5f };
static const XMVECTORF32 g_vZero = { 0.0f, 0.0f, 0.0f,
0.0f };
XMVECTORF32 vRightTop = {
vViewFrust.RightSlope,
vViewFrust.TopSlope,
1.0f,1.0f
};
XMVECTORF32 vLeftBottom = {
vViewFrust.LeftSlope,
vViewFrust.BottomSlope,
1.0f,1.0f
};
2. mogucnost je
// Conversion types for constants
__declspec(align(16)) struct XMVECTORF32
{
union
{
float f[4];
XMVECTOR v;
};
inline operator XMVECTOR() const { return v; }
inline operator const float*() const { return f; }
#if !defined(_XM_NO_INTRINSICS_) &&
defined(_XM_SSE_INTRINSICS_)
inline operator __m128i() const { return
_mm_castps_si128(v); }
inline operator __m128d() const { return
_mm_castps_pd(v); }
#endif
};
You can also create a constant XMVECTOR of integer data using XMVECTORU32:
static const XMVECTORU32 vGrabY = {
0x00000000,0xFFFFFFFF,0x00000000,0x00000000
1.6.5 Преоптерећени оператери
KSMVECTOR ima nekoliko preopterećenih operatora radi vršenja vektora, oduzimanje i skalarno
množenje.
XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V);
XMVECTOR XM_CALLCONV operator- (FXMVECTOR V);
XMVECTOR& XM_CALLCONV operator+= (XMVECTOR& V1,
FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator-= (XMVECTOR& V1,
FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator*= (XMVECTOR& V1,
FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator/= (XMVECTOR& V1,
FXMVECTOR V2);
XMVECTOR& operator*= (XMVECTOR& V, float S);
XMVECTOR& operator/= (XMVECTOR& V, float S);
XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V1,
FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator- (FXMVECTOR V1,
FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator* (FXMVECTOR V1,
FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V1,
FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator* (FXMVECTOR V, float
S);
XMVECTOR XM_CALLCONV operator* (float S, FXMVECTOR
V);
XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V, float
S);
1.6.7 Функције подешавања
Direcx3d matematika dokazuje pratece funkcije I postavlja ih u kontekst XMvectors:
DirectX Math provides the following functions to set the contents of an XMVECTOR:
// Returns the zero vector 0
XMVECTOR XM_CALLCONV XMVectorZero();
// Returns the vector (1, 1, 1, 1)
XMVECTOR XM_CALLCONV XMVectorSplatOne();
// Returns the vector (x, y, z, w)
XMVECTOR XM_CALLCONV XMVectorSet(float x, float y,
float z, float w);
// Returns the vector (s, s, s, s)
XMVECTOR XM_CALLCONV XMVectorReplicate(float Value);
// Returns the vector (vx, vx, vx, vx)
XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V);
// Returns the vector (vy, vy, vy, vy)
XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V);
// Returns the vector (vz, vz, vz, vz)
XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V);
The following program illustrates most of these functions:
#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
// Overload the “<<” operators so that we can use cout
to
// output XMVECTOR objects.
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR
v)
{
XMFLOAT3 dest;
XMStoreFloat3(&dest, v);
os << “(” << dest.x << “, ” << dest.y << “, ” <<
dest.z << “)”;
return os;
} int main()
{
cout.setf(ios_base::boolalpha);
// Check support for SSE2 (Pentium4, AMD K8, and
above).
if (!XMVerifyCPUSupport())
{
cout << “directx math not supported” << endl;
return 0;
}
XMVECTOR p = XMVectorZero();
XMVECTOR q = XMVectorSplatOne();
XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
XMVECTOR v = XMVectorReplicate(-2.0f);
XMVECTOR w = XMVectorSplatZ(u);
cout << “p = ” << p << endl;
cout << “q = ” << q << endl;
cout << “u = ” << u << endl;
cout << “v = ” << v << endl;
cout << “w = ” << w << endl;
return 0;
}
1.6.8.Vektorske funkcije
DirectKs Math pruža sledeće funkcije za obavljanje različitih operacija vektora. Mi
ilustriraju 3D verzije, ali postoje analogne verzije za 2D i 4D; verzije 2D i 4D imaju iste nazive kao i 3D
verzije, sa izuzetkom 2 i 4 zamenjene za 3, respektivno.
XMVECTOR XM_CALLCONV XMVector3Length( // Returns
||v||
FXMVECTOR V); // Input v
XMVECTOR XM_CALLCONV XMVector3LengthSq( // Returns
||v||2
FXMVECTOR V); // Input v
XMVECTOR XM_CALLCONV XMVector3Dot( // Returns v1·v2
FXMVECTOR V1, // Input v1
FXMVECTOR V2); // Input v2
XMVECTOR XM_CALLCONV XMVector3Cross( // Returns v1 ×
v2
FXMVECTOR V1, // Input v1
FXMVECTOR V2); // Input v2
XMVECTOR XM_CALLCONV XMVector3Normalize( // Returns
v/||v||
FXMVECTOR V); // Input v
XMVECTOR XM_CALLCONV XMVector3Orthogonal( // Returns
a vector orthogonal to v
FXMVECTOR V); // Input v
XMVECTOR XM_CALLCONV
XMVector3AngleBetweenVectors( // Returns the angle
between v1 and v2
FXMVECTOR V1, // Input v1
FXMVECTOR V2); // Input v2
void XM_CALLCONV XMVector3ComponentsFromNormal(
XMVECTOR* pParallel, // Returns projn(v)
XMVECTOR* pPerpendicular, // Returns perpn(v)
FXMVECTOR V, // Input v
FXMVECTOR Normal); // Input n
bool XM_CALLCONV XMVector3Equal( // Returns v1 = v2
FXMVECTOR V1, // Input v1
FXMVECTOR V2); // Input v2
bool XM_CALLCONV XMVector3NotEqual( // Returns v1 ≠ v2
FXMVECTOR V1, // Input v1
FXMVECTOR V2); // Input v2
Ovaj demo program vam pokazuje kako se odredjene funkcije koriste:
#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
// Overload the “<<” operators so that we can use cout
to
// output XMVECTOR objects.
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR
v)
{
XMFLOAT3 dest;
XMStoreFloat3(&dest, v);
os << “(” << dest.x << “, ” << dest.y << “, ” <<
dest.z << “)”;
return os;
}
int main()
{
cout.setf(ios_base::boolalpha);
// Check support for SSE2 (Pentium4, AMD K8, and
above).
if (!XMVerifyCPUSupport())
{
cout << “directx math not supported” << endl;
return 0;
}
XMVECTOR n = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 0.0f);
XMVECTOR w = XMVectorSet(0.707f, 0.707f, 0.0f,
0.0f);
// Vector addition: XMVECTOR operator +
XMVECTOR a = u + v;
// Vector subtraction: XMVECTOR operator -
XMVECTOR b = u - v;
// Scalar multiplication: XMVECTOR operator *
XMVECTOR c = 10.0f*u;
// ||u||
XMVECTOR L = XMVector3Length(u);
// d = u / ||u||
XMVECTOR d = XMVector3Normalize(u);
// s = u dot v
XMVECTOR s = XMVector3Dot(u, v);
// e = u x v
XMVECTOR e = XMVector3Cross(u, v);
// Find proj_n(w) and perp_n(w)
XMVECTOR projW;
XMVECTOR perpW;
XMVector3ComponentsFromNormal(&projW, &perpW, w, n);
// Does projW + perpW == w?
bool equal = XMVector3Equal(projW + perpW, w) != 0;
bool notEqual = XMVector3NotEqual(projW + perpW, w)
!= 0;
// The angle between projW and perpW should be 90
degrees.
XMVECTOR
angleVec = XMVector3AngleBetweenVectors(projW, perpW);
float angleRadians = XMVectorGetX(angleVec);
float
angleDegrees = XMConvertToDegrees(angleRadians);
cout << “u = ” << u << endl;
cout << “v = ” << v << endl;
cout << “w = ” << w << endl;
cout << “n = ” << n << endl;
cout << “a = u + v = ” << a << endl;
cout << “b = u - v = ” << b << endl;
cout << “c = 10 * u = ” << c << endl;
cout << “d = u / ||u|| = ” << d << endl;
cout << “e = u x v = ” << e << endl;
cout << “L = ||u|| = ” << L << endl;
cout << “s = u.v = ” << s << endl;
cout << “projW = ” << projW << endl;
cout << “perpW = ” << perpW << endl;
cout << “projW + perpW == w = ” << equal << endl;
cout << “projW + perpW != w = ” << notEqual << endl;
cout << “angle = ” << angleDegrees << endl;
return 0; }
1.6.9 Greske sa plutajucim tackama
Dok smo na temu rada sa vektori na računaru, trebali bismo biti svjesni Sledeći. Kada upoređujete
brojeve sa plutajućim tačkama, treba voditi računa o nepreciznosti sa plutajućim tačkama. Dva brojeva
sa plutajućim tačkama koje očekujemo da budu jednake mogu se malo razlikovati. Na primer,
matematički, očekivali smo da normalizovani vektor ima dužinu od 1, ali u računarskom programu,
dužina će biti samo približno 1. Štaviše, matematički, 1p = 1 za bilo koji stvarni broj p, ali kada smo samo
ima numeričku aproksimaciju za 1, vidimo da aproksimacija podignuta na pth moć povećava grešku; tako
se numerička greška takođe akumulira. Sledeći kratki program ilustruje ove ideje:
#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
int main()
{
cout.precision(8);
// Check support for SSE2 (Pentium4, AMD K8, and
above).
if (!XMVerifyCPUSupport())
{
cout << “directx math not supported” << endl;
return 0;
}
XMVECTOR u = XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f);
XMVECTOR n = XMVector3Normalize(u);
float LU = XMVectorGetX(XMVector3Length(n));
// Mathematically, the length should be 1. Is it
numerically?
cout << LU << endl;
if (LU == 1.0f)
cout << “Length 1” << endl;
else
cout << “Length not 1” << endl;
// Raising 1 to any power should still be 1. Is it?
float powLU = powf(LU, 1.0e6f);
cout << “LU^(10^6) = ” << powLU << endl;
}
Da bi kompenzovali nepreciznost sa plutajućim tačkama, testiramo da li su dva brojeva sa plutajućim
tačkama približno jednaka. Mi to radimo definisanjem konstante Epsilona, što je vrlo mala vrijednost
koju koristimo kao "pufer". Mi kažemo da su dvije vrijednosti približno jednake ako je njihova udaljenost
manja od Epsilona. Drugim rečima, Epsilon nam daje neku toleranciju za nepreciznost sa plutajućim
tačkama. Sledeća funkcija ilustruje kako se Epsilon može koristiti za testiranje ako su dve vrijednosti sa
plutajućim tačkama jednake:
Slika 4.2 prikazuje jednostavnu scenu, u kojoj neki objekti delimično zatamnjavaju predmete iza njih. Da
bi Direct3D mogao da odredi koji pikseli objekta su ispred drugog, koristi tehniku nazvanu "buffering
depth" ili z-buffering. Da naglasimo da sa dubinskim baferovanjem, redosled u kome se izvlačimo objekti
nije bitan.
Da biste rešili problem dubine, može se predložiti crtanje objekata u sceni u ređi od najdalje do najbližih.
Na taj način, objekti blizu boje će biti naslikani preko dalekih objekata, a tačni rezultati bi trebali biti
prikazani. Ovako bi slikar nacrtao scenu. Međutim, ovaj metod ima svoje probleme sortiranja velikog
skupa podataka u zadnjem redu i preseku geometrije. Osim toga, grafički hardver nam daje besplatnu
dubinu.
Da bismo ilustrovali kako funkcioniše dubina puferovanja, pogledajmo primer. Razmotrimo sliku 4.3,
koja prikazuje jačinu zvuka koju gledalac vidi i 2D pogled sa strane na tom volumenu. Na slici vidimo da
se tri različita piksla takmiče kako bi se prikazali na pikselu P u prozoru za prikaz. (Naravno, znamo da bi
najbliži piksel trebao biti prikazan na P, jer zatamnjuje one koji stoje iza njega, ali računar to ne radi.)
Prvo, prije nego što se bilo kakvo rendering postiže zadnji bafer na podrazumevanu boju i dubinu bafer
je obrisan na podrazumevanu vrednost - obično 1.0 (najdalja vrednost dubine koju piksel može imati).
Sada, pretpostavimo da se objekti prikazuju po redosledu cilindra, sfere i konusa. Sledeća tabela sumira
kako se pikel P i odgovarajuća vrednost dubine d ažuriraju dok se objekti nacrtaju; sličan proces se
dešava kod drugih piksela.
Slika 4.3. Prozor prikaza odgovara 2D slici (rezervni bafer) mi generiše 3D scenu. Vidimo da se tri različita
piksla mogu projektovati na piksela P. Intuicija nam govori da P1 treba pisati u P jer je bliže gledaocu i
blokira ostale dve piksele. Algoritam za bafer dubine obezbeđuje mehaničku proceduru za određivanje
ovog na računaru. Imajte na umu da prikazujemo vrednosti dubine u odnosu na 3D scenu koja se gleda,
ali su u stvari normalizovana u opsegu [0.0, 1.0] kada se čuva u dubinskom poliranju.
4.1.6 Resursi i deskriptori
Tokom procesa renderinga, GPU će pisati na resurse (npr., Zadnjeg bafera, bafer za dubinu / šablon) i
čitati iz resursa (npr. teksture koje opisuju izgled površina, puferi koji čuvaju 3D položaje geometrije u
sceni).
Pre nego što izdamo naredbu za izvlačenje, moramo vezati (ili povezati) resurse sa renderingom cevovod
na koji će se pozivati na poziv. Neki od resursa mogu promenite po pozivu poziva, tako da je potrebno
ažurirati vezu po pozivu za izvlačenje.
Međutim, GPU resursi nisu direktno vezani. Umesto toga, resurs se referira kroz deskriptorski objekat,
koji se može smatrati laka struktura koja opisuje resursa za GPU. U suštini, to je nivo indirectiona; s
obzirom na deskriptor resursa, GPU može dobiti podatke o stvarnim resursima i znati potrebne
informacije o tome. Mi vezati resurse na liniju za prikazivanje tako što ćete precizirati deskriptore koji će
biti referenca u pozivu za izvlačenje.
Zašto idete na ovaj dodatni nivo indirection sa deskriptorima? Razlog je taj GPU resursi su u suštini
generički delovi memorije. Resursi se drže generički tako da oni mogu se koristiti u različitim fazama
renderinga; uobičajeni primer je da koristite a tekstura kao ciljna površina (tj., Direct3D se vraća u
teksturu), a kasnije kao šater resurs (tj. tekstura će se uzorkovati i služiti kao ulazni podaci za shader).
Resurs samo po sebi ne kaže da li se koristi kao meta za render, dubina / stencil bafer ili shader resurs.
Takođe, možda samo želimo da vezujemo podregiju podataka o resursima sa renderiranje gasovoda -
kako to možemo uraditi imajući u vidu ceo resurs? Štaviše, resurs može se kreirati sa tipom bez tipova,
tako da GPU ne bi ni znao za format resurs. Ovde dolaze deskriptori. Pored identifikovanja podataka o
resursima, deskriptori opisuju resurs za GPU: oni govore Direct3D kako će biti resurs korišćeni (tj., u kojoj
fazi plinovoda ćete ga povezati), gde je moguće, možemo odrediti a subregion resursa koji želimo vezati
u deskriptoru, i ako je format resursa bio koji je određen kao bez tipova u vreme stvaranja, onda
moramo sada da navedemo vrstu pri kreiranjudeskriptor.Pogled je sinonim za deskriptor. Termin
"pogled" je korišten u prethodnom verzije Direct3D-a, i još se koristi u nekim delovima Direct3D 12 API-
ja. U ovoj knjizi koristimo oboje; na primer, konstantni prikaz bafera I konstantni buffer deskriptor
označava istu stvar.
Deskriptori imaju tip, a tip implicira kako će se resurs koristiti. Tipovi deskriptora koje koristimo u ovoj
knjizi su:
1. Deskriptori CBV / SRV / UAV opisuju konstantne bafere, resurse shadera i
neuređeni resursi za pregled pristupa.
2. Deskriptori uzorkovanja opisuju resurse za uzorkovanje (koji se koriste u teksturiranju).
3. RTV deskriptori opisuju ciljne resurse.
4. DSV deskriptori opisuju resurse dubine / šablona.
Deskriptori kup je niz deskriptora; to je podrška memorije za sve
deskriptori određenog tipa koji vaša aplikacija koristi. Za svaki tip deskriptora biće vam potreban
poseban skok descripta. Takođe možete stvoriti više heapa istog tipa deskriptora. Možemo imati više
deskriptora koji se odnose na isti izvor. Na primer, možemo imati više deskriptora koji se odnose na
različite podregije resursa. Takođe, kao što je već pomenuto, resursi mogu biti vezani za različite faze
plinovoda za rendering. Za svaku fazu, potreban nam je poseban deskriptor. Za primer korišćenja
teksture kao cilja renderera i resursa shadera, trebalo bi da napravimo dva deskriptora: deskriptor
deskriptora RTV-a, i deskriptor tipa SRV. Slično tome, ako stvarate izvor sa tipom bez tipova, moguće je
da se elementi teksture posmatraju kao vrijednosti sa plutajućim tačkom ili kao cijeli broj, na primjer; to
bi zahtevalo dva deskriptora, gde jedan deskriptor specificira format sa plutajućim tačkama, a drugi
integer format.
U narednom odeljku, od nas će biti zatraženo da popunite strukturu DKSGI_SAMPLE_DESC.Ova struktura
ima dva člana i definiše se na sledeći način:
typedef struct DXGI_SAMPLE_DESC
{
UINT Count;
UINT Quality;
} DXGI_SAMPLE_DESC;
Član Count-a određuje broj uzoraka koji se uzimaju po pikselu, a član Kvaliteta se koristi za određivanje
željenog nivoa kvaliteta (šta "nivo kvaliteta" znači da se može razlikovati od proizvođača hardvera). Veći
broj uzoraka ili viši kvalitet je skuplji za renderovanje, pa se mora napraviti kompromis između kvaliteta i
brzine. Raspon nivoa kvaliteta zavisi od formata teksture i broja uzoraka koji se uzimaju po pikselu.
Možemo da upišemo broj nivoa kvaliteta za datu formu teksta i broj uzoraka pomoću ID3D12Device ::
CheckFeatureSupport metoda kao što je:
typedef struct
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS {
DXGI_FORMAT Format;
UINT SampleCount;
D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG Flags;
UINT NumQualityLevels;
} D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS;
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS
msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags =
D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msQualityLevels,
sizeof(msQualityLevels)));
Imajte na umu da je drugi parametar i ulazni i izlazni parametar. Za ulaz, moramo navesti format teksta,
broj uzoraka i zastavu za koju želimo da zatražimo podršku za multisampling. Funkcija će zatim popuniti
nivo kvaliteta kao izlaz. Validni nivoi kvaliteta za format teksture i kombinaciju broja uzoraka varira od
nule do NumKualitiLevels-1. Maksimalan broj uzoraka koji se mogu uzeti po pikselu definiše se pomoću:
#define D3D11_MAKS_MULTISAMPLE_SAMPLE_COUNT (32)
Međutim, broj uzoraka od 4 ili 8 je uobičajen za održavanje performansi i
memorijski trošak za multisampling razumno. Ako ne želite da koristite multisampling, podesite broj
uzoraka na 1 i nivo kvaliteta na 0. Svi uređaji sa mogućnošću Direct3D 11 podržavaju 4Ks multisampling
za sve prikazane ciljne formate.
enum D3D_FEATURE_LEVEL
{
D3D_FEATURE_LEVEL_9_1 = 0x9100,
D3D_FEATURE_LEVEL_9_2 = 0x9200,
D3D_FEATURE_LEVEL_9_3 = 0x9300,
D3D_FEATURE_LEVEL_10_0 = 0xa000,
D3D_FEATURE_LEVEL_10_1 = 0xa100,
D3D_FEATURE_LEVEL_11_0 = 0xb000,
D3D_FEATURE_LEVEL_11_1 = 0xb100
}D3D_FEATURE_LEVEL;
Nivoi funkcija definišu strog skup funkcionalnosti (pogledajte SDK dokumentaciju za
specifične mogućnosti podržavaju svaki nivo funkcija). Na primer, GPU koji podržava funkciju
nivo 11 mora podržati čitavu mogućnost Direct3D 11, sa nekoliko izuzetaka (neki
stvari kao što je multisampling broj još uvek treba da budu upitane, jer im je dozvoljeno da variraju
između različitih Direct3D 11 hardvera). Karakteristike uređaja olakšavaju razvoj - jednom
poznajete podržani skup funkcija, poznajete Direct3D funkcionalnost koju imate kod vas
odlaganje.
Ako hardver korisnika nije podržao određeni nivo funkcija, aplikacija bi mogla
povratak na stariji nivo. Na primer, za podršku širem auditorijumu, aplikaciji
možda podržava hardverski nivo Direct3D 11, 10 i 9.3. Aplikacija bi proverila
podrška nivoa funkcionalnosti od najnovije do najstarije: to jest, aplikacija bi prvo proverila da li je
Direct3D 11 je podržan, drugi Direct3D 10, i konačno Direct3D 9.3. U ovoj knjizi, mi
uvek je potrebna podrška za nivo funkcije D3D_FEATURE_LEVEL_11_0. Međutim, stvarni svet
aplikacije trebaju brinuti o podršci starijih hardvera kako bi ih maksimizirale
publika.
4.1.10 DirectKs Graphics Infrastructure
DirectKs Graphics Infrastructure (DKSGI) je API koji se koristi zajedno sa Direct3D. The osnovna ideja
DKSGI je da su neki zadaci vezani za grafiku uobičajeni za više grafike API-ovi. Na primer, API-u 2D
renderinga bi trebali zamenjivati lanse i flipping stranice glatka animacija baš kao i 3D rendering API; pa
tako i interfejs lanca zamene IDKSGISvapChain (§4.1.4) je zapravo deo DKSGI API-ja. DKSGI rukuje drugim
zajednička grafička funkcionalnost kao što su tranzicije na celom ekranu, nabrojajući grafički
informacije o sistemu kao što su adapteri za prikaz, monitori i podržani režimi prikaza (rezolucija, brzina
osvežavanja i sl.); takođe definiše različite podržane formate površine (DKSGI_FORMAT).
Ukratko ćemo opisati neke DKSGI koncepte i interfejse koji će se koristiti tokom našeg
Direct3D inicijalizacija. Jedan od ključnih DKSGI interfejsa je interfejs IDKSGIFactori,
koji se prvenstveno koristi za kreiranje interfejsa IDKSGISvapChain i popisivanje displeja
adapteri. Displai adaptori implementiraju grafičku funkcionalnost. Obično je adapter za prikaz
je fizički deo hardvera (npr., grafička kartica); međutim, sistem može takođe imati a
adapter za softver koji emulira hardversku grafičku funkcionalnost. Sistem može imati
nekoliko adaptera (npr., ako ima nekoliko grafičkih kartica). Adapter je predstavljen od strane
IDKSGIAdapter interfejs. Mi možemo nabrojati sve adaptere na sistem sa
void D3DApp::LogAdapters()
{
UINT i = 0;
IDXGIAdapter* adapter = nullptr;
std::vector<IDXGIAdapter*> adapterList;
while(mdxgiFactory->EnumAdapters(i, &adapter) !=
DXGI_ERROR_NOT_FOUND)
{
DXGI_ADAPTER_DESC desc;
adapter->GetDesc(&desc);
std::wstring text = L”***Adapter: “;
text += desc.Description;
text += L”\n”;
OutputDebugString(text.c_str());
adapterList.push_back(adapter);
++i;
}
for(size_t i = 0; i < adapterList.size(); ++i)
{
LogAdapterOutputs(adapterList[i]);
ReleaseCom(adapterList[i]);
}
4.1.9 Nivoi osobina
Direct3D 11 uvodi koncept nivoa funkcija (prikazan u kodu pomoću D3D_FEATURE_LEVEL navedenog
tipa), koji približno odgovaraju različitim Direct3D verzijama od verzije 9 do 11:
void D3DApp::LogAdapterOutputs(IDXGIAdapter* adapter)
{
UINT i = 0;
IDXGIOutput* output = nullptr;
while(adapter->EnumOutputs(i, &output) !=
DXGI_ERROR_NOT_FOUND)
{
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);
std::wstring text = L”***Output: “;
text += desc.DeviceName;
text += L”\n”;
OutputDebugString(text.c_str());
LogOutputDisplayModes(output,
DXGI_FORMAT_B8G8R8A8_UNORM);
ReleaseCom(output);
++i;
}
}
Imajte na umu da po dokumentaciji "Microsoft Basic Render Driver" nema izlaze ekrana. Svaki monitor
ima set režima prikaza koji podržava. Režim prikaza se odnosi na sledeće podatke u DKSGI_MODE_DESC:
typedef struct DXGI_MODE_DESC
{
UINT Width; // Resolution width
UINT Height; // Resolution height
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format; // Display format
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
//Progressive vs. interlaced
DXGI_MODE_SCALING Scaling; // How the image is
stretched
// over the monitor.
} DXGI_MODE_DESC;
typedef struct DXGI_RATIONAL
{
UINT Numerator;
UINT Denominator;
} DXGI_RATIONAL;
typedef enum DXGI_MODE_SCANLINE_ORDER
{
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED = 0,
DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE = 1,
DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST = 2,
DXGI_MODE_SCANLINE_ORDER_LOWER_FIELD_FIRST = 3
} DXGI_MODE_SCANLINE_ORDER;
typedef enum DXGI_MODE_SCALING
{
DXGI_MODE_SCALING_UNSPECIFIED = 0,
DXGI_MODE_SCALING_CENTERED = 1,
DXGI_MODE_SCALING_STRETCHED = 2
} DXGI_MODE_SCALING;
Popravljamo format prikaza prikaza, možemo dobiti listu svih podržanih režima prikaza an izlaz podržava
u tom formatu pomoću sledećeg koda:
void D3DApp::LogOutputDisplayModes(IDXGIOutput*
output, DXGI_FORMAT format)
{
UINT count = 0;
UINT flags = 0;
// Call with nullptr to get list count.
output->GetDisplayModeList(format, flags, &count,
nullptr);
std::vector<DXGI_MODE_DESC> modeList(count);
output->GetDisplayModeList(format, flags, &count,
&modeList[0]);
for(auto& x : modeList)
{
UINT n = x.RefreshRate.Numerator;
UINT d = x.RefreshRate.Denominator;
std::wstring text =
L”Width = ” + std::to_wstring(x.Width) + L” ” +
L”Height = ” + std::to_wstring(x.Height) + L” ”
+
L”Refresh = ” + std::to_wstring(n) + L”/” +
std::to_wstring(d) +
L”\n”;
::OutputDebugString(text.c_str());
}
}
An example of some of the output from this code is as follows:
***Output: \.\DISPLAY2
… Width =
1920 Height =
1080 Refresh =
59950/1000
Width = 1920 Height = 1200 Refresh = 59950/1000
Brojanje režima prikaza je posebno važno kada se ide u režim celog ekrana. Da biste dobili optimalne
performanse na celom ekranu, navedeni režim prikaza (uključujući brzinu osvežavanja) mora odgovarati
tačno režimu prikaza koji monitor podržava. Određivanje prikazanog režima prikaza garantuje ovo. Za
više referentnih materijala na DKSGI preporučujemo čitanje sljedećih članaka: "DKSGI pregled",
"DirectKs Graphics Infrastructure: Best Practices" i "DKSGI 1.4 Improvements" dostupni na mreži na:
DXGI Overview: http://msdn.microsoft.com/enus/
library/windows/desktop/bb205075(v=vs.85).aspx
DirectX Graphics Infrastructure: Best Practices: http://msdn.microsoft.com/enus/
library/windows/desktop/ee417025(v=vs.85).aspx
DXGI 1.4 Improvements: https://msdn.microsoft.com/enus/
library/windows/desktop/mt427784%28v=vs.85%29.aspx
4.1.11 Provera funkcije podrške
Već smo koristili ID3D12Device :: CheckFeatureSupport metod da proverite podršku za multisampling od
trenutnog grafičkog upravljačkog programa. Međutim, to je samo jedna podrška za funkciju koju
možemo proveriti sa ovom funkcijom. Prototip ovog metoda je sledeći:
HRESULT ID3D12Device::CheckFeatureSupport(
D3D12_FEATURE Feature,
void *pFeatureSupportData,
UINT FeatureSupportDataSize);
1.Funkcija: Član D3D12_FEATURE navedenog tipa koji identifikuje
vrsta funkcija koje želimo da proverimo podršku:
1. D3D12_FEATURE_D3D12_OPTIONS: Proverava podršku za različite Direct3D
12 karakteristika.
2. D3D12_FEATURE_ARCHITECTURE: Proverava podršku za hardver
arhitektonske karakteristike.
3. D3D12_FEATURE_FEATURE_LEVELS: Proverava podršku na nivou funkcije.
4. D3D12_FEATURE_FORMAT_SUPPORT: Provjerite funkcionalnu podršku za datu
format teksta (npr. da li se format može koristiti kao meta za prikazivanje, može li biti format
koristi se sa mešanjem).
5. D3D12_FEATURE_MULTISAMPLE_KUALITI_LEVELS: Proveri
podrška za multisampling funkcije.
2. pFeatureSupportData: pokazivač na strukturu podataka za preuzimanje podrške za funkcije
informacije. Tip strukture koju koristite zavisi od onoga što ste naveli za
Parametar funkcije:
1. Ako ste odredili D3D12_FEATURE_D3D12_OPTIONS, onda prosledite jednu instancu
od D3D12_FEATURE_DATA_D3D12_OPTIONS.
2. Ako ste odredili D3D12_FEATURE_ARCHITECTURE, onda prosledite instancu od
D3D12_FEATURE_DATA_ARCHITECTURE.
3. Ako ste odredili D3D12_FEATURE_FEATURE_LEVELS, onda prosledite jednu instancu
D3D12_FEATURE_DATA_FEATURE_LEVELS.
4. Ako ste odredili D3D12_FEATURE_FORMAT_SUPPORT, onda prosledite jednu instancu
D3D12_FEATURE_DATA_FORMAT_SUPPORT.
5. Ako ste odredili D3D12_FEATURE_MULTISAMPLE_KUALITI_LEVELS,
zatim prosledite instancu
D3D12_FEATURE_DATA_MULTISAMPLE_KUALITI_LEVELS.
3. FeatureSupportDataSize: veličina strukture podataka preneta u
pFeatureSupportData parametar.
Funkcija ID3D12Device :: CheckFeatureSupport proverava puno podrške
karakteristika, od kojih mnogi ne trebamo da proveravamo ovu knjigu i da smo napredni; pogledaj
SDK dokumentaciju za detalje o članovima podataka za svaku strukturu funkcija. Međutim,
kao primer, prikazaćemo ispod kako da proverite nivo podržanih funkcija (§4.1.9):
1. nodeMask: Podesite na 0 za jedan GPU sistem. U suprotnom, maska čvora identifikuje fizički GPU sa
kojim je ova komandna lista povezana. U ovoj knjizi pretpostavljamo pojedinačne GPU sisteme.
2. tip: tip komandne liste: ili _COMMAND_LIST_TIPE_DIRECT ili D3D12_COMMAND_LIST_TIPE_BUNDLE.
3. pCommandAllocator: Rasporednik koji se povezuje sa kreiranom komandnom listom. Tip raspodele
komandi mora odgovarati tipu naredbe.
4. pInitialState: Određuje početno stanje plinovoda u komandnoj listi. Ovo može biti nula za sve pakete,
au posebnom slučaju kada se komandna lista izvrši radi inicijalizacije i ne sadrži nijedne naredbe za
izvlačenje. U Poglavlju 6 razmatramo ID3D12PipelineState.
5. riid: COM ID interfejsa ID3D12CommandList koje želimo da napravimo.
6. ppCommandList: Prikazuje pokazivač na kreiranu komandnu listu.
Možete kreirati više komandnih lista povezanih sa istim raspodjelom, ali ne možete istovremeno snimati.
To jest, sve komandne liste moraju biti zatvorene osim onih čije komande ćemo snimiti. Stoga, sve
naredbe sa date komandne liste će biti dodate u raspodjeljivače uzastopno. Imajte na umu da kada se
kreira ili resetuje komandna lista, nalazi se u "otvorenom" stanju. Ako smo pokušali da napravimo dve
komandne liste u nizu sa istim raspodelom, dobićemo grešku:
D3D12 ERROR: ID3D12CommandList::
{Create,Reset}CommandList: The command allocator is
currently in-use by another command list.
Ovaj metod stavlja komandnu listu u isto stanje kao da je upravo kreiran, ali nam omogućava da ponovo
koristimo unutrašnju memoriju i izbjegnemo deaktiviranje stare komandne liste i dodjelu novog. Imajte
na umu da poništavanje liste komandi ne utiče na komande u redosledu naredbe jer pridruženi raspored
komandi i dalje ima komande u memoriji koje ukazuje na redosled naredbe. Nakon što smo uputili
naredbe za rendering za kompletan okvir u GPU, želeli bismo da ponovo koristimo memoriju u raspodeli
naredbe za sledeći okvir. Za to se može koristiti metod
ID3D12CommandAllocator :: Reset:HRESULT ID3D12CommandAllocator :: Reset (void); Ideja o tome je
analogna pozivanjem std :: vector :: clear, koji veliA? A vektor vraća na nulu, ali zadržava trenutni
kapacitet isti. Međutim, zato što komandni red može biti referentni podatak u alokatoru, alatka za
komandu se ne sme resetovati dok ne budemo sigurni da je GPU završio izvršavanje svih komandi u
alokatoru; kako to učiniti je pokriveno u sledećem odeljku.
Zbog dva procesora koji rade paralelno, pojavljuje se određeni broj problema s sinhronizacijom.
Pretpostavimo da imamo neki resurs R koji čuva položaj neke geometrije koju želimo izvući. Osim toga,
pretpostavimo da CPU ažurira podatke od R za spremanje pozicije p1, a zatim dodaje komandu crtanja C
koja upućuje R u komandni red sa namerom crtanja geometrije na poziciji p1. Dodavanje komandi u
komandni red ne blokira CPU, pa CPU nastavlja. Bilo bi greška da CPU nastavi i prepisuje podatke R za
čuvanje nove pozicije p2 pre nego što GPU izvrši komandu izvlačenja C (pogledajte sliku 4.7).
Jedno rešenje ove situacije je da se CPU prisili na čekanje dok GPU ne završi obradu svih komandi u redu
do određene tačke ograde. Ovo zovemo redom komandnog reda. To možemo uraditi koristeći ogradu.
Ograda je predstavljena ID3D12Fence interfejsom i koristi se za sinhronizaciju GPU-a i CPU-a. Objekt
ograde se može kreirati sledećim metodom:
HRESULT ID3D12Device::CreateFence(
UINT64 InitialValue,
D3D12_FENCE_FLAGS Flags,
REFIID riid,
void **ppFence);
// Example
ThrowIfFailed(md3dDevice->CreateFence(
0,
D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
Objekt ograde održava vrijednost UINT64, što je samo cijeli broj za identifikaciju tačke ograde u
vremenu. Počinjemo od vrednosti nula i svaki put kada nam trebamo obeležiti novu tačku ograde, samo
povećavamo ceo broj. Sada, sledeći šifri / komentari pokazuju kako možemo koristiti ogradu za ispiranje
redova za naredbe.
UINT64 mCurrentFence = 0;
void D3DApp::FlushCommandQueue()
{
// Advance the fence value to mark commands up to
this fence point.
mCurrentFence++;
// Add an instruction to the command queue to set a
new fence point.
// Because we are on the GPU timeline, the new fence
point won’t be
// set until the GPU finishes processing all the
commands prior to
// this Signal().
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(),
mCurrentFence));
// Wait until the GPU has completed commands up to
this fence point.
if(mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false,
false, EVENT_ALL_ACCESS);
// Fire event when GPU hits current fence.
ThrowIfFailed(mFence-
>SetEventOnCompletion(mCurrentFence, eventHandle));
// Wait until the GPU hits current fence event is
fired.
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
Dakle, u prethodnom primeru, nakon što je CPU izdao komandu izvlačenja C, on bi ispustio naredbeni
red prije prepisivanja podataka od R za čuvanje nove pozicije p2. Ovo rešenje nije idealno, jer to znači da
je procesor u stanju mirovanja dok čeka GPU da završi, ali obezbeđuje jednostavno rešenje koje ćemo
koristiti do Poglavlja 7. Možete da ispunimo komandni red u gotovo bilo kojoj tački (ne obavezno samo
jednom Ram); ako imate nekoliko inicijalnih GPU komandi, možete isprazniti redosled naredbe da
izvršite inicijalizaciju pre nego što unesete glavnu petlju za rendering, na primer. Imajte na umu da se
ispiranje redova komandi može koristiti i za rešavanje problema koji smo pomenuli na kraju poslednjeg
odjeljka; to jest, možemo isprazniti komandni red da budemo sigurni da su sve GPU komande izvršene
pre nego što resetujemo raspodjelu naredbe.
4.2.3 Prelazak resursa
Da biste primenili uobičajene efekte renderinga, uobičajeno je da GPU piše na
resursa R u jednom koraku, a zatim u kasnijom koraku čitati iz resursa R. Međutim, to bi bilo opasnost od
resursa za čitanje iz resursa ako GPU nije završio pisanje uopšte nije započeo pisanje. Da bi rešio ovaj
problem, Direct3D povezuje državu sa resursa. Resursi su u podrazumevanom stanju kada se kreiraju i to
je na nivou aplikacija da reče Direct3D bilo koji prelazak stanja. Ovo omogućava GPU da radi bilo koji rad
treba učiniti kako bi se izvršila tranzicija i sprečila opasnost od resursa. Na primer, ako smo pisanje u
resurs, reći teksturu, postavićemo stanje teksture u ciljno stanje; kada treba da pročitamo teksturu, mi
ćemo promeniti stanje u državu resursa shadera. Od strane informišući Direct3D tranzicije, GPU može
preduzeti korake da izbegne opasnost na primer, čekajući da sve operacije pisanja završe pre čitanja
resurs. Breme tranzicije resursa pada na programer aplikacije za iz razloga učinka. Programer aplikacije
zna kada su ti prelazi događaj. Sistem automatskog praćenja tranzicije bi nametnuo dodatne troškove.
Prelazak resursa je specificiran postavljanjem niza barijera resursa tranzicije na komandnoj listi; to je niz
u slučaju da želite da prelazite sa više resursa jedan API poziv. U kodu, barijera resursa predstavlja
D3D12_RESOURCE_BARRIER_DESC struktura. Sledeća pomoćna funkcija (definisana u d3dk12.h) vraća
opis barijere resursa tranzicije za određeni resurs, I određuje pre i posle stanja:
struct CD3DX12_RESOURCE_BARRIER : public
D3D12_RESOURCE_BARRIER
{
// […] convenience methods
static inline CD3DX12_RESOURCE_BARRIER Transition(
_In_ ID3D12Resource* pResource,
D3D12_RESOURCE_STATES stateBefore,
D3D12_RESOURCE_STATES stateAfter,
UINT subresource =
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
D3D12_RESOURCE_BARRIER_FLAGS flags =
D3D12_RESOURCE_BARRIER_FLAG_NONE)
{
CD3DX12_RESOURCE_BARRIER result;
ZeroMemory(&result, sizeof(result));
D3D12_RESOURCE_BARRIER &barrier = result;
result.Type =
D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
result.Flags = flags;
barrier.Transition.pResource = pResource;
barrier.Transition.StateBefore = stateBefore;
barrier.Transition.StateAfter = stateAfter;
barrier.Transition.Subresource = subresource;
return result;
}
// […] more convenience methods
};
4.3 INITIALIZIRANJE DIRECT3D
Sledeći pododjeljci pokazuju kako inicijalizirati Direct3D za naš demo okvir. To je dugačak proces, ali to
treba samo jednom. Naš proces inicijalizacije Direct3D-a može se razvrstati u sledeće korake:
1. Kreirajte ID3D12Device pomoću funkcije D3D12CreateDevice.
2. Kreirajte ID3D12Fence veličinu deskriptora objekta i upita.
3. Proverite 4Ks MSAA podršku za nivo kvaliteta.
4. Kreirajte komandni red, alatator za naredbe i glavnu listu komandi.
5. Opišite i kreirajte svap lanac.
6. Kreirajte deskriptorske gomile koje aplikacija zahteva.
7. Promijenite veličinu zadnjeg pufera i kreirajte ciljni prikaz mjere do zadnjeg bafera.
8. Kreirajte bafer dubine / stencila i pripadajući prikaz dubine / šablona.
9. Postavite pravougaonike pogleda i škare.
4.3.1 Kreirajte uređaj
Inicijalizacija Direct3D počinje stvaranjem Direct3D 12 uređaja (ID3D12Device). Uređaj predstavlja
adapter za prikaz. Obično je adapter ekrana fizički deo 3D hardvera (npr. Grafičke kartice); Međutim,
sistem može imati i adapter za softver koji emulira 3D hardversku funkcionalnost (npr., VARP adapter).
Direct3D 12 uređaj se koristi za proveru podrške za funkcije i kreira sve druge objekte Direct3D interfejsa
kao što su resursi, prikazi i komandne liste. Uređaj se može kreirati sa sledećom funkcijom:
HRESULT WINAPI D3D12CreateDevice(
IUnknown* pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
REFIID riid, // Expected: ID3D12Device
void** ppDevice );
1.pAdapter: Određuje adapter za prikaz koji želimo da predstavlja kreirani uređaj. Podešavanje nula za
ovaj parametar koristi primarni adapter za prikaz. U primarnim programima ove knjige uvek koristimo
primarni adapter. §4.1.10 je pokazao kako da nabroje sve adaptere za prikazivanje sistema.
2. MinimumFeatureLevel: minimalni nivo svojstva za koju aplikacija zahteva podršku za; stvaranje
uređaja će propasti ako adapter ne podržava ovaj nivo funkcije. U našem okviru, specificiramo
D3D_FEATURE_LEVEL_11_0 (tj. Podršku za Direct3D 11).
3. riid: COM ID interfejsa ID3D12Device koje želimo da napravimo.
4. ppDevice: vraća kreirani uređaj. Evo primera poziva ove funkcije:
#if defined(DEBUG) || defined(_DEBUG)
// Enable the D3D12 debug layer.
{
ComPtr<ID3D12Debug> debugController;
ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
debugController->EnableDebugLayer();
}#
endif
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));
// Try to create hardware device.
HRESULT hardwareResult = D3D12CreateDevice(
nullptr, // default adapter
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice));
// Fallback to WARP device.
if(FAILED(hardwareResult))
{
ComPtr<IDXGIAdapter> pWarpAdapter;
ThrowIfFailed(mdxgiFactory-
>EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));
ThrowIfFailed(D3D12CreateDevice(
pWarpAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice)));
}
Obratite pažnju da prvo omogućimo debug sloj za izgradnju debug moda. Kada je debug sloj omogućen,
Direct3D će omogućiti ekstra debug i slanje debug poruka u VC ++ izlazni prozor kao što je sljedeće:
D3D12 ERROR: ID3D12CommandList :: Reset: Reset ne uspeva zato što komandna lista nije zatvorena.
Takođe, obratite pažnju da ako naš poziv na D3D12CreateDevice ne uspije, vratimo se na VARP uređaj,
koji je softverski adapter. VARP je Vindovs Advanced Rasterization Platform. U operativnom sistemu
Vindovs 7 i nižim, VARP uređaj podržava do nivoa funkcije 10.1; na Vindovsu 8, VARP uređaj podržava do
nivoa 11.1. Da bi kreirali VARP adapter, potrebno je kreirati IDKSGIFactori4 objekt kako bismo mogli
ComPtr <IDKSGIFactori4> mdkgiFactori;
CreateDKSGIFactori1 (IID_PPV_ARGS (& mdkgiFactori));
mdkgiFactori-> EnumVarpAdapter (
IID_PPV_ARGS (& pVarpAdapter));
Objekt mdkgiFactori će se takođe koristiti za kreiranje našeg zamjenskog lanca, jer je dio DKSGI.
4.3.2 Kreirajte veličinu ograde i deskriptora
Nakon što smo napravili naš uređaj, možemo napraviti svoj ograđeni objekt za sinhronizaciju CPU / GPU-
a. Pored toga, kada jednom dođemo do rada sa deskriptorima, moramo da znamo njihovu veličinu.
Deskriptori veličine mogu da variraju između GPU-a, tako da je potrebno upiti ove informacije.Cestitamo
veličine deskriptora tako da je dostupna kada nam je potrebna za različite tipove deskriptora:
ThrowIfFailed(md3dDevice->CreateFence(
0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
mRtvDescriptorSize = md3dDevice-
>GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice-
>GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvDescriptorSize = md3dDevice-
>GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
4.3.3 Proverite 4Ks MSAA podršku za kvalitet
U ovoj knjizi proveravamo podršku za 4Ks MSAA. Izabrali smo 4Ks jer dobijaju dobar napredak bez
previše skupog i zato što svi uređaji sa mogućnošću Direct3D 11 podržavaju 4Ks MSAA sa svim ciljnim
formatima. Zbog toga je garantovano da će biti dostupan na Direct3D 11 hardveru i nećemo morati da
potvrdimo podršku za to. Međutim, moramo proveriti podržani nivo kvaliteta koji se može uraditi s
sljedećim metodom:
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS
msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags =
D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msQualityLevels,
sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && “Unexpected MSAA quality
level.”);
Pozovite iz §4.2.1 da je komandni red prikazan pomoću ID3D12CommandKueue interfejsa, alatka za
komandu predstavlja ID3D12CommandAllocator interfejs, a komandna lista predstavlja
ID3D12GraphicsCommandList interfejs. Sledeća funkcija pokazuje kako kreiramo redosled komandi,
raspored komandi i komandnu listu:
ComPtr<ID3D12CommandQueue> mCommandQueue;
ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
ComPtr<ID3D12GraphicsCommandList> mCommandList;
void D3DApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(
&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // Associated command
allocator
nullptr, // Initial PipelineStateObject
IID_PPV_ARGS(mCommandList.GetAddressOf())));
// Start off in a closed state. This is because the
first time we
// refer to the command list we will Reset it, and
it needs to be
// closed before calling Reset.
mCommandList->Close();
}
Obratite pažnju na to za CreateCommandList, nule navedemo null za parametar objekta state pipeline. U
uzornom programu ovog poglavlja ne izdavamo nikakve komande za izvlačenje, tako da nam ne treba
valjani objekt objektnog objekta. Razgovaralićemo o objektima koji se odnose na gasovod u Poglavlju 6.
4.3.5 Opis i stvaranje Svap lanca
Sledeći korak u procesu inicijalizacije je kreiranje lanca zamene. Ovo se radi tako što prvo napišemo
instancu strukture DKSGI_SVAP_CHAIN_DESC, koja opisuje karakteristike svap lanca koji ćemo kreirati.
Ova struktura je definisana na sledeći način:
typedef struct DXGI_SWAP_CHAIN_DESC
{
DXGI_MODE_DESC BufferDesc;
DXGI_SAMPLE_DESC SampleDesc;
DXGI_USAGE BufferUsage;
UINT BufferCount;
HWND OutputWindow;
BOOL Windowed;
DXGI_SWAP_EFFECT SwapEffect;
UINT Flags;
} DXGI_SWAP_CHAIN_DESC;
The DXGI_MODE_DESC type is another structure, defined as:
typedef struct DXGI_MODE_DESC
{
UINT Width; // Buffer resolution width
UINT Height; // Buffer resolution height
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format; // Buffer display format
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
//Progressive vs. interlaced
DXGI_MODE_SCALING Scaling; // How the image is
stretched
// over the monitor.
} DXGI_MODE_DESC;
U sledećim opisima članova podataka, mi pokrivamo samo zajedničke zastavice I opcije koje su
najvažnije za početnika u ovom trenutku. Za dalje opisivanje zastave i opcije, pogledajte dokumentaciju
SDK-a.
1. BufferDesc: Ova struktura opisuje osobine bafera koji mi želimo
stvoriti. Glavna svojstva na koje smo zabrinuti su širina i visina, i piksel
format; pogledajte SDK dokumentaciju za više detalja o drugim članovima.
2. SampleDesc: Broj multisamplesa i nivoa kvaliteta; vidi §4.1.8. Za single
uzorkovanje, navedite broj uzoraka od 1 i nivo kvaliteta od 0.
3. BufferUsage: Specificirajte DKSGI_USAGE_RENDER_TARGET_OUTPUT od kada smo
(tj. koristiti ga kao cilj za prikazivanje).
4. BufferCount: broj pufera koji se koriste u svap lancu; navesti dva za
double buffering.
5. OutputVindov: Drška za prozor u koji se prikazujemo.
6. Prozor: podesite true za pokretanje u prozoru ili false za celokupan ekran
mod.
7. SvapEffect: Specificirajte DKSGI_SVAP_EFFECT_FLIP_DISCARD.
8. Zastave: opcionalne zastavice. Ako navedete
DKSGI_SVAP_CHAIN_FLAG_ALLOV_MODE_SVITCH, onda kada aplikacija prelazi na režim celog ekrana,
odabiće način prikaza koji najbolje odgovara trenutne dimenzije prozora aplikacije. Ako ova oznaka nije
specificirana, onda kada je aplikacija prelazi na režim celog ekrana, koristiće trenutni ekran radne
površine mod.Nakon što smo opisali svap lanac, možemo ga kreirati sa IDKSGIFactori :: CreateSvapChain
metod:
HRESULT IDXGIFactory::CreateSwapChain(
IUnknown *pDevice, // Pointer to
ID3D12CommandQueue.
DXGI_SWAP_CHAIN_DESC *pDesc, // Pointer to swap chain
description.
IDXGISwapChain **ppSwapChain);// Returns created swap
chain interface.
Sledeći kod pokazuje kako kreiramo svap lanac u našem okviru uzorka.
Obratite pažnju da je ova funkcija dizajnirana tako da se može nazvati više puta. To će uništiti star svap
lanac prije nego što napravite novi. Ovo nam omogućava da ponovo zamenimo svap lanac sa različitim
podešavanjima; naročito možemo promeniti postavke multisampling prilikom izvršavanja.
DXGI_FORMAT mBackBufferFormat =
DXGI_FORMAT_R8G8B8A8_UNORM;
void D3DApp::CreateSwapChain()
{
// Release the previous swapchain we will be
recreating.
mSwapChain.Reset();
DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth;
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering =
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling =
DXGI_MODE_SCALING_UNSPECIFIED;
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ?
(m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
// Note: Swap chain uses queue to perform flush.
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
}
4.3.6.Kreiranje deskriptora masa
Moramo kreirati deskriptorske heapove za čuvanje deskriptora / prikaza (§4.1.6) našoj aplikaciji. Skript
deskriptora predstavlja ID3D12DescriptorHeap interfejs. Šaka je kreirana metodom ID3D12Device ::
CreateDescriptorHeap. U uzornom programu ovog poglavlja, potreban je SvapChainBufferCount
mnogim prikazivanjem ciljnih prikaza (RTV) da bi se opisali resursi bafera u lancu zamene koju ćemo
izvršiti, i jedan prikaz dubine / šablona (DSV) za opis dubinskog / stencilnog pufera za ispitivanje dubine .
Zbog toga nam je potrebna gomila za skladištenje RTV-ova SvapChainBufferCount-a, i potreban nam je
kupac za skladištenje jednog DSV-a. Ovi kupci se kreiraju sa sledećim kodom:
ComPtr<ID3D12DescriptorHeap> mRtvHeap;
ComPtr<ID3D12DescriptorHeap> mDsvHeap;
void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&rtvHeapDesc,
IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&dsvHeapDesc,
IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}
In our application framework, we define
static const int SwapChainBufferCount = 2;
int mCurrBackBuffer = 0;
i pratimo trenutni indeks bafera sa mCurrBackBuffer-om (podsećamo da se prednji i zadnji odbojnici
zamenjuju u flippingu stranice, tako da treba pratiti koji je bafer trenutni bafer za bafer, tako da znamo
na koji se to odnosi). Nakon što napravimo kupe, moramo biti u mogućnosti da pristupimo
deskriptorima koji ih čuvaju. Naša aplikacija opisuje deskriptore kroz ručke. Drška prvog deskriptora u
kupu dobijena je metodom ID3D12DescriptorHeap :: GetCPUDescriptorHandleForHeapStart. Sledeće
funkcije dobijaju trenutni odbojnik RTV i DSV, odnosno:
D3D12_CPU_DESCRIPTOR_HANDLE
CurrentBackBufferView()const
{
// CD3DX12 constructor to offset to the RTV of the
current back buffer.
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),//
handle start
mCurrBackBuffer, // index to offset
mRtvDescriptorSize); // byte size of descriptor
}
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView()const
{
return mDsvHeap-
>GetCPUDescriptorHandleForHeapStart();
}
Sada vidimo primer gde je potrebna veličina deskriptora. Da bi se uklonio trenutni deskriptor RTV
deskriptora, potrebno je znati veličinu bita deskriptora RTV-a.
4.3.7 Kreirajte prikaz cilja Rendera
Kao što smo rekli u §4.1.6, direktno ne povezujemo izvor sa fazama gasovoda; umjesto toga, moramo
napraviti pogled resursa (deskriptor) u resurs i povezati pogled s fazom cevovoda. Konkretno, kako
bismo vezali zadnje pufer na izlaznu fazu sjedinjavanja cjevovoda (tako da Direct3D može da se uključi
na njega), potrebno je kreirati ciljni prikaz rendera na zadnje pufer. Prvi korak je dobijanje resursa bafera
koji se čuvaju u svap lancu:
HRESULT IDXGISwapChain::GetBuffer(
UINT Buffer,
REFIID riid,
void **ppSurface);
1.Buffer: Indeks koji identifikuje određeni bafer koji želimo dobiti (u slučaju
postoji više od jednog).
2. riid: COM ID ID3D12Resource interfejsa za koji želimo da dobijemo
pokazivač na.
3. ppSurface: Vraća pokazivač na ID3D12Resource koji predstavlja pozadinu
pufer.
Poziv na IDKSGISvapChain :: GetBuffer povećava broj referentnih brojeva COM-a
do bafera, tako da ga moramo pustiti kada završimo s njim. Ovo je učinjeno
automatski ako koristite ComPtr.
ID3D12Device::CreateRenderTargetView method:
void ID3D12Device::CreateRenderTargetView(
ID3D12Resource *pResource,
const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,
D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor);
1.pResource: Određuje resurs koji će se koristiti kao cilja rendera, što je, u prethodnom primeru, zadnji
buffer (tj., Kreiramo ciljni prikaz na zadnjem baferu).
2. pDesc: pokazivač na D3D12_RENDER_TARGET_VIEV_DESC. Između ostalog, ova struktura opisuje tip
podataka (format) elemenata u resursu. Ako je resurs kreiran pomoću otkucanog formata (tj.
Neupotrebljivog), onda ovaj parametar može biti null, što ukazuje na kreiranje pogleda na prvi mipmap
nivo ovog resursa (zadnji bafer ima samo jedan mipmap nivo) s formatom resurs je kreiran sa. (Mipmaps
su razmatrani u Poglavlju 9.) Pošto smo odredili vrstu našeg bafera za zadnje strane, za ovaj parametar
numeriramo null.
3. DestDescriptor: Rukovati se deskriptoru koji će sačuvati kreiran render ciljni prikaz. Dole je primer
naziva ova dva metoda gde stvaramo RTV za svaki bafer u svap lancu:
ComPtr<ID3D12Resource>
mSwapChainBuffer[SwapChainBufferCount];
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(
mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
// Get the ith buffer in the swap chain.
ThrowIfFailed(mSwapChain->GetBuffer(
i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
// Create an RTV to it.
md3dDevice->CreateRenderTargetView(
mSwapChainBuffer[i].Get(), nullptr,
rtvHeapHandle);
// Next entry in heap.
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
Prva četiri člana podataka definišu pravougaonik pogleda u odnosu na zadnje pufer
(obratite pažnju da možemo odrediti delimične koordinate piksela, jer su članovi podataka
tip float). U Direct3D-u, dubinske vrednosti se čuvaju u baferu dubine u normalizovanom
opseg od 0 do 1. MinDepth i MakDepth članovi koriste se za transformaciju dubine
interval [0, 1] do dubinskog intervala [MinDepth, MakDepth]. Biti u stanju da transformišu
dubinski opseg može se koristiti za postizanje određenih efekata; na primer, možete postaviti
MinDepth = 0 i MakDepth = 0, tako da će svi objekti izvučeni sa ovim prikazom imati
dubinske vrednosti od 0 i pojavljuju se ispred svih ostalih objekata u sceni. Međutim, obično
MinDepth je podešen na 0 i MakDepth je podešen na 1, tako da vrednosti dubine nisu
modifikovan.
Jednom kada smo popunili strukturu D3D12_VIEVPORT, podesili smo prikaz
Direct3D sa metodom ID3D12CommandList :: RSSetVievports. The
sledeći primjer stvara i postavlja vidik koji se izvlači na cijeli zadnji odbojnik:
D3D12_VIEWPORT vp;
vp.TopLeftX = 0.0f;
vp.TopLeftY = 0.0f;
vp.Width = static_cast<float>(mClientWidth);
vp.Height = static_cast<float>(mClientHeight);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
mCommandList->RSSetViewports(1, &vp);
Možete koristiti vizuelni prozor da implementirate podeljene ekrane za režim igre sa dva igrača, za
primer. Kreirali biste dva prikaza, jednu za levu polovinu ekrana i jednu za
desnu polovinu ekrana. Zatim biste izvlačili 3D scenu iz perspektive
Igrač 1 u lijevom pogledu i nacrtati 3D scenu iz perspektive igrača 2 u
Pravi pogled.
4.3.10 Postavite pravougaone škare
Mo'emo definisati pravougaonik makaze u odnosu na bafer za pozadinu, tako da pikseli napolju
ovaj pravougaonik je izbačen (tj. nije rasterisan u zadnje pufer). Ovo se može koristiti
optimizacije. Na primer, ako znamo da će oblast ekrana sadržavati pravougaoni UI
element na vrhu svega, ne trebamo obrađivati piksele 3D sveta da
Element UI će biti nejasan.
Pravokutnik makaze definisan je strukturom D3D12_RECT u kojoj se unosi tekst
sledeća struktura:
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
Postavili smo pravougaonik makaze sa Direct3D sa ID3D12CommandList :: RSSetScissorRects metod.
Sledeći primer kreira i postavlja pravougaonik makaze koji pokriva gornji levi kvadrant u zadnjem puferu:
mScissorRect = {0, 0, mClientVidth / 2, mClientHeight / 2 };
mCommandList-> RSSetScissorRects (1, & mScissorRect);
Kao i RSSetVievports, prvi parametar je broj pravokutnika škare
da se veže (koristeći više od jednog za napredne efekte), a drugi parametar je a
pokazivač na niz pravougaonika
4.4 tajming I ANIMACIJA
Da pravilno uradimo animaciju, morat ćemo voditi računa o vremenu. Posebno ćemo morati da
izmerimo vreme koje protiče između okvira animacije. Ako je brzina kadra visoka, ovi vremenski intervali
između okvira će biti vrlo kratki; Stoga nam treba
tajmer sa visokim nivoom tačnosti.
4.4.1 Tajmer za performanse
Za tačna vremenska merenja koristimo tajmer performanse (ili performanse
brojač). Da bi koristili Vin32 funkcije za upućivanje na tajmer performansi, moramo
#include <vindovs.h>.
Tajmer za merenje vremena meri u jedinicama koje se nazivaju brojanje. Dobijamo struju
vremenska vrednost, merena brojevima, tajmer performansi sa
KueriPerformanceCounter funkcija:
__int64 currTime;
KueriPerformanceCounter ((LARGE_INTEGER *) & currTime);
Obratite pažnju da ova funkcija vraća trenutnu vremensku vrijednost kroz svoj parametar, što je
je 64-bitna cijela vrijednost.
Da bismo dobili frekvenciju (broj po sekundi) tajmera performansi, mi koristimo
KueriPerformanceFrekuenci funkcija:
__int64 countsPerSec;
KueriPerformanceFrekuenci ((LARGE_INTEGER *) i countsPerSec);
Tada je broj sekundi (ili frakcija sekunde) po broju samo recipročni
brojanja u sekundi:
mSecondsPerCount = 1,0 / (dvostruki) countsPerSec;
Stoga, kako bi konvertovali vrijednost čitanjaInCounts u sekunde, samo smo ga pomnožili
faktor konverzije mSecondsPerCount
valueInSecs = valueInCounts * mSecondsPerCount;
Vrednosti koje je vratila KueriPerformanceCounter funkcija nisu
posebno zanimljive i za sebe. Ono što radimo jeste dobiti trenutnu vrednost vremena
koristeći KueriPerformanceCounter, a zatim dobijete trenutnu vrednost vremena malo kasnije
ponovo koristite KueriPerformanceCounter. Zatim, vreme koje je prošlo između njih
dva puta poziva je samo razlika. To jest, mi uvek gledamo na relativnu razliku
između dve vremenske markice za merenje vremena, a ne stvarne vrednosti koje je vratio
brojač performansi. Sledeće bolje ilustruje ideju:
__int64 A = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&A);
/* Do work */
__int64 B = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&B);
Tako je bilo potrebno (B-A) da rade na poslu, ili (B-A) * mSecondsPerCount sekundi da uradi posao.
4.4.2
U naredna dva poglavlja ćemo razmotriti implementaciju sledećih
GameTimer klasa.
class GameTimer
{p
ublic:
GameTimer();
float GameTime()const; // in seconds
float DeltaTime()const; // in seconds
void Reset(); // Call before message loop.
void Start(); // Call when unpaused.
void Stop(); // Call when paused.
void Tick(); // Call every frame.
private:
double mSecondsPerCount;
double mDeltaTime;
__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;
__int64 mPrevTime;
__int64 mCurrTime;
bool mStopped;
};
The constructor, in particular, queries the frequency of the performance counter. The other member
functions are discussed in the next two sections.
GameTimer::GameTimer()
: mSecondsPerCount(0.0), mDeltaTime(-1.0),
mBaseTime(0),
mPausedTime(0), mPrevTime(0), mCurrTime(0),
mStopped(false)
{
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
mSecondsPerCount = 1.0 / (double)countsPerSec;
}
Klasa i implementacije GameTimer su u GameTimer.h i
GameTimer.cpp datoteke, koje se mogu naći u Common direktorijum uzorka koda.
4.4.3 Vremenski period između ramova
Kada pružimo svoje okvire animacije, moraćemo da znamo koliko ima vremena
proteklo je između okvira tako da možemo da ažuriramo naše igračke objekte na osnovu koliko vremena
položio je. Računanje vremena koje je proteklo između frejmova vrši se na sledeći način. Neka ti bude
vreme koje se vraća brojač performansi tokom i-ti rama i pustite ti-1 vrijeme
vraća brojač performansi tokom prethodnog kadra. Zatim je proteklo vreme
između čitanja ti-1 i čitanja ti je Dt = ti-ti-1. Za rendering u realnom vremenu, mi
obično zahtevaju najmanje 30 slika u sekundi za glatku animaciju (i obično imamo
mnogo veće stope); Stoga, Dt = ti-ti-1 ima tendenciju da bude relativno mali broj.
Sledeći kod pokazuje kako je Dt izračunat u kodu:
void GameTimer::Tick()
{
if( mStopped )
{
mDeltaTime = 0.0;
return;
}
// Get the time this frame.
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mCurrTime = currTime;
// Time difference between this frame and the
previous.
mDeltaTime = (mCurrTime -
mPrevTime)*mSecondsPerCount;
// Prepare for next frame.
mPrevTime = mCurrTime;
// Force nonnegative. The DXSDK’s CDXUTTimer
mentions that if the
// processor goes into a power save mode or we get
shuffled to
// another processor, then mDeltaTime can be
negative.
if(mDeltaTime < 0.0)
{
mDeltaTime = 0.0;
}
}
float GameTimer::DeltaTime()const
{
return (float)mDeltaTime;
}
The function Tick is called in the application message loop as follows:
int D3DApp::Run()
{
MSG msg = {0};
mTimer.Reset();
while(msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Otherwise, do animation/game stuff.
else
{
mTimer.Tick();
if( !mAppPaused )
{
CalculateFrameStats();
Update(mTimer);
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}
return (int)msg.wParam;
}
Na ovaj način, Dt se izračunava u svakom okviru i unosi se u metodu UpdateScene tako da se scena
može ažurirati na osnovu vremena koliko je prošlo od prethodnog okvira animacije. Primjena metode
Reset je:
void GameTimer::Reset()
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mBaseTime = currTime;
mPrevTime = currTime;
mStopTime = 0;
mStopped = false;
}
Neke od prikazanih varijabli još nisu raspravljane (pogledajte §4.4.4). Međutim, mi
vidite da ovo inicijalizuje mPrevTime do trenutnog vremena kada se poziva Reset. To je
važno je to učiniti jer u prvom okviru animacije nema prethodnog okvira,
i stoga, bez prethodnog vremenskog pečata ti-1. Stoga ova vrijednost treba inicijalizirati u
Metod resetovanja pre početka petlje poruke.
4.4.4 Ukupno vreme
Drugo merenje vremena koje može biti korisno je količina vremena koja je protekla
pošto aplikacija počne, ne računajući pauzirano vreme; mi ćemo zvati ovo ukupno vreme. The
sledeća situacija pokazuje kako bi to moglo biti korisno. Pretpostavimo da igrač ima 300 sekundi
popunite nivo. Kada se nivo započne, možemo dobiti tstart vremena koja je istekla vremena
pošto je aplikacija počela. Zatim, nakon što je nivo započeo, svako često možemo proveriti
vreme t nakon što je aplikacija započela. Ako t - tstart> 300s (vidi sliku 4.10), onda plejer
je na nivou preko 300 sekundi i gubi. Očigledno u ovoj situaciji, mi ne znamo
želite da brojite bilo kada kada je igra zaustavljena protiv igrača.
Druga primena ukupnog vremena je kada želimo da animirate količinu kao funkciju
vremena. Na primer, pretpostavimo da želimo da svetlina bude u funkciji vremena.
Njegov položaj se može opisati parametarskim jednačinama:
Ovde t predstavlja vreme, a dok se t (vreme) povećava, koordinate svetla su
ažurirano tako da se svetlo pomera u krugu sa radijusom 10 u i = 20 ravni. Za ovu vrstu
animacije, takođe ne želimo da brojimo pauzirano vreme; videti sliku 4.11.
Slika 4.11. Ako smo zaustavili na t1 i neupareni u t2, i prebrojali pauzirano vreme, onda
kada odjavimo, pozicija će naglo skočiti sa p (t1) do p (t2).
Za realizaciju ukupnog vremena koristimo sljedeće varijable:
__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;
Kao što smo videli u §4.4.3, mBaseTime se inicijalizuje do trenutnog vremena kada je Reset bio
pozvani. O tome možemo razmišljati kao vrijeme kada je aplikacija započela. U većini slučajeva, vi
samo će ponoviti poziv Ponovo jednom pre poruke o petlji, tako da mBaseTime ostaje konstantan
tokom celog životnog veka aplikacije. Promenljiva mPausedTime akumulira sve
vreme koje prolazi dok smo pauzirani. Moramo nagomilati ovaj put, tako da možemo da oduzmemo
to iz ukupnog vremenskog perioda, kako ne bi računalo pauzirano vreme. The mStopTime
varijabla nam daje vreme kada je tajmer zaustavljen (pauzirana); ovo se koristi da nam pomogne
trajanje pauziranog vremena.
Dva važna metoda klase GameTimer su Stop i Start. Oni
treba pozvati kada je aplikacija pauzirana i neiskorišćena, respektivno, tako da
GameTimer može pratiti pauzirano vreme. Komentari koda objašnjavaju detalje o
ove dve metode.
void GameTimer::Stop()
{
// If we are already stopped, then don’t do
anything.
if( !mStopped )
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
// Otherwise, save the time we stopped at, and
set
// the Boolean flag indicating the timer is
stopped.
mStopTime = currTime;
mStopped = true;
}
}
void GameTimer::Start()
{
__int64 startTime;
QueryPerformanceCounter((LARGE_INTEGER*)&startTime);
// Accumulate the time elapsed between stop and
start pairs.
//
// |<––-d––->|
// –––––*–––––—*––––> time
// mStopTime startTime
// If we are resuming the timer from a stopped
state…
if( mStopped )
{
// then accumulate the paused time.
mPausedTime += (startTime - mStopTime);
// since we are starting the timer back up, the
current
// previous time is not valid, as it occurred
while paused.
// So reset it to the current time.
mPrevTime = startTime;
// no longer stopped…
mStopTime = 0;
mStopped = false;
}
}
Finally, the TotalTime member function, which returns the time elapsed since
Reset was called not counting paused time, is implemented as follows:
float GameTimer::TotalTime()const
{/
/ If we are stopped, do not count the time that has
passed
// since we stopped. Moreover, if we previously
already had
// a pause, the distance mStopTime - mBaseTime
includes paused
// time,which we do not want to count. To correct
this, we can
// subtract the paused time from mStopTime:
//
// previous paused time
// |<–––—>|
// –*––––*––––-*––-*–––—*––> time
// mBaseTime mStopTime mCurrTime
if( mStopped )
{
return (float)(((mStopTime - mPausedTime)-
mBaseTime)*mSecondsPerCount);
}
// The distance mCurrTime - mBaseTime includes paused
time,
// which we do not want to count. To correct this, we
can subtract
// the paused time from mCurrTime:
//
// (mCurrTime - mPausedTime) - mBaseTime
//
// |<—paused time—>|
// –-*–––––*–––––—*––––*––> time
// mBaseTime mStopTime startTime mCurrTime
else
{
return (float)(((mCurrTime-mPausedTime)-
mBaseTime)*mSecondsPerCount);
}
}
class DxException
{
public:
DxException() = default;
DxException(HRESULT hr, const std::wstring&
functionName,
const std::wstring& filename, int lineNumber);
std::wstring ToString()const;
HRESULT ErrorCode = S_OK;
std::wstring FunctionName;
std::wstring Filename;
int LineNumber = -1;
};
#ifndef ThrowIfFailed
#define ThrowIfFailed(x) \
{\
HRESULT hr__ = (x); \
std::wstring wfn = AnsiToWString(__FILE__); \
if(FAILED(hr__)) { throw DxException(hr__, L#x, wfn,
__LINE__); } \
}#
Endif
Obratite pažnju da ThrovIfFailed mora biti makro, a ne funkcija; inače
__FILE__ i __LINE__ se odnose na datoteku i liniju funkcije
implementacija umesto datoteke i linije gde je ThrovIfFailed napisan.
L # k pretvara Token argumenta ThrovIfFaileda u Unicode string.
Na ovaj način možemo da izađemo poziv koji je izazvao grešku u okviru za poruke.
Za Direct3D funkciju koja vraća HRESULT, koristimo makro kao:
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_MISC_NONE,
&depthStencilDesc,
D3D12_RESOURCE_USAGE_INITIAL,
IID_PPV_ARGS(&mDepthStencilBuffer)));
Our entire application exists in a try/catch block:
try
{
InitDirect3DApp theApp(hInstance);
if(!theApp.Initialize())
return 0;
return theApp.Run();
}
catch(DxException& e)
{
MessageBox(nullptr, e.ToString().c_str(), L”HR
Failed”, MB_OK);
return 0; }
4.7 REZIME
1. Direct3D se može smatrati posrednikom između programera i grafike
hardvare. Na primer, programer poziva funkcije Direct3D da povezuje resurs
pogleda na ploče za rendering hardvera, da konfigurišete izlaz iz renderinga
cevovod i crtanje 3D geometrije.
2. Model komponente objekta (COM) je tehnologija koja omogućava DirectKsu da bude
nezavisno od jezika i imaju kompatibilnost sa unazad. Direct3D programeri
ne treba znati detalje o COM-u i kako to funkcioniše; samo treba znati
kako nabaviti COM interfejse i kako ih pustiti.
3. 1D tekstura je kao 1D niz elemenata podataka, 2D tekstura je kao 2D niz
elementi podataka i 3D tekstura su kao 3D niz elemenata podataka. Elementi a
tekstura mora imati format koji je opisao član DKSGI_FORMAT-a
popisani tip. Teksture obično sadrže podatke o slici, ali mogu sadržavati i druge
podaci, kao što su informacije o dubini (npr., bafer dubine). GPU može učiniti posebnim
operacije na teksturima, kao što su filtriranje i multisample.
4. Da biste izbegli treperenje u animaciji, najbolje je crtati ceo animacijski okvir
tekst na ekranu koji se zove bafer. Jednom kada se cela scena povuče
Back buffer za datu animaciju, on se prikazuje na ekranu kao jedan
kompletan okvir; na ovaj način, gledalac ne gleda kako se okvir privlači.
Nakon što je okvir okrenuo na zadnje pufer, uloge zadnjeg pufera i
prednji bafer je obrnut: zadnji bafer postaje prednji bafer i napred
Bufer postaje zadnji bafer za sledeći okvir animacije. Zamenite uloge
pozadinskog i prednjeg odbojnika se naziva prezentacija. Prednji i zadnji odbojnik formiraju a
svap lanac, predstavljen interfejsom IDKSGISvapChain. Korišćenje dva bafera
(napred i nazad) naziva se dvostruko puferovanje.
5. Pretpostavljajući neprozirne objekte scene, tačke najbliže kameri okružuju bilo koje tačke
iza njih. Buferiranje dubine je tehnika za određivanje tačaka u sceni
najbliže kameri. Na ovaj način, ne moramo da brinemo o redosledu u kojem
crtamo naše scene objekata.
6. U Direct3D, resursi nisu direktno povezani sa gasovodom. Umjesto toga, vezujemo se
resursa za rendering plinovode precizirajući deskriptore koji će biti
referenca u pozivu za izvlačenje. Deskriptorski objekat se može smatrati lakim
struktura koja identifikuje i opisuje resurs GPU-u. Različiti deskriptori
može se napraviti jedan resurs. Na ovaj način se može videti jedan resurs
različiti načini; na primer, vezani za različite faze izvođenja plinovoda ili
imaju svoje bitove tumačene kao različite DKSGI_FORMAT. Aplikacije kreiraju deskriptor
gomile koje čine memorijsku podršku deskriptora.
7. ID3D12Device je glavni Direct3D interfejs koji se može smatrati našim
softver kontroler hardvera fizičkog grafičkog uređaja; kroz to, možemo
kreira grafičke resurse i kreira druge specijalizovane interfejse koji se koriste za kontrolu
grafički hardver i uputiti ga da radi stvari.
8. GPU ima komandni red. CPU dostavlja naredbe u red
Direct3D API koristeći komandne liste. Komanda daje instrukcije GPU-u
nešto. GPU ne izvršava poslate komande dok ne dostignu
ispred reda. Ako redosled zadataka postane prazan, GPU će raditi u stanju mirovanja jer je to
nema posla; s druge strane, ako je komandni red preopterećen,
CPU će u nekom trenutku morati da radi u stanju mirovanja dok GPU uhvati. Oba ova
scenariji nedovoljno koriste hardverske resurse sistema.
9. GPU je drugi procesor u sistemu koji radi paralelno sa CPU-om.
Ponekad će CPU i GPU morati biti sinhronizovani. Na primer, ako je GPU
ima komandu u svom redosledu koji se odnosi na resurs, CPU ne sme da modifikuje ili
uništi taj resurs dok GPU ne završi s njim. Bilo koji metodi sinhronizacije
prouzrokuje da jedan od procesora čeka i da se u stanju mirovanja treba minimizirati, jer to znači da smo
mi
ne uzimajući u potpunosti prednost dva procesora.
10. Brojač performansi je tajmer visoke rezolucije koji omogućava precizno merenje vremena
merenja potrebne za merenje malih vremenskih razlika, kao što je proteklo vreme
između okvira. Tajmer za performanse radi u vremenskim jedinicama nazvanim counts. The
KueriPerformanceFrekuenci izlazi brojeve po sekundi od
tajmer za performanse, koji se onda može koristiti za pretvaranje iz jedinica brojanja na
sekunde. Trenutna vremenska vrednost tajmera performansi (merena brojem) je
dobijene pomoću funkcije KueriPerformanceCounter.
11. Da izračunamo prosečne okvire u sekundi (FPS), računamo broj okvira
obrađeni tokom nekog vremenskog intervala Dt. Neka je n broj okvira koji su prebrojani
vreme Dt, onda su prosečni ramovi u sekundi u tom vremenskom intervalu
. Stopa frejmova može dati pogrešne zaključke o učinku; vreme koje je potrebno
procesiranje okvira je informativnije. Količina vremena, u sekundama, potrošena
obrada okvira je recipročna brzina kadra, tj. 1 / fpsavg.12. Okvir uzorka se koristi da obezbedi
konzistentan interfejs koji sledi sva demoapplikacija u ovoj knjizi. Kod koji je dat u d3dUtil.h, d3dUtil.cpp,
d3dApp.h i d3dApp.cpp fajlove, obrišite standardni inicijalizacijski kod koji svaka primena mora da
implementira. Obmotavši ovaj kod, mi ga sakrivamo, što dozvoljava temama da budu više usredsređene
na demonstraciju trenutne teme.13. Za debag mod builds, omogućujemo debug sloj (debugController->
EnableDebugLaier ()). Kada je debug sloj omogućen, Direct3D će poslati poruke na VC ++ izlazni prozor.
Poglavlje 5 OBUHVATNI CIPEL
Primarna tema ovog poglavlja je rendering pipeline. S obzirom na geometrijski
opis 3D scene sa pozicioniranom i orijentisanom virtuelnom kamerom, renderingom
cevovod se odnosi na čitav niz koraka neophodnih za generiranje 2D slike na osnovu
šta vidi virtualna kamera (slika 5.1). Ovo poglavlje je uglavnom teoretsko - sledeće
poglavlje stavlja teoriju u praksu dok naučimo da crtamo sa Direct3D-om. Pre nego što počnemo
pokrivenost plinovoda za rendering, imamo dve kratke stanice: Prvo, raspravljamo o nekima
elementi 3D iluzije (tj. iluzije koju gledamo u 3D svet kroz a
flat screen 2D monitor); i drugo, objašnjavamo kako će se boje predstaviti i
radio matematički iu Direct3D kodu.
Ciljevi:
1. Otkriti nekoliko ključnih signala koji se koriste za prenošenje realnog osećaja zapremine i prostora
dubina u 2D slici.
2. Da saznate kako predstavljamo 3D objekte u Direct3D-u.
3. Da saznate kako modelujemo virtuelnu kameru.
4. Da razumete rendering pipeline-proces uzimanja geometrijskog opisa
3D scene i generisanje 2D slike iz nje.
Slika 5.1. Lijeva slika prikazuje bočni prikaz nekih objekata postavljenih u 3D
svet sa kamerom postavljenim i usmjerenim; srednja slika prikazuje istu scenu,
ali sa gledišta odozgo prema dolje. Obim "piramide" određuje zapreminu prostora koji je
gledalac može da vidi; objekti (i delovi predmeta) izvan ove zapremine nisu vidljivi.
Slika sa desne strane prikazuje 2D sliku stvorenu na osnovu onoga što kamera "vidi".
5.1 3D ILLUZIJA
Pre nego što krenemo na putovanje 3D kompjuterske grafike, ostaje jednostavno pitanje
izuzetno: Kako prikazati 3D svet sa dubinom i jačinom na ravnom 2D monitoru
ekran? Na sreću za nas, ovaj problem je dobro proučavan, kao i umetnici
slikanje 3D scena na 2D platna vekovima. U ovom odeljku izrađujemo nekoliko ključnih
tehnike koje čine sliku 3D izgledom, iako je zapravo nacrtana na 2D ravni.
Pretpostavimo da ste naišli na železničku stazu koja se ne krivi, ali ide zajedno
Prava linija za dugu distancu. Sada železničke šine ostaju paralelne jedna drugoj
sve vreme, ali ako stojite na železnici i pogledate niz put, primetite to
dve železničke šine se približavaju i približavaju se dok rastojanje od vas povećava i
na kraju se konvergiraju na beskonačnoj udaljenosti. To je jedna opservacija koja karakteriše
naš sistem gledanja ljudi: paralelne linije vida konvergiraju se na tačku nestanka; vidi
Slika 5.2.
Još jedno jednostavno posmatranje kako ljudi vide stvari jeste veličina objekta izgleda da se smanjuje sa
dubinom; to jest, objekti blizu nas izgledaju veći od objekata daleko. Na primer, kuća daleko na brdu će
izgledati vrlo mala, dok će stablo blizu nas izgledati veoma velika u poređenju. Slika 5.3 prikazuje
jednostavnu scenu gde paralelni redovi kolona postavljaju se jedan za drugim jedan za drugim. Kolone su
zapravo iste veličine, ali kako se njihove dubine povećavaju od gledatelja, postaju sve manji i manji.
Takođe primjetite kako kolone se konvergiraju na tačku nestanka na horizontu.
Slika 5.3. Ovde su svi stupci iste veličine, ali posmatrač posmatra a smanjuje veličinu u odnosu na
fenomen dubine.
Svi se osećamo preklapanjem predmeta (Slika 5.4), što se odnosi na činjenicu da je neprozirno predmete
zamračuju delove (ili sve) objekata iza njih. Ovo je važna percepcija, jer prenosi vezu za određivanje
dubine objekata u sceni. Već diskutovano (Poglavlje 4) kako Direct3D koristi dubinski bafer da bi utvrdio
koji su pikseli nejasan i zato se ne bi trebao izvlačiti.
Slika 5.4. Grupa objekata koji delimično zamagljuju jedni druge jer je jedan u njemu
ispred druge, itd. (preklapaju se).
Razmotrimo sliku 5.5. S leve strane imamo neosvetljenu sferu, a desno, imamo
osvetljena sfera. Kao što vidite, sfera sa leve strane izgleda prilično ravna - možda čak i nije
sfera, ali samo teksturalni 2D krug! Dakle, osvetljenje i senčenje igraju veoma
važnu ulogu u prikazu čvrstog oblika i obima 3D objekata.
Slika 5.5. (a) neosvetljena sfera koja izgleda 2D. (b) Sjajna sfera koja izgleda 3D.
Na kraju, na slici 5.6 prikazan je svemirski brod i njegova senka. Senka služi dva ključa
svrhe. Prvo, govori nam o poreklu izvora svetlosti u sceni. I drugo, to pruža nam grubu ideju o tome
koliko je svemirski brod visok.
Zapažanja o kojima smo upravo razgovarali, bez sumnje, intuitivno su očigledna od današnjeg dana
iskustva. Bez obzira na to, korisno je eksplicitno navesti ono što znamo i zadržati
ova opažanja imaju na umu dok proučavamo i radimo na 3D računarskoj grafici.
5.2 PREDSTAVLJANJE MODELA
Čvrsti 3D objekt predstavlja aproksimaciju mrežnog trougla i, s toga,
trouglovi čine osnovni građevinski blokovi objekata koje modelujemo. Kao što pokazuje slika 5.7,
možemo približiti bilo koji stvarni 3D objekat pomoću mrežnog trougla. Uopšte, više
trouglovi koji koristite da približite objekat, to bolje aproksimacije, kao što možete modelirati
finiji detalji. Naravno, što više trouglova koje koristimo, potrebno je više procesorske snage,
tako da se balans mora izvršiti na osnovu hardverske snage cilja aplikacije
publika. Pored trouglova, ponekad je korisno crtati linije ili tačke. Za
primer, kriva bi se grafički nacrtala sekvencom kratkih linija segmenta piksela
debeo.
Slika 5.7. (Levo) Automobil aproksiran trouglastom mrežom. (Desno) Lobanja
aproksimira mrežnom trouglom.
Veliki broj trouglova korišćenih na slici 5.7 čini jednu stvar jasnom: to bi bilo
izuzetno glomazna da ručno popisuje trouglove 3D modela. Za sve osim za
jednostavniji modeli, specijalne 3D aplikacije zvane 3D modeleri se koriste za generisanje i
manipulira 3D objektima. Ovi modeli uređaja omogućavaju korisniku da izgradi kompleksne i realistične
mreža u vizuelnom i interaktivnom okruženju sa bogatim alatom, čime se pravi
čitav proces modeliranja mnogo lakši. Primeri popularnih modelera koji se koriste za igru
razvoj su 3D Studio Mak (http://usa.autodesk.com/3ds-mak/), LightVave 3D
(https://vvv.lightvave3d.com/), Maia (http://usa.autodesk.com/maia/), Softimage | KSSI
(vvv.softimage.com) i Blender (vvv.blender.org/). (Blender ima prednost
hobistima da su otvoreni i slobodni.) Ipak, za prvi deo ove knjige mi
generišeće naše 3D modele ručno rukom ili putem matematičke formule (
Lista trouglova za cilindre i sfere, na primer, može se lako generisati
parametarske formule). U trećem delu ove knjige prikazujemo kako da učitate i prikazujete 3D
modeli izvezeni iz 3D modelarskih programa.
5.3 BASIC COMPUTER COLOR
Računarski monitori emituju mešavinu crvene, zelene i plave svetlosti kroz svaki piksel.
Kada mešavina svetlosti ulazi u oko i udari područje mrežnjače, ćelije receptora ćelije
stimulišu se i nervni impulsi se šalju kroz optički nerv prema mozgu. The
mozak tumači signal i stvara boju. Kako se mešavina svetlosti razlikuje, ćelije su
drugačije stimulisane, što zauzvrat stvara drugačiju boju u umu. Slika 5.8
prikazuje neke primjere miješanja crvene, zelene i plave boje za različite boje; to takođe pokazuje
različiti intenzitet crvene boje. Koristeći različite intenzitete za svaku komponentu boje i
mešajući ih zajedno, možemo opisati sve boje koje trebamo da prikažemo realistične slike.
Najbolji način da se olakšate opisivanje boja RGB (crvena, zelena, plava)
vrednosti su da se koristi program boja kao što je Adobe Photoshop, pa čak i Vin32
ChooseColor dijalog bok (Slika 5.9), i eksperimentirajte sa različitim RGB
kombinacije da bi videli boje koje proizvode.
5.3.1 Operacije u boji
Neke vektorske operacije se takođe primjenjuju na vektore boje. Na primer, možemo dodati boju
vektori za dobijanje novih boja:
(0,0, 0,5, 0) + (0, 0,0, 0,25) = (0,0, 0,5, 0,25)
Kombinacijom zelene boje srednjeg intenziteta sa plavom bojom niske intenziteta dobijamo a
tamnozelene boje.
Boje se takođe mogu oduzeti kako bi dobili nove boje:
(1, 1, 1) - (1, 1, 0) = (0, 0, 1)
To jest, počinjemo s bijelim i oduzeti crvene i zelene dijelove, i završimo
sa plavim bojama.
Skalarno množenje takođe ima smisla. Uzmite u obzir sledeće:
0.5 (1, 1, 1) = (0.5, 0.5, 0.5)
To jest, počinjemo sa bijelim i pomnožimo sa 0,5, a završimo s srednjom hladom
sive boje. S druge strane, operacija 2 (0,25, 0, 0) = (0,5, 0, 0) udvostručuje intenzitet
crvena komponenta.
Očigledno da izrazi poput tačnog proizvoda i unakrsnog proizvoda nemaju smisla
vektori boja. Međutim, vektori boje dobijaju vlastitu posebnu funkciju u boji
modulacija ili komplementarno množenje. Definisano je kao:
Ova operacija se uglavnom koristi u jednačinama osvetljenja. Na primer, pretpostavimo da imamo
dolazni svetlosni zrak sa bojama (r, g, b) i udara površinu koja odražava 50% crvene boje
svetlost, 75% zeleno svetlo i 25% plavo svetlo, i apsorbuje ostalo. Zatim boja boje
reflektovani svetlosni zrak daje:
Tako da možemo videti da je svetlosni zrak izgubio određeni intenzitet kada je pogodio površinu, jer je
površina je apsorbovala neku od svetlosti.
Kada radite u radu s bojom, moguće je da vaše komponente u boji napolju
[0, 1] interval; razmotri jednačinu, (1, 0,1, 0,6) + (0, 0,3, 0,5) = (1, 0,4, 1,1), za
primer. Pošto 1.0 predstavlja maksimalni intenzitet komponente boje, ne možete
postati intenzivniji od njega. Tako 1.1 je isto toliko intenzivno kao 1.0. Dakle, ono što radimo je stezanje
1.1
→ 1.0. Takođe, monitor ne može emitovati negativno svetlo, pa bilo koja negativna komponenta boja
(što bi moglo da rezultira iz operacije oduzimanja) treba da se stegne do 0.0.
5.3.2 128-bitna boja
Uobičajeno je uključiti dodatnu komponentu boje, nazvanu alfa
sastavni deo. Alfa komponenta se često koristi za označavanje prozirnosti boje, što je
korisno u mešanju (Poglavlje 10). (Pošto još ne koristimo mešanje, samo postavite alfa
komponenta do sada.) Uključujući alfa komponentu, znači da možemo predstaviti boju
4D vektor boja (r, g, b, a) gde 0 ≤ r, g, b, a ≤ 1. Da bi predstavio boju sa 128 bita,
koristimo vrednost plutajuće tačke za svaku komponentu. Zato što je matematički boja samo a
4D vektor, možemo koristiti KSMVECTOR tip da predstavimo boju u kodu, a mi dobijamo
Koristite SIMD operacije kad god koristimo funkcije DirectKSMath vektora
operacije u boji (npr. dodavanje boja, oduzimanje, skalarno množenje). Za
komplementarno množenje, DirectKs Math biblioteka nudi sledeću funkciju:
KSMVECTOR KSM_CALLCONV KSMColorModulate (// Vraća c1
⊗ c2
FKSMVECTOR C1,
FKSMVECTOR C2);
5.3.3 32-bitna boja
Za predstavljanje boje sa 32 bita, bajta se daje svakoj komponenti. Od svake boje
daje 8-bitni bajt, možemo predstaviti 256 različitih nijansi za svaku komponentu boje-0
jer nema intenziteta, 255 je pun intenziteta, a srednja vrednost je posredna
intenziteta. Bajt po komponenti boje može izgledati malog, ali kada pogledamo sve
kombinacije (256 × 256 × 256 = 16, 777, 216), možemo videti milione različitih boja
predstavljeni. Biblioteka DirectKs Math (#include <DirectKSPackedVector.h>) pruža
sledeće strukture, u DirectKs :: PackedVector imenskom prostoru, za čuvanje 32-bitnog
boja:
5.3.1 Operacije u boji
Neke vektorske operacije se takođe primjenjuju na vektore boje. Na primer, možemo dodati boju
vektori za dobijanje novih boja:
(0,0, 0,5, 0) + (0, 0,0, 0,25) = (0,0, 0,5, 0,25)
Kombinacijom zelene boje srednjeg intenziteta sa plavom bojom niske intenziteta dobijamo a
tamnozelene boje.
Boje se takođe mogu oduzeti kako bi dobili nove boje:
(1, 1, 1) - (1, 1, 0) = (0, 0, 1)
To jest, počinjemo s bijelim i oduzeti crvene i zelene dijelove, i završimo
sa plavim bojama.
Skalarno množenje takođe ima smisla. Uzmite u obzir sledeće:
0.5 (1, 1, 1) = (0.5, 0.5, 0.5)
To jest, počinjemo sa bijelim i pomnožimo sa 0,5, a završimo s srednjom hladom
sive boje. S druge strane, operacija 2 (0,25, 0, 0) = (0,5, 0, 0) udvostručuje intenzitet
crvena komponenta.
Očigledno da izrazi poput tačnog proizvoda i unakrsnog proizvoda nemaju smisla
vektori boja. Međutim, vektori boje dobijaju vlastitu posebnu funkciju u boji
modulacija ili komplementarno množenje. Definisano je kao:
Ova operacija se uglavnom koristi u jednačinama osvetljenja. Na primer, pretpostavimo da imamo
dolazni svetlosni zrak sa bojama (r, g, b) i udara površinu koja odražava 50% crvene boje
svetlost, 75% zeleno svetlo i 25% plavo svetlo, i apsorbuje ostalo. Zatim boja boje
reflektovani svetlosni zrak daje:
Tako da možemo videti da je svetlosni zrak izgubio određeni intenzitet kada je pogodio površinu, jer je
površina je apsorbovala neku od svetlosti.
Kada radite u radu s bojom, moguće je da vaše komponente u boji napolju
[0, 1] interval; razmotri jednačinu, (1, 0,1, 0,6) + (0, 0,3, 0,5) = (1, 0,4, 1,1), za
primer. Pošto 1.0 predstavlja maksimalni intenzitet komponente boje, ne možete
postati intenzivniji od njega. Tako 1.1 je isto toliko intenzivno kao 1.0. Dakle, ono što radimo je stezanje
1.1
→ 1.0. Takođe, monitor ne može emitovati negativno svetlo, pa bilo koja negativna komponenta boja
(što bi moglo da rezultira iz operacije oduzimanja) treba da se stegne do 0.0.
5.3.2 128-bitna boja
Uobičajeno je uključiti dodatnu komponentu boje, nazvanu alfa
sastavni deo. Alfa komponenta se često koristi za označavanje prozirnosti boje, što je
korisno u mešanju (Poglavlje 10). (Pošto još ne koristimo mešanje, samo postavite alfa
komponenta do sada.) Uključujući alfa komponentu, znači da možemo predstaviti boju
4D vektor boja (r, g, b, a) gde 0 ≤ r, g, b, a ≤ 1. Da bi predstavio boju sa 128 bita,
koristimo vrednost plutajuće tačke za svaku komponentu. Zato što je matematički boja samo a
4D vektor, možemo koristiti KSMVECTOR tip da predstavimo boju u kodu, a mi dobijamo
Koristite SIMD operacije kad god koristimo funkcije DirectKSMath vektora
operacije u boji (npr. dodavanje boja, oduzimanje, skalarno množenje). Za
komplementarno množenje, DirectKs Math biblioteka nudi sledeću funkciju:
KSMVECTOR KSM_CALLCONV KSMColorModulate (// Vraća c1
⊗ c2
FKSMVECTOR C1,
FKSMVECTOR C2);
5.3.3 32-bitna boja
Za predstavljanje boje sa 32 bita, bajta se daje svakoj komponenti. Od svake boje
daje 8-bitni bajt, možemo predstaviti 256 različitih nijansi za svaku komponentu boje-0
jer nema intenziteta, 255 je pun intenziteta, a srednja vrednost je posredna
intenziteta. Bajt po komponenti boje može izgledati malog, ali kada pogledamo sve
kombinacije (256 × 256 × 256 = 16, 777, 216), možemo videti milione različitih boja
predstavljeni. Biblioteka DirectKs Math (#include <DirectKSPackedVector.h>) pruža
sledeće strukture, u DirectKs :: PackedVector imenskom prostoru, za čuvanje 32-bitnog
boja:
namespace DirectX
{n
amespace PackedVector
{/
/ ARGB Color; 8-8-8-8 bit unsigned normalized integer
components packed
// into a 32 bit integer. The normalized color is
packed into 32 bits
// using 8 bit unsigned, normalized integers for the
alpha, red, green,
// and blue components.
// The alpha component is stored in the most
significant bits and the
// blue component in the least significant bits
(A8R8G8B8):
// [32] aaaaaaaa rrrrrrrr gggggggg bbbbbbbb [0]
struct XMCOLOR
{
union
{
struct
{
uint8_t b; // Blue: 0/255 to 255/255
uint8_t g; // Green: 0/255 to 255/255
uint8_t r; // Red: 0/255 to 255/255
uint8_t a; // Alpha: 0/255 to 255/255
};
uint32_t c;
};
XMCOLOR() {}
XMCOLOR(uint32_t Color) : c(Color) {}
XMCOLOR(float _r, float _g, float _b, float _a);
explicit XMCOLOR(_In_reads_(4) const float *pArray);
operator uint32_t () const { return c; }
XMCOLOR& operator= (const XMCOLOR& Color) { c =
Color.c; return *this; }
XMCOLOR& operator= (const uint32_t Color) { c =
Color; return *this; }
};
} // end PackedVector namespace
} // end DirectX namespace
32-bitna boja može se pretvoriti u 128-bitnu boju mapirajući opseg integra [0,
255] na realni interval [0, 1]. Ovo se radi deljenjem sa 255. To jest, ako je 0 ≤ n
Zatim ≤ 255 je ceo broj
daje intenzitet u normalizovanom opsegu od 0 do 1. Na primer, 32-bitna boja (80,
140, 200, 255) postaje:
Sa druge strane, 128-bitna boja može se konvertovati u 32-bitnu boju množenjem
svaku komponentu sa 255 i zaokruživanjem do najbližeg celog broja. Na primer:
Dodatne bitne operacije obično se moraju izvršiti pri konvertovanju 32-bitne boje na a
128-bitnu boju i obratno zato što su 8-bitne komponente u boji obično upakovane u a
32-bitna cijela vrijednost (npr. Nepotpisana int), kao što je u KSMCOLOR-u. DirectKSMath
biblioteka definiše sledeću funkciju koja uzima KSMCOLOR i vraća KSMVECTOR
iz njega:
KSMVECTOR KSM_CALLCONV PackedVector :: KSMLoadColor (
const KSMCOLOR * pSource);
Slika 5.10. 32-bitna boja, gde se bajt dodjeljuje za svaku komponentu boje
alfa, crvena, zelena i plava.
Slika 5.10 prikazuje kako su 8-bitne komponente boje upakovane u UINT. Napomenuti da
ovo je samo jedan način za pakovanje komponenti boje. Drugi format može biti ABGR ili
RGBA, umesto ARGB; međutim, klasa KSMCOLOR koristi ARGB raspored. The
DirectKs Math biblioteka takođe nudi funkciju pretvaranja KSMVECTOR boje na a
KSMCOLOR:
void KSM_CALLCONV PackedVector :: KSMStoreColor (
KSMCOLOR * pDestination,
FKSMVECTOR V);
Tipično se koriste 128-bitne vrijednosti boja gdje su operacije visoke preciznosti u boji
potrebno (npr. u piksel shaderu); na ovaj način, imamo puno bita tačnosti za
proračuni tako da se aritmetička greška ne akumulira previše. Konačna boja piksela,
međutim, obično se čuva u 32-bitnoj vrijednosti boje u zadnjem puferu; trenutna fizička
uređaji za prikaz ne mogu iskoristiti boju visoke rezolucije [Verth04].
5.4 PREGLED OPREMLJIVOG POTROŠNJA
S obzirom na geometrijski opis 3D scene sa pozicioniranim i orijentiranim virtuelnim
kamera, rendering pipeline se odnosi na čitav niz koraka neophodnih za generisanje
2D sliku zasnovana na onome što virtualna kamera vidi. Slika 5.11 prikazuje dijagram
faze koje čine plinovodi renderinga, kao i resursi GPU memorije na
strana. Strelica koja ide iz bazena memorije resursa na scenu znači fazu može
pristup resurse kao ulaz; na primer, faza piksel shadera možda će trebati pročitati podatke
iz resursa teksture koji se čuva u memoriji radi obavljanja njegovog rada. Strelica koja ide od a
faza u memoriju znači da pozornica piše na GPU resurse; na primer, izlazno spajanje
faza piše podatke na teksture kao što su bafer za pozadinu i bafer dubine / stencila. Obratite pažnju na
to
strelica za izlaznu fazu spajanja je dvosmerna (čita i piše GPU-u
resursa). Kao što vidimo, većina faza ne piše na GPU resurse. Umesto toga, njihov izlaz
samo se napaja kao ulaz u sledeću fazu gasovoda; na primer, faza Vertek Shader
unosi podatke iz faze Input Assembler, vrši sopstveni rad, a zatim daje rezultate
na stadionu Geometri Shader. Sledeći odeljci daju pregled svake faze
rendering pipeline.
5.5 FAZA SISTEMA INPUTA
Input asembler (IA) faza čita geometrijske podatke (vertices i indeksi) iz
memorije i koristi ga za sastavljanje geometrijskih primitiva (npr. trouglova, linija). (Indeksi su
pokriveni u kasnijoj potomci, ali ukratko, oni definišu kako treba postaviti vertikale
zajedno da formiraju primitive.)
5.5.1 Vertices
Matematički, vertikali trougla su gde se susreću dva ivica; vrhove a
linija su krajnje tačke; za jednu tačku, sama poenta je tačka. Slika 5.12
ilustrira vertikale pictorialli.
Slika 5.12. Trougao definisan trima vertikama v0, v1, v2; liniju definisana od strane
dve vertices p0, p1; tačka definisana vertek K.
Iz slike 5.12 čini se da je vertek samo posebna tačka u geometrijskoj
primitivna. Međutim, u Direct3D-u, teme su mnogo općenite od toga. U suštini, a
Vertek u Direct3D može se sastojati od dodatnih podataka pored prostorne lokacije, što nam dozvoljava
da izvode sofisticirane efekte renderinga. Na primer, u Poglavlju 8, dodaćemo
normalne vektore za naše vertikale za implementaciju osvetljenja, au Poglavlju 9, dodaćemo teksturu
koordinira naše vertikale da sprovede teksturisanje. Direct3D nam daje fleksibilnost
definišemo sopstvene formate vertek-a (tj. omogućava nam da definišemo komponente vertek-a) i
videćemo kod koji smo koristili za to u sledećem poglavlju. U ovoj knjizi ćemo definisati nekoliko
različite formate verteka zasnovane na učinku renderinga koji radimo.
5.5.2 Primitivna topologija
Vertices su vezani za rendering pipeline u posebnoj Direct3D strukturi podataka koja se zove
bafer verteka. Bušač verteka samo čuva listu vertisa u susednoj memoriji.
Međutim, ne govori kako bi ove vertikale trebalo spojiti kako bi se formiralo geometrijsko
primitivi. Na primer, ako svaka dva vertikala u baferu verteka treba tumačiti kao a
liniju ili treba li svaka tri vertikala u baferu verteka tumačiti kao trougao? Kažemo
Direct3D kako formirati geometrijske primitive iz podataka verteka navodeći
primitivna topologija:
void
ID3D12GraphicsCommandList::IASetPrimitiveTopology(
D3D_PRIMITIVE_TOPOLOGY Topology);
typedef enum D3D_PRIMITIVE_TOPOLOGY
{
D3D_PRIMITIVE_TOPOLOGY_UNDEFINED = 0,
D3D_PRIMITIVE_TOPOLOGY_POINTLIST = 1,
D3D_PRIMITIVE_TOPOLOGY_LINELIST = 2,
D3D_PRIMITIVE_TOPOLOGY_LINESTRIP = 3,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST = 4,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5,
D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = 10,
D3D_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = 11,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = 12,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ = 13,
D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST =
33,
D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST =
34,
.
.
.
D3D_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST =
64,
} D3D_PRIMITIVE_TOPOLOGY;
All subsequent drawing calls will use the currently set primitive topology until the
topology is changed via the command list. The following code illustrates:
mCommandList->IASetPrimitiveTopology(
D3D_PRIMITIVE_TOPOLOGY_LINELIST);
/* …draw objects using line list… */
mCommandList->IASetPrimitiveTopology(
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
/* …draw objects using triangle list… */
mCommandList->IASetPrimitiveTopology(
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
/* …draw objects using triangle strip… */
Vertex quad[6] = {
v0, v1, v2, // Triangle 0
v0, v2, v3, // Triangle 1
};
Vertex octagon[24] = {
v0, v1, v2, // Triangle 0
v0, v2, v3, // Triangle 1
v0, v3, v4, // Triangle 2
v0, v4, v5, // Triangle 3
v0, v5, v6, // Triangle 4
v0, v6, v7, // Triangle 5
v0, v7, v8, // Triangle 6
v0, v8, v1 // Triangle 7
};
5.5.2.1 Lista tačaka
Lista tačaka određuje D3D_PRIMITIVE_TOPOLOGI_POINTLIST. Sa
tačka, svaki vrh u pozivu izvlačenja je nacrtan kao pojedinačna tačka, kao što je prikazano na slici
5.13a.
5.5.2.2 Linijski pojas
Liniju linija određuje D3D_PRIMITIVE_TOPOLOGI_LINESTRIP. Sa linijom
traka, vertices u pozivu za prikupljanje su povezani u formu linija (vidi sliku 5.13b); tako n + 1
vertici indukuju n linije.
5.5.2.3 Lista linija
Spisak linija je D3D_PRIMITIVE_TOPOLOGI_LINELIST. Sa linijom
spisak, svaka dva vertikala u pozivu za prikupljanje formiraju individualnu liniju (pogledajte sliku 5.13c);
tako 2n
vertici indukuju n linije. Razlika između liste linija i trake je da linije u
lista linija može biti isključena, dok linija linija automatski pretpostavlja da je ona
povezan; pretpostavljajući povezanost, može se koristiti manje vertikala od svake unutrašnje verteka
se deli sa dve linije.
5.5.2.4 Traka triangle
Dijagram trake triangle je D3D_PRIMITIVE_TOPOLOGI_TRIANGLESTRIP.
Sa trakom trougla pretpostavlja se da su trouglovi spojeni kao što je prikazano na slici 5.13d do
formiraju strip. Preuzimanjem veze, vidimo da su vertikale podeljene između susednih
trouglovi i n vertices indukuju n-2 trouglove.
Obratite pažnju da redosled navoja za trokutke u trouglu traku razlikuje od
čudni trouglovi, čime se uzrokuju problemi u odbacivanju (vidi §5.10.2). Da biste rešili ovaj problem, GPU
interno zamenjuju redosled prvih dve vertikale ravnih trouglova, tako da su oni
dosledno naručiti kao čudni trouglovi.
Slika 5.13. (a) Lista tačaka; (b) linijski trak; (c) listu linija; (d) trouglasti trak.
5.5.2.5 Lista trougla
Lista trouglova određuje D3D_PRIMITIVE_TOPOLOGI_TRIANGLELIST.
Sa popisom trougla, svaka tri vertikala u pozivu za prikupljanje formiraju individualni trougao (videti
Slika 5.14a); tako da 3n vertices indukuju n triangle. Razlika između liste trouglova i
traka je da trouglovi u listi trougla mogu biti odvojeni, dok je trougao traka
pretpostavlja da su povezani.
Slika 5.14. (a) Lista trougla; (b) Lista trouglova sa susedom-posmatrajte to
svaki trougao zahtijeva 6 vertikala da ga opiše i susedne trouglove. Tako 6n
vertices indukuju n triangle sa informacijama o susednosti.
5.5.2.6 Primitivi sa susedstvom
Lista trougla sa susednošću je gde, za svaki trougao, uključite i tri
susedni trouglovi nazivi susedni trouglovi; videti sliku 5.14b da biste videli kako se to radi
trouglovi su definisani. Ovo se koristi za geometrijski shader, gde je određena geometrija
algoritama za senčenje je potreban pristup susednim trouglovima. Da bi geometrijski shader
da biste dobili te susedne trouglove, susedni trouglovi moraju biti dostavljeni na gasovod
u baferima verteka / indeksa zajedno sa samim trouglom, i
D3D_PRIMITIVE_TOPOLOGI_TRIANGLELIST_ADJ topologija mora biti precizirana
da cevovod zna kako izgraditi trougao i njene susedne trouglove iz
vertek buffer. Imajte na umu da se vertices susednih primitiva koriste samo kao ulaz u
geometrijski shader - oni nisu nacrtani. Ako ne postoji geometrijski shader, susedni
Primitivi još uvek nisu nacrtani.
Takođe je moguće imati listu linija sa susedstvom, linijskom trakom sa susedom i
trougao sa primitivnim susedstvom; pogledajte SDK dokumentaciju za detalje.
5.5.2.7 Spisak popisa kontrolne tačke
Topologija D3D_PRIMITIVE_TOPOLOGI_N_CONTROL_POINT_PATCHLIST
tip označava da se podaci verteka tumače kao patch liste sa N kontrolom
bodova. Ovi se koriste u (opcionoj) fazi tezelacije plinovoda za rendering, i
stoga ćemo odložiti raspravu o njima do Glave 14.
5.5.3 Indeksi
Kao što smo već pomenuli, trouglovi su osnovni elementi za čvrste 3D objekte. The
sledeći kod pokazuje nizova verteka koja se koriste za konstrukciju kvadrata i oktagona
liste trougla (tj., svaka tri tačke formiraju trougao).
Vertex quad[6] = {
v0, v1, v2, // Triangle 0
v0, v2, v3, // Triangle 1
};
Vertex octagon[24] = {
v0, v1, v2, // Triangle 0
v0, v2, v3, // Triangle 1
v0, v3, v4, // Triangle 2
v0, v4, v5, // Triangle 3
v0, v5, v6, // Triangle 4
v0, v6, v7, // Triangle 5
v0, v7, v8, // Triangle 6
v0, v8, v1 // Triangle 7
};
Redosled kojim odredite vertikale trougla je važan i naziva se
narudžbina; pogledati §5.10.2 za detalje.
Kao što je prikazano na slici 5.15, trouglovi koji formiraju 3D objekat dele mnoge od istih
vertices. Konkretno, svaki trougao četvrtine na slici 5.15a deli a vertikale v0
i v2. Dok kopiranje dve vertikale nije toliko loše, dupliranje je gore
primer oktagon (slika 5.15b), pošto svaki trougao duplikuje središnju vertek v0, a svaki
Vertek na perimetru osmougaone deli se sa dva trougla. Generalno, broj
duplih vertikala se povećava s obzirom na detalje i složenost modela.
Slika 5.15. (a) Kuad izgrađen od dva trougla. (b) osmogodišnjak izgrađen od osam
trouglovi.
Postoje dva razloga zašto ne želimo duplirati vertikale:
1. Povećani zahtevi za memorijom. (Zašto čuvati iste podatke verteka više od jednom?)
2. Povećana obrada grafičkim hardverom. (Zašto obrađivati iste podatke o vertici
Više od jedanput?)
Trougao trake mogu pomoći duplicirati problem verteka u nekim situacijama, pod uslovom
geometrija se može organizovati u stripu poput mode. Međutim, lista trouglova su fleksibilnija
(trouglovi ne moraju biti povezani), pa je tako vredno napraviti metod za uklanjanje
duplicate vertices za liste trouglova. Rešenje je korištenje indeksa. Radi ovako: Mi
kreirajte listu verteka i listu indeksa. Lista verteka sastoji se od svih jedinstvenih vertikala i
lista indeksa sadrži vrijednosti koje indeksiraju u listu verteka kako bi definisale kako su vertices
biti sastavljeni da formiraju trouglove. Vraćajući se na oblike na slici 5.15, lista verteka
kuad će biti izgrađen na sledeći način:
Vertek v [4] = {v0, v1, v2, v3};
Tada indeksna lista mora definisati kako treba postaviti vertices u listi verteka
zajedno da formiraju dva trougla.
UINT indekList [6] = {0, 1, 2, // Triangle 0
0, 2, 3}; // Trougao 1
Na listi indeksa, svaka tri elementa definišu trougao. Dakle, spomenuta lista indeksa kaže,
"Formira trougao 0 koristeći vertices v [0], v [1], i v [2], i formira trougao 1
koristeći vertices v [0], v [2] i v [3]. "
Slično tome, lista verteka za krug bi se konstruisala na sledeći način:
Vertek v [9] = {v0, v1, v2, v3, v4, v5, v6, v7, v8};
a lista indeksa bi bila:
UINT indexList[24] = {
0, 1, 2, // Triangle 0
0, 2, 3, // Triangle 1
0, 3, 4, // Triangle 2
0, 4, 5, // Triangle 3
0, 5, 6, // Triangle 4
0, 6, 7, // Triangle 5
0, 7, 8, // Triangle 6
0, 8, 1 // Triangle 7
};
Kada se obradjuju jedinstvene vertikale na listi verteka, grafička kartica može koristiti
indeksnu listu da stavite vertikale zajedno da formiraju trouglove. Obratite pažnju da smo se preselili
"dupliranje" na indeksnu listu, ali ovo nije loše od:
1. Indeksi su jednostavno integrali i ne uzimaju toliko memorije kao potpunu vertek
struktura (i vertek strukture mogu postati velike jer dodamo još više komponenata).
2. Sa dobrim porudžbanjem keš memorije, grafički hardver neće morati da obrađuje
duplirane vertikale (previše često).
5.6 VERTEKS SHADER STAGE
Nakon prikupljanja primitiva, vertices se uvode u vertek shader
faza. Verovatni shader se može smatrati funkcijom koja ulazi u tačku i izlazi a
vertek. Svaka narezana verzija će se prebacivati kroz vertek shader; u stvari, možemo
konceptualno razmislite o sledećem događaju na hardveru:
za (UINT i = 0; i <numVertices; ++ i)
outputVertek [i] = VertekShader (inputVertek [i]);
Funkcija vertek-shadera je nešto što implementiramo, ali ga izvršava GPU
za svaki verteks, tako da je vrlo brzo.
Mnogi specijalni efekti se mogu učiniti u vertek shaderu kao što su transformacije,
osvetljenje i mapiranje pomaka. Zapamtite da ne samo da imamo pristup
ulazne podatke verteka, ali takođe možemo pristupiti teksturima i drugim podacima sačuvanim u GPU
memoriji
kao što su matrice transformacije i scene.
U ovoj knjizi ćemo videti mnoge primere različitih vertek shadera; tako da
na kraju, trebalo bi da imate dobru ideju o tome šta se može uraditi s njima. Za naš prvi kod
primer, međutim, samo ćemo koristiti vertek shader da transformišemo vertikale. Sledeći
Podsekcije objašnjavaju vrste transformacija koje se obično trebaju učiniti.
5.6.1 Lokalni prostor i svetski prostor
Pretpostavimo da na trenutak radite na filmu i vaš tim mora da konstruiše
minijaturna verzija scene voza za neke specijalne efekte. Konkretno, pretpostavimo to
imate zadatak da napravite mali most. Sada, ne bi izgradio most u
sredinom scene, gde biste verovatno morali da radite sa teškim uglom i budite
pazite da ne zabrljite druge miniaturu koja sastavlja scenu. Umesto toga, ti bi
radite na mostu na vašem radnom stazu daleko od scene. Onda kada je sve urađeno, ti
postaviti most na ispravan položaj i ugao u sceni.
3D umetnici rade nešto slično kada konstruišu 3D objekte. Umjesto izgradnje
geometrija objekta sa koordinatama u odnosu na globalni koordinatni sistem scene (svet
prostor), oni ih određuju u odnosu na lokalni koordinatni sistem (lokalni prostor); lokalna
koordinatni sistem će obično biti neki pogodan koordinatni sistem koji se nalazi blizu
objekat i osa poravnat sa objektom. Kada su vertikali 3D modela bili
definisana u lokalnom prostoru, postavljena je na globalnoj sceni. Da bismo to uradili, moramo da
definišemo
kako su povezani lokalni prostor i svetski prostor; ovo se vrši tako što se precizira gde želimo
poreklo i osi lokalnog koordinatnog sistema u odnosu na globalnu scenu
koordinatni sistem i izvršavanje promene koordinatne transformacije (pogledajte sliku 5.16
i podsjetiti §3.4). Proces promjene koordinata u odnosu na lokalni koordinatni sistem
u globalnu scenu koordinatni sistem naziva se transformacija sveta, i
odgovarajuća matrica se zove svetska matrica. Svaki objekat u sceni ima svoje
svetska matrica. Nakon što se svaki objekt transformiše iz svog lokalnog prostora u svet
prostor, onda su sve koordinate svih objekata relativne prema istom koordinatnom sistemu
(svetski prostor). Ako želite definirati objekat direktno u svetskom prostoru, onda možete
snabdevanje matrice identiteta identiteta.
Slika 5.16. (a) Točke svakog objekta definišu se sa relativnim koordinatama
u svoj lokalni koordinatni sistem. Pored toga, definišemo poziciju i
orijentacija svakog lokalnog koordinatnog sistema u odnosu na svetsku koordinatnu površinu
sistem zasnovan na tome gdje želimo objekat u sceni. Zatim izvršimo promenu
koordinatnu transformaciju kako bi se sve koordinate u odnosu na svetski svemirski sistem.
(b) Posle svetske transformacije, vertices objekata imaju koordinate sve u odnosu na
isti svetski sistem.
Slika 5.17. Točke kocke se lako određuju kada je kocka
usredsređen na poreklo i poravnato sa koordinatnim sistemom. To nije tako lako
odredite koordinate kada je kubica na proizvoljnoj poziciji i orijentaciji sa
poštovanje koordinatnog sistema. Stoga, kada konstruišemo geometriju an
objekat, obično uvek izaberemo odgovarajući koordinatni sistem u blizini objekta i
u skladu sa objektom, odakle će se objekat izgraditi.
Definisanje svakog modela u odnosu na sopstveni lokalni koordinatni sistem ima nekoliko
prednosti:
1. Lakše je. Na primjer, obično u lokalnom prostoru objekt će biti usredsređen na
porekla i simetrična u odnosu na jednu od glavnih ose. Kao još jedan primjer,
vrhove kocke mnogo je lakše odrediti ako izaberemo lokalni koordinatni sistem sa poreklom
centriranom na kocki i sa osama koje su ortogonalne prema kockastim licima, videti sliku 5.17.2. Objekt
se može ponovo koristiti u više scena, u kom slučaju nema smisla dodati koordinate objekta u odnosu na
određenu scenu. Umesto toga, bolje je postaviti svoje koordinate u odnosu na lokalni koordinatni
sistem, a zatim definisati, putem izmjene koordinatne matrice, kako su lokalni koordinatni sistem i
svetski koordinatni sistem povezani za svaku scenu. Konačno, ponekad istu stvar vadimo više puta u
sceni, ali indiferentne pozicije, orijentacije i skale (npr., Objekt stabla može se ponoviti nekoliko puta za
izgradnju šume). Bilo bi rasipno da se dupliraju objekti i vertek i indeksni podaci objekta za svaku
instancu. Umesto toga, čuvamo jednu kopiju geometrije (tj., Vertek i indeksne liste) u odnosu na svoj
lokalni prostor. Zatim crtamo objekat nekoliko puta, ali svaki put sa drugom svetskom matricom da
odredimo položaj, orijentaciju i skalu instance u svetskom prostoru. Ovo se zove instancing.
Kao što se vidi u §3.4.3, svetska matrica za objekat se daje opisom njegovog lokalnog prostora
sa koordinatama u odnosu na svetski prostor i postavljanjem ovih koordinata u redove a
matrica. Ako je Kv = (Kk, Ki, Kz, 1), uv = (uk, ui, uz, 0), vv = (vk, vi, vz, 0), i vv = (vk, vi,
vz, 0) opisuju, po pravilu, poreklo, k-, i- i z-ose lokalnog prostora sa
homogene koordinate u odnosu na svetski prostor, onda znamo iz §3.4.3 da je
Promena koordinatne matrice od lokalnog prostora do svetskog prostora je:
To vidimo da bi se napravila svetska matrica, moramo direktno shvatiti koordinate
lokalnog prostora i osi u odnosu na svetski prostor. Ovo ponekad nije tako lako
ili intuitivno. Često je pristup V je definisan kao niz transformacija,
recimo V = SRT, proizvod matrice S za skaliranje S da skali objekat u svet,
praćena matricom rotacije R za definisanje orijentacije lokalnog prostora u odnosu na
svetski prostor, a zatim i matrica prevođenja T da bi se definisalo poreklo lokalnog prostora
u odnosu na svetski prostor. Od §3.5, znamo da ovaj niz transformacija može
može se tumačiti kao promjena koordinatne transformacije i da vektori redova V =
SRT čuva homogene koordinate k-ose, i-osa, z-ose i poreklo lokalnih
prostora u odnosu na svetski prostor.
5.6.2 Pregled prostora
Kako bismo formirali 2D sliku scene, moramo postaviti virtuelnu kameru na scenu.
Kamera određuje koju količinu sveta gledalac može videti i na taj način kakav volumen
sveta treba da generišemo 2D sliku. Prikačimo lokalni koordinatni sistem
(nazvani prostor za razmak, prostor oko ili prostor za fotoaparat) na fotoaparat kao što je prikazano na
slici 5.19;
to jest, fotoaparat sedi u porijeklu gledajući niz pozitivnu z-osu, k-osa ima za cilj
desno od kamere, a i-osa ima za cilj iznad kamere. Umesto da opisujemo našu scenu
tačaka u odnosu na svetski prostor, pogodna je za kasnije faze renderinga
cevovod da ih opiše u odnosu na koordinatni sistem fotoaparata. Promena
koordinatna transformacija iz svetskog prostora da se vidi prostor naziva se transformacija gledišta,
a odgovarajuća matrica se zove matrica prikaza.
Slika 5.19. Konvertovati koordinate vertikala u odnosu na svetski prostor
napravite ih u odnosu na prostor fotoaparata.
Ako je KV = (Kk, Ki, Kz, 1), uV = (uk, ui, uz, 0), vV = (vk, vi, vz, 0), i vV = (vk, vi,
vz, 0) opisuju, zapravo, poreklo, k-, i- i z-ose prostora za prikaz sa
homogene koordinate u odnosu na svetski prostor, onda znamo iz §3.4.3 da je
promena koordinatne matrice iz prostora za gledanje na svetski prostor je:
Međutim, ovo nije transformacija koju želimo. Želimo reversnu transformaciju
iz svetskog prostora da vidi prostor. Ali, recite se iz §3.4.5 da je obrnuta transformacija pravedna
dati inverznim. Tako se V-1 transformiše iz svetskog prostora da vidi prostor.
Svetski koordinatni sistem i pogled koordinatnog sistema obično se razlikuju po položaju
i orijentacija, tako da čini intuitivan osećaj da je V = RT (tj., svetska matrica može biti
raspadnuti u rotaciju, nakon čega sledi prevod). Ovaj oblik olakšava obrnuto
izračunati:
Matrica prikaza ima oblik:
Sada pokazujemo intuitivan način za konstrukciju vektora potrebnih za izgradnju pogleda
matrica. Neka K bude pozicija kamere i neka T bude ciljna tačka kamere
naciljano ka. Pored toga, pustite j jedinični vektor koji opisuje smer "gore"
svetski prostor. (U ovoj knjizi koristimo svetski kz-plane kao naš svetski "zemaljski avion" i
svetska i-osa opisuje smer "gore"; stoga, j = (0, 1, 0) je samo jedinični vektor
paralelno sa svetskom i osom. Međutim, ovo je samo konvencija, a neke aplikacije
može izabrati ki-ravninu kao zemlju, a z-ose kao "up" pravac.)
Pozivajući se na sliku 5.20, smjer koji kamera gleda daje:
Slika 5.20. Izgradnja koordinatnog sistema fotoaparata uz kameru
poziciju, ciljnu tačku i svetski "up" vektor.
Ovaj vektor opisuje lokalnu z-os kamere. Jedinični vektor koji ima za cilj
"Desno" od v daje:
Ovaj vektor opisuje lokalnu k-osu kamere. Na kraju, vektor koji opisuje
lokalna i osa kamere daje:
v=v×u
Pošto su v i u ortogonalni vektori jedinice, v × u je nužno jedinični vektor, i tako
nije potrebno normalizovati.
Stoga, s obzirom na poziciju kamere, ciljnu tačku i svetski "up" smer,
uspeli smo da izvedemo lokalni koordinatni sistem kamere, za koji se može koristiti
formira matricu prikaza.
Biblioteka DirectKSMath pruža sledeću funkciju za izračunavanje prikaza
matrica zasnovana na upravo opisanom procesu:
KSMMATRIKS KSM_CALLCONV KSMMatrikLookAtLH (// Prikaz izlaza
matrica V
FKSMVECTOR EiePosition, // Pozicija ulazne kamere K
FKSMVECTOR FocusPosition, // Ulazna ciljna tačka T
FKSMVECTOR UpDirection); // Input svet up up j
Obično svetska i-osa odgovara smeru "gore", tako da je "up" vektor
obično uvek j = (0, 1, 0). Kao primer, pretpostavimo da želimo da postavimo kameru na
tačku (5, 3, -10) u odnosu na svetski prostor, a kameru pogledajte na početak
svet (0, 0, 0). Mi možemo napraviti matricu prikaza pisanjem:
KSMVECTOR pos = KSMVectorSet (5, 3, -10, 1.0f);
KSMVECTOR target = KSMVectorZero ();
KSMVECTOR up = KSMVectorSet (0,0f, 1,0f, 0,0f, 0,0f);
KSMMATRIKS V = KSMMatrikLookAtLH (pos, cilj, gore);
5.6.3 Projekcija i homogeni prostor
Do sada smo opisali poziciju i orijentaciju kamere na svetu, ali
postoji još jedna komponenta za kameru, što je zapremina prostora koji kamera vidi.
Ovaj volumen je opisan frustumom (Slika 5.21).
5.6.2 Pregled prostora
Kako bismo formirali 2D sliku scene, moramo postaviti virtuelnu kameru na scenu.
Kamera određuje koju količinu svetskog gledatelja može videti i na taj način kakav je volumen
sveta treba da generišemo 2D sliku. Prikačimo lokalni koordinatni sistem
(nazvan prostor za razmak, prostor oko ili prostor za fotoaparat) na fotoaparatu kao što je prikazano na
slici 5.19;
da jest, fotoaparat sedi u porijeklu gledajući niz pozitivne z-osu, k-osa ima za cilj
desno od kamere, a i-osa ima za cilj iznad kamere. Umesto da opisujemo našu scenu
tačaka u odnosu na svetski prostor, pogodna je za kasnije renderinge
cevovod da ih opiše u odnosu na koordinatni sistem fotoaparata. Promena
koordinatna transformacija iz svetskog prostora da se vidi prostor naziva se transformacija gledišta,
odgovarajuća matrica se zove matrica prikaza.
Slika 5.19. Konvertovati koordinate vertikale u odnosu na svetski prostor
napravite ih u odnosu na prostor fotoaparata.
Ako je KV = (Kk, Ki, Kz, 1), uV = (uk, ui, uz, 0), vV = (vk, vi, vz, 0), i vV = (vk,
vz, 0) opisuju, zapravo, poreklo, k-, i- i z-ose prostora za prikaz sa
homogena koordinata u odnosu na svetski prostor, onda znamo iz §3.4.3 da je
promena koordinatne matrice iz prostora za gledanje na svetski prostor je:
Međutim, ovo nije transformacija koju želimo. Želimo reversnu transformaciju
iz svetskog prostora da vidi prostor. Ali, recite se iz §3.4.5 da je obrnuta transformacija pravedna
dati inverznim. Tako se V-1 transformiše iz svetskog prostora da vidi prostor.
Svetski koordinatni sistem i pogled koordinatnog sistema obično se razlikuju po položaju
i orijentacija, tako da čini intuitivan osećaj da je V = RT (tj., svetska matrica može biti
raspadnuti u rotaciju, nakon čega sledi prevod). Ovaj oblik olakšava obrnuto
izračunati:
Matrica prikaza ima oblik:
Sada pokazujemo intuitivan način za konstrukciju vektora potrebnih za izgradnju pogleda
matrica. Neka K bude pozicija kamere i neka T bude ciljna tačka kamere
naciljano ka. Pored toga, pustite j jedinični vektor koji opisuje smer "gore"
svetski prostor. (U ovoj knjizi koristimo svetski kz-plane kao naš svetski "zemaljski avion" i
svetska i-osa opisuje smer "gore"; stoga, j = (0, 1, 0) je samo jedinični vektor
paralelno sa svetskim i osom. Međutim, ovo je samo konvencija, a neke aplikacije
može izabrati ki-ravninu kao zemlju, a z-ose kao "up" pravac.)
Pozivajući se na sliku 5.20, smjer koji kamera gleda daje:
Slika 5.20. Izgradnja koordinatnog sistema fotoaparata uz kameru
poziciju, ciljnu tačku i svetski "up" vektor.
Ovaj vektor opisuje lokalne z-os kamere. Jedinični vektor koji ima za cilj
"Desno" od v daje:
Ovaj vektor opisuje lokalne k-osu kamere. Na kraju, vektor koji opisuje
lokalna i osa kamere daje:
v=v×u
Pošto su v i ortogonalni vektori jedinice, v × u je neophodan jedinični vektor, i tako
nije potrebno normalizovati.
Stoga, s obzirom na poziciju kamere, ciljnu tačku i svetsku "up" smer,
uspeli smo da izvedemo lokalni koordinatni sistem kamere, za koji se može koristiti
formira matricu prikaza.
Biblioteka DirectKSMath pruža sledeću funkciju za izračunavanje prikaza
matrica zasnovana na upravo opisanom procesu:
KSMMATRIKS KSM_CALLCONV KSMMatrikLookAtLH (// Prikaz izlaza
matrica V
FKSMVECTOR EiePosition, // Pozicija ulazne kamere K
FKSMVECTOR FocusPosition, // Ulazna ciljna tačka T
FKSMVECTOR UpDirection); // Input svet up up j
Obično svetska i-osa odgovara smeru "gore", tako da je "up" vektor
obično uvek j = (0, 1, 0). Kao primer, pretpostavimo da želimo postaviti kameru na
tačku (5, 3, -10) u odnosu na svetski prostor, a kamera pogledajte na početak
svet (0, 0, 0). Mi možemo napraviti matricu prikaza pisanjem:
KSMVECTOR pos = KSMVectorSet (5, 3, -10, 1.0f);
KSMVECTOR target = KSMVectorZero ();
KSMVECTOR up = KSMVectorSet (0,0f, 1,0f, 0,0f, 0,0f);
KSMMATRIKS V = KSMMatrikLookAtLH (pos, cilj, gore);
5.6.3. Projekcija i homogeni prostor
Do sada smo opisali poziciju i orijentaciju kamere na svetu, ali
postoji još jedna komponenta za kameru, što je prostor u kojem se kamera vidi.
Ovaj volumen je opisan frustumom (Slika 5.21).
Naš sljedeći zadatak je da projektujemo 3D geometriju unutar frustuma na 2D projekciju
prozor. Projekcija se mora izvršiti na takav način da se paralelne linije konvergiraju na a
tačka nestajanja, a kako se 3D dubina objekta povećava, veličina njegove projekcije
smanjuje; ovo je perspektivna projekcija i ilustrovana je na slici 5.22. Mi zovemo
linija od vrha do oka pokazuje liniju projekcije vertek-a. Zatim definišemo
perspektivna transformacija transformacije kao transformacija koja transformiše 3D vertek v
do tačke v 'gde se njegova linija projekcije preseca 2D projekciona ravnina; mi kažemo da v '
je projekcija v. Projekcija 3D objekta odnosi se na projekciju svih
tačke koje čine predmet.
Slika 5.22. Oba cilindra u 3D prostoru su iste veličine, ali su postavljena na
različite dubine. Projekcija cilindra bliže oku je veća od
projekcija daljeg cilindra. Geometrija unutar frustuma je projektovana na a
Projection prozor; geometrija van frustuma, projektuje se na projekciju
ali će ležati izvan projekcije projekcija.
5.6.3.1 Definisanje frustuma
Možemo definisati frustum u prostoru pogleda, sa centrom projekcije na početku i
gledajući niz pozitivnu z-osu, sledeće četiri veličine: blizu ravnice n, daleko ravno
f, vertikalnog ugla gledanja a, i razmera r. Imajte na umu da u pogledu prostora, blizu ravni
i dalja ravnina su paralelna sa ki-ravninom; tako da jednostavno odredimo njihovu distancu od
poreklo duž z-ose. Odnos prosjeka je definisan r = v / h gdje je v širina
projekcioni prozor i h je visina projekcionog prozora (jedinice u prostoru pogleda). The
projekcioni prozor je u suštini 2D slika scene u prostoru pogleda. Slika ovde
na kraju će biti preslikani u bafer za pozadinu; Stoga nam se sviđa odnos projekcije
dimenzije prozora su iste kao odnos dimenzija zadnjeg pufera. Dakle, odnos
dimenzija zadnjeg pufera obično se naznačava kao odnos proporcija (to je odnos koji ima
nema jedinica). Na primer, ako su dimenzije zadnjeg pufera 800 × 600, onda ćemo odrediti
. Ako razmera prozora projekcionog prozora i zadnjeg pufera nisu iste, onda a
nejednako skaliranje bi bilo neophodno za mapiranje projekcionog prozora na zadnje pufer,
što bi izazvalo izobličenje (npr., krug na projekcionom prozoru može se proširiti
u elipse kada se preslikava na zadnje pufer).
Mi označavamo horizontalni ugao gledanja b, a to je određeno vertikalnim poljem
ugla vidljivosti a i razmera r. Da vidimo kako nam r pomaže da nađemo b, pogledajte Sliku 5.23.
Imajte na umu da stvarne dimenzije projekcionog prozora nisu bitne, samo aspekt
treba zadržati odnos. Zbog toga ćemo izabrati pogodnu visinu od 2, i
dakle širina mora biti:
Slika 5.23. Izvođenje horizontalnog ugla gledanja b s obzirom na vertikalno polje
ugla gledanja a i razmera r.
Da bi se odredilo vertikalno vidno polje a, prozor projekcije mora biti
postavio rastojanje d od porekla:
Sada smo fiksirali rastojanje d prozora za projekciju duž z-ose kako bi imali a
vertikalno vidno polje a kada je visina projekcionog prozora 2. Sada možemo da rešimo
za b. Gledajući kz-ravninu na slici 5.23, sada vidimo da:
Dakle, s obzirom na ugao vertikalnog vidnog polja a i razmeru r, uvek možemo dobiti
horizontalni ugao vidnog polja b:
Slika 5.24. Slični trouglovi.
5.6.3.2 Projekat Vertices
Pogledajte sliku 5.24. S obzirom na tačku (k, i, z), želimo da nađemo svoju projekciju (k ', i', d)
projekciona ravnina z = d. Razmatrajući k- i i-koordinate odvojeno i koristeći
slični trouglovi, nalazimo:
5.6.3.3 Normalizovane koordinate uređaja (NDC)
Koordinate projektovanih tačaka u prethodnom odeljku izračunavaju se u vidu
prostor. U pogledu prostora, projekcioni prozor ima visinu od 2 i širinu 2r, gde je r
aspect ratio. Problem je u tome što dimenzije zavise od odnosa aspekta.
To znači da ćemo morati da kažemo hardveru odnos proporcije, pošto hardver to želi
kasnije treba obaviti neke operacije koje uključuju dimenzije projekcije projekcija
(na primer, mapira ga u zadnje pufer). Bilo bi pogodnije ako bismo to mogli ukloniti
zavisnost od aspekta. Rešenje je da skenira projektovana k-koordinata od
interval [-r, r] do [-1, 1] takav:
Nakon ove mapiranja, za k- i i-koordinate se kaže da su normalni uređaj
koordinate (NDC) (z-koordinata još uvek nije normalizovana), a tačka (k, i, z) je
unutar frustuma ako i samo ako
Transformacija iz prostora za prikaz u NDC prostor se može posmatrati kao pretvaranje u jedinici.
Imamo odnos da jedna jedinica NDC jednaka r jedinicama u prostoru pogleda (tj. 1ndc = r vs)
na k-osi. Dakle dati k pogledati prostorne jedinice, možemo koristiti ovaj odnos da pretvorimo jedinice:
Mi možemo modifikovati naše formule projekcije da bismo dobili projektovane k- i i-koordinate
direktno u NDC koordinate:
Imajte na umu da u koordinatama NDC projekcioni prozor ima visinu od 2 i širinu
2. Dakle, sada su dimenzije fiksne, a hardver ne treba da zna razmeru, ali
naša je odgovornost da uvek isporučujemo projektovane koordinate u NDC prostoru (
grafički hardver pretpostavlja da ćemo).
5.6.3.4 Pisanje projekcionih jednačina sa matricom
Za uniformnost želimo da izrazimo transformaciju projekcije pomoću matrice.
Međutim, jednačina 5.1 je nelinearna, tako da ona nema matričnu reprezentaciju. The
"Trik" je da ga odvojite na dva dela: linearni deo i nelinearni deo. Nelinearni deo
je podela z. Kao što ćemo razmotriti u sledećem odeljku, mi ćemo normalizovati
z-koordinata; ovo znači da nećemo imati originalni z-koordinat za podelu.
Zbog toga moramo da sačuvamo ulaznu z-koordinatu pre nego što se transformiše; da uradimo ovo,
uzimamo
prednost homogenih koordinata i kopiranje ulazne z-koordinate na izlaznu koordinatu.
U smislu množenja matrica, ovo se vrši podešavanjem unosa [2] [3] = 1 i
unos [3] [3] = 0 (nulti indeksi). Naša projekciona matrica izgleda ovako:
Imajte na umu da smo postavili konstante (koje treba odrediti u sledećem odjeljku) A i B
matrica; ove konstante će se koristiti za transformaciju ulazne z-koordinate u
normalizovan opseg. Multiplikacijom proizvoljne tačke (k, i, z, 1) pomoću ove matrice daje:
Nakon što se množi po projekcionoj matrici (linearni deo), popunjavamo
transformacija deljenjem svake koordinate pomoću v = z (nelinearni deo):
Usput, možda se pitate o mogućem razdvajanju po nuli; međutim, blizu ravni
treba da bude veća od nule, tako da bi takva tačka bila obeležena (§5.9). Divizija od v je
ponekad se naziva podjela perspektive ili homogena podjela. Vidimo to projektovano
k- i i-koordinate se slažu sa jednačinom 5.1.
5.6.3.5 Normirana vrednost dubine
Može izgledati kao nakon projekcije, možemo odbaciti prvobitnu 3D z-koordinatu, kao i sve
projicirane tačke sada leže na prozoru 2D projekcije, koji formira 2D sliku
vidio je oko. Međutim, i dalje nam je potrebna 3D informacija o dubini za dubinu
buffering algoritam. Baš kao što Direct3D želi projektovane k- i i-koordinate u a
Normalni opseg, Direct3D želi koordinate dubine u normalizovanom opsegu [0, 1].
Zbog toga moramo konstruisati funkciju očuvanja narudžbine g (z) koja mapira interval [n, f]
na [0, 1]. Pošto je funkcija očuvanje naloga, ako z1, z2 ∈ [n, f] i z1 <z2, onda je g (z1)
<g (z2); tako da iako su vrednosti dubine transformisane, relativna dubina
odnosi ostaju netaknuti, tako da i dalje možemo pravilno upoređivati dubine u normalizovanom
interval, što je sve što nam je potrebno za algoritam puferovanja dubine.
5.6.3.3 Normalizovane koordinate uređaja (NDC)
Koordinate projektovanih tačaka u prethodnom odeljku izračunavaju se u vidu
prostor. U pogledu prostora, projekcioni prozor ima visinu od 2 i širinu 2r, gde je r
aspect ratio. Problem je u tome što dimenzije zavise od odnosa aspekta.
To znači da ćemo morati da kažemo hardveru odnos proporcije, pošto hardver to želi
kasnije treba obaviti neke operacije koje uključuju dimenzije projekcije projekcija
(na primer, mapira ga u zadnje pufer). Bilo bi pogodnije ako bismo to mogli ukloniti
zavisnost od aspekta. Rešenje je da skenira projektovana k-koordinata od
interval [-r, r] do [-1, 1] takav:
Nakon ove mapiranja, za k- i i-koordinate se kaže da su normalni uređaj
koordinate (NDC) (z-koordinata još uvek nije normalizovana), a tačka (k, i, z) je
unutar frustuma ako i samo ako
Transformacija iz prostora za prikaz u NDC prostor se može posmatrati kao pretvaranje u jedinici.
Imamo odnos da jedna jedinica NDC jednaka r jedinicama u prostoru pogleda (tj. 1ndc = r vs)
na k-osi. Dakle dati k pogledati prostorne jedinice, možemo koristiti ovaj odnos da pretvorimo jedinice:
Mi možemo modifikovati naše formule projekcije da bismo dobili projektovane k- i i-koordinate
direktno u NDC koordinate:
Imajte na umu da u koordinatama NDC projekcioni prozor ima visinu od 2 i širinu
2. Dakle, sada su dimenzije fiksne, a hardver ne treba da zna razmeru, ali
naša je odgovornost da uvek isporučujemo projektovane koordinate u NDC prostoru (
grafički hardver pretpostavlja da ćemo).
5.6.3.4 Pisanje projekcionih jednačina sa matricom
Za uniformnost želimo da izrazimo transformaciju projekcije pomoću matrice.
Međutim, jednačina 5.1 je nelinearna, tako da ona nema matričnu reprezentaciju. The
"Trik" je da ga odvojite na dva dela: linearni deo i nelinearni deo. Nelinearni deo
je podela z. Kao što ćemo razmotriti u sledećem odeljku, mi ćemo normalizovati
z-koordinata; ovo znači da nećemo imati originalni z-koordinat za podelu.
Zbog toga moramo da sačuvamo ulaznu z-koordinatu pre nego što se transformiše; da uradimo ovo,
uzimamo
prednost homogenih koordinata i kopiranje ulazne z-koordinate na izlaznu koordinatu.
U smislu množenja matrica, ovo se vrši podešavanjem unosa [2] [3] = 1 i
unos [3] [3] = 0 (nulti indeksi). Naša projekciona matrica izgleda ovako:
Imajte na umu da smo postavili konstante (koje treba odrediti u sledećem odjeljku) A i B
matrica; ove konstante će se koristiti za transformaciju ulazne z-koordinate u
normalizovan opseg. Multiplikacijom proizvoljne tačke (k, i, z, 1) pomoću ove matrice daje:
Nakon što se množi po projekcionoj matrici (linearni deo), popunjavamo
transformacija deljenjem svake koordinate pomoću v = z (nelinearni deo):
Usput, možda se pitate o mogućem razdvajanju po nuli; međutim, blizu ravni
treba da bude veća od nule, tako da bi takva tačka bila obeležena (§5.9). Divizija od v je
ponekad se naziva podjela perspektive ili homogena podjela. Vidimo to projektovano
k- i i-koordinate se slažu sa jednačinom 5.1.
5.6.3.5 Normirana vrednost dubine
Može izgledati kao nakon projekcije, možemo odbaciti prvobitnu 3D z-koordinatu, kao i sve
projicirane tačke sada leže na prozoru 2D projekcije, koji formira 2D sliku
vidio je oko. Međutim, i dalje nam je potrebna 3D informacija o dubini za dubinu
buffering algoritam. Baš kao što Direct3D želi projektovane k- i i-koordinate u a
Normalni opseg, Direct3D želi koordinate dubine u normalizovanom opsegu [0, 1].
Zbog toga moramo konstruisati funkciju očuvanja narudžbine g (z) koja mapira interval [n, f]
na [0, 1]. Pošto je funkcija očuvanje naloga, ako z1, z2 ∈ [n, f] i z1 <z2, onda je g (z1)
<g (z2); tako da iako su vrednosti dubine transformisane, relativna dubina
odnosi ostaju netaknuti, tako da i dalje možemo pravilno upoređivati dubine u normalizovanom
interval, što je sve što nam je potrebno za algoritam puferovanja dubine.
5.6.3.6 KSMMatrikPerspectiveFovLH
Matrica projekcionih perspektiva može se izgraditi s sledećim DirectKs Math
funkcija:
// Vrati matricu projekcije
KSMMATRIKS KSM_CALLCONV KSMMatrikPerspectiveFovLH (
float FovAngleI, // vertikalno polje ugla gledanja
u radijancima
float Aspect, // razmer razmera = širina / visina
float NearZ, // rastojanje u blizini ravni
float FarZ); // Udaljenost od daleke ravni
Sledeći fragment koda ilustruje kako koristiti KSMMatrikPerspectiveFovLH.
Ovdje odredimo 45 ° vertikalno vidno polje, blizu ravnice na z = 1 i daleko ravno na z =
1000 (ove dužine su u prostoru pogleda).
KSMMATRIKS P = KSMMatrikPerspectiveFovLH (0,25f * KSM_PI,
AspectRatio (), 1.0f, 1000.0f);
Odnos slike je prilagođen našim prozorskim odnosima:
float D3DApp :: AspectRatio () const
{
return static_cast <float> (mClientVidth) /
mClientHeight;
}
5.7 FUNKCIJE TESTIRANJA
Tessellation se odnosi na podelu trouglova mreže da bi se dodali novi trouglovi. Ove
Novi trouglovi se zatim mogu prebaciti na nove pozicije kako bi se postigli finiji detalji mreže (vidi Sliku
5.26).
Slika 5.26. Lijeva slika prikazuje originalnu mrežu. Prava slika pokazuje
mreža posle tezelacije.
Postoji niz pogodnosti za teselacije:
1. Možemo primeniti mehanizam nivoa detalja (LOD), gde trouglovi u blizini
kamera je dodata još detalja, a trouglovi su daleko od fotoaparata
nije teksiran. Na ovaj način koristimo samo više trouglova gde će biti dodatni detalji
Primetio.
2. Zadržavamo jednostavnije niske poli mreže (nizak poli znači nizak broj trouglova) u memoriji,
i dodati dodatne trouglove na mušu, čime se uštede memorija.
3. Mi radimo operacije poput animacije i fizike na jednostavnijoj, nisko-poli mreži, i samo
koristite tezeliranu visoko-poli mrežu za rendering.
Postupci tessellation su novi u Direct3D 11, i oni pružaju način za tessellate
geometrija na grafičkom procesoru. Pre Direct3D 11, ako ste želeli da implementirate neki oblik
tezeliranje, to bi moralo biti učinjeno na CPU-u, a zatim nova tezelirana geometrija
morao bi se vratiti u GPU za rendering. Međutim, učitavanje novih
geometrija od CPU memorije do GPU memorije je spora, a takođe i opterećuje procesor
računanje tezelacije. Iz tog razloga, metode tezelacije nisu bile veoma velike
popularan za grafiku u realnom vremenu pre Direct3D 11. Direct3D 11 obezbeđuje API
tezeliranje potpuno u hardveru sa Direct3D 11 sposobnom video karticom. Ovo pravi
tezelacija mnogo atraktivnija tehnika. Faze tezanja su neobavezne (vi
samo ga treba koristiti ako želite tezelaciju). Odložili smo našu pokrivenost tezelaciji do
Poglavlje 14.
5.8 FAZA GEOMETRIJSKE ŠEŠERICE
Stepen geometrijskog shadera je neobavezan i mi ga ne koristimo sve do Glave 12, tako da mi
ovde će biti kratko. Geometrijski shader ulazi celim primitivima. Na primer, ako smo bili
crtanje trokutnih listi, tada bi ulaz u geometrijski shader bio tri tačke
definisanje trougla. (Imajte na umu da će tri vertikala već proći kroz
vertek shader.) Glavna prednost geometrijskog shadera je to što može da stvara ili uništi
geometrija. Na primer, ulazni primitiv može se proširiti u jedan ili više drugih
primitivi ili geometrijski shader mogu da odaberu da ne izvode primitiv na osnovu nekih
stanje. Ovo je u suprotnosti sa vertek shader-om, koji ne može stvoriti vertikale: ona ulazi u jedan
vertek i izlazi jedan vertek. Čest slučaj geometrijskog shadera je da proširite a
uperite u četvoro ili proširite liniju u četvoro.
Takođe primetićemo strelicu "izlazak" iz slike 5.11. To jest, geometrijski shader
može pretočiti podatke verteka u bafer u memoriji, koji se kasnije može izvući. Ovo je
naprednu tehniku i biće razmotren u kasnijom poglavlju.
Položaji verteksa koji ostavljaju geometrijski shader moraju se transformisati u
homogeni prostor za klipove.
5.9 CLIPPING
Geometrija koja je u potpunosti izvan gledišta frustuma treba odbaciti, i
geometrija koja preseka granicu frustuma mora biti iscrtana, tako da samo ona
unutrašnji deo ostaje; pogledajte sliku 5.27 za ideju prikazanu u 2D.
5.6.3.6 KSMMatrikPerspectiveFovLH
Matrica projekcionih perspektiva može se izgraditi s sledećim DirectKs Math
funkcija:
// Vrati matricu projekcije
KSMMATRIKS KSM_CALLCONV KSMMatrikPerspectiveFovLH (
float FovAngleI, // vertikalno polje ugla gledanja
u radijancima
float Aspect, // razmer razmera = širina / visina
float NearZ, // rastojanje u blizini ravni
float FarZ); // Udaljenost od daleke ravni
Sledeći kod odlomaka ilustruje kako koristiti KSMMatrikPerspectiveFovLH.
Ovdje odredimo 45 ° vertikalno vidno polje, blizu ravnice na z = 1 i daleko ravno na z =
1000 (ove dužine su u prostoru pogleda).
KSMMATRIKS P = KSMMatrikPerspectiveFovLH (0,25f * KSM_PI,
AspectRatio (), 1.0f, 1000.0f);
Odnos slike je prilagođen našim prozorskim odnosima:
float D3DApp :: AspectRatio () const
{
return static_cast <float> (mClientVidth) /
mClientHeight;
}
5.7 FUNKCIJE TESTIRANJA
Tessellation se odnosi na podelu trouglova mreže da bi se dodali novi trouglovi. Ove
Novi trouglovi se zatim mogu prebaciti na nove pozicije kako bi se postigli finiji detalji mreže (vidi Sliku
5.26).
Slika 5.26. Lijeva slika prikazuje izvornu mrežu. Prava slika pokazuje
mreža posle tezelacije.
Postoji niz pogodnosti za teselacije:
1. Možemo primeniti mehanizam nivoa detalja (LOD), gdje trouglovi u blizini
kamera je dodata još detalja, a trouglovi su daleko od fotoaparata
nije teksiran. Na ovaj način koristimo samo više trouglova gde će biti dodatni detalji
Primetio.
2. Zadržavamo jednostavnije niske pole mreže (nizak poli označava nizak broj trouglova) u memoriji,
dodam dodatne trouglove na mušu, čime se uštede memorija.
3. Mi radimo operacije poput animacije i fizike na jednostavnijoj, nisko-polnoj mreži, i samo
koristite tezeliranu visoko-polu mrežu za rendering.
Postupci tessellation su novi u Direct3D 11, i oni pružaju način za tessellate
geometrija na grafičkom procesoru. Pre Direct3D 11, ako želite da implementirate neki oblik
tezeliranje, to bi trebalo da bude učinjeno na CPU-u, a zatim nova tezelirana geometrija
morao bi se vratiti u GPU za rendering. Međutim, učitavanje novih
geometrija od CPU memorije do GPU memorije je spora, a također i opterećuje procesor
računanje tezelacije. Iz tog razloga, metode tezelacije nisu bile velike
popularno za grafiku u realnom vremenu pre Direct3D 11. Direct3D 11 obezbeđuje API
tezeliranje potpuno u hardveru sa Direct3D 11 sposobnim video karticom. Ovo pravi
tezelacija mnogo atraktivnija tehnika. Faze tezanja su neobavezne (vi
samo ga treba koristiti ako želite tezelaciju). Odložili smo našu pokrivenost tezelaciji do
Poglavlje 14.
5.8 FAZA GEOMETRIJSKE ŠEŠERICE
Stepen geometrijskog šadera je neobavezan i mi ga ne koristimo sve do Glave 12, tako da mi
ovde će biti kratko. Geometrijski shader ulazi celim primitivima. Na primer, ako smo bili
crtanje trikutnih listi, onda bi ulaz u geometrijski shader bio tri tačke
definisanje trougla. (Imajte na umu da će tri vertikala već proći kroz
vertek shader.) Glavna prednost geometrijskog shadera je to što može stvarati ili uništiti
geometrija. Na primer, ulazni primitiv može se proširiti u jednu ili više drugih
primitivi ili geometrijski shader mogu da izaberu da ne izvode primitiv na osnovu nekih
stanje. Ovo je u suprotnosti sa vertekom šaderom, koji ne može napraviti vertikale: ona ulazi u jedan
vertek i izlazi jedan vertek. Čest slučaj geometrijskog shadera je da proširite a
uperite u četvoro ili proširite liniju u četvoro.
Takođe primetićemo strelicu "izlazak" iz slike 5.11. Da jeste, geometrijski shader
može prenijeti podatke o pravcu u bafer u memoriji, koji se kasnije može izvući. Ovo je
naprednu tehniku i biće razmotren u kasnijom poglavlju.
Položaji verteksa koji ostavljaju geometrijski shader moraju se transformisati u
homogeni prostor za klipove.
5.9 CLIPPING
Geometrija koja je potpuno izvan gledišta frustuma treba odbaciti, i
geometrija koja preseka granicu frustuma mora biti iscrtana, tako da samo ona
unutrašnji deo ostaje; pogledajte sliku 5.27 za ideju prikazanu u 2D.
Možemo smatrati frustumom kao regionom ograničenom od šest aviona: vrha,
dno, levo, desno, blizu i daleko. Da zakačimo poligon protiv frustum-a, mi ga isečemo
protiv svakog frustum aviona jedan po jedan. Kada iscrtavate poligon protiv ravni (Sl
5.28), deo u pozitivnom polu-prostoru ravni održava se, a deo negativan
pola prostora se odbacuje. Klipovanje konveksnog poligona protiv ravni uvek će rezultirati a
konveksni poligon. S obzirom da se hardver obavlja za nas, nećemo pokriti detalje
ovde; Umesto toga, upućujemo čitatelja u popularni algoritam klipinga Sutherland-Hodgeman
[Sutherland74]. U osnovi se odnosi na pronalaženje tačaka preseka između aviona
i poligonske ivice, a zatim naručuje vertikale kako bi se formirao novi poligon.
Slika 5.28. (a) Clipping trougao protiv ravni. (b) Urezani trougao.
Imajte na umu da je trougao sa klizačem nije trougao, već kvadrata. Tako će hardver biti
potrebno je triangulirati kvadrant koji je rezultat, što je jednostavno učiniti za konveksno
poligone.
[Blinn78] opisuje kako se isecanje može izvršiti u 4D homogenom prostoru. Posle
perspektivna podela, tačke
unutar pogleda frustum su u normalizovanim koordinatama uređaja i ograničeni na sledeći način:
-1 ≤ k / v ≤ 1
-1 ≤ i / v ≤ 1
0≤z/v≤1
Dakle, u homogenom prostoru za snimanje, pre razdvajanja, 4D tačke (k, i, z, v) unutar
frustrirani su ograničeni na sledeći način:
-V ≤ k ≤ v
-V ≤ i ≤ v
0≤z≤v
To znači da su točke ograničene jednostavnim 4D avionima:
Levo: v = -k
Desno: v = k
Dno: v = -i
Vrh: v = i
Blizu: z = 0
Daleko: z = v
Jednom kada znamo jednačine frustriranih ravnina u homogenom prostoru, možemo da primenimo a
algoritam klipinga (kao što je Sutherland-Hodgeman). Imajte na umu da je matematika
segmentni / ravni intersekcioni test generalizuje se na R4, tako da možemo testirati sa 4D tačkama i
4D ravni u homogenom prostoru za snimanje.
Slika 5.29. Frustrirane granice u kv-ravni u homogenom prostoru klipova.
5.10 FAZA RASTERIZACIJE
Glavni zadatak rasterizacije faze je izračunati boje piksela iz projektovanih
3D trouglovi.
5.10.1 Vievport Transform
Nakon klipovanja, hardver može učiniti perspektivnu podelu za transformaciju
homogeni prostor za klizu za normalizovane koordinate uređaja (NDC). Kada se vrate vertikale
NDC prostor, 2D k- i i- koordinate koji formiraju 2D sliku pretvaraju se u a
pravougaonik na baferu koji se zove vizir (opoziv §4.3.9). Nakon ove transformacije, kand
i-koordinate su u jedinicama piksela. Obično gledanja transformacija ne
modifikujte z-koordinate, kako se koristi za bafer dubine, ali može se modifikovati
MinDepth i MakDepth vrednosti strukture D3D12_VIEVPORT. The
Vrednosti MinDepth i MakDepth moraju biti između 0 i 1.
5.10.2 Povlačenje iz pozadine
Trougao ima dve strane. Da bismo razlikovali dve strane, koristićemo sledeće
konvencija. Ako su vertikali trougla naručeni v0, v1, v2 onda izračunamo trougao
normalno n:
Strana od kojeg normalni vektor emanira je prednja strana, a druga strana je
nazad. Slika 5.30 ilustruje ovo.
Slika 5.30. Levi trougao je prednjačen sa našeg gledišta, a desno
trougao se suočava sa našeg gledišta.
Kažemo da je trougao okrenut prema frontu ako gledalac vidi prednju stranu trougla, i
kažemo da je trougao okrenut prema nazad, ako gledalac vidi zadnju stranu trougla. Od našeg
perspektiva na slici 5.30, levi trougao je okrenut ka prednjoj strani, dok je pravi trougao retardiran.
Štaviše, iz naše perspektive, levi trougao se poručuje u smeru kazaljke na satu dok je
desni trougao se poručuje u suprotnom smeru. Ovo nije slučajnost: sa konvencijom mi
izabrali (tj. način na koji izračunamo trougao normalan), trougao je naručen u smeru kazaljke na satu
(prema tom gledaocu) je okrenuta prema frontu, a trougao je naručen u suprotnom smeru kazaljke na
satu (sa
poštovanje tog gledatelja) se suočava sa leđima.
Sada, većina predmeta u 3D svetovima su zatvoreni čvrsti objekti. Pretpostavimo da se slažemo
izgraditi trouglove za svaki predmet tako da su normali uvek usmereni
napolju. Zatim fotoaparat ne vidi trouglove okrenutog ka leđima čvrstog predmeta, jer
trouglovi okrenuti prema frontu okreću trouglove okrenuti unazad; Slika 5.31 ilustruje ovo u
2D i 5.32 u 3D. Pošto trouglovi okrenuti prema frontu okreću trouglove okrenuti prema nazad
nema smisla da ih nacrtate. Povlačenje iz pozadine odnosi se na proces odbacivanja povratka
trouglovi iz cevovoda. Ovo može potencijalno smanjiti količinu trouglova
treba da se obradi za pola.
Slika 5.31. (a) Čvrsti objekt sa prednjim i trouglastim okretima. (b)
Scena nakon izbacivanja trouglova okrenutih unazad. Imajte na umu da odbacivanje pozadi ne znači
utiču na konačnu sliku jer su trouglovi okrenuti unazad okrenuti prednjim okom
one.
Slika 5.32. (Levo) Crtećemo kocke providno, tako da možete videti sve
šest strana. (Desno) Crtežemo kocke kao čvrste blokove. Imajte na umu da ne vidimo
tri strane unazad, s obzirom da tri fronta su okrenuta njima - na taj način
trouglovi koji se suočavaju sa leđima mogu se odbaciti iz dalje obrade i nikoga
primetiće. Podrazumevano, Direct3D tretira trouglove sa redosledom navrtanja u smeru kazaljke na satu
(u odnosu na gledaoc) kao okrenutom prema frontu i trouglima sa porudžbenicima u smeru suprotnom
od kazaljke na satu (sa poštovanje prema gledaocu) kao okrenuti nazad. Međutim, ova konvencija može
biti izmenjena sa a Postavka stanja renderera Direct3D.
5.10.3 Interpolacija atributa verteka
Podsjetimo da definišemo trougao tako što odredimo njegove vertikale. Pored pozicije, mi
mogu dodati atribute vertikama kao što su boje, normalni vektori i koordinate teksture.
Nakon transformacije prikaza, ovi atributi moraju biti interpolirani za svaki piksel
pokrivajući trougao. Pored atributa verteka, potrebno je dobiti i dubinske vrednosti glave
interpolisani tako da svaki piksel ima vrednost dubine za algoritam puferovanja dubine. The
Atribut verteka je interpoliran u prostoru ekrana tako da su atributi interpolirano linearno preko trougla
u 3D prostoru (slika 5.33); ovo zahteva takozvani perspektivna ispravna interpolacija. U suštini,
interpolacija nam omogućava da koristimo vrednosti verteka za izračunavanje vrednosti za unutrašnje
piksele. Slika 5.33. Vrednost atributa p (s, t) na trouglu može se dobiti linearno interpoliraju se između
vrijednosti atributa u vertikalama trougla. Matematički detalji za ispravnu perspektivu interpolacije
atributa nisu nešto o čemu treba da brinemo jer hardver to radi; zainteresovani čitač može pronađite
matematički derivat u [Eberli01]. Međutim, Slika 5.34 daje osnovnu ideju
šta se dešava.
Slika 5.34. 3D projekcija se projektuje na projekcionom prozoru ( projekcija je 2D linija u ekranu). Vidimo
da uzimajući ujednačene veličine koraka 3D linija odgovara uzimanju nejednakih veličina koraka u 2D
ekranu. Zbog toga da radimo linearnu interpolaciju u 3D prostoru, moramo učiniti nelinearnu
interpolacija u ekranu.
5.11 FAZA PIKSEL SHADER
Pikel shaderi su programi koje pišemo koje se izvršavaju na GPU-u. Piksel shader je
izvršeno za svaki fragment piksela i koristi interpolirane atribute verteka kao ulaz
izračunati boju. Piksel shader može biti jednostavan kao što se vraća konstantnom bojom
komplikovanije stvari kao što su per-pikel osvetljenje, refleksije i efekti senčenja.
5.12
Nakon što je piksel shader generisao fragmente piksela, oni se kreću na
output merger (OM) faza renderinga. U ovoj fazi, neki fragmenti piksela
može biti odbačena (npr., iz dubinskih ili šablonskih testova). Pikselski fragmenti koji nisu
odbijene su upisane u zadnje pufer. Blendiranje se takođe vrši u ovoj fazi, gde je piksel
može se mešati sa pikselom koji se trenutno nalazi na zadnjem baferu umesto da ga prevlada
u potpunosti. Neki specijalni efekti kao što je transparentnost primenjuju se sa mešanjem;
Poglavlje 10 je posvećeno mešanju.
Shaderi su napisani na jeziku koji se naziva jezički jezik s visokim nivoom (HLSL),
koji ima sličnu sintaksu u C ++, pa je lako naučiti. Dodatak B daje koncizan
referenca na HLSL. Naš pristup učenju HLSL-a i programskih shadera će biti
biti zasnovan na primjeru. To jest, dok prolazimo kroz knjigu, uvodićemo sve nove
HLSL koncepta koji su nam potrebni da bi se implementirao demo pri ruci. Shaderi su obično
napisana u tekstualnim datotekama sa. Hlsl ekstenzijom.
Vertek shader je funkcija zvana VS. Imajte na umu da možete dati vertek shader
bilo koje važeće ime funkcije. Ova verzija shadera ima četiri parametra; prva dva su ulazna
parametri, a poslednja dva su izlazni parametri (označeni sa ključnom riječom). The
HLSL nema referenci ili pokazivače, tako da vraćaju više vrednosti iz funkcije,
morate koristiti ili strukture ili izvan parametara. U HLSL-u, funkcije su uvek inlajn.
Prva dva ulazna parametra formiraju ulazni potpis vertek shadera i
odgovara članovima podataka u našoj prilagođenoj strukturi verteka koju koristimo za izvlačenje. The
Semantika parametara ": POSITION" i ": COLOR" se koriste za mapiranje elemenata u
struktura verteka za parametre ulaznih parametara vertek-a, kao što je prikazano na slici 6.4.
Slika 6.4. Svaki element vertek-a ima pridruženu semantiku koju odredi
D3D12_INPUT_ELEMENT_DESC niz. Svaki parametar shadera verteka takođe
ima pričuvan semantik. Semantika se koristi za usklađivanje elemenata verteka sa
parametri shadera verteka.
Izlazni parametri imaju priloženu semantiku (": SV_POSITION" i
": BOJA"). Ovo se koristi za mapiranje izlaza vertek shadera na odgovarajuće ulaze
sledeća faza (bilo geometrijski shader ili piksel shader). Imajte na umu da SV_POSITION
Semantičan je poseban (SV predstavlja sistemsku vrijednost). Koristi se za označavanje vertek shadera
izlazni element koji drži položaj verteksa u homogenom prostoru za snimanje. Moramo se priključiti
SV_POSITION semantika na izlazu iz položaja jer GPU mora biti svjestan
ova vrednost zato što je uključena u operacije u koje nisu uključeni drugi atributi
kao klipovanje, ispitivanje dubine i rasterska obrada. Semantičko ime za izlazne parametre
nisu sistemske vrednosti mogu biti bilo koji važeći semantički naziv.
Prva linija transformiše poziciju verteksa iz lokalnog prostora u homogeni klip
razmak množenjem pomoću matrice 4 × 4 gVorldVievProj:
// Transform to homogeneous clip space.
oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
Sintaksa konstruktora float4 (iPosL, 1.0f) konstruiše 4D vektor i jeste ekvivalentan float4 (iPosL.k, iPosL.i,
iPosL.z, 1.0f); jer mi znaju da su pozicije vertikala tačke a ne vektori, postavljamo 1 u četvrti
komponenta (v = 1). Tipovi float2 i float3 predstavljaju 2D i 3D vektore, redom. Matrica varijable
gVorldVievProj živi u onome što se zove konstanta buffer, o čemu će biti diskutovano u narednom
odeljku. Koristi se ugrađena funkcija mul množenje vektora i matrica. Usput, funkcija mul je
preopterećena za matricu množenja različitih veličina; na primer, možete ga koristiti da pomnožite dva 4
× 4 matrice, dve matrice 3 × 3 ili vektor 1 × 3 i matrica 3 × 3. Poslednja linija u tijelo shadera samo kopira
ulaznu boju na izlazni parametar tako da se boje hrane u sledeću fazu gasovoda:
oColor = iColor;
Mi možemo ekvivalentno prepisati gornju vertek shader iznad koristeći strukture za povratni tip i ulazni
potpis (za razliku od liste dugih parametara):
1. pRootSignature: Pointer to the root signature to be bound with this PSO. The root signature must be
compatible with the shaders specified with this PSO.
2. VS: The vertex shader to bind. This is specified by the D3D12_SHADER_BYTECODE structure which is a
pointer to the compiled bytecode data, and the size of the bytecode data in bytes.
typedef struct D3D12_SHADER_BYTECODE {
const BYTE *pShaderBytecode;
SIZE_T BytecodeLength;
} D3D12_SHADER_BYTECODE;
4. DS: Shader domena koji će se vezati (o tome ćemo kasnije raspravljati o ovoj vrsti shadera
poglavlje).
5. HS: Shader za trup koji će se vezati (o tome ćemo shvatati u sledećem poglavlju).
6. GS: geometrijski shader koji će se vezati (o tome ćemo kasnije raspravljati o ovom tipu shadera
poglavlje).
7. StreamOutput: Koristi se za naprednu tehniku zvanu stream-out. Mi samo nula
ovo polje za sada.
8. BlendState: Specificira stanje mešanja koje konfigurira mešanje. Ćemo diskutovati
ovu državnu grupu u kasnijom poglavlju; za sada navedite podrazumevano
CD3DKS12_BLEND_DESC (D3D12_DEFAULT). 9. SampleMask: Multisampling može da ima do 32 uzorka.
Ova 32-bitna cijela vrijednost koristi se za uključivanje / isključivanje uzoraka. Na primer, ako isključite 5.
bit, a zatim
5. uzorak neće biti uzet. Naravno, isključivanje petog uzorka samo ima posledica ako koristite
multisampling sa najmanje 5 uzoraka. Ako aplikacija koristi pojedinačno uzorkovanje, onda je važno
samo prvi bit ovog parametra. Uopšteno, koristi se 0kffffffff podrazumevana vrednost, koja ne
onemogućava nikakve uzorke.
10. RasterizerState: Određuje stanje rasterizacije koje konfiguriše
rasterizer.
11. DepthStencilState: Specificira stanje dubine / šablona koji konfiguriše
test dubine / šablona. Razgovaralićemo o ovoj državnoj grupi u kasnijom poglavlju; za sada, navedite
podrazumevani CD3DKS12_DEPTH_STENCIL_DESC (D3D12_DEFAULT).
12. InputLaiout: Opis ulaznog rasporeda koji je jednostavno niz
Elementi D3D12_INPUT_ELEMENT_DESC i broj elemenata u
niz.
typedef struct D3D12_INPUT_LAYOUT_DESC
{
const D3D12_INPUT_ELEMENT_DESC
*pInputElementDescs;
UINT NumElements;
} D3D12_INPUT_LAYOUT_DESC;
13. PrimitiveTopologyType: Specifies the primitive topology type.
typedef enum D3D12_PRIMITIVE_TOPOLOGY_TYPE {
D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED = 0,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT = 1,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE = 2,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE = 3,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH = 4
} D3D12_PRIMITIVE_TOPOLOGY_TYPE;
14. NumRenderTargets: Broj ciljeva rendera koje koristimo istovremeno.
15. RTVFormats: Omogućavaju oblikovanje ciljnih formata. Ovo je niz koji podržava pisanje simultani
ciljevi višestrukih renderovanja. Ovo bi trebalo da odgovara postavkama renderera cilj kojim koristimo
PSO sa.
16. DSVFormat: Format dubine / stencil pufera. Ovo bi trebalo da odgovara postavkama
bafera dubine / šablona koristimo PSO sa.
17. SampleDesc: Opisuje brojni broj i nivo kvaliteta. Ovo bi trebalo
podudaraju se sa podešavanjima cilja rendera koje koristimo.
Nakon što smo popunili a
Primer D3D12_GRAPHICS_PIPELINE_STATE_DESC, kreiramo
ID3D12PipelineState objekat pomoćuID3D12Device ::CreateGraphicsPipelineState method:
ComPtr<ID3D12RootSignature> mRootSignature;
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;
ComPtr<ID3DBlob> mvsByteCode;
ComPtr<ID3DBlob> mpsByteCode;
… D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
ZeroMemory(&psoDesc,
sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
psoDesc.InputLayout = { mInputLayout.data(),
(UINT)mInputLayout.size() };
psoDesc.pRootSignature = mRootSignature.Get();
psoDesc.VS =
{
reinterpret_cast<BYTE*>(mvsByteCode-
>GetBufferPointer()),
mvsByteCode->GetBufferSize()
};
psoDesc.PS =
{
reinterpret_cast<BYTE*>(mpsByteCode-
>GetBufferPointer()),
mpsByteCode->GetBufferSize()
};
psoDesc.RasterizerState =
CD3D12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3D12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState =
CD3D12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType =
D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = mBackBufferFormat;
psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
psoDesc.SampleDesc.Quality = m4xMsaaState ?
(m4xMsaaQuality - 1) : 0;
psoDesc.DSVFormat = mDepthStencilFormat;
ComPtr<ID3D12PipelineState> mPSO;
md3dDevice->CreateGraphicsPipelineState(&psoDesc,
IID_PPV_ARGS(&mPSO)));
Ovo je dosta stanja u jednom agregatnom ID3D12PipelineState objektu. Mi
odredite sve ove objekte kao agregat grafičkog plinovoda za performanse. Od strane
Specificirajući ih kao agregat, Direct3D može potvrditi da je sve stanje kompatibilno i
vozač može generisati sve šifre unapred kako bi programirao stanje hardvera. U
Direct3D 11 državni model, ove stavke stanja izrađene su zasebno. Međutim
države su povezane; ako se jedan dio države promeni, može dodatno zatražiti od vozača
reprogramirati hardver za drugi deo zavisnog stanja. Kao i mnoge države
promenjen da konfiguriše gasovod, stanje hardvera bi moglo biti reprogramirano
redundantno. Da bi izbegli ovu redundanciju, vozači su obično odložili programiranje
stanje hardvera sve dok se poziv za poziv ne izda kada bi bilo poznato stanje cjevovoda.
Ali ovo odlaganje zahtijeva dodatni rad knjigovodstva od strane vozača u toku vožnje; to treba
staze koje su se stanja promenile, a zatim generišu kod za programiranje hardverskog stanja
u toku rada. U novom Direct3D 12 modelu, vozač može generisati sve potrebne kodove
programirati stanje gasovoda u inicijalizacijskom vremenu jer smo odredili većinu gasovoda
stanje kao agregat.
Zbog toga što PSO validacija i stvaranje mogu biti dugotrajne, PSO-ovi bi trebali
biti generisani u inicijalizacijskom vremenu. Jedan izuzetak od ovoga može biti stvaranje a
PSO u radnom vremenu po zahtevu prvi put da se pominje; onda ga čuvajte u a
sakupljanje kao sto je hash sto, tako da se brzo mogu primeniti za buduću upotrebu.
Nisu sva stanja renderovanja ugrađena u PSO. Neke države poput prikaza i
pravokutne špice su naznačene nezavisno od PSO-a. Takvo stanje se može efikasno postaviti
nezavisno od drugog stanja gasovoda, tako da nije postignuta prednost uključivanjem njih
PSO.
Direct3D je u osnovi državna mašina. Stvari ostanu u njihovom trenutnom stanju do nas
promenite ih. Ako neki objekti koje crtate koriste jedan PSO i drugi objekti koje ste
crtanje zahteva drugačiji PSO, onda morate strukturirati svoj kod ovako:
// Reset specifies initial PSO.
mCommandList->Reset(mDirectCmdListAlloc.Get(),
mPSO1.Get())
/* …draw objects using PSO 1… */
// Change PSO
mCommandList->SetPipelineState(mPSO2.Get());
/* …draw objects using PSO 2… */
// Change PSO
mCommandList->SetPipelineState(mPSO3.Get());
/* …draw objects using PSO 3… */
6.10 STRUKTURA GEOMETRIJSKE HELPERA
Korisno je stvoriti strukturu koja grupi verteka i indeksnog pufera zajedno definišite grupu geometrije.
Pored toga, ova struktura može zadržati sistemsku memoriju za podršku vertikalnih i indeksnih podataka
tako da ih CPU može pročitati. CPU će trebati pristup geometrijskim podacima za stvari kao što su
biranje i otkrivanje sudara. Pored toga, struktura skuplja važna svojstva verteksa i indeksnih bafera, kao
što je format i štrajk, i pruža metode koje vraćaju prikaze u odbojnike. Koristimo sledeću strukturu
MeshGeometrija (definisanu u d3dUtil.h) u čitavoj knjizi kad god definišemo deo geometrije.
std::unique_ptr<UploadBuffer<PassConstants>> PassCB =
nullptr;
std::unique_ptr<UploadBuffer<ObjectConstants>>
ObjectCB = nullptr;
Ako imamo 3 frejma resursa i n render stavke, onda imamo tri 3n objektne konstantne bafere i 3 pasne
konstantne bafere. Zbog toga su nam potrebni 3 (n + 1) konstantni prikazi pufera (CBVs). Tako ćemo
morati da izmenimo našu kupu CBV da uključimo dodatne deskriptore:
void ShapesApp::BuildDescriptorHeaps()
{
UINT objCount = (UINT)mOpaqueRitems.size();
// Need a CBV descriptor for each object for each
frame resource,
// +1 for the perPass CBV for each frame resource.
UINT numDescriptors = (objCount+1) *
gNumFrameResources;
// Save an offset to the start of the pass CBVs.
These are the last 3 descriptors.
mPassCbvOffset = objCount * gNumFrameResources;
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = numDescriptors;
cbvHeapDesc.Type =
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags =
D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice-
>CreateDescriptorHeap(&cbvHeapDesc,
IID_PPV_ARGS(&mCbvHeap)));
}
Sada, možemo kopirati CBV kup s sledećim kodom gde deskriptori 0 do n- 1 sadrže objekat CBV-a za
resursa 0 kadra, deskriptori n na 2n-1 sadrže objekt CBV-i za resurse prvog rama, deskriptori 2n do 3n-1
sadrže CBV-ove objekata resurs drugog rama i deskriptori 3n, 3n + 1 i 3n + 2 sadrže pasus CBV za Resursi
0, 1 i 2 rama, respektivno:
void ShapesApp::BuildConstantBufferViews()
{
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof
(ObjectConstants));
UINT objCount = (UINT)mOpaqueRitems.size();
// Need a CBV descriptor for each object for each
frame resource.
for(int frameIndex = 0; frameIndex <
gNumFrameResources; ++frameIndex)
{
auto objectCB = mFrameResources[frameIndex]-
>ObjectCB->Resource();
for(UINT i = 0; i < objCount; ++i)
{
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = objectCB-
>GetGPUVirtualAddress();
// Offset to the ith object constant buffer in
the current buffer.
cbAddress += i*objCBByteSize;
// Offset to the object CBV in the descriptor
heap.
int heapIndex = frameIndex*objCount + i;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mCbvHeap-
>GetCPUDescriptorHandleForHeapStart());
handle.Offset(heapIndex,
mCbvSrvUavDescriptorSize);
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = objCBByteSize;
md3dDevice->CreateConstantBufferView(&cbvDesc,
handle);
}
}
UINT passCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof
(PassConstants));
// Last three descriptors are the pass CBVs for each
frame resource.
for(int frameIndex = 0; frameIndex <
gNumFrameResources; ++frameIndex)
{
auto passCB = mFrameResources[frameIndex]->PassCB-
>Resource();
// Pass buffer only stores one cbuffer per frame
resource.
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = passCB-
>GetGPUVirtualAddress();
// Offset to the pass cbv in the descriptor heap.
int heapIndex = mPassCbvOffset + frameIndex;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mCbvHeap->GetCPUDescriptorHandleForHeapStart());
handle.Offset(heapIndex,
mCbvSrvUavDescriptorSize);
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes = passCBByteSize;
md3dDevice->CreateConstantBufferView(&cbvDesc,
handle);
}
}
Podsetimo da možemo dobiti ručicu prvom deskriptoru u gomili sa
ID3D12DescriptorHeap :: GetCPUDescriptorHandleForHeapStart metodu. Međutim, sada kada naša
kupina ima više od jednog deskriptora, ovaj metod više nije dovoljan. Moramo biti u mogućnosti da se
usredsredimo na druge deskriptore u kupu. Da bismo to uradili, moramo znati veličinu da se povećava u
kupovini da bi stigli do sledećeg deskriptora. Ovo je specifično za hardver, tako da moramo da upišemo
ove informacije sa uređaja i to zavisi od tipa kupovine. Podsjetimo da naša klasa D3DApp krije ove
informacije:
mRtvDescriptorSize = md3dDevice>
GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice-
>GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvUavDescriptorSize = md3dDevice-
>GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
Jednom kada znamo veličinu inkrementa deskriptora, možemo koristiti jednu od dva
CD3DKS12_CPU_DESCRIPTOR_HANDLE :: Metode offseta da se ručica pomeri n deskriptori:
// Specify the number of descriptors to offset times
the descriptor
// Offset by n descriptors:
CD3DX12_CPU_DESCRIPTOR_HANDLE handle = mCbvHeap-
>GetCPUDescriptorHandleForHeapStart();
handle.Offset(n * mCbvSrvDescriptorSize);
// Or equivalently, specify the number of descriptors
to offset,
// followed by the descriptor increment size:
CD3DX12_CPU_DESCRIPTOR_HANDLE handle = mCbvHeap-
>GetCPUDescriptorHandleForHeapStart();
handle.Offset(n, mCbvSrvDescriptorSize);
CD3DX12_GPU_DESCRIPTOR_HANDLE has the same Offset methods.
7.5.4 Crtanje scene
Konačno možemo da nacrtamo naše proizvode. Možda je jedini nezgodan deo kompenzacija
ispravite CBV u kupu za objekat koji želimo izvući. Obratite pažnju na to kako stavka rendera čuva indeks
u konstantnom baferu koji je povezan sa stavkom renderera.
void ShapesApp::DrawRenderItems(
ID3D12GraphicsCommandList* cmdList,
const std::vector<RenderItem*>& ritems)
{
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
auto objectCB = mCurrFrameResource->ObjectCB-
>Resource();
// For each render item…
for(size_t i = 0; i < ritems.size(); ++i)
{
auto ri = ritems[i];
cmdList->IASetVertexBuffers(0, 1, &ri->Geo-
>VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo-
>IndexBufferView());
cmdList->IASetPrimitiveTopology(ri-
>PrimitiveType);
// Offset to the CBV in the descriptor heap for
this object and
// for this frame resource.
UINT cbvIndex = mCurrFrameResourceIndex*
(UINT)mOpaqueRitems.size()
+ ri->ObjCBIndex;
auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(
mCbvHeap->GetGPUDescriptorHandleForHeapStart());
cbvHandle.Offset(cbvIndex,
mCbvSrvUavDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(0,
cbvHandle);
cmdList->DrawIndexedInstanced(ri->IndexCount, 1,
ri->StartIndexLocation, ri->BaseVertexLocation,
0);
}
}
The DrawRenderItems method is invoked in the main Draw call:
void ShapesApp::Draw(const GameTimer& gt)
{
auto cmdListAlloc = mCurrFrameResource-
>CmdListAlloc;
// Reuse the memory associated with command
recording.
// We can only reset when the associated command
lists have
// finished execution on the GPU.
ThrowIfFailed(cmdListAlloc->Reset());
// A command list can be reset after it has been
added to the
// command queue via ExecuteCommandList.
// Reusing the command list reuses memory.
if(mIsWireframe)
{
ThrowIfFailed(mCommandList->Reset(
cmdListAlloc.Get(),
mPSOs[“opaque_wireframe”].Get()));
}
else
{
ThrowIfFailed(mCommandList-
>Reset(cmdListAlloc.Get(), mPSOs[“opaque”].Get()));
}
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);
// Indicate a state transition on the resource
usage.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET));
// Clear the back buffer and depth buffer.
mCommandList-
>ClearRenderTargetView(CurrentBackBufferView(),
Colors::LightSteelBlue, 0, nullptr);
mCommandList-
>ClearDepthStencilView(DepthStencilView(),
D3D12_CLEAR_FLAG_DEPTH |
D3D12_CLEAR_FLAG_STENCIL,
1.0f, 0, 0, nullptr);
// Specify the buffers we are going to render to.
mCommandList->OMSetRenderTargets(1,
&CurrentBackBufferView(),
true, &DepthStencilView());
ID3D12DescriptorHeap* descriptorHeaps[] = {
mCbvHeap.Get() };
mCommandList-
>SetDescriptorHeaps(_countof(descriptorHeaps),
descriptorHeaps);
mCommandList-
>SetGraphicsRootSignature(mRootSignature.Get());
int passCbvIndex = mPassCbvOffset +
mCurrFrameResourceIndex;
auto passCbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(
mCbvHeap->GetGPUDescriptorHandleForHeapStart());
passCbvHandle.Offset(passCbvIndex,
mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(1,
passCbvHandle);
DrawRenderItems(mCommandList.Get(), mOpaqueRitems);
// Indicate a state transition on the resource
usage.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT));
// Done recording commands.
ThrowIfFailed(mCommandList->Close());
// Add the command list to the queue for execution.
ID3D12CommandList* cmdsLists[] = {
mCommandList.Get() };
mCommandQueue-
>ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// Swap the back and front buffers
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) %
SwapChainBufferCount;
// Advance the fence value to mark commands up to
this fence point.
mCurrFrameResource->Fence = ++mCurrentFence;
// Add an instruction to the command queue to set a
new fence point.
// Because we are on the GPU timeline, the new fence
point won’t be
// set until the GPU finishes processing all the
commands prior to this Signal().
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}
7.5.4 Crtanje scene
Konačno možemo da nacrtamo naše proizvode. Možda je jedini nezgodan deo kompenzacija
ispravite CBV u kupu za objekat koji želimo izvući. Obratite pažnju na to kako stavka rendera čuva indeks
u konstantnom baferu koji je povezan sa stavkom renderera.
7.6 PODEŠAVANJE POTPISA KORETA
Uneli smo koren potpisa u §6.6.5 prethodnog poglavlja. Root potpis
definiše koji resursi moraju biti vezani za gasovod prije izdavanja pozivnog poziva i
kako se ti resursi mapiraju u registre ulaznih senatora. Koji resursi treba da budu
Vezivanje zavisi od kojih resursa očekuju trenutni programi shadera. Kada je PSO
kreirani, kombinacija programa root root i shader programa će biti validirana.
7.6.1 Parametri korena
Podsjetimo da je korenski potpis definisan nizom root parametara. Do sada imamo
stvorio je samo root parametar koji čuva deskriptorsku tabelu. Međutim, root parametar može
zapravo jedan od tri vrste:
1. Tabela deskriptora: Očekuje da tabela deskriptora predstavlja referentni opseg u a
kupe koji identifikuje izvor koji treba vezati.
2. Deskriptor korena (inline deskriptor): Očekuje da se descriptor direktno postavlja
identifikuje resurs koji treba vezati; deskriptor ne mora biti u gomilu. Samo
CBVs do konstantnih bafera, a SRV / UAVs u bafere mogu biti vezani kao root
deskriptor. Konkretno, to znači da SRV-ovi tekstura ne mogu biti vezani kao root
deskriptor.
3. Konstanta korena: Očekuje da se lista 32-bitnih konstantnih vrednosti direktno vezuje.
Za performanse postoji ograničenje od 64 DVORD-a koje se mogu staviti u korijenski potpis.
Tri tipa parametara root-a imaju sledeće troškove:
1. Deskriptori: 1 DVORD
2. Root deskriptor: 2 DVORDs
3. Konstanta korena: 1 DVORD po 32-bitnoj konstanti
Možemo napraviti proizvoljan koren potpis, pod uslovom da ne prelazimo šezdeset i četri
DVORD ograničenje. Konstanti korena su vrlo zgodni, ali njihovi troškovi dodaje brzo. Za
primer, ako su jedini stalni podaci koji su nam bili potrebni matrica projekcije svetskog pogleda, mi
mogao je koristiti 16 konstanta kontejnera da bi ih uskladištio, što nas ne bi trebalo uznemiravati sa
konstantni pufer i CBV kup. Međutim, to jede četvrtinu našeg korenskog potpisa budžet. Korišćenjem
deskriptora korena biće samo dva DVORD-a, a tabela deskriptora je samo jedan DVORD. Kako naše
aplikacije postaju složenije, naši konstantni podaci o puferu će postati veći, i malo je verovatno da ćemo
moći da se izvučemo koristeći samo koren konstante. U aplikaciji iz stvarnog sveta, verovatno ćete
koristiti kombinaciju svih tri tipovi root parametara. U kodu se parametar korena opisuje popunjavanjem
a Struktura CD3DKS12_ROOT_PARAMETER. Kao što smo videli kod CD3DKS koda,
CD3DKS12_ROOT_PARAMETER proširuje D3D12_ROOT_PARAMETER i dodati
funkcije inicijalizacije helpera.
typedef struct D3D12_ROOT_PARAMETER
{
D3D12_ROOT_PARAMETER_TYPE ParameterType;
union
{
D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable;
D3D12_ROOT_CONSTANTS Constants;
D3D12_ROOT_DESCRIPTOR Descriptor;
};
D3D12_SHADER_VISIBILITY ShaderVisibility;
}
D3D12_ROOT_PARAMETER;
1.ParameterTipe: Član sledeće navedenog tipa koji ukazuje na korentip parametra (tabela deskriptora,
konstanta korena, deskriptor korena CBV, root SRV deskriptor, deskriptor korena UAV-a).
enum D3D12_ROOT_PARAMETER_TYPE
{
D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE = 0,
D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS= 1,
D3D12_ROOT_PARAMETER_TYPE_CBV = 2,
D3D12_ROOT_PARAMETER_TYPE_SRV = 3 ,
D3D12_ROOT_PARAMETER_TYPE_UAV = 4
} D3D12_ROOT_PARAMETER_TYPE;
2. DescriptorTable / Constants / Deskriptori: Struktura koja opisuje koren parametar. Član sindikata koji
popunjavate zavisi od tipa root parametra. §7.6.2, §7.6.3, i §7.6.4 raspravljaju o ovim strukturama.
3. ShaderVisibiliti: Član sledećeg popisa koji određuje koje programe za shader programira ovaj root
parametar. Obično u ovoj knjizi mi navedite D3D12_SHADER_VISIBILITI_ALL. Međutim, ako znamo resurs
samo će se koristiti u pikel shader-u, na primer, onda možemo odrediti
D3D12_SHADER_VISIBILITI_PIKSEL. Ograničavanje vidljivosti korena parametar može potencijalno
dovesti do nekih optimizacija.
enum D3D12_SHADER_VISIBILITY
{
D3D12_SHADER_VISIBILITY_ALL = 0,
D3D12_SHADER_VISIBILITY_VERTEX = 1,
D3D12_SHADER_VISIBILITY_HULL = 2,
D3D12_SHADER_VISIBILITY_DOMAIN = 3,
D3D12_SHADER_VISIBILITY_GEOMETRY = 4,
D3D12_SHADER_VISIBILITY_PIXEL = 5
} D3D12_SHADER_VISIBILITY;
7.6.2 Deskriptori tabele
Parametar root deskriptora se dalje definiše popunjavanjem DescriptorTable član
D3D12_ROOT_PARAMETER.
typedef struct D3D12_ROOT_DESCRIPTOR_TABLE
{
UINT NumDescriptorRanges;
const D3D12_DESCRIPTOR_RANGE *pDescriptorRanges;
} D3D12_ROOT_DESCRIPTOR_TABLE;
This simply specifies an array of D3D12_DESCRIPTOR_RANGEs and the number of
ranges in the array.
The D3D12_DESCRIPTOR_RANGE structure is defined like so:
typedef struct D3D12_DESCRIPTOR_RANGE
{
D3D12_DESCRIPTOR_RANGE_TYPE RangeType;
UINT NumDescriptors;
UINT BaseShaderRegister;
UINT RegisterSpace;
UINT OffsetInDescriptorsFromTableStart;
} D3D12_DESCRIPTOR_RANGE;
1.RangeTipe: Član sledećeg popisnog tipa koji označava vrstu deskriptori u ovom opsegu:
enum D3D12_DESCRIPTOR_RANGE_TYPE
{
D3D12_DESCRIPTOR_RANGE_TYPE_SRV = 0,
D3D12_DESCRIPTOR_RANGE_TYPE_UAV = 1,
D3D12_DESCRIPTOR_RANGE_TYPE_CBV = 2 ,
D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER = 3
} D3D12_DESCRIPTOR_RANGE_TYPE;
2. NumDescriptors: Broj deskriptora u opsegu.
3. BaseShaderRegister: Argumenti baznog shader registra su vezani. Za primer, ako postavite
NumDescriptors na 3, BaseShaderRegister na 1, a tip opsega je CBV (za konstantne baferere), onda ćete
biti vezani za HLSL register
cbuffer cbA : register(b1) {…};
cbuffer cbB : register(b2) {…};
cbuffer cbC : register(b3) {…};
4. RegisterSpace: Ova svojstva vam daje drugu dimenziju da odredite shader
registre. Na primer, slijedeća dva registra se preklapaju slot registra t0,
ali su različiti registri jer žive u različitim prostorima: Tekture2D
gDiffuseMap: registruje (t0, prostor0);
Tekture2D gNormalMap: registruje (t0, prostor1);
Ako u shaderu nije eksplicitno određen registar prostora, automatski se podrazumeva
space0. Obično koristimo prostor0, ali za nizove resursa korisno je koristiti više
prostore i neophodne ako su nizovi nepoznate veličine.
5. OffsetInDescriptorsFromTableStart: Offset ovog opsega
deskriptori od početka tabele. Pogledajte primer ispod.
Parametar slota koji je inicijalizovan kao deskriptorska tablica uzima niz
D3D12_DESCRIPTOR_RANGE instance jer možemo da mešamo različite vrste
deskriptori u jednom stolu. Pretpostavimo da smo definisali tabelu od šest deskriptora sljedećim
tri opsege po redu: dva CBVs, tri SRVs i jedan UAV. Ova tabela bi bila definisana
ovako:
// Create a table with 2 CBVs, 3 SRVs and 1 UAV.
CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(
D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // descriptor type
2, // descriptor count
0, // base shader register arguments are bound to
for this root
// parameter
0, // register space
0);// offset from start of table
descRange[1].Init(
D3D12_DESCRIPTOR_RANGE_TYPE_SRV, // descriptor type
3, // descriptor count
0, // base shader register arguments are bound to
for this root
// parameter
0, // register space
2);// offset from start of table
descRange[2].Init(
D3D12_DESCRIPTOR_RANGE_TYPE_UAV, // descriptor type
1, // descriptor count
0, // base shader register arguments are bound to
for this root
// parameter
0, // register space
5);// offset from start of table
slotRootParameter[0].InitAsDescriptorTable(
3, descRange, D3D12_SHADER_VISIBILITY_ALL);
As usual, there is a CD3DX12_DESCRIPTOR_RANGE variation that inherits from
D3D12_DESCRIPTOR_RANGE, and we use the following initialization function:
void CD3DX12_DESCRIPTOR_RANGE::Init(
D3D12_DESCRIPTOR_RANGE_TYPE rangeType,
UINT numDescriptors,
UINT baseShaderRegister,
UINT registerSpace = 0,
UINT offsetInDescriptorsFromTableStart =
D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND);
Ova tabela pokriva šest deskriptora, a aplikacija se očekuje da će se vezati za susedne raspon deskriptora
u skripcu deskriptora koji uključuje dva CBV-a, a zatim tri SRV-a a zatim jedan UAV. Vidimo da svi tipovi
raspona počinju u registru 0, ali nema "Preklapaju" konflikt jer se CBVs, SRVs i UAVs vezuju za različite
register tipovi, od kojih svaki počinje u registru 0.
Možemo imati Direct3D računati OffsetInDescriptorsFromTableStart vrednost za nas određivanjem
D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; ovo upućuje Direct3D da koristi
Prethodni opseg deskriptora broji u tablici da bi izračunao ofset. Imajte na umu da
CD3DKS12_DESCRIPTOR_RANGE :: Init metod podrazumeva prostor za registraciju na 0,
i OffsetInDescriptorsFromTableStart to
D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND.
7.6.3 Deskriptori korena
Parametar root root deskriptora se dalje definiše popunjavanjem Descriptora
član D3D12_ROOT_PARAMETER.
typedef struct D3D12_ROOT_DESCRIPTOR
{
UINT ShaderRegister;
UINT RegisterSpace;
}D3D12_ROOT_DESCRIPTOR;
1.ShaderRegister: Shader registruje deskriptor će biti vezan za. Za primer, ako navedete 2, a ovaj root
parametar je CBV onda dobije parameter mapirana na konstantni bafer u registru (b2): cbuffer cbPass:
register (b2) {...};
2. RegisterSpace: Pogledajte D3D12_DESCRIPTOR_RANGE :: RegisterSpace. Za razliku od deskriptorskih
tabela koje zahtevaju da postavimo deskriptorsku ručku u opisivaču kupiti, da postavimo deskriptor
korena, jednostavno vezujemo virtuelnu adresu resursa direktno.
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof
(ObjectConstants));
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB-
>GetGPUVirtualAddress();
// Offset to the constants for this object in the
buffer.
objCBAddress += ri->ObjCBIndex*objCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(
0, // root parameter index
objCBAddress);
7.6.4 Konstanti korena
Parametar root deskriptora se dalje definiše popunjavanjem Konstansa član D3D12_ROOT_PARAMETER.
typedef struct D3D12_ROOT_CONSTANTS
{
UINT ShaderRegister;
UINT RegisterSpace;
UINT Num32BitValues;
} D3D12_ROOT_CONSTANTS;
1. ShaderRegister: See D3D12_ROOT_DESCRIPTOR::ShaderRegister.
2. RegisterSpace: See D3D12_DESCRIPTOR_RANGE::RegisterSpace.
3. Num32BitValues: The number of 32-bit constants this root parameter expects.
Setting root constants still maps the data to a constant buffer from the shader’s
perspective. The following example illustrates:
// Application code: Root signature definition.
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
slotRootParameter[0].InitAsConstants(12, 0);
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1,
slotRootParameter,
0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
// Application code: to set the constants to register
b0.
auto weights = CalcGaussWeights(2.5f);
int blurRadius = (int)weights.size() / 2;
cmdList->SetGraphicsRoot32BitConstants(0, 1,
&blurRadius, 0);
cmdList->SetGraphicsRoot32BitConstants(0,
(UINT)weights.size(), weights.data(), 1);
// HLSL code.
cbuffer cbSettings : register(b0)
{
// We cannot have an array entry in a constant
buffer that gets
// mapped onto root constants, so list each
element.
int gBlurRadius;
// Support up to 11 blur weights.
float w0;
float w1;
float w2;
float w3;
float w4;
float w5;
float w6;
float w7;
float w8;
float w9;
float w10;
};
The
ID3D12GraphicsCommandList::SetGraphicsRoot32BitConstants
method has the following prototype:
void
ID3D12GraphicsCommandList::SetGraphicsRoot32BitConstants(
UINT RootParameterIndex,
UINT Num32BitValuesToSet,
const void *pSrcData,
UINT DestOffsetIn32BitValues);
1. RootParameterIndex: Index of the root parameter we are setting.
2. Num32BitValuesToSet: The number of 32-bit values to set.
3. pSrcData: Pointer to an array of 32-bit values to set.
4. DestOffsetIn32BitValues: Offset in 32-bit values in the constant buffer.
As with root descriptors, setting root constants bypasses the need for a descriptor
Heap.
7.6.5. Još komplikovaniji primer potpisa korena
Razmotrite shader koji očekuje sledeće resurse:
12.2.4 SV_PrimitiveID
Geometrijski shader u ovom primeru uzima poseban nepotpisani integer parametar sa semantičkim
SV_PrimitiveID.
[maxvertexcount(4)]
void GS(point VertexOut gin[1],
uint primID : SV_PrimitiveID,
inout TriangleStream<GeoOut> triStream)
Kada je specificiran ovaj semantik, on automatski prikazuje fazu asemblerskog unosa
generišite primitivnu identifikaciju za svaki primitiv. Kada se izvrši poziv za izvlačenje da bi se nacrtao n
Primitivi, prvi primitiv je označen kao 0; drugi primitiv je označen 1; i tako dalje,
dok se poslednji primitiv u pozivu poziva ne označava n-1. Primitivni ID-ovi su jedinstveni
za jedan poziv za izvlačenje. U našem primeru bilborda, geometrijski shader ne koristi ovaj ID
(iako bi geometrijski shader mogao); Umesto toga, geometrijski shader piše primitivni ID
na izlazne vertikale, pa je prosledio na fazu piksel shadera. Piksel shader
koristi primitivni ID za indeksiranje u niz teksture, što nas vodi do sledećeg odeljka.
float4 PS(VertexOut pin, uint primID :
SV_PrimitiveID) : SV_Target
{
// Pixel shader body…
}
Takođe je moguće imati ulazni asembler da generiše ID verteka. Uraditi
ovo, dodajte dodatni parametar tipa uint do vertek shader potpisa
sa semantičkim SV_VertekID:
Sledeći potpis vertek shader pokazuje kako se to radi:
VertexOut VS(VertexIn vin, uint vertID :
SV_VertexID)
{
// vertex shader body…
}
For a Draw call, the vertices in the draw call
will be labeled with IDs from 0, 1, …, n-1, where
n is the number of vertices in the draw call. For
a DrawIndexed call, the vertex IDs correspond to
the vertex index values.
12.3 TEKSTURNI ARRAJI
12.3.1 Pregled
Niz teksture čuva niz tekstura. U C ++ kodu je predstavljen niz tekstura
interfejsom ID3D12Resource baš kao i svi resursi (teksture i baferi).
Kada kreirate ID3D12Resource objekat, zapravo postoji imovina koja se zove
DepthOrArraiSize koji se može podesiti da odredi broj elemenata teksture
prodavnice teksture (ili dubina za 3D teksturu). Kada kreiramo teksturu u dubini / šablonu
d3dApp.cpp, uvek postavljamo ovo na 1. Ako pogledate CreateD3DResources12
funkcija u Common / DDSTektureLoader.cpp videćete kako kod podržava kreiranje
teksture i teksture zapremine. U HLSL datoteci, niz teksture je predstavljen od strane
Tekture2DArrai tip:
Tekture2DArrai gTreeMapArrai;
Sada se pitate zašto nam trebaju nizovi tekstura. Zašto ne samo to uradite:
Tekture2D TekArrai [4];
...
float4 PS (GeoOut pin): SV_Target
{float4 c
=
TekArrai [pin.PrimID% 4]. Primer (samLinear,
pin.Tek);
U shader modelu 5.1 (novi u Direct3D 12), mi to zapravo možemo učiniti. Međutim, ovo je bilo
nije dozvoljeno u prethodnim Direct3D verzijama. Štaviše, indeksiranje tekstura kao što je ovo može
imati malo iznad glave u zavisnosti od hardvera, pa ćemo u ovom poglavlju držati teksturu nizova.
12.3.2 Uzimanje uzoraka teksture
Na demonstraciji Billboard-a, uzorkujemo niz tekstura sa sledećim kodom:
float3 uvw = float3(pin.Tex, pin.PrimID%4);
float4 diffuseAlbedo = gTreeMapArray.Sample(
gsamAnisotropicWrap, uvw) * gDiffuseAlbedo;
Kada koristite niz teksture, potrebne su tri koordinate teksture. Prva dva
koordinate teksture su uobičajene koordinate 2D teksture; treća koordinata teksture je
indeksirati u niz teksture. Na primer, 0 je indeks prve teksture u nizu, 1 je
indeks drugoj teksturi u nizu, 2 je indeks treće teksture u nizu,
i tako dalje.
Na demonstracionom Bilbordu koristimo teksturni niz sa četiri elementa teksture, svaki sa a
različita tekstura drveta (Slika 12.7). Međutim, jer crtamo više od četiri stabla
po pozivu poziva, primitivni ID će postati veći od tri. Dakle, uzimamo
Primitivni ID modulo 4 (pin.PrimID% 4) da mapira primitivni ID na 0, 1, 2 ili 3,
koji su važeći indikatori polja za niz sa četiri elementa.
Slika 12.7. Bilbord slike.
Jedna od prednosti sa teksturnim nizovima jeste da smo mogli izvući kolekciju
primitivi, sa različitim teksturama, u jednom pozivu. Normalno, moramo imati a
odvojena render-stavka za svaku mrežu sa različitom teksturom:
SetTextureA();
DrawPrimitivesWithTextureA();
SetTextureB();
DrawPrimitivesWithTextureB();
…
SetTextureZ();
DrawPrimitivesWithTextureZ();
Each set and draw call has some overhead associated with it. With texture arrays, we
could reduce this to one set and one draw call:
SetTextureArray();
DrawPrimitivesWithTextureArray();
12.3.3 Umetanje nizova teksture
Naš DDS kod za učitavanje u Common / DDSTektureLoader.h / .cpp podržava DDS
datoteke koje skladište teksturne nizove. Dakle ključ je kreirati DDS datoteku koja sadrži teksturu
niz. Da bismo to uradili, koristićemo alatku za tekassemble koju nudi Microsoft
https://directktek.codeplek.com/vikipage?title=Tekassemble&referringTitle=Tekconv. The
sledeća sintaksa pokazuje kako kreirati teksturni niz pod nazivom treeArrai.dds iz 4 slike
t0.dds, t1.dds, t2.dds i t3.dds:
tekassemble -arrai-o treeArrai.dds t0.dds t1.dds
t2.dds t2.dds
Imajte na umu da prilikom izgradnje niza tekstura sa teksasemblom, ulazne slike trebaju
samo imaju jedan mipmap nivo. Nakon što se pozovete na tekassemble da biste napravili teksturu
niz, možete koristiti tekconv (https://directktek.codeplek.com/vikipage?title=Tekconv) za
generišu mipmape i promenite format piksela ako je potrebno:
tekconv -m 10 -f BC3_UNORM treeArrai.dds
12.3.4 Podnaslovi za teksturu
Sada kada smo razgovarali o nizovima tekstura, možemo razgovarati o podresorima. Figura
12.8 prikazuje primjer nizova teksture sa nekoliko tekstura. Zauzvrat, svaka tekstura ima svoju
sopstveni mipmap lanac. Direct3D API koristi pojam arrai slice da se odnosi na element u a
teksture zajedno sa svojim kompletnim mipmap lancem. Direct3D API koristi pojam mip slice
da se odnosi na sve mipmape na određenom nivou u nizu teksture. Podresno se odnosi na
jedan mipmap nivo u elementu nizova teksture.
Slika 12.8. Teksturni niz sa četiri teksture. Svaka tekstura ima tri mipmap-ove
nivoa.
S obzirom na indeks nizova teksture i nivo mipmap-a, možemo pristupiti pod-resursu u a
tekture arrai. Međutim, podresvori se takođe mogu označiti linearnim indeksom; Direct3D
koristi linearni indeks koji je naručen kao što je prikazano na slici 12.9.
Slika 12.9. Podnaslovi u nizu teksture označeni linearnim indeksom.
Sljedeća pomoćna funkcija se koristi za izračunavanje zadatog indeksa linearnog pod-izvora
nivo mip, indeks matrice i broj mipmap nivoa:
inline UINT D3D12CalcSubresource( UINT MipSlice, UINT
ArraySlice,
UINT PlaneSlice, UINT MipLevels, UINT ArraySize )
{
return MipSlice + ArraySlice * MipLevels +
PlaneSlice * MipLevels * ArraySize;
}
12.4 ALPHA-TO-COVERAGE
Kada se pokrene demo "Tree Billboard", primetite da na nekim razdaljinama ivice
Izrezi iz bilborda se javljaju bloki. Ovo je uzrokovano klip funkcijom, koju koristimo
da maskiraju piksele teksture koje nisu deo drveta; funkcija klipa ili
drži piksel ili odbacuje - nema glatke tranzicije. Udaljenost od očiju do
Bilbord igra ulogu jer kratke rastojanja rezultiraju povećanjem, što čini
blok artefakti veći, a kratke rastojanja rezultiraju u nivou mipmap manjih rezolucija
koristi se.
Jedan od načina za rješavanje ovog problema je korištenje blende transparentnosti umjesto alfa testa.
Zahvaljujući linearnom filtriranju tekstura, pikseli ivice će biti blago zamagljeni, čineći glatku
prelazak sa belih (neprozirnih piksela) u crni (maskirani pikseli). Transparentnost
mešanje će zbog toga prouzrokovati gladak izlivanje duž ivica od neprozirnih piksela do
maskirani pikseli. Nažalost, mešanje transparentnosti zahteva sortiranje i renderovanje
prednji red. Gornja površina za sortiranje malog broja bilborda nije visoka,
ali ako napravimo šumu ili travu preriju, sortiranje može biti skupo koliko i mora biti
obrađeni u svakom okviru; i gore je to što je rendering u redovnom porudžbini rezultirao masovnim
(vidi Vježba 8 u poglavlju 11), koja može ubiti performanse.
Možda se predlaže da MSAA (multisampling antialiasing-vidi §4.1.7) može pomoći, kao
MSAA se koristi za pomeranje blokiranih ivica poligona. Zaista, trebalo bi da bude u stanju da pomogne,
ali postoji problem. MSAA izvršava piksel shader jednom po pikselu, u centru piksela,
a zatim dijeli informacije o boji sa svojim podpikselima na osnovu vidljivosti (
test dubine / šablona se procenjuje po podpikselu) i pokrivenost (leži li centar subpiksela)
unutar ili izvan poligona?). Ključ je da se pokrivenost određuje na poligonu
nivo. Zbog toga, MSAA neće otkriti ivice izreza iz bilborda kao
definisani od strane alfa kanala - samo će pogledati ivice kvadrata koje su teksture
mapirana na. Dakle, postoji li način da se Direct3D kaže da uzme alfa kanal
razmatranje prilikom izračunavanja pokrivenosti? Odgovor je da, i vodi nas do
tehnika poznata kao alfa-do-pokrivenost.
Kada je MSAA omogućena, i omogućena je alpha-to-coverage (član je
D3D12_BLEND_DESC :: AlphaToCoverageEnable = true), hardver će
pogledajte vrijednost alfa koja je vratila piksel shader i koristite je za određivanje pokrivenosti
[NVIDIA05]. Na primer, sa 4Ks MSAA, ako je piksel shader alfa 0,5, onda možemo
pretpostavimo da su pokrivena dva od četiri potpikla i ovo će stvoriti gladak rub.
Opšti savet je da uvek želite da koristite alfa-za-pokrivanje alfa
maskirani izrezati teksture poput listja i ograda. Međutim, to zahtijeva da je MSAA
omogućeno. Imajte u vidu da smo u konstruktoru naše demo aplikacije postavili:
mEnable4kMsaa = true;
Ovo uzrokuje naš okvir uzorka da kreira odbojnike zadnje i dubine sa 4Ks MSAA podrška.
Poglavlje 13 THE COMPUTE SHADER
GPU-ovi su optimizovani da obraduju veliku količinu memorije iz jedne
lokacija ili sekvencijalnih lokacija (tzv. operacija striminga); ovo je u suprotnosti sa a
CPU dizajniran za slučajne pristupe memoriji [Boid10]. Štaviše, zato što su vertices i
pikseli se mogu samostalno obrađivati, grafički procesori su arhitekturirani da budu masivni
paralelno; Na primer, NVIDIA "Fermi" arhitektura podržava do šesnaest strimovanja
multiprocessors od trideset dve CUDA jezgre za ukupno 512 CUDA jezgara [NVIDIA09].
Očigledno je da grafika koristi ovu GPU arhitekturu, kao i arhitektura
dizajniran za grafiku. Međutim, neke ne-grafičke aplikacije imaju koristi od
ogromna količina računske snage koju GPU može pružiti svojom paralelnom arhitekturom.
Korišćenje GPU-a za ne-grafičke aplikacije naziva se GPU GPU (GPGPU)
programiranje. Nisu svi algoritmi idealni za implementaciju GPU-a; GPU-ovi trebaju dataljerne
algoritama da iskoriste prednosti paralelne arhitekture GPU-a. To jest, mi
trebaju velika količina elementa podataka koji će imati slične operacije na njima
tako da se elementi mogu paralelno obrađivati. Grafičke operacije kao što su senki pikseli
je dobar primer, jer svaki pikselni fragment koji se nacrta upravlja pikselnim shaderom.
Kao još jedan primer, ako pogledate kod za našu simulaciju talasa iz prethodnog
poglavlja, videćete to u koraku ažuriranja izvršimo izračunavanje na svakoj mreži
element. Ovo je takođe dobar kandidat za implementaciju grafičkog procesora, kao i svaki element
mreže
GPU može paralelno ažurirati. Sistemi čestica pružaju još jedan primjer,
gde se fizika svake čestice može izračunati nezavisno pod uslovom da uzmemo
pojednostavljivanje da čestice ne međusobno komuniciraju.
Za GPGPU programiranje, korisniku je obično potrebno pristupiti rezultatima računanja
nazad na CPU. Ovo zahteva kopiranje rezultata iz video memorije u sistemsku memoriju,
što je sporo (vidi sliku 13.1), ali može biti zanemarljivo pitanje u poređenju sa brzinom
iz vršenja računanja na GPU-u. Za grafiku obično koristimo računanje
rezultira kao ulaz za rendering pipeline, tako da nije potreban transfer od GPU-a do CPU-a. Za
na primer, možemo zamućiti teksturu pomoću kompjuterskog shadera, a zatim vezati izvor sidra
pogledajte tu zamućenu teksturu u shader kao ulaz.
Slika 13.1. Slika je prepravljena sa [Boid10]. Relativna memorija
brzine protoka između CPU-a i RAM-a, CPU-a i GPU-a, GPU-a i VRAM-a.
Ovi brojevi su samo ilustrativni brojevi koji pokazuju red veličine
razlika između propusnih opsega. Obratite pažnju da prenos memorije između CPU-a
i GPU je usko grlo.
Compute Shader je programabilni shader koji Direct3D otkriva što nije direktno
deo renderinga. Umesto toga, sjede na stranu i može čitati iz GPU-a
resursa i pišite na GPU resurse (slika 13.2). U suštini, Compute Shader
omogućava nam pristup GPU-u da implementiramo paralelne algoritme podataka bez crtanja
bilo šta. Kao što je pomenuto, ovo je korisno za programiranje GPGPU, ali ima još mnogo toga
grafički efekti koji se mogu implementirati i na kompjuterskom shaderu - tako da je i dalje
veoma relevantno za grafičkog programera. I kao što je već pomenuto, jer Compute
Shader je deo Direct3D-a, čita i piše na Direct3D resurse, što omogućava
mi da vezujemo izlaz kompjuterskog shadera direktno na rendering pipeline.
Slika 13.2. Kompjuterski shader nije deo ploče za rendering, ali se seli
u stranu. Računarski shader može da čita i piše na GPU resurse. Račun
shader se može mešati sa grafičkim renderingom ili se koristi samo za GPGPU
programiranje.
Ciljevi:
1. Da biste naučili kako da programirate izračunavanje shadera.
2. Da biste dobili osnovno razumevanje na visokom nivou o tome kako se hardver obrađuje
grupe i nitove unutar njih.
3. Da biste otkrili koji Direct3D resursi mogu biti postavljeni kao ulaz u izračunavajući shader i
koji Direct3D resursi mogu biti postavljeni kao izlaz u računarski shader.
4. Da biste razumeli različite ID-ove nitova i njihove upotrebe.
5. Da biste saznali o dijeljenoj memoriji i kako ga možete koristiti za optimizaciju performansi.
6. Da biste saznali gdje dobiti detaljnije informacije o programiranju GPGPU-a.
13.1 NIVO I NERVOZNE GRUPE
Kod GPU programiranja, broj navojenih tema za izvršavanje je podeljen u a
mreža nitnih grupa. Grupa nit je izvršena na jednom multiprocesoru. Stoga, ako
Imali ste GPU sa šesnaest multiprocessora, želeli biste da razbijete svoj problem
u najmanje šesnaest grupa navoja, tako da svaki multiprocesor ima posla. Za bolje
performanse, želeli biste bar dve grupe navoja po multiprocessoru od a
multiprocessor može preći na obradu niti u drugoj grupi da bi sakrili štandove
[Fung10] (može se pojaviti kašnjenje, na primer, ako shader treba da sačeka operaciju teksture
rezultat pre nego što nastavi sa sledećom instrukcijom).
Svaka grupa navoja dobija deljeno m emori koji mogu pristupiti svim temama u toj grupi; athread ne
može pristupiti dijeljenoj memoriji u drugoj grupi navoja. Operacije sinhronizacije kanala mogu se
odvijati među nitima u grupi navoja, ali različite grupne grupe ne mogu biti sinhronizovane. Zapravo, mi
nemamo kontrolu nad redosledom u kojem se obrađuju različite grupe nitova. Ovo ima smisla jer se
nitske grupe mogu izvesti na različitim multiprocessorima. Grupa sa nitima se sastoji od n nizova.
Hardver ustvari deli ove navoje upinto varps-a (trideset dva niza na osnove), a osnove obrađuje
multiprocessor inSIMD32 (tj. Iste upute se izvršavaju za trideset dva threada istovremeno). Svaki CUDA
jezgro obrađuje nit i podseća na to da "Fermi" multiprocesor ima trideset dve CUDA jezgre (pa je CUDA
jezgro kao SIMD "traka.") InDirect3D, možete odrediti veličinu grupe threada sa dimenzijama koje nisu
više od trideset -dva, ali iz razloga performansi, dimenzije grupe nitova treba uvek biti bemultiples of
varp size [Fung10]. Trežne veličine od 256 izgleda da su dobra polazna tačka koja treba dobro raditi na
različitim hardverima. Zatim eksperimentišite sa drugim veličinama. Promena broja thread pergroup će
promijeniti broj poslatih grupa.NVIDIA hardver koristi osnove veličine od trideset i dva navoja. ATI koristi
veličinu od 66 obrtaja i preporučuje da grupa nitova bude uvek višestruka od veličine talasnog fronta
[Bilodeau10]. Takođe, varijabla ili veličina vrana mogu se promijeniti u budućim generacijama hardvera.
U Direct3D grupi navoja se pokreću pomoću sledećeg poziva:
void ID3D12GraphicsCommandList::Dispatch(
UINT ThreadGroupCountX,
UINT ThreadGroupCountY,
UINT ThreadGroupCountZ);
Ovo vam omogućava da pokrenete 3D mrežu nitnih grupa; međutim, u ovoj knjizi ćemo
samo se bave 2D mrežama nitnih grupa. Sledeći primeri pokrenuti poziv
tri grupe u pravcu k i dve grupe u pravcu i za ukupno 3 × 2 = 6
nitne grupe (vidi sliku 13.3).Slika 13.3. Raspodela mreža sa 3 × 2 navojnim grupama. Svaka grupa tema
ima 8× 8 niti.
13.2 SIMPLE COMPUTE SHADER
Ispod je jednostavan kompjuterski shader koji sumira dve teksture, pod pretpostavkom da su sve
teksture iste veličine. Ovaj shader nije baš interesantan, ali ilustruje osnovnu sintaksu pisanje
kompjuterskog shadera.
cbuffer cbSettings
{
// Compute shader can access values in constant
buffers.
};
// Data sources and outputs.
Texture2D gInputA;
Texture2D gInputB;
RWTexture2D<float4> gOutput;
// The number of threads in the thread group. The
threads in a group can
// be arranged in a 1D, 2D, or 3D grid layout.
[numthreads(16, 16, 1)]
void CS(int3 dispatchThreadID : SV_DispatchThreadID)
// Thread ID
{
// Sum the xyth texels and store the result in the
xyth texel of
// gOutput.
gOutput[dispatchThreadID.xy] =
gInputA[dispatchThreadID.xy] +
gInputB[dispatchThreadID.xy];
}
Računarski shader se sastoji od sledećih komponenti:
1. Globalni promenljivi pristup preko konstantnih bafera.
2. Input i izlazne resurse, o kojima se govori u sledećem odeljku.
3. Atribut [numthreads (Ks, I, Z)], koji određuje broj navoja
u grupi navoja kao 3D mreža niti.
4. Telo shadera koje ima uputstva za izvršavanje za svaku nit.
5. Parametri vrednosti sistema za identifikaciju navoja (opisani u § 13.4).
Obratite pažnju da možemo definisati različite topologije grupe navoja; na primer, a
grupa navoja može biti jedna linija Ks niti [numthreads (Ks, 1, 1)] ili singl
kolona I threada [numthreads (1, I, 1)]. 2D nitne grupe Ks × I niti
može se napraviti postavljanjem z-dimenzije na 1 ovako [numthreads (Ks, I, 1)]. The
topologija koju izaberete biće diktirana problemom na kome radite. Kao što je pomenuto u
prethodni odeljak, ukupna brojanja nitova po grupi treba da bude višestruka od veličine osnove
(trideset dve za NVIDIA kartice) ili višestruke veličine na talasnoj fazi (šezdeset i četiri za ATI
kartice). Višestruka veličina na talasnoj fazi je takođe višestruka od veličine osnove, tako da izaberete a
višestruka veličina na talasima radi za obe vrste kartica.
13.2.1 Izračunajte PSO
Da bi omogućili računar shader, koristićemo specijalni "opis stanja stanja gasovoda".
Ova struktura ima daleko manje polja od D3D12_GRAPHICS_PIPELINE_STATE_DESC
jer kompjuterski shader sedi na stranu grafičke linije, pa sve grafike
Stanje na gasovodu se ne odnosi na izračunavanje shadera i stoga ga ne treba postavljati. Ispod
prikazuje primer stvaranja objekta za izračunavanje stanja gasovoda:
D3D12_COMPUTE_PIPELINE_STATE_DESC wavesUpdatePSO = {};
wavesUpdatePSO.pRootSignature =
mWavesRootSignature.Get();
wavesUpdatePSO.CS =
{
reinterpret_cast<BYTE*>(mShaders[“wavesUpdateCS”]-
>GetBufferPointer()),
mShaders[“wavesUpdateCS”]->GetBufferSize()
};
wavesUpdatePSO.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateComputePipelineState(
&wavesUpdatePSO,
IID_PPV_ARGS(&mPSOs[“wavesUpdate”])));
Računarski shader se sastoji od sledećih komponenti:
1. Globalni promenljivi pristup preko konstantnih bafera.
2. Input i izlazne resurse, o kojima se govori u sledećem odeljku.
3. Atribut [numthreads (Ks, I, Z)], koji određuje broj navoja
u grupi navoja kao 3D mreža niti.
4. Telo shadera koje ima uputstva za izvršavanje za svaku nit.
5. Parametri vrednosti sistema za identifikaciju navoja (opisani u § 13.4).
Obratite pažnju da možemo definisati različite topologije grupe navoja; na primer, a
grupa navoja može biti jedna linija Ks niti [numthreads (Ks, 1, 1)] ili singl
kolona I threada [numthreads (1, I, 1)]. 2D nitne grupe Ks × I niti
može se napraviti postavljanjem z-dimenzije na 1 ovako [numthreads (Ks, I, 1)]. The
topologija koju izaberete biće diktirana problemom na kome radite. Kao što je pomenuto u
prethodni odeljak, ukupna brojanja nitova po grupi treba da bude višestruka od veličine osnove
(trideset dve za NVIDIA kartice) ili višestruke veličine na talasnoj fazi (šezdeset i četiri za ATI
kartice). Višestruka veličina na talasnoj fazi je takođe višestruka od veličine osnove, tako da izaberete a
višestruka veličina na talasima radi za obe vrste kartica.
13.2.1 Izračunajte PSO
Da bi omogućili računar shader, koristićemo specijalni "opis stanja stanja gasovoda".
Ova struktura ima daleko manje polja od D3D12_GRAPHICS_PIPELINE_STATE_DESC
jer kompjuterski shader sedi na stranu grafičke linije, pa sve grafike
Stanje na gasovodu se ne odnosi na izračunavanje shadera i stoga ga ne treba postavljati. Ispod
prikazuje primer stvaranja objekta za izračunavanje stanja gasovoda:
Root potpis definiše koje parametre shader očekuje kao ulaz (CBVs, SRVs,
itd.). CS polje je gde smo odredili računarski shader. Sledeći kod pokazuje
Primer kompajliranja shadera u bajtod:
mShaders ["vavesUpdateCS"] = d3dUtil :: CompileShader (
L "Shaders \ VaveSim.hlsl", nullptr, "UpdateVavesCS",
"Cs_5_0");
13.3 INPUT PODATAKA I IZVORNI RESURSI
Dve vrste resursa mogu biti vezane za računarski shader: baferi i teksture. Mi
već su radili sa baferima kao što su vertikalni i indeksni baferi, i konstantni baferi.
Takođe smo upoznati sa teksturnim resursima iz Poglavlja 9.
13.3.1 Ulazi za teksturu
Kompjuterski shader definisan u prethodnom odeljku definisao je dve ulazne teksture
resursi:
Tekture2D gInputA;
Tekture2D gInputB;
Ulazne teksture gInputA i gInputB su vezane kao ulazi u shader od strane
kreiranje (SRVs) tekstura i njihovo prosleđivanje kao argument za korijenske parametre; za
primer:
cmdList-> SetComputeRootDescriptorTable (1, mSrvA);
cmdList-> SetComputeRootDescriptorTable (2, mSrvB);
Ovo je upravo onakav način na koji vezujemo prikaz resursa shadera na piksel shadere. Napomenuti da
SRV su samo za čitanje.
13.3.2 Izvori teksta i neuređeni pristupi (UAV)
Računarski shader definisan u prethodnom odeljku definisao je jedan izlazni izvor:
RVTekture2D <float4> gOutput;
Izlazi se tretiraju posebno i imaju poseban prefiks za njihov tip "RV", koji
označava čitanje i pisanje, a kao što to podrazumeva, možete čitati i pisati elemente u ovom
resurs u kompjuterskom shaderu. Nasuprot tome, teksture gInputA i gInputB su
samo za čitanje. Takođe, potrebno je odrediti vrstu i dimenzije izlaza sa
sintaksa sa uglovima u obliku šablona <float4>. Ako je naš izlaz bio 2D integer kao što je
DKSGI_FORMAT_R8G8_SINT, onda bismo umesto toga napisali:
RVTekture2D <int2> gOutput;
Međutim, vezivanje izlaznog resursa razlikuje se od unosa. Da biste povezali resurs koji je
mi ćemo da pišemo u izračunavajućem shaderu, moramo da je vezemo koristeći novi tip prikaza koji se
zove an
neuređen prikaz pristupa (UAV), koji je kodiran kodom pomoću descriptor drške i
opisan u kodu strukture D3D12_UNORDERED_ACCESS_VIEV_DESC. Ovo je
stvoren na sličan način prikazu resursa shadera. Evo primera koji stvara UAV
do resursa teksture:
D3D12_RESOURCE_DESC texDesc;
ZeroMemory(&texDesc, sizeof(D3D12_RESOURCE_DESC));
texDesc.Dimension =
D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texDesc.Alignment = 0;
texDesc.Width = mWidth;
texDesc.Height = mHeight;
texDesc.DepthOrArraySize = 1;
texDesc.MipLevels = 1;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
texDesc.Flags =
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&texDesc,
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(&mBlurMap0)));
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping =
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = mFormat;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = 1;
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.Format = mFormat;
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
uavDesc.Texture2D.MipSlice = 0;
md3dDevice->CreateShaderResourceView(mBlurMap0.Get(),
&srvDesc, mBlur0CpuSrv);
md3dDevice-
>CreateUnorderedAccessView(mBlurMap0.Get(),
nullptr, &uavDesc, mBlur0CpuUav);
Obratite pažnju da ako tekstura bude vezana kao UAV, onda mora biti kreirana sa
D3D12_RESOURCE_FLAG_ALLOV_UNORDERED_ACCESS zastava; u prethodnom primeru, tekstura će biti
vezana kao UAV i kao SRV (ali ne i simultano). Ovo je obično, jer često koristimo kompjuterski shader da
izvršimo neku operaciju na teksturi (tako tekstura će biti vezana za računarski shader kao UAV), a zatim
nakon toga, želimo geometriju teksture sa njim, tako da će biti vezana za verteks ili piksel shader kao
SRV.
Podsetimo se na tipku tipografskog opisa
D3D12_DESCRIPTOR_HEAP_TIPE_CBV_SRV_UAV može miješati CBVs, SRVs i UAVs sve u istom kupu.
Stoga, mi možemo staviti UAV opise u tu kupu. Jednom kada su u kupom, mi jednostavno prosleđemo
ručke deskriptora kao argumente za parametre korena koji se vezuju resursa do gasovoda za
otpremnicu. Razmotrite sledeći korijenski potpis za a izračunati shader:
void BlurApp::BuildPostProcessRootSignature()
{
CD3DX12_DESCRIPTOR_RANGE srvTable;
srvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1,
0);
CD3DX12_DESCRIPTOR_RANGE uavTable;
uavTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1,
0);
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[3];
// Perfomance TIP: Order from most frequent to least
frequent.
slotRootParameter[0].InitAsConstants(12, 0);
slotRootParameter[1].InitAsDescriptorTable(1,
&srvTable);
slotRootParameter[2].InitAsDescriptorTable(1,
&uavTable);
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(3,
slotRootParameter,
0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
// create a root signature with a single slot which
points to a
// descriptor range consisting of a single constant
buffer
ComPtr<ID3DBlob> serializedRootSig = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
HRESULT hr =
D3D12SerializeRootSignature(&rootSigDesc,
D3D_ROOT_SIGNATURE_VERSION_1,
serializedRootSig.GetAddressOf(),
errorBlob.GetAddressOf());
if(errorBlob != nullptr)
{
::OutputDebugStringA((char*)errorBlob-
>GetBufferPointer());
}
ThrowIfFailed(hr);
ThrowIfFailed(md3dDevice->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(mPostProcessRootSignature.GetAddressOf())));
}
The root signature defines that the shader expects a constant buffer for root parameter
slot 0, an SRV for root parameter slot 1, and a UAV for root parameter slot 2. Before a
dispatch invocation, we bind the constants and descriptors to use for this dispatch call:
cmdList->SetComputeRootSignature(rootSig);
cmdList->SetComputeRoot32BitConstants(0, 1,
&blurRadius, 0);
cmdList->SetComputeRoot32BitConstants(0,
(UINT)weights.size(), weights.data(), 1);
cmdList->SetComputeRootDescriptorTable(1,
mBlur0GpuSrv);
cmdList->SetComputeRootDescriptorTable(2,
mBlur1GpuUav);
UINT numGroupsX = (UINT)ceilf(mWidth / 256.0f);
cmdList->Dispatch(numGroupsX, mHeight, 1);
13.3.3 Indeksiranje i uzorkovanje tekstura
Elementima tekstura se može pristupiti pomoću 2D indeksa. U izračunavajućem shaderu definisanoj u §
13.2, indeksiramo teksturu na osnovu ID-a otpreme (ID-ovi nitova su opisani u § 13.4). Svaki nit dobija
jedinstven ID otpreme.
[numthreads(16, 16, 1)]
void CS(int3 dispatchThreadID : SV_DispatchThreadID)
{
// Sum the xyth texels and store the result in the
xyth texel of
// gOutput.
gOutput[dispatchThreadID.xy] =
gInputA[dispatchThreadID.xy] +
gInputB[dispatchThreadID.xy];
}
Pod pretpostavkom da smo otpremili dovoljne grupe navoja da pokrijemo teksturu (tj je jedna nit
izvršena za jedan teksel), onda ovaj kod sumira slike teksture I čuva rezultat u teksturi gOutput.
Ponašanje indeksa izvan granica je dobro definisano u računarskom shaderu.
Izvan granica čita povratak 0, a izvan granica piše rezultiraju ne-ops [Boid08].
Pošto se računarski shader izvršava na GPU, on ima pristup uobičajenom grafičkom procesoru
alati. Konkretno, možemo uzorkovati teksture koristeći filtriranje teksture. Postoje dva pitanja,
Međutim. Prvo, ne možemo koristiti Sample metod, već moramo koristiti
SampleLevel metod. SampleLevel ima dodatni treći parametar
određuje mipmap nivo teksture; 0 uzima najviši nivo, 1 uzme drugi
nivo mip itd., a delne vrednosti se koriste za interpolaciju između dva mip nivoa
omogućeno je linearno mip filtriranje. S druge strane, Sample automatski bira najbolje
mipmap nivo koji će se koristiti na osnovu broja piksela na ekranu koji će pokriti teksturu. Od
izračunati shadere se ne koriste za rendiranje direktno, ne zna kako se automatski
izaberite mipmap nivo ovakav, i stoga moramo eksplicitno odrediti nivo sa
SampleLevel u kompjuterskom shaderu. Drugo pitanje je da kada izmerimo teksturu,
Koristimo normalizovane teksturne koordinate u opsegu [0, 1] 2 umjesto celih indeksa.
Međutim, veličina teksture (širina, visina) se može podesiti na konstantnu varijablu bafera, a zatim
Normirane koordinate teksture mogu se izvesti iz integralnih indeksa (k, y):
U=v/width V=y/height
Sledeći kod prikazuje kompjuterski shader koji koristi integrisane indekse, a drugi
ekvivalentnu verziju koja koristi teksturne koordinate i SampleLevel, gde se pretpostavlja
veličina teksture je 512 × 512 i samo nam je potreban mip top level:
//
// VERSION 1: Using integer indices.
//
cbuffer cbUpdateSettings
{
float gWaveConstant0;
float gWaveConstant1;
float gWaveConstant2;
float gDisturbMag;
int2 gDisturbIndex;
};
RWTexture2D<float> gPrevSolInput : register(u0);
RWTexture2D<float> gCurrSolInput : register(u1);
RWTexture2D<float> gOutput : register(u2);
[numthreads(16, 16, 1)]
void CS(int3 dispatchThreadID : SV_DispatchThreadID)
{
int x = dispatchThreadID.x;
int y = dispatchThreadID.y;
gNextSolOutput[int2(x,y)] =
gWaveConstants0*gPrevSolInput[int2(x,y)].r +
gWaveConstants1*gCurrSolInput[int2(x,y)].r +
gWaveConstants2*(
gCurrSolInput[int2(x,y+1)].r +
gCurrSolInput[int2(x,y-1)].r +
gCurrSolInput[int2(x+1,y)].r +
gCurrSolInput[int2(x-1,y)].r);
}
//
// VERSION 2: Using SampleLevel and texture
coordinates.
//
cbuffer cbUpdateSettings
{
float gWaveConstant0;
float gWaveConstant1;
float gWaveConstant2;
float gDisturbMag;
int2 gDisturbIndex;
};
SamplerState samPoint : register(s0);
RWTexture2D<float> gPrevSolInput : register(u0);
RWTexture2D<float> gCurrSolInput : register(u1);
RWTexture2D<float> gOutput : register(u2);
[numthreads(16, 16, 1)]
void CS(int3 dispatchThreadID : SV_DispatchThreadID)
{
// Equivalently using SampleLevel() instead of
operator [].
int x = dispatchThreadID.x;
int y = dispatchThreadID.y;
float2 c = float2(x,y)/512.0f;
float2 t = float2(x,y-1)/512.0;
float2 b = float2(x,y+1)/512.0;
float2 l = float2(x-1,y)/512.0;
float2 r = float2(x+1,y)/512.0;
gNextSolOutput[int2(x,y)] =
gWaveConstants0*gPrevSolInput.SampleLevel(samPoint,
c, 0.0f).r +
gWaveConstants1*gCurrSolInput.SampleLevel(samPoint,
c, 0.0f).r +
gWaveConstants2*(
gCurrSolInput.SampleLevel(samPoint, b, 0.0f).r
+
gCurrSolInput.SampleLevel(samPoint, t, 0.0f).r
+
gCurrSolInput.SampleLevel(samPoint, r, 0.0f).r
+
gCurrSolInput.SampleLevel(samPoint, l, 0.0f).r);
}
13.3.4 Strukturisani resursi bafera
Sledeći primeri pokazuju kako su strukturni baferi definisani u HLSL-u:
struct Data
{
float3 v1;
float2 v2;
};
StructuredBuffer<Data> gInputA : register(t0);
StructuredBuffer<Data> gInputB : register(t1);
RWStructuredBuffer<Data> gOutput : register(u0);
Strukturni bafer je jednostavno bafer elemenata istog tipa - u suštini an
niz. Kao što vidite, tip može biti korisnički definisana struktura u HLSL-u.
Strukturirani bafer koji se koristi kao SRV može se kreirati baš kao što smo mi stvarali
verteka i indeksnih bafera. Strukturni bafer koji se koristi kao UAV je skoro stvoren isti
osim toga da moramo da odredimo zastavu
D3D12_RESOURCE_FLAG_ALLOV_UNORDERED_ACCESS, i dobra je praksa da
stavite je u stanje D3D12_RESOURCE_STATE_UNORDERED_ACCESS.
struct Data
{
XMFLOAT3 v1;
XMFLOAT2 v2;
};
// Generate some data to fill the SRV buffers with.
std::vector<Data> dataA(NumDataElements);
std::vector<Data> dataB(NumDataElements);
for(int i = 0; i < NumDataElements; ++i)
{
dataA[i].v1 = XMFLOAT3(i, i, i);
dataA[i].v2 = XMFLOAT2(i, 0);
dataB[i].v1 = XMFLOAT3(-i, i, 0.0f);
dataB[i].v2 = XMFLOAT2(0, -i);
}
UINT64 byteSize = dataA.size()*sizeof(Data);
// Create some buffers to be used as SRVs.
mInputBufferA = d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(),
mCommandList.Get(),
dataA.data(),
byteSize,
mInputUploadBufferA);
mInputBufferB = d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(),
mCommandList.Get(),
dataB.data(),
byteSize,
mInputUploadBufferB);
// Create the buffer that will be a UAV.
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
nullptr,
IID_PPV_ARGS(&mOutputBuffer)));
Strukturirani baferi su vezani za gasovod kao i teksture. Mi kreiramo SRV ili UAV deskriptori za njih i
prenose ih kao argumente za root parametre koji uzimaju descriptor tabele. Alternativno, možemo
definisati root potpis kako bi usvojili root deskriptore tako da mi može preneti virtuelnu adresu resursa
direktno kao root argumente bez potrebe za odlaskom kroz skripte deskriptora (ovo radi samo za SRVs i
UAVs da bafer resursa, ne teksture). Razmotrite sledeći opis korenskog potpisa:
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[3];
// Perfomance TIP: Order from most frequent to least
frequent.
slotRootParameter[0].InitAsShaderResourceView(0);
slotRootParameter[1].InitAsShaderResourceView(1);
slotRootParameter[2].InitAsUnorderedAccessView(0);
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(3,
slotRootParameter,
0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_NONE);
Then we can bind our buffers like so to be used for a dispatch call:
mCommandList-
>SetComputeRootSignature(mRootSignature.Get());
mCommandList->SetComputeRootShaderResourceView(0,
mInputBufferA->GetGPUVirtualAddress());
mCommandList->SetComputeRootShaderResourceView(1,
mInputBufferB->GetGPUVirtualAddress());
mCommandList->SetComputeRootUnorderedAccessView(2,
mOutputBuffer->GetGPUVirtualAddress());
mCommandList->Dispatch(1, 1, 1);
13.3.5 Kopiranje rezultata CS na sistemsku memoriju
Tipično, kada koristimo kompjuterski shader za obradu teksture, to ćemo prikazati
obrađena tekstura na ekranu; stoga vizuelno vidimo rezultat kako bismo potvrdili tačnost
našeg kompjuterskog shadera. Sa strukturnim proračunom bafera i GPGPU računarima
uopšte, mi možda nećemo prikazivati rezultate. Dakle, pitanje je kako ćemo dobiti
rezultati iz GPU memorije (zapamtite kada pišemo u strukturirani bafer preko UAV-a,
taj bafer je memorisan u GPU memoriji) nazad u sistemsku memoriju. Potreban je način da se
kreirajte bafer sistemske memorije sa svojstvima kupovine D3D12_HEAP_TIPE_READBACK.
Zatim možemo da koristimo ID3D12GraphicsCommandList :: CopiResource metodu
kopirajte grafički resurs na sistemsku memorijsku resurs. Resurs sistema memorije
mora biti isti tip i veličina kao izvor koji želimo da kopirate. Na kraju, možemo da mapiramo
sistemski memorijski bafer sa API za mapiranje da bi ga pročitao na CPU-u. Odatle možemo
zatim kopirajte podatke u niz sistemske memorije za dalju obradu na CPU strani,
sačuvajte podatke u datoteku ili šta imate.
Uključili smo strukturirani demo buffer za ovo poglavlje pod nazivom "VecAdd", koji
jednostavno sumira odgovarajuće vektorske komponente koje se čuvaju u dva strukturna bafera:
struct Data
{
float3 v1;
float2 v2;
};
StructuredBuffer<Data> gInputA : register(t0);
StructuredBuffer<Data> gInputB : register(t1);
RWStructuredBuffer<Data> gOutput : register(u0);
[numthreads(32, 1, 1)]
void CS(int3 dtid : SV_DispatchThreadID)
{
gOutput[dtid.x].v1 = gInputA[dtid.x].v1 +
gInputB[dtid.x].v1;
gOutput[dtid.x].v2 = gInputA[dtid.x].v2 +
gInputB[dtid.x].v2;
}
Za jednostavnost, strukturirani baferi sadrže samo trideset i dva elementa; stoga, mi
samo treba da pošalju jednu nitsku grupu (pošto jedna nit grupa obrađuje trideset dva
elementi). Nakon što kompjuterski shader završi svoj rad za sve teme u ovom demo-u, mi
kopirajte rezultate u sistemsku memoriju i sačuvajte ih u datoteku. Sledeći kod pokazuje kako
da napravite bafer sistemske memorije i kako da kopirate GPU rezultate u CPU memoriju:
// Create a system memory version of the buffer to
read the
// results back from.
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_READBACK),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&mReadBackBuffer)));
// …
//
// Compute shader finished!
struct Data
{
XMFLOAT3 v1;
XMFLOAT2 v2;
};
// Schedule to copy the data to the default buffer to
the readback buffer.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mOutputBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COPY_SOURCE));
mCommandList->CopyResource(mReadBackBuffer.Get(),
mOutputBuffer.Get());
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mOutputBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_COMMON));
// Done recording commands.
ThrowIfFailed(mCommandList->Close());
// Add the command list to the queue for execution.
ID3D12CommandList* cmdsLists[] = { mCommandList.Get()
};
mCommandQueue-
>ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// Wait for the work to finish.
FlushCommandQueue();
// Map the data so we can read it on CPU.
Data* mappedData = nullptr;
ThrowIfFailed(mReadBackBuffer->Map(0, nullptr,
reinterpret_cast<void**>(&mappedData)));
std::ofstream fout(“results.txt”);
for(int i = 0; i < NumDataElements; ++i)
{
fout << “(” << mappedData[i].v1.x << “, ” <<
mappedData[i].v1.y << “, ” <<
mappedData[i].v1.z << ”, ” <<
mappedData[i].v2.x << “, ” <<
mappedData[i].v2.y << “)” << std::endl;
}
mReadBackBuffer->Unmap(0, nullptr);
In the demo, we fill the two input buffers with the
following initial data:
std::vector<Data> dataA(NumDataElements);
std::vector<Data> dataB(NumDataElements);
for(int i = 0; i < NumDataElements; ++i)
{
dataA[i].v1 = XMFLOAT3(i, i, i);
dataA[i].v2 = XMFLOAT2(i, 0);
dataB[i].v1 = XMFLOAT3(-i, i, 0.0f);
dataB[i].v2 = XMFLOAT2(0, -i);
}
The resulting text file contains the following data, which confirms that the compute
shader is working as expected.
(0, 0, 0, 0, 0)
(0, 2, 1, 1, -1)
(0, 4, 2, 2, -2)
(0, 6, 3, 3, -3)
(0, 8, 4, 4, -4)
(0, 10, 5, 5, -5)
(0, 12, 6, 6, -6)
(0, 14, 7, 7, -7)
(0, 16, 8, 8, -8)
(0, 18, 9, 9, -9)
(0, 20, 10, 10, -10)
(0, 22, 11, 11, -11)
(0, 24, 12, 12, -12)
(0, 26, 13, 13, -13)
(0, 28, 14, 14, -14)
(0, 30, 15, 15, -15)
(0, 32, 16, 16, -16)
(0, 34, 17, 17, -17)
(0, 36, 18, 18, -18)
(0, 38, 19, 19, -19)
(0, 40, 20, 20, -20)
(0, 42, 21, 21, -21)
(0, 44, 22, 22, -22)
(0, 46, 23, 23, -23)
(0, 48, 24, 24, -24)
(0, 50, 25, 25, -25)
(0, 52, 26, 26, -26)
(0, 54, 27, 27, -27)
(0, 56, 28, 28, -28)
(0, 58, 29, 29, -29)
(0, 60, 30, 30, -30)
(0, 62, 31, 31, -31)
1.Svaku grupu navoja sistem dodeljuje ID; ovo se zove ID grupe i
ima sistemsku vrednost semantički SV_GroupID. Ako su Gk × Gi × Gz brojevi
otpuštene grupe navoja, onda se ID grupe kreće od (0, 0, 0) do (Gk - 1, Gi - 1,
Gz - 1).
2. U grupi navoja, svaka nit ima jedinstveni ID u odnosu na svoju grupu. Ako je
grupa navoja ima veličinu Ks × I × Z, onda će ID grupe grupe biti od (0, 0, 0) do
(Ks - 1, I - 1, Z - 1). Semantički sistem vrednosti za ID grupe grupe je
SV_GroupThreadID.
3. Poziv za otpremu šalje mrežu grupa navoja. ID otpremanja navoja
jedinstveno identifikuje nit u odnosu na sve teme koje generiše Dispečerski poziv.
Drugim rečima, dok ID thread grupe jedinstveno identifikuje nit u odnosu na njegovu
grupa navoja, identifikaciona navoja ID jedinstveno identifikuje nit u odnosu na sindikat
svih navoja iz svih grupa navoja poslatih putem Dispečerskog poziva. Dozvoliti,
ThreadGroupSize = (Ks, I, Z) je veličina thread grupe, zatim otpremanje
ID teksta može biti izveden iz ID grupe i ID grupe grupe na sledeći način:
dispatchThreadID.kiz = groupID.kiz *
ThreadGroupSize.kiz + groupThreadID.kiz;
ID otpremanske niti ima semantičku vrednost sistema
SV_DispatchThreadID. Ako se otpremaju 3 × 2 grupe navoja, gde je svaka
grupa navoja je 10 × 10, onda se šalje ukupno 60 niti i otprema
ID-ji nitova će se kreću od (0, 0, 0) do (29, 19, 0).
4. Verzija linearnog indeksa ID grupe grupe nam daje Direct3D kroz
Vrednost sistema SV_GroupIndek; izračunava se kao:
groupIndek =
groupThreadID.z * ThreadGroupSize.k * ThreadGroupSize.i +
groupThreadID.i * ThreadGroupSize.k +
groupThreadID.k;
Što se tiče indeksiranja koordinatnog poretka, prva koordinata daje kposition
(ili kolona), a druga koordinata daje i-poziciju (ili red).
Ovo je u suprotnosti sa običnim matričnim zapisima, pri čemu Mij označava element u
ista i jth kolona.
Pa zašto nam trebaju ove vrednosti ID-a nitima. Pa obično shader obično uzima
neka ulazna struktura podataka i izlazi u neku strukturu podataka. Možemo koristiti ID niti
vrednosti kao indeksi u ove strukture podataka:
Texture2D gInputA;
Texture2D gInputB;
RWTexture2D<float4> gOutput;
[numthreads(16, 16, 1)]
void CS(int3 dispatchThreadID : SV_DispatchThreadID)
{
// Use dispatch thread ID to index into output and
input textures.
gOutput[dispatchThreadID.xy] =
gInputA[dispatchThreadID.xy] +
gInputB[dispatchThreadID.xy];
13.5 PRIPREMA I POTROŠNI BUFERI
Pretpostavimo da imamo bafer čestica definisanih strukturom:
struct Particle
{
float3 Position;
float3 Velocity;
float3 Acceleration;
};
i želimo da ažuriramo položaje čestica na osnovu njihovog konstantnog ubrzanja I brzina u
kompjuterskom shaderu. Osim toga, pretpostavimo da nam nije briga za naređenje čestice se ažuriraju,
niti red koji su upisani u izlazni bafer. Consume i dodajte strukturirane odbojnike su idealne za ovaj
scenario, a one pružaju pogodnost Ne moramo brinuti o indeksiranju:
struct Particle
{
float3 Position;
float3 Velocity;
float3 Acceleration;
};
float TimeStep = 1.0f / 60.0f;
ConsumeStructuredBuffer<Particle> gInput;
AppendStructuredBuffer<Particle> gOutput;
[numthreads(16, 16, 1)]
void CS()
{
// Consume a data element from the input buffer.
Particle p = gInput.Consume();
p.Velocity += p.Acceleration*TimeStep;
p.Position += p.Velocity*TimeStep;
// Append normalized vector to output buffer.
gOutput.Append( p );
}
nit će konzumirati tačno jedan element podataka. I ponovo, naglašavamo da je red
elementi se troše i dodani su nepoznati; stoga, generalno nije slučaj
da se i-ti element u ulaznom baferu upisuje u i-ti element u izlaznom baferu.
Dodavanje strukturiranih bafera ne dinamički raste. I dalje moraju biti velike
dovoljno da sačuvate sve elemente koje ćete dodati.
13.6 SHARED MEMORI AND SINCHRONIZATION
Grupe navoja dobijaju deo tzv. Deljene memorije ili lokalne memorije niti.
Pristupanje ovom memoriji je brzo i može se smatrati brzinom kao hardverskom keš memorijom. In
kod računskog shadera, deljena memorija proglašena je tako:
grouphared float4 gCache [256];
Veličina niza može biti šta god želite, ali maksimalna veličina grupe deli
memorija je 32kb. Pošto je zajednička memorija lokalna za grupu navoja, ona je indeksirana
sa SV_ThreadGroupID; tako, na primer, možete dati svaku nit u grupi
pristup jednom slotu u deljenoj memoriji.
Korišćenje previše deljene memorije može dovesti do problema sa performansama [Fung10], kao
sledeći primer ilustruje. Pretpostavimo da multiprocessor podržava 32kb deljene memorije,
i vaš računar shader zahteva 20kb deljene memorije. To znači da samo jedan
Grupa navoja će se spojiti na multiprocesor jer nema dovoljno memorije
druga nit grupa [Fung10], kao 20kb + 20kb = 40kb> 32kb. Ovo ograničava paralelizam
GPU-a, kao multiprocesor ne može se isključiti između grupa navoja da bi sakrili latentnost
(podsjetimo iz §13.1 da se preporučuje najmanje dvije grupe navoja po multiprocessoru).
Stoga, iako hardver tehnički podržava 32kb deljene memorije,
poboljšanja performansi se mogu postići korišćenjem manje.
Uobičajena primjena dijeljene memorije je da se u njemu čuvaju teksture. Izvestan
algoritmi, kao što je zamagljivanje, zahtevaju više istih teksela. Uzimanje uzorka
teksture su zapravo jedna od sporijih GPU operacija zbog propusnog opsega i memorije
Latencija memorije nije poboljšana koliko i sirova računarska snaga GPU-a
[Moller08]. Grupa navoja može da izbegne redundantne privlačne teksture tako što unapred učitava sve
potrebne uzorke teksture u deljeni memorijski niz. Tada algoritam nastavlja da gleda
uzorke teksture u nizu deljene memorije, što je vrlo brzo. Pretpostavimo da smo
implementirati ovu strategiju sa sledećim pogrešnim kodom:
Texture2D gInput;
RWTexture2D<float4> gOutput;
groupshared float4 gCache[256];
[numthreads(256, 1, 1)]
void CS(int3 groupThreadID : SV_GroupThreadID,
int3 dispatchThreadID : SV_DispatchThreadID)
{
// Each thread samples the texture and stores the
// value in shared memory.
gCache[groupThreadID.x] =
gInput[dispatchThreadID.xy];
// Do computation work: Access elements in shared
memory
// that other threads stored:
// BAD!!! Left and right neighbor threads might not
have
// finished sampling tzZhe texture and storing it in
shared memory.
float4 left = gCache[groupThreadID.x - 1];
float4 right = gCache[groupThreadID.x + 1];
…
}
Problem proizlazi iz ovog scenarija jer nemamo nikakvu garanciju da su sve teme u grupi navoja
istovremeno završava. Na taj način nit bi mogla pristupiti dijeljenom memorijski element koji još nije
inicijalizovan jer su susedne niti odgovorne Inicijalizacija tih elemenata još nije završena. Da rešite ovaj
problem, pre računanja shader može da nastavi, mora da sačeka dok se sve niti ne učine u svoje teksture
deljena memorija. Ovo se postiže sinhronizacijom naredbom:
Texture2D gInput;
RWTexture2D<float4> gOutput;
groupshared float4 gCache[256];
[numthreads(256, 1, 1)]
void CS(int3 groupThreadID : SV_GroupThreadID,
int3 dispatchThreadID : SV_DispatchThreadID)
{
// Each thread samples the texture and stores the
// value in shared memory.
gCache[groupThreadID.x] =
gInput[dispatchThreadID.xy];
// Wait for all threads in group to finish.
GroupMemoryBarrierWithGroupSync();
// Safe now to read any element in the shared
memory
//and do computation work.
float4 left = gCache[groupThreadID.x - 1];
float4 right = gCache[groupThreadID.x + 1];
…
}
void BlurApp::OnResize()
{
D3DApp::OnResize();
// The window resized, so update the aspect ratio
and
// recompute the projection matrix.
XMMATRIX P = XMMatrixPerspectiveFovLH(
0.25f*MathHelper::Pi, AspectRatio(),
1.0f, 1000.0f);
XMStoreFloat4x4(&mProj, P);
if(mBlurFilter != nullptr)
{
mBlurFilter->OnResize(mClientWidth,
mClientHeight);
}
}
void BlurFilter::OnResize(UINT newWidth, UINT
newHeight)
{
if((mWidth != newWidth) || (mHeight != newHeight))
{
mWidth = newWidth;
mHeight = newHeight;
// Rebuild the off-screen texture resource with
new dimensions.
BuildResources();
// New resources, so we need new descriptors to
that resource.
BuildDescriptors();
}
}
MBlur varijabla je instanca BlurFilter helper klase koju napravimo. Ovo
klasa inkapsira teksturne resurse na teksture A i B, inkapsulira SRVs i UAVs
na teksture i daje metod koji počinje stvarno zamućivanje na
izračunati shader, čiju implementaciju ćemo videti za trenutak.
Klasa BlurFilter obuhvata resurse teksture. Za vezivanje ovih resursa
cevovod koji treba koristiti za komandu izvlačenja / otpreme, moraćemo da napravimo
deskriptori ovih resursa. To znači da ćemo morati dodijeliti dodatnu sobu u
D3D12_DESCRIPTOR_HEAP_TIPE_CBV_SRV_UAV skripata sa deskriptorima za čuvanje ovih
deskriptori. Metoda BlurFilter :: BuildDescriptors preuzima deskriptor
rukuje se na početnu lokaciju u gomilu deskriptora za čuvanje deskriptora koji koriste
BlurFilter. Metoda koči ručice za sve deskriptore koje mu je potrebno, a zatim
stvara odgovarajuće deskriptore. Razlog zbog koga se drže ručice je to što može
ponovo kreirajte deskriptore kada se resursi menjaju, što se dešava kada je ekran
promenjena:
void BlurFilter::BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuDescriptor,
CD3DX12_GPU_DESCRIPTOR_HANDLE hGpuDescriptor,
UINT descriptorSize)
{
// Save references to the descriptors.
mBlur0CpuSrv = hCpuDescriptor;
mBlur0CpuUav = hCpuDescriptor.Offset(1,
descriptorSize);
mBlur1CpuSrv = hCpuDescriptor.Offset(1,
descriptorSize);
mBlur1CpuUav = hCpuDescriptor.Offset(1,
descriptorSize);
mBlur0GpuSrv = hGpuDescriptor;
mBlur0GpuUav = hGpuDescriptor.Offset(1,
descriptorSize);
mBlur1GpuSrv = hGpuDescriptor.Offset(1,
descriptorSize);
mBlur1GpuUav = hGpuDescriptor.Offset(1,
descriptorSize);
BuildDescriptors();
}
void BlurFilter::BuildDescriptors()
{
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping =
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = mFormat;
srvDesc.ViewDimension =
D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = 1;
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.Format = mFormat;
uavDesc.ViewDimension =
D3D12_UAV_DIMENSION_TEXTURE2D;
uavDesc.Texture2D.MipSlice = 0;
md3dDevice-
>CreateShaderResourceView(mBlurMap0.Get(), &srvDesc,
mBlur0CpuSrv);
md3dDevice-
>CreateUnorderedAccessView(mBlurMap0.Get(),
nullptr, &uavDesc, mBlur0CpuUav);
md3dDevice-
>CreateShaderResourceView(mBlurMap1.Get(), &srvDesc,
mBlur1CpuSrv);
md3dDevice-
>CreateUnorderedAccessView(mBlurMap1.Get(),
nullptr, &uavDesc, mBlur1CpuUav);
}
// In BlurApp.cpp…Offset to location in heap to
// store descriptors for BlurFilter
mBlurFilter->BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE(
mCbvSrvUavDescriptorHeap-
>GetCPUDescriptorHandleForHeapStart(),
3, mCbvSrvUavDescriptorSize),
CD3DX12_GPU_DESCRIPTOR_HANDLE(
mCbvSrvUavDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart(),
3, mCbvSrvUavDescriptorSize),
mCbvSrvUavDescriptorSize);
Pretpostavimo da naša slika ima širinu v i visinu h. Kao što ćemo videti u sledećem odeljku kada
pogledamo kompjuterski shader, za horizontalno 1D zamućenje, naša nit grupa je horizontalna
line segment od 256 niti, i svaki thread je odgovoran za zamućivanje jednog piksela u
slika. Zato moramo da pošaljemo
navojne grupe u k-smjeru i grupi navoja u pravcu i za svaki
piksel na slici je zamućen. Ako se 256 ne podeli jednako u v, poslednja horizontalna
grupa navoja će imati eksterne navoje (pogledajte sliku (13.8)). Nema ničega
možemo to uraditi s obzirom na veličinu grupe nitova. Mi se pobrinemo za van granica
sa stezanjem čekova kod šemera.
Situacija je slična za vertikalno 1D zamućenje. Ponovo, naša nit grupa je vertikalna
line segment od 256 niti, i svaki thread je odgovoran za zamućivanje jednog piksela u
slika. Zato moramo da pošaljemo
grupe navoja u smjeru i-pravca i v-navoja u k-smjeru, kako bi svaka bila
piksel na slici je zamućen.
Spodnji kod pokazuje koliko grupa navoja treba da pošalje u svakom pravcu,
i pokreće stvarnu zamućenost na računaru za izračunavanje:
void BlurFilter::Execute(ID3D12GraphicsCommandList*
cmdList,
ID3D12RootSignature* rootSig,
ID3D12PipelineState* horzBlurPSO,
ID3D12PipelineState* vertBlurPSO,
ID3D12Resource* input,
int blurCount)
{
auto weights = CalcGaussWeights(2.5f);
int blurRadius = (int)weights.size() / 2;
cmdList->SetComputeRootSignature(rootSig);
cmdList->SetComputeRoot32BitConstants(0, 1,
&blurRadius, 0);
cmdList->SetComputeRoot32BitConstants(0,
(UINT)weights.size(), weights.
data(), 1);
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(input,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_COPY_SOURCE));
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(mBlurMap0.
Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COPY_DEST));
// Copy the input (back-buffer in this example) to
BlurMap0.
cmdList->CopyResource(mBlurMap0.Get(), input);
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(mBlurMap0.
Get(),
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_GENERIC_READ));
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(mBlurMap1.
Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
for(int i = 0; i < blurCount; ++i)
{
//
// Horizontal Blur pass.
//
cmdList->SetPipelineState(horzBlurPSO);
cmdList->SetComputeRootDescriptorTable(1,
mBlur0GpuSrv);
cmdList->SetComputeRootDescriptorTable(2,
mBlur1GpuUav);
// How many groups do we need to dispatch to cover
a row of pixels, where
// each group covers 256 pixels (the 256 is
defined in the ComputeShader).
UINT numGroupsX = (UINT)ceilf(mWidth / 256.0f);
cmdList->Dispatch(numGroupsX, mHeight, 1);
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mBlurMap0.Get(),
D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mBlurMap1.Get(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_GENERIC_READ));
//
// Vertical Blur pass.
//
cmdList->SetPipelineState(vertBlurPSO);
cmdList->SetComputeRootDescriptorTable(1,
mBlur1GpuSrv);
cmdList->SetComputeRootDescriptorTable(2,
mBlur0GpuUav);
// How many groups do we need to dispatch to cover
a column of pixels,
// where each group covers 256 pixels (the 256 is
defined in the
// ComputeShader).
UINT numGroupsY = (UINT)ceilf(mHeight / 256.0f);
cmdList->Dispatch(mWidth, numGroupsY, 1);
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mBlurMap0.Get(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_GENERIC_READ));
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mBlurMap1.Get(),
D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
}
}
Slika 13.8. Razmotrite strukturu 28 × 14, gde su naše horizontalne grupe navoja
8 × 1 i naše vertikalne grupe navoja su 1 × 8 (Ks × I format). Za horizontalni prolaz,
kako bi pokrili sve piksele koje trebamo poslati
grupe navoja u k-smjeru i 14 grupa navoja u pravcu i. Od 28 je
ne više od 8, završimo sa stranim nitima koji ne rade ništa
najviše desne grupe. Za vertikalni prolaz, kako bismo pokrili sve piksele, mi
treba da pošaljete
grupe navoja u pravcu i i 28 grupa navoja u pravcu k. Od 14 je
ne više od 8, završimo sa stranim nitima koji ne rade ništa
grupe sa najviše navojem. Isti koncepti odnose se na veću teksturu sa navojem
grupe veličine 256.
Slika 13.9. Levo: Snimak ekrana "Blur" gde je slika bila
zamućen dvaput. Desno: snimak ekrana "Blur" demo gde je slika bila
zamućen osam puta.
13.7.4 Compute Shader Program
U ovom odeljku, pogledamo program izračunavanja shadera koji zapravo vrši zamućenje.
Mi ćemo razgovarati samo o horizontalnom slučaju zamućenja. Vertikalno zamućenje je analogno, ali je
situacija prenesena.
Kao što je pomenuto u prethodnom odeljku, naša nit grupa je horizontalni segment linije
256 niti, a svaka nit je odgovorna za zamućivanje jednog piksela na slici. An
neefikasan prvi pristup je jednostavno implementirati algoritam zamućenja direktno. To je, svako
nit jednostavno vrši ponderisani prosek matrice redova (matrica redova zato što mi
rade 1D horizontalni prolaz) piksela centriranih oko piksela koji je nit
obrada. Problem sa ovim pristupom je što zahtijeva preuzimanje istog teksela
više puta (vidi sliku 13.10).
Slika 13.10. Razmotrite samo dva susedna piksla na ulaznoj slici, i
pretpostavimo da je jezgro zamućenja 1 × 7. Obratite pažnju na šest od osam jedinstvenih piksela
se uzorkuju dvaput - jednom za svaki piksel.
Možemo optimizovati sledeci strategiju opisanu u § 13.6 i iskoristiti
deljena memorija. Svaki thread može čitati u vrijednosti tekela i čuvati ga u dijeljenoj memoriji. Posle
sve su završene čitajući svoje vrijednosti tekela u dijeljenu memoriju, nitovi mogu
nastavite da izvršite zamućenje, ali tamo čita tekele iz deljene memorije, što je
je brza za pristup. Jedina nezgodna stvar u vezi sa ovim je da je nit grupa od n = 256 niti
zahteva n + 2R tekels da izvrši zamućenje, gdje je R - radijus zamućenja (Slika 13.11).
Slika 13.11. Pikseli u blizini granica grupe navoja će čitati piksele
izvan grupe navoja zbog radijusa zamućenja.
Rešenje je jednostavno; mi dodeljujemo n + 2R elemente deljene memorije, i imamo 2R
teme pretražuju dvije tekel vrednosti. Jedino što je zabrinuto zbog ovoga je da to zahteva a
malo više knjiga drži prilikom indeksiranja u dijeljenu memoriju; mi više nemamo
ID grupe grupe koja odgovara i-ti elementu u deljenoj memoriji. Slika 13.12
prikazuje mapiranje od tema do deljene memorije za R = 4.
Slika 13.12. U ovom primjeru, R = 4. Četiri najvažnije nitke čitaju dva
tekel vrednosti i čuvati ih u dijeljenoj memoriji. Svaka četvorica najbitnije su čitale
dve tekel vrednosti i čuvati ih u dijeljenoj memoriji. Svaki drugi nit samo pročita
jednu vrijednost tekela i čuva ga u dijeljenoj memoriji. Ovo nam daje sve tekel vrijednosti
potrebno je zamućiti N piksele sa radijusom zamućenja R.
Konačno, poslednja situacija o kojoj se govori jeste da je grupa sa najviše naviše i najviše
grupa navoja može indeksirati ulaznu sliku izvan granica, kao što je prikazano na slici 13.13.
Slika 13.13. Situacije gde možemo čitati izvan granica slike
Čitanje iz indeksa van granica nije nelegalno - definisano je da vratite 0 (i
pisanje izvan granica indeksa rezultata u no-op). Međutim, ne želimo da čitamo 0
kada izađemo van granica, jer to znači da će 0 boja (tj. crna) otići u
zamućenje na granicama. Umesto toga, želimo da implementiramo nešto slično sponu
režim naslova teksture, gde ako pročitamo vrednost izvan granica, ona vraća istu vrijednost
kao granični teksel. Ovo se može primeniti pritiskanjem indeksa:
// Clamp out of bound samples that occur at left image
borders.
int x = max(dispatchThreadID.x - gBlurRadius, 0);
gCache[groupThreadID.x] = gInput[int2(x,
dispatchThreadID.y)];
// Clamp out of bound samples that occur at right
image borders.
int x = min(dispatchThreadID.x + gBlurRadius,
gInput.Length.x-1);
gCache[groupThreadID.x+2*gBlurRadius] =
gInput[int2(x, dispatchThreadID.y)];
// Clamp out of bound samples that occur at image
borders.
gCache[groupThreadID.x+gBlurRadius] =
gInput[min(dispatchThreadID.xy, gInput.Length.xy-1)];
The full shader code is shown below:
//====================================================================
// Performs a separable Guassian blur with a blur
radius up to 5 pixels.
//====================================================================
cbuffer cbSettings : register(b0)
{
// We cannot have an array entry in a constant
buffer that gets mapped onto
// root constants, so list each element.
int gBlurRadius;
// Support up to 11 blur weights.
float w0;
float w1;
float w2;
float w3;
float w4;
float w5;
float w6;
float w7;
float w8;
float w9;
float w10;
};
static const int gMaxBlurRadius = 5;
Texture2D gInput : register(t0);
RWTexture2D<float4> gOutput : register(u0);
#define N 256
#define CacheSize (N + 2*gMaxBlurRadius)
groupshared float4 gCache[CacheSize];
[numthreads(N, 1, 1)]
void HorzBlurCS(int3 groupThreadID : SV_GroupThreadID,
int3 dispatchThreadID :
SV_DispatchThreadID)
{
// Put in an array for each indexing.
float weights[11] = { w0, w1, w2, w3, w4, w5, w6,
w7, w8, w9, w10 };
//
// Fill local thread storage to reduce bandwidth. To
blur
// N pixels, we will need to load N + 2*BlurRadius
pixels
// due to the blur radius.
//
// This thread group runs N threads. To get the
extra 2*BlurRadius
// pixels, have 2*BlurRadius threads sample an extra
pixel.
if(groupThreadID.x < gBlurRadius)
{
// Clamp out of bound samples that occur at image
borders.
int x = max(dispatchThreadID.x - gBlurRadius, 0);
gCache[groupThreadID.x] = gInput[int2(x,
dispatchThreadID.y)];
}
if(groupThreadID.x >= N-gBlurRadius)
{
// Clamp out of bound samples that occur at image
borders.
int x = min(dispatchThreadID.x + gBlurRadius,
gInput.Length.x-1);
gCache[groupThreadID.x+2*gBlurRadius] =
gInput[int2(x, dispatchThreadID.y)];
}
// Clamp out of bound samples that occur at image
borders.
gCache[groupThreadID.x+gBlurRadius] =
gInput[min(dispatchThreadID.xy, gInput.Length.xy-1)];
// Wait for all threads to finish.
GroupMemoryBarrierWithGroupSync();
//
// Now blur each pixel.
//
float4 blurColor = float4(0, 0, 0, 0);
for(int i = -gBlurRadius; i <= gBlurRadius; ++i)
{
int k = groupThreadID.x + gBlurRadius + i;
blurColor += weights[i+gBlurRadius]*gCache[k];
}
gOutput[dispatchThreadID.xy] = blurColor;
}
[numthreads(1, N, 1)]
void VertBlurCS(int3 groupThreadID : SV_GroupThreadID,
int3 dispatchThreadID :
SV_DispatchThreadID)
{
// Put in an array for each indexing.
float weights[11] = { w0, w1, w2, w3, w4, w5, w6,
w7, w8, w9, w10 };
//
// Fill local thread storage to reduce bandwidth. To
blur
// N pixels, we will need to load N + 2*BlurRadius
pixels
// due to the blur radius.
//
// This thread group runs N threads. To get the
extra 2*BlurRadius
// pixels, have 2*BlurRadius threads sample an extra
pixel.
if(groupThreadID.y < gBlurRadius)
{
// Clamp out of bound samples that occur at image
borders.
int y = max(dispatchThreadID.y - gBlurRadius, 0);
gCache[groupThreadID.y] =
gInput[int2(dispatchThreadID.x, y)];
}
if(groupThreadID.y >= N-gBlurRadius)
{
// Clamp out of bound samples that occur at image
borders.
int y = min(dispatchThreadID.y + gBlurRadius,
gInput.Length.y-1);
gCache[groupThreadID.y+2*gBlurRadius] =
gInput[int2(dispatchThreadID.x, y)];
}
// Clamp out of bound samples that occur at image
borders.
gCache[groupThreadID.y+gBlurRadius] =
gInput[min(dispatchThreadID.xy,
gInput.Length.xy-1)];
// Wait for all threads to finish.
GroupMemoryBarrierWithGroupSync();
//
// Now blur each pixel.
//
float4 blurColor = float4(0, 0, 0, 0);
for(int i = -gBlurRadius; i <= gBlurRadius; ++i)
{
int k = groupThreadID.y + gBlurRadius + i;
blurColor += weights[i+gBlurRadius]*gCache[k];
}
gOutput[dispatchThreadID.xy] = blurColor;
}
For the last line
gOutput[dispatchThreadID.xy] = blurColor;
moguće je da u grupi koja ima najviše navoja ima eksterne niti koje ne
odgovara elementu u izlaznoj teksturi (Slika 13.13). To je
dispatchThreadID.ki će biti indeks izvan granica za izlaznu teksturu.
Međutim, ne moramo brinuti o rukovanju ovom slučajem, kao izvanrednom pisanju
rezultira ne-op.
13.8 FURTHER RESOURCES
Računanje shader programiranja je tema sama po sebi, a postoji i nekoliko knjiga
o korišćenju GPU-a za računarske programe:
1. Programiranje masivni paralelni procesori: pristup na ruke od strane David B.
Kirk i Ven-mei V. Hvu.
2. Vodič za programiranje OpenCL Aaftab Munshi, Benedict R. Gaster, Timothi G.
Mattson, James Fung i Dan Ginsburg.
Tehnologije poput CUDA i OpenCL su samo različiti API-ji za pristup GPU-u
za pisanje računskih programa. Takođe su najbolje prakse za CUDA i OpenCL programe
najbolje prakse za Direct Compute programe, jer se programi izvršavaju na istom
hardvare. U ovom poglavlju pokazali smo većinu direktne kompjutere i tako dalje
ne bi trebalo da imate problema sa prenosom programa CUDA ili OpenCL u Direct Compute.
Chuck Valbourn je objavio blog stranicu koja se sastoji od veza sa mnogim Direct Compute
prezentacije:
http://blogs.msdn.com/b/chuckv/archive/2010/07/14/directcompute.aspk
Pored toga, Microsoftov kanal 9 poseduje seriju video zapisa o direktnom izračunavanju
programiranje:
http://channel9.msdn.com/tags/DirectCompute-Lecture-Series/
Na kraju, NVIDIA ima čitav odeljak o CUDA treningu.
http://developer.nvidia.com/cuda-training
Konkretno, na Univerzitetu postoje puno video predavanja o CUDA programiranju
Illinois, što mi preporučujemo. Opet, naglašavamo da je CUDA samo još jedna
API za pristup funkcionalnosti računara GPU-a. Kada shvatite sintaksu,
teži deo o GPU računarima je učenje kako pisati efikasne programe za to. Od strane
proučavajući ova predavanja o CUDA-u, dobićete bolju ideju kako funkcioniše GPU hardver
tako da možete napisati optimalan kod.
Poglavlje 14 faze mozaika
Faze tezelacije se odnose na tri faze u rendering plinovodu
tessellating geometri. Jednostavno rečeno, tezelacija se odnosi na podelu geometrije na manje
trougla i na neki način pomeraju novo generisane vertikale. Motivacija za
povećati broj trouglova je da dodate detalje u mrežu. Ali zašto ne samo da napravite detaljne
informacije
da se započne i da se uradi? Dole su tri razloga za tezelaciju.
1. Dinamički LOD na GPU. Dinamično možemo prilagoditi detalj mreže na osnovu
njenu daljinu od kamere i druge faktore. Na primer, ako je mreža veoma daleko
daleko, bilo bi rasipno da bi napravio visoku verziju verzije, kao što to ne bi bilo
u svakom slučaju može da vidi sve te detalje. Kako se objekt približava fotoaparatu, možemo
kontinuirano povećava tezelaciju da bi se povećao detalj objekta.
2. Efikasnost fizike i animacije. Možemo izvoditi izračun fizike i animacije
na nisko-poli mrežu, a zatim tezelirati na višu verziju poligona. Ovo štedi
snaga računanja tako što izvrši izračunavanje fizike i animacije na nižim
frekvencija.
3. Ušteda memorije. Možemo da skladištimo niže poligonske mreže u memoriji (na disku, RAM-u,
i VRAM), a zatim GPU tessellate na višu verziju poligona na
leti.
Slika 14.1 pokazuje da su faze tezelacije sjede između vertek shadera i
geometrijski shader. Ove etape su neobavezne, jer ih ne koristimo u ovoj knjizi
dosad.
Slika 14.1. Podskupina renderinga pokazuje faze tezelacije.
Ciljevi:
1. Da otkrijemo primitivne tipove pač koji se koriste za tezelaciju.
2. Da biste stekli razumevanje o tome šta svaka faza tezelacije radi, i koja je to
očekivani ulazi i izlazi su za svaku fazu.
3. Da biste mogli da uklonite geometriju pisanjem trupa i programa za shader domena.
4. Da se upoznamo sa različitim strategijama za određivanje vremena kada se tessellate i da
upoznajte se sa performansama u pogledu hardverske tezelacije.
5. Da biste naučili matematiku krivih i površina Beziera i kako ih implementirati
u fazama tezelacije.
14.1 PRIMITIVNI TIPOVI TESSELATION
Kada radimo za tezelaciju, ne podnosimo trouglove u fazu IA. Umesto toga,
mi dostavljamo zakrpe sa nekoliko kontrolnih tačaka. Direct3D podržava zakrpe sa 1-32
kontrolne tačke, a one su opisane sledećim primitivnim tipovima:
D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST =
34,
D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST =
35,
D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST =
36,
.
.
.
D3D_PRIMITIVE_TOPOLOGY_31_CONTROL_POINT_PATCHLIST =
63,
D3D_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST =
64,
Trougao se može smatrati trikotnom zakrčkom sa tri kontrolne tačke
(D3D_PRIMITIVE_3_CONTROL_POINT_PATCH), tako da i dalje možete podneti svoj uobičajeni triangle
mreže koje treba da se tezeliraju. Jednostavni kvadratni patch može se podneti sa četiri kontrole tačke
(D3D_PRIMITIVE_4_CONTROL_POINT_PATCH). Ovi su zakrpe eventualno tezelirani u trouglove fazama
tezelacije. Kada prenosite primitivne tipove kontrolne tačke
ID3D12GraphicsCommandList :: IASetPrimitiveTopologi, trebate da postavite
D3D12_GRAPHICS_PIPELINE_STATE_DESC :: PrimitiveTopologiTipe polje na
D3D12_PRIMITIVE_TOPOLOGI_TIPE_PATCH:
opakuePsoDesc.PrimitiveTopologiTipe = D3D12_PRIMITIVE_TOPOLOGI_TIPE_PATCH;
Pa šta je sa pločama sa većim brojem kontrolnih tačaka? Ideja kontrole
tačke dolaze od konstrukcije određenih vrsta matematičkih krivih i površina.
Ako ste ikada radili sa krivinama Bezier u programu za crtanje kao što je Adobe Illustrator,
onda znate da oblik krive oblikujete preko kontrolnih tačaka. Matematika
Bezierovih krivina može biti generalizovano na površine Bezier. Na primer, možete kreirati a
Bezier kuad patch koji koristi devet kontrolnih tačaka da ga oblikuje ili šesnaest kontrolnih tačaka;
povećanje broja kontrolnih tačaka daje vam više stepena slobode u oblikovanju
zakrpa. Dakle, motivacija za sve ove primitivne tipove kontrole je da pruže podršku za ove
vrste ukrivljenih površina. Dali smo objašnjenje i demo Bezier kuad obliži u ovom
poglavlje.
14.1.1 Tessellation i Vertek Shader
Zato što predaju kontrolne tačke za popravke na rendering pipeline, kontrolne tačke
šta se pumpa kroz vertek shader. Stoga, kada je tessellation omogućen,
vertek shader je zaista "vertek shader za kontrolne tačke", i možemo izvršiti bilo koju kontrolu
tačan rad koji nam je potreban prije početka tezelacije. Tipično, proračuni animacije ili fizike
se vrši u vertikalnom shaderu na nižim frekvencijama pre nego što se geometrija tezela.
14.2 HULL SHADER
U sledećim pododeljima istražujemo šater za trup, u kojem se zapravo sastoji
dva shadera:
1. Constant Hull Shader
2. Kontrolna točka Hull Shader
14.2.1 Konstantni Hull Shader
Ovaj konstantni šalter trupa se procenjuje po patchu, i zadužen je za izvođenje takozvanog
tezelacioni faktori mreže. Faktori tezelacije ukazuju na tezelaciju
faza koliko da zapečatiš patch. Evo primera kuad patch-a sa četiri
Kontrolne tačke, gde ga tezeljemo ravnomerno tri puta.
struct PatchTess
{
float EdgeTess[4] : SV_TessFactor;
float InsideTess[2] : SV_InsideTessFactor;
// Additional info you want associated per patch.
};
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch,
uint patchID : SV_PrimitiveID)
{
PatchTess pt;
// Uniformly tessellate the patch 3 times.
pt.EdgeTess[0] = 3; // Left edge
pt.EdgeTess[1] = 3; // Top edge
pt.EdgeTess[2] = 3; // Right edge
pt.EdgeTess[3] = 3; // Bottom edge
pt.InsideTess[0] = 3; // u-axis (columns)
pt.InsideTess[1] = 3; // v-axis (rows)
return pt;
}
Kontinualni shader trupa ulazi na sve kontrolne tačke patch-a, koje je definisano tip InputPatch
<VertekOut, 4>. Recimo da su kontrolne tačke prve pumpa kroz vertek shader, tako da je njihov tip
određen vrstom izlaza vertek shader VertekOut. U ovom primeru naš patch ima četiri kontrolne tačke,
tako da mi navedite 4 za drugi parametar šablona za InputPatch. Sistem takođe pruža a identifikaciju ID
zakrpa pomoću SV_PrimitiveID semantika koji se može koristiti ako je potrebno; ID jedinstveno
identifikuje zakrpe u pozivu za izvlačenje. Konstantni shader trupa mora izlaziti faktori za tezelaciju;
faktori tezelacije zavise od topologije patcha. Pored faktora tezelacije (SV_TessFactor I
SV_InsideTessFactor), možete izlaziti iz drugih informacija iz zakrpa stalni trup šader. Shader domena
prima izlaz iz konstanta shader trupa kao ulaz, i mogao bi koristiti ovu dodatnu informaciju.
Tessellating kuad patch sastoji se od dva dela:
1. Četiri faktora tezelacije ivice kontroliraju koliko će se tezkati duž svake ivice.
2. Dva unutrašnja tezelacijska faktora ukazuju na to kako da se tesaže četvero patch (jedan
faktor tezelacije za horizontalnu dimenziju kvadrata i jednu tezelaciju
faktor za vertikalnu dimenziju kvada).
Na slici 14.2 prikazani su primeri različitih kuad konfiguracija pač koji možemo dobiti kada
faktori tezelacije nisu isti. Proučite ove brojke dok vam se ne dopadne
kako funkcionišu faktori ivice i unutrašnjosti.
Tezaljivanje trikotaže se sastoji i iz dva dela:
1. Tri faktora tezelacije ivice kontroliraju koliko da se teza po svakoj ivici.
2. Jedan faktor za tezeliranje unutrašnje strane pokazuje koliko je potrebno za uklanjanje trepavice.
Na slici 14.3 prikazani su primeri različitih konfiguracija triangle patcha koje možemo dobiti kada
faktori tezelacije nisu isti.
Maksimalni tessellation faktor koji podržava hardver Direct3D 11 je 64. Ako je sve
faktori tezelacije su nula, patch se odbija od dalje obrade. Ovo nam dozvoljava
da implementiraju optimizaciju kao što je frustum lupanje i odbacivanje pozadine na jedan patch
osnove.
1. Ako patka nije vidljiva od strane frustuma, onda možemo odbaciti patch dalje
obrada (ukoliko smo to uradili, tezelirani trouglovi bi bili odbijeni tokom
triangle clipping).
2. Ako je patch backfacing, onda možemo odbaciti patch od dalje obrade (ako smo
uradili su ga tezave, tezelirani trouglovi bi bili odbijeni u delu za odbacivanje leđa
rasterizacije).
Prirodno pitanje koje treba postaviti je koliko treba da uradite. Pa zapamti to
osnovna ideja tezelacije je dodavanje detalja vašim mrežama. Međutim, ne želimo
nepotrebno dodati detalje ako ih korisnik ne može ceniti. Sledeće su
neke uobičajene pokazatelje koji se koriste za određivanje iznosa za tessellate:
1. Udaljenost od kamere: Što je objekat dalje od oka, to je manje
primetiti fine podatke; stoga možemo napraviti nisko-poli verziju objekta kada je to
je daleko, a tezalj više, dok se približava oku.
2. Pokrivenost područja ekrana: Možemo proceniti broj piksela koji objekat pokriva na
ekran. Ako je ovaj broj mali, onda možemo da napravimo nisko-poli verziju objekta.
Pošto se pokrivenost ekrana povećava, možemo više da zategnemo.
3. Orijentacija: Orijentacija trougla u odnosu na oko se uzima
uzimajući u obzir ideju da će trouglovi duž rubova silueta biti prefinjeniji
od drugih trouglova.
4. Hrapavost: grubim površinama sa puno detalja će biti potrebno više tezelja nego
glatke površine. Vrednost hrapavosti može se prethodno izračunati ispitivanjem površine
teksture, koje se mogu koristiti da bi se odlučilo koliko da se tessellate.
[Stori10] daje sledeći savet o učinku:
1. Ako su faktori tezelacije 1 (što u suštini znači da zapravo nismo tesili)
razmislite o tome kako ćete napraviti patch bez tezelacije, jer ćemo trošiti GPU
iznad glave kroz faze tezelacije kada ne rade ništa.
2. Iz razloga performansi koji se odnose na implementaciju grafičkog procesora, nemojte to uraditi tako
trouglovi su toliko mali da pokrivaju manje od osam piksela.
3. Pozivi za izvlačenje serije koji koriste tessellation (tj., Uključivanje i isključivanje tessellation između
izvlačenje poziva je skupo).
cmdList->DrawIndexedInstanced(ri->IndexCount, 1,
ri->StartIndexLocation, ri->BaseVertexLocation,
0);
Drugi parametar, InstanceCount, određuje broj puta za primjer
geometrija koju crtamo. Ako odredimo deset, geometrija će biti izvučena 10 puta.
Međutim, crtež objekta deset puta sam zaista ne pomaže. Objekat će biti
na istom mestu koristeći iste materijale i teksture. Dakle, sledeći korak je da shvatimo
kako odrediti dodatne podatke na pojedinačnoj instanci, tako da možemo da varijamo instance pomoću
čineći ih različitim transformacijama, materijalima i teksturama.
16.1.2 Podaci o instanci
Prethodna verzija ove knjige dobila je primere podataka iz sklopa ulaza
faza. Prilikom kreiranja ulaznog rasporeda, možete navesti tokove podataka u pojedinačnoj instanci
umesto na frekvenciji per verteka koristeći
D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA umesto
D3D12_INPUT_CLASSIFICATION_PER_VERTEKS_DATA, respektivno. Ti bi
zatim vezati sekundarni bafer bafera na ulazni tok koji je sadržao podatke za instanciju.
Direct3D 12 i dalje podržava ovakav način ishrane podataka za unos u gasovod, ali smo se odlučili
za moderniji pristup.
Savremeni pristup je kreiranje strukturnog bafera koji sadrži po primjeru
podatke za sve naše instance. Na primer, ako idemo na primjer objekat 100
puta, napravili smo strukturirani bafer sa 100 elementa podataka po jednoj instanci. Onda
povezati strukturirani resurs bafera na liniju za rendering i indeksirati u njega u vrhu
shader zasnovan na primjeru koji crtamo. Kako da znamo koji primer je
vučeni u šateru verteka? Direct3D daje identifikator vrednosti sistema
SV_InstanceID koji možete da koristite u šateru glasa. Na primer, vertices of the
prva instanca će imati id 0, vrhovi drugog stepena će imati id 1 i tako dalje. Dakle, unutra
našeg shadera vertek-a, možemo da indeksiramo u strukturirani bafer da bi dobili podatke za
pojedinačne instance
potreba. Sledeći shader kod pokazuje kako sve to funkcioniše:
// Defaults for number of lights.
#ifndef NUM_DIR_LIGHTS
#define NUM_DIR_LIGHTS 3
#endif
#ifndef NUM_POINT_LIGHTS
#define NUM_POINT_LIGHTS 0
#endif
#ifndef NUM_SPOT_LIGHTS
#define NUM_SPOT_LIGHTS 0
#endif
// Include structures and functions for lighting.
#include “LightingUtil.hlsl”
struct InstanceData
{
float4x4 World;
float4x4 TexTransform;
uint MaterialIndex;
uint InstPad0;
uint InstPad1;
uint InstPad2;
};
struct MaterialData
{
float4 DiffuseAlbedo;
float3 FresnelR0;
float Roughness;
float4x4 MatTransform;
uint DiffuseMapIndex;
uint MatPad0;
uint MatPad1;
uint MatPad2;
};
// An array of textures, which is only supported in
shader model 5.1+.
// Unlike Texture2DArray, the textures in this array
can be different
// sizes and formats, making it more flexible than
texture arrays.
Texture2D gDiffuseMap[7] : register(t0);
// Put in space1, so the texture array does not
overlap with these.
// The texture array above will occupy registers t0,
t1, …, t6 in
// space0.
StructuredBuffer<InstanceData> gInstanceData :
register(t0, space1);
StructuredBuffer<MaterialData> gMaterialData :
register(t1, space1);
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
// Constant data that varies per pass.
cbuffer cbPass : register(b0)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
// Indices [0, NUM_DIR_LIGHTS) are directional
lights;
// indices [NUM_DIR_LIGHTS,
NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;
// indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS,
// NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)
// are spot lights for a maximum of MaxLights per
object.
Light gLights[MaxLights];
};
struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float2 TexC : TEXCOORD;
// nointerpolation is used so the index is not
interpolated
// across the triangle.
nointerpolation uint MatIndex : MATINDEX;
};
VertexOut VS(VertexIn vin, uint instanceID :
SV_InstanceID)
{
VertexOut vout = (VertexOut)0.0f;
// Fetch the instance data.
InstanceData instData = gInstanceData[instanceID];
float4x4 world = instData.World;
float4x4 texTransform = instData.TexTransform;
uint matIndex = instData.MaterialIndex;
vout.MatIndex = matIndex;
// Fetch the material data.
MaterialData matData = gMaterialData[matIndex];
// Transform to world space.
float4 posW = mul(float4(vin.PosL, 1.0f), world);
vout.PosW = posW.xyz;
// Assumes nonuniform scaling; otherwise, need to
use inverse-transpose
// of world matrix.
vout.NormalW = mul(vin.NormalL, (float3x3)world);
// Transform to homogeneous clip space.
vout.PosH = mul(posW, gViewProj);
// Output vertex attributes for interpolation across
triangle.
float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f),
texTransform);
vout.TexC = mul(texC, matData.MatTransform).xy;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
// Fetch the material data.
MaterialData matData = gMaterialData[pin.MatIndex];
float4 diffuseAlbedo = matData.DiffuseAlbedo;
float3 fresnelR0 = matData.FresnelR0;
float roughness = matData.Roughness;
uint diffuseTexIndex = matData.DiffuseMapIndex;
// Dynamically look up the texture in the array.
diffuseAlbedo *=
gDiffuseMap[diffuseTexIndex].Sample(gsamLinearWrap,
pin.TexC);
// Interpolating normal can unnormalize it, so
renormalize it.
pin.NormalW = normalize(pin.NormalW);
// Vector from point being lit to eye.
float3 toEyeW = normalize(gEyePosW - pin.PosW);
// Light terms.
float4 ambient = gAmbientLight*diffuseAlbedo;
Material mat = { diffuseAlbedo, fresnelR0, roughness
};
float4 directLight = ComputeDirectLighting(gLights,
mat,
pin.PosW, pin.NormalW, toEyeW);
float4 litColor = ambient + directLight;
// Common convention to take alpha from diffuse
albedo.
litColor.a = diffuseAlbedo.a;
return litColor;
}
Imajte na umu da više ne posedujemo konstantni bafer po objektu. Podaci po objektu dolaze
iz pufera instance. Obratite pažnju i na to kako koristimo dinamičko indeksiranje za povezivanje a
različiti materijal za svaku instancu i drugu teksturu. U jednom pozivnom pozivu smo u mogućnosti da
dobijemo dosta per-instance sorte! Za kompletnost, odgovarajući opis root korena je prikazan ispod koji
odgovara gore navedenim programima shadera:
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 7, 0,
0);
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[4];
// Perfomance TIP: Order from most frequent to least
frequent.
slotRootParameter[0].InitAsShaderResourceView(0, 1);
slotRootParameter[1].InitAsShaderResourceView(1, 1);
slotRootParameter[2].InitAsConstantBufferView(0);
slotRootParameter[3].InitAsDescriptorTable(1,
&texTable, D3D12_SHADER_VISIBILITY_PIXEL);
auto staticSamplers = GetStaticSamplers();
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4,
slotRootParameter,
(UINT)staticSamplers.size(), staticSamplers.data(),
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
Kao iu poslednjem poglavlju, vezujemo sve materijale i teksture na sceni jednom per-frame,
i jedini poziv za poziv na poziv koji moramo postaviti je strukturni bafer sa instanced data:
void InstancingAndCullingApp::Draw(const GameTimer&
gt)
{
…
// Bind all the materials used in this scene. For
structured buffers, we
// can bypass the heap and set as a root descriptor.
auto matBuffer = mCurrFrameResource->MaterialBuffer>
Resource();
mCommandList->SetGraphicsRootShaderResourceView(1,
matBuffer->GetGPUVirtualAddress());
auto passCB = mCurrFrameResource->PassCB-
>Resource();
mCommandList->SetGraphicsRootConstantBufferView(2,
passCB->GetGPUVirtualAddress());
// Bind all the textures used in this scene.
mCommandList->SetGraphicsRootDescriptorTable(3,
mSrvDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart());
DrawRenderItems(mCommandList.Get(), mOpaqueRitems);
…
}
void InstancingAndCullingApp::DrawRenderItems(
ID3D12GraphicsCommandList* cmdList,
const std::vector<RenderItem*>& ritems)
{
// For each render item…
for(size_t i = 0; i < ritems.size(); ++i)
{
auto ri = ritems[i];
cmdList->IASetVertexBuffers(0, 1, &ri->Geo-
>VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo-
>IndexBufferView());
cmdList->IASetPrimitiveTopology(ri-
>PrimitiveType);
// Set the instance buffer to use for this renderitem.
// For structured buffers, we can bypass
// the heap and set as a root descriptor.
auto instanceBuffer = mCurrFrameResource-
>InstanceBuffer->Resource();
mCommandList->SetGraphicsRootShaderResourceView(
0, instanceBuffer->GetGPUVirtualAddress());
cmdList->DrawIndexedInstanced(ri->IndexCount,
ri->InstanceCount, ri->StartIndexLocation,
ri->BaseVertexLocation, 0);
}
}
16.1.3 Kreiranje Instanced Buffer-a
Buffer instance čuva podatke koji se razlikuju po primjeru. Izgleda puno poput podataka koje smo ranije
stavili u naš konstantni pufer za svaki objekat. Na strani CPU, naša instanca podataka izgleda ovako:
struct InstanceData
{
DirectX::XMFLOAT4X4 World =
MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 TexTransform =
MathHelper::Identity4x4();
UINT MaterialIndex;
UINT InstancePad0;
UINT InstancePad1;
UINT InstancePad2;
};
Podaci na instanci u sistemskoj memoriji se čuvaju kao deo strukture render-stavke, pošto stavka
render-a održava koliko puta treba da se instalira:
struct RenderItem
{
…
std::vector<InstanceData> Instances;
…
};
Da bi grafički procesor potrošio podatke instance, potrebno je kreirati strukturirani bafer sa tipom
elementa InstanceData. Štaviše, ovaj bafer će biti dinamičan (tj., Bafer za otpremanje) tako da ga
možemo ažurirati u svaki okvir; u našoj demo kopiramo instancirane podatke samo vidljivih instanci u
strukturni bafer (ovo je povezano sa frustumom, vidi § 16.3), a skup vidljivih instanci će se promeniti dok
se kamera pomera / pogleda unaokolo. Kreiranje dinamičnog bafera je jednostavno s našom klikom na
UploadBuffer pomoću:
struct FrameResource
{ public:
FrameResource(ID3D12Device* device, UINT passCount,
UINT maxInstanceCount, UINT materialCount);
FrameResource(const FrameResource& rhs) = delete;
FrameResource& operator=(const FrameResource& rhs) =
delete;
˜FrameResource();
// We cannot reset the allocator until the GPU is
done processing the commands.
// So each frame needs their own allocator.
Microsoft::WRL::ComPtr<ID3D12CommandAllocator>
CmdListAlloc;
// We cannot update a cbuffer until the GPU is done
processing the commands
// that reference it. So each frame needs their own
cbuffers.
// std::unique_ptr<UploadBuffer<FrameConstants>>
FrameCB = nullptr;
std::unique_ptr<UploadBuffer<PassConstants>> PassCB
= nullptr;
std::unique_ptr<UploadBuffer<MaterialData>>
MaterialBuffer = nullptr;
// NOTE: In this demo, we instance only one renderitem,
so we only have
// one structured buffer to store instancing data.
To make this more
// general (i.e., to support instancing multiple
render-items), you
// would need to have a structured buffer for each
render-item, and
// allocate each buffer with enough room for the
maximum number of
// instances you would ever draw. This sounds like a
lot, but it is
// actually no more than the amount of per-object
constant data we
// would need if we were not using instancing. For
example, if we
// were drawing 1000 objects without instancing, we
would create a
// constant buffer with enough room for a 1000
objects. With instancing,
// we would just create a structured buffer large
enough to store the
// instance data for 1000 instances.
std::unique_ptr<UploadBuffer<InstanceData>>
InstanceBuffer = nullptr;
// Fence value to mark commands up to this fence
point. This lets us
// check if these frame resources are still in use
by the GPU.
UINT64 Fence = 0;
};
FrameResource::FrameResource(ID3D12Device* device,
UINT passCount, UINT maxInstanceCount, UINT
materialCount)
{
ThrowIfFailed(device->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(CmdListAlloc.GetAddressOf())));
PassCB =
std::make_unique<UploadBuffer<PassConstants>>(
device, passCount, true);
MaterialBuffer =
std::make_unique<UploadBuffer<MaterialData>>(
device, materialCount, false);
InstanceBuffer =
std::make_unique<UploadBuffer<InstanceData>>(
device, maxInstanceCount, false);
}
Imajte na umu da InstanceBuffer nije konstantni bafer, tako da smo zadali pogrešno parametar.
16.2 OBLASTI I ODRŽAVANJA
Da bi se sprovelo frustrirano ubiranje, moramo se upoznati sa matematička reprezentacija frustuma i
različitih ograničavajućih volumena. Bounding zapremine su primitivni geometrijski objekti koji
približavaju zapreminu objekta-vidi Slika 16.1. Kompromis je da, iako ograničavajuća zapremina samo
aproksimira objekt njegov oblik ima jednostavnu matematičku reprezentaciju, što olakšava rad sa.
Slika 16.1. Mreža izvedena sa AABB i graničnom sferom.
16.2.1 DirectKs Math Collision
Koristimo uslužnu biblioteku DirectKSCollision.h, koja je deo DirectKs Math-a. Ovo biblioteka obezbeđuje
brze implementacije uobičajenim geometrijskim primitivnim testovima intersekcije kao što su presek
zraka / trougla, presek zraka / kutija, presek kutije / kutije, kutija / ravni raskrsnica, kutija / frustum,
sfera / frustum i još mnogo toga. Vježba 3 vas pita da istražite ovu biblioteku da se upoznaju sa onim što
nudi.
16.2.2 Kutije
Graničasta kutija (AABB) poravnate sa osovinom je kutija koja čvrsto okružuje mreža i tako da su njegova
lica paralelna sa glavnim osama. AABB se može opisati minimalna tačka vmin i maksimalna tačka vmak
(vidi Sliku 16.2). Minimalna tačka Vmin se pronalazi tako što pretražuje sve vertikale mreže i pronalazi
minimum k-, i- i z-koordinate, a maksimalna tačka vmak se može naći kroz pretragu kroz sve vrhove
mreže i pronalaženje maksimalnih k-, i- i z-koordinata. Slika 16.2. AABB skupa bodova koristeći
minimalnu i maksimalnu tačku predstavljanje. Alternativno, AABB se može predstaviti sa središnjom
tačkom c i širinama kutije vektor e, koji čuva rastojanje od središnje tačke do bočnih strana kutije duž
koordinate osi (vidi sliku 16.3). Slika16.3. AABB skupa bodova koristeći centar i ekstenzije predstavljanje.
Biblioteka sudara za DirectKs koristi predstavljanje centra / ekstenzija:
struct BoundingBox
{
static const size_t CORNER_COUNT = 8;
XMFLOAT3 Center; // Center of the box.
XMFLOAT3 Extents; // Distance from the center to
each side.
…
Lako je pretvoriti iz jedne reprezentacije u drugu. Na primjer, s obzirom na ograničavajuća kutija
definisana od strane vmin i vmak, predstavljanje centra / ekstenzija se daje sledećim: Sledeći kod
pokazuje kako izračunavamo graničnu kutiju mreže lobanje u demo ovog poglavlja:
XMFLOAT3 vMinf3(+MathHelper::Infinity,
+MathHelper::Infinity, +MathHelper::Infinity);
XMFLOAT3 vMaxf3(-MathHelper::Infinity, -
MathHelper::Infinity, -MathHelper::Infinity);
XMVECTOR vMin = XMLoadFloat3(&vMinf3);
XMVECTOR vMax = XMLoadFloat3(&vMaxf3);
std::vector<Vertex> vertices(vcount);
for(UINT i = 0; i < vcount; ++i)
{
fin >> vertices[i].Pos.x >> vertices[i].Pos.y >>
vertices[i].Pos.z;
fin >> vertices[i].Normal.x >> vertices[i].Normal.y
>> vertices[i].Normal.z;
XMVECTOR P = XMLoadFloat3(&vertices[i].Pos);
// Project point onto unit sphere and generate
spherical texture coordinates.
XMFLOAT3 spherePos;
XMStoreFloat3(&spherePos, XMVector3Normalize(P));
float theta = atan2f(spherePos.z, spherePos.x);
// Put in [0, 2pi].
if(theta < 0.0f)
theta += XM_2PI;
float phi = acosf(spherePos.y);
float u = theta / (2.0f*XM_PI);
float v = phi / XM_PI;
vertices[i].TexC = { u, v };
vMin = XMVectorMin(vMin, P);
vMax = XMVectorMax(vMax, P);
}
BoundingBox bounds;
XMStoreFloat3(&bounds.Center, 0.5f*(vMin + vMax));
XMStoreFloat3(&bounds.Extents, 0.5f*(vMax - vMin));
16.2.2.1 Rotacije i kutije za poravnavanje osi
Slika 16.4 pokazuje da se osa kutije u jednom koordinatnom sistemu ne može osmisliti
sa različitim koordinatnim sistemom. Konkretno, ako izračunamo AABB a
mreža u lokalnom prostoru, ona se transformiše u orijentisane granične kutije (OBB) u svetu
prostor. Međutim, uvek možemo da se transformišemo u lokalni prostor mreže i uradimo
raskrsnica tamo gde je kutija poravnata sa osovinom.
Slika 16.4. Ograničavajuća kutija je osa poravnata sa ki-kadrom, ali ne sa
KSI-okvir.
Alternativno, možemo da preusmerimo AABB u svetski prostor, ali to može dovesti do a
"Fatter" kutija koja je lošija aproksimacija stvarne zapremine (vidi Sliku 16.5).
Slika 16.5. Ograničavajuća kutija je osa poravnata sa KSI-okvirom.
Još jedna alternativa je napustiti granične kutije poravnate na osovinama i samo raditi
orijentisane granične kutije, gde održavamo orijentaciju kutije u odnosu na
svetski prostor. DirectKs collision biblioteka nudi sledeću strukturu za
predstavljajući orijentisanu graničnu kutiju.
struct BoundingOrientedBox
{
static const size_t CORNER_COUNT = 8;
XMFLOAT3 Center; // Center of the box.
XMFLOAT3 Extents; // Distance from the center
to each side.
XMFLOAT4 Orientation; // Unit quaternion
representing rotation (box -> world).
U ovom poglavlju videćete spomenute kvaternione za predstavljanje rotacije / orijentacije. Ukratko,
jedinica kvaterniona može predstavljati rotaciju samo kao matrica rotacije može. Pokrivamo kvaternione
u Poglavlju 22. Za sada, samo pomislite da predstavljaju rotaciju kao rotacionu matricu. AABB i OBB se
takođe mogu konstruisati iz skupa tačaka pomoću DirectKs-a biblioteka sudara sa sledećim funkcijama
statičkog člana:
void BoundingBox::CreateFromPoints(
_Out_ BoundingBox& Out,
_In_ size_t Count,
_In_reads_bytes_(sizeof(XMFLOAT3)+Stride*(Count-1))
const XMFLOAT3* pPoints,
_In_ size_t Stride );
void BoundingOrientedBox::CreateFromPoints(
_Out_ BoundingOrientedBox& Out,
_In_ size_t Count,
_In_reads_bytes_(sizeof(XMFLOAT3)+Stride*(Count-1))
const XMFLOAT3* pPoints,
_In_ size_t Stride );
If your vertex structure looks like this:
struct Basic32
{
XMFLOAT3 Pos;
XMFLOAT3 Normal;
XMFLOAT2 TexC;
};
And you have an array of vertices forming your mesh:
std::vector<Vertex::Basic32> vertices;
Then you call this function like so:
BoundingBox box;
BoundingBox::CreateFromPoints(
box,
vertices.size(),
&vertices[0].Pos,
sizeof(Vertex::Basic32));
Korak pokazuje koliko bajtova treba preskočiti da bi došli do sledećeg elementa položaja.
Da biste izračunali ograničene zapremine vaših mreža, potrebno je da imate sistem
dostupna memorijska kopija vaše liste verteka, kao što je jedna sačuvana
u std :: vektoru. To je zato što CPU ne može da pročita iz bafera verteka
kreiran za rendering. Prema tome, uobičajeno je da aplikacije održavaju sistem
memorijske kopije ovakvih stvari, kao i izbora (Poglavlje 17) i sudara
otkrivanje.
16.2.3 Sfera
Ograničavajuća sfera mreže je sfera koja čvrsto okružuje mrežu. A
granična sfera može se opisati sa središnjom tačkom i poluprečnikom. Jedan od načina za izračunavanje
ograničavajuća sfera mreže je da prvo izračunava svoj AABB. Zatim idemo u centar
AABB kao centar granične sfere:
Zatim se radiu da je radijus maksimalno rastojanje između vertek p u mrežu
iz centra c:
Pretpostavimo da izračunavamo graničnu sferu mreže u lokalnom prostoru. Posle svega
transformacija, granična sfera možda ne čvrsto okružuje mrežu zbog skaliranja. Prema tome
radijus mora biti preklapan prema tome. Za kompenzaciju nejednakog skaliranja, moramo
skalira radijus najvećom komponentom skaliranja, tako da sfera obuhvata
transformisana mreža. Druga moguća strategija je izbjegavanje sveobuhvatnog povezivanja tako što će
imati sve tvoja mreža modelirana na isti nivo sveta igre. Na ovaj način, modelima neće biti potrebno
da se preklapaju kada se učita u aplikaciju.
DirectKs collision biblioteka nudi sledeću strukturu za predstavljanje a ograničavajuća sfera:
struct BoundingSphere
{
XMFLOAT3 Center; // Center of the sphere.
float Radius; // Radius of the sphere.
…
And it provides the following static member function for creating one from a set of
points:
void BoundingSphere::CreateFromPoints(
_Out_ BoundingSphere& Out,
_In_ size_t Count,
_In_reads_bytes_(sizeof(XMFLOAT3)+Stride*(Count-1))
const XMFLOAT3* pPoints,
_In_ size_t Stride );
16.2.4 Frustums
Mi smo dobro upoznati sa frustomom iz poglavlja 5. Jedan od načina za određivanje frustuma
matematički je kao presek šest aviona: levi / desni plan, vrh / dno
aviona i blizu / dalekih aviona. Pretpostavljamo da su šest frustriranih aviona "unutra"
vidi sliku 16.6.
Slika 16.6. Presek pozitivnog polusta frustracijskih ravni
definiše zapreminu frustuma.
Ovakvo predstavljanje u ravni 6 olakšava frustraciju i ograničenje volumena
testovi raskrsnice.
16.2.4.1 Izgradnja Frustum aviona
Jedan jednostavan način za konstrukciju frustum planeta je u pogledu prostora, gde je frustracija
preuzima kanonsku formu usredsređenu na porijeklo gledajući niz pozitivnu z-osu. Ovde,
bliske i dalekosežne ravni su trijumfalno određene njihovim rastojanjima duž z-ose, levo
i desne ravni su simetrične i prolaze kroz poreklo (opet pogledajte Sliku 16.6) i
gornji i donji ravni su simetrični i prolaze kroz poreklo. Shodno tome, mi ne
čak i potrebno je da sačuvamo punu jednačinu jednačine da predstavljaju frustum u prostoru pogleda,
mi samo
potrebni su kosini aviona za gornje / donje / desne / desne ravni i z rastojanja za blizinu
i dalekog aviona. DirectKs collision biblioteka nudi sledeću strukturu za
predstavljaju frustraciju:
struct BoundingFrustum
{
static const size_t CORNER_COUNT = 8;
XMFLOAT3 Origin; // Origin of the frustum
(and projection).
XMFLOAT4 Orientation; // Quaternion representing
rotation.
float RightSlope; // Positive X slope (X/Z).
float LeftSlope; // Negative X slope.
float TopSlope; // Positive Y slope (Y/Z).
float BottomSlope; // Negative Y slope.
float Near, Far; // Z of the near plane and
far plane.
…
U lokalnom prostoru frustuma (npr., Pogledajte prostor za kameru), Origin bi biti nula, a Orijentacija bi
predstavljala transformaciju identiteta (bez rotacije). Mi može odrediti i orijentisati frustum negde u
svetu tako što određuje poreklo pozicija i orijentacioni kvaternion.
Ako smo zakopčali frustrirano vertikalno polje gledišta, razmeru razmera, u blizini i daljih ravnina našeg
kamera, onda možemo malo da odredimo jednačine frustomske ravni u prostoru pogleda matematički
napor. Međutim, takođe je moguće izvesti jednačine ravnoteže frustracije u pogledajte prostor iz
projekcione matrice na više načina (pogledajte [Lengiel02] ili [Moller08] na dva različita načina).
Biblioteka sudara KSNA uzima sljedeće strategija. U prostoru NDC, frustum pogleda je uperen u kutiju [-
1,1] × [-1,1] × [0,1]. Dakle, osam uglova gledišta frustriraju se jednostavno:
// Corners of the projection frustum in homogenous
space.
static XMVECTORF32 HomogenousPoints[6] =
{
{ 1.0f, 0.0f, 1.0f, 1.0f }, // right (at far
plane)
{ -1.0f, 0.0f, 1.0f, 1.0f }, // left
{ 0.0f, 1.0f, 1.0f, 1.0f }, // top
{ 0.0f, -1.0f, 1.0f, 1.0f }, // bottom
{ 0.0f, 0.0f, 0.0f, 1.0f }, // near
{ 0.0f, 0.0f, 1.0f, 1.0f } // far
};
Možemo izračunati inverznu matricu projekcija (takođe je inverzna homogena podela), da se osam
kutova pretvori iz NDC prostora nazad da bi se pogledao prostor. Imamo osam uglova frustuma u
pogledu prostora, neka jednostavna matematika je koristi se za izračunavanje ravninskih jednačina
(opet, ovo je jednostavno jer u pogledu prostora, frustum je pozicioniran na početku, a poravnata je
osa). Sledeći DirectKs sudar kod izračunava frustum u prostoru za prikaz iz projekcione matrice:
//–––––––––––––––––––––––––-
// Build a frustum from a persepective projection
matrix. The matrix may only
// contain a projection; any rotation, translation or
scale will cause the
// constructed frustum to be incorrect.
//–––––––––––––––––––––––––-
_Use_decl_annotations_
inline void XM_CALLCONV
BoundingFrustum::CreateFromMatrix(
BoundingFrustum& Out,
FXMMATRIX Projection )
{
// Corners of the projection frustum in homogenous
space.
static XMVECTORF32 HomogenousPoints[6] =
{
{ 1.0f, 0.0f, 1.0f, 1.0f }, // right (at far
plane)
{ -1.0f, 0.0f, 1.0f, 1.0f }, // left
{ 0.0f, 1.0f, 1.0f, 1.0f }, // top
{ 0.0f, -1.0f, 1.0f, 1.0f }, // bottom
{ 0.0f, 0.0f, 0.0f, 1.0f }, // near
{ 0.0f, 0.0f, 1.0f, 1.0f } // far
};
XMVECTOR Determinant;
XMMATRIX matInverse = XMMatrixInverse( &Determinant,
Projection );
// Compute the frustum corners in world space.
XMVECTOR Points[6];
for( size_t i = 0; i < 6; ++i )
{
// Transform point.
Points[i] = XMVector4Transform(
HomogenousPoints[i], matInverse );
}
Out.Origin = XMFLOAT3( 0.0f, 0.0f, 0.0f );
Out.Orientation = XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f
);
// Compute the slopes.
Points[0] = Points[0] * XMVectorReciprocal(
XMVectorSplatZ( Points[0] ) );
Points[1] = Points[1] * XMVectorReciprocal(
XMVectorSplatZ( Points[1] ) );
Points[2] = Points[2] * XMVectorReciprocal(
XMVectorSplatZ( Points[2] ) );
Points[3] = Points[3] * XMVectorReciprocal(
XMVectorSplatZ( Points[3] ) );
Out.RightSlope = XMVectorGetX( Points[0] );
Out.LeftSlope = XMVectorGetX( Points[1] );
Out.TopSlope = XMVectorGetY( Points[2] );
Out.BottomSlope = XMVectorGetY( Points[3] );
// Compute near and far.
Points[4] = Points[4] * XMVectorReciprocal(
XMVectorSplatW( Points[4] ) );
Points[5] = Points[5] * XMVectorReciprocal(
XMVectorSplatW( Points[5] ) );
Out.Near = XMVectorGetZ( Points[4] );
Out.Far = XMVectorGetZ( Points[5] );
}
16.2.4.2 Frustum / Sphere Intersection
Za frustrirano izlečenje, jedan test koji ćemo želeti da izvedemo je presjek frustranja / sfere test. Ovo
nam govori da li se sfera preseca frustracijom. Zapamtite to sferu u potpunosti unutar frustuma se
računa kao raskrsnica, jer mi tretiramo frustum kao volumen, ne samo granica. Zato što modelujemo
frustum kao šest aviona koji su okrenuti prema dolje, a frustum / sfera test se može navesti na sledeći
način: Ako postoji frustrirana ravnina L takva da je sfera je u negativnom poluprostoru L, onda možemo
zaključiti da je sfera potpuno izvan frustrima. Ako takav avion ne postoji, onda zaključujemo da je
sfera preseca frustraciju. Dakle, test ukrštanja frustracije / sfere smanjuje se na šest sfernih i ravni
testova. Slika 16.7 pokazuje postavku sferne / ravni unakrsne provere. Neka sfera ima središnju tačku c I
radius r. Potom je potpisano rastojanje od centra sfere do ravnine k = n · c + d (Dodatak C). Ako | k | ≤ r
onda sfera preseca ravni. Ako je k <-r onda sfera je iza aviona. Ako je k> r, sfera je ispred ravnine i sfera
se preseca pozitivnog poluprostora aviona. Za potrebe preseka frustuma / sfere test, ako je sfera ispred
aviona, onda to smatramo kao raskrsnicu jer je to preseca pozitivni poluprostor koji definiše avion.
Slika 16.7. Sfera / raskrsnica. (a) k> r, a sfera se preseca pozitivan poluprostor aviona. (b) k <-r i sfera je
potpuno iza avion u negativnom polu-prostoru. (c) | k | ≤ r, a sfera se preseca na ravni. Klasa
BoundingFrustum pruža sljedeću funkciju člana da testira ako je a sfera preseca frustraciju. Imajte na
umu da sfera i frustum moraju biti isti koordinatni sistem za test ima smisla.
enum ContainmentType
{
// The object is completely outside the frustum.
DISJOINT = 0,
// The object intersects the frustum boundaries.
INTERSECTS = 1,
// The object lies completely inside the frustum
volume.
CONTAINS = 2,
};
ContainmentType BoundingFrustum::Contains(
_In_ const BoundingSphere& sphere ) const;
BoundingSphere contains a symmetrical member function:
ContainmentType BoundingSphere::Contains(
_In_ const BoundingFrustum& fr ) const;
16.2.4.3 Presek Frustum / AABB
Ispitivanje frustum / AABB presek prati istu strategiju kao i test frustum / sfera. Pošto modelujemo
frustum kao šest uopšte okrenutih ravni, frustrirani / AABB test se može navesti na sledeći način: Ako
postoji frustrirana ravnina L takva da je kutija u negativnom poluprostoru L, onda možemo zaključiti da
je kutija u potpunosti van frustuma. Ako takav avion ne postoji, onda zaključujemo da se kutija preseca
frustum. Dakle, frustrirani / AABB test za intersekciju smanjuje se na šest AABB / ravni testova.
Algoritam za test AABB / ravni je sljedeći. Nađite dijagonalni vektorski okvir v =, koji prolazi kroz centar
kutije, koji je najviše poravnat sa normalnom n. Na Slici 16.8, (a) ako je P ispred ravnine, onda K mora biti
ispred ravnine; (b) ako je K iza ravni, onda mora P biti i iza ravni; (c) ako je P iza ravnoteže i K je ispred
ravnine, onda kutija preseca ravninu. Slika 16.8. AABB / test raskrsnice ravni. Dijagonala je uvek
dijagonala najčešće usmjerena sa normalnom ravninom.
Pronalaženje PK koji je najviše poravnat sa normalnim vektorskim brojem n može se izvršiti sa
sledeći kod:
// For each coordinate axis x, y, z…
for(int j = 0; j < 3; ++j)
{
// Make PQ point in the same direction as
// the plane normal on this axis.
if( planeNormal[j] >= 0.0f )
{
P[j] = box.minPt[j];
Q[j] = box.maxPt[j];
}
else
{
P[j] = box.maxPt[j];
Q[j] = box.minPt[j];
}
}
Ovaj kod samo gleda na jednu dimenziju istovremeno, i bira Pi i Ki tako da Ki -
Pi ima isti znak kao normalna ravninska ravnina (slika 16.9). Slika 16.9. (Vrh) Normalna komponenta duž
ista osa je pozitivna, pa mi izaberite Pi = vMin [i] i Ki = vMak [i] tako da Ki - Pi ima isti znak kao i ravnina
normalna koordinata ni. (Dno) Normalna komponenta duž ista os je negativna, tako da izaberemo Pi =
vMak [i] i Ki = vMin [i], tako da Ki-Pi ima isti znak kao ravninski normalni koordinatni. Klasa
BoundingFrustum pruža sledeću funkciju člana da bi testirala ako je AABB preseca frustraciju. Imajte na
umu da AABB i frustum moraju biti isti koordinatni sistem za test ima smisla.
ContainmentType BoundingFrustum::Contains(
_In_ const BoundingBox& box ) const;
BoundingBox contains a symmetrical member function:
ContainmentType BoundingBox::Contains(
_In_ const BoundingFrustum& fr ) const;
16.3
Pozovite iz poglavlja 5 da hardver automatski odbacuje trokutke koji su van gledanja frustuma u fazi
iscrtavanja. Međutim, ako imamo milione trouglovi, svi trouglovi se i dalje šalju na cevovod za rendering
putem pozivnih poziva (koji ima API nadbiskup), a svi trouglovi prolaze kroz vertek shader, moguće
kroz faze tezelacije, a možda i kroz geometrijski shader, samo da bude odbačeno tokom faze isečavanja.
Jasno je, ovo je rasipna neefikasnost. Ideja o frustriranjem izlečenju je da aplikacijski kod odvodi grupe
trouglova u a viši nivo nego na osnovu trouglova. Slika 16.10 prikazuje jednostavan primer. Mi gradimo
ograničavajući volumen, kao što je sfera ili kutija, oko svakog objekta u sceni. Ako je ograničena
zapremina ne preseca frustum, onda ne moramo da podnesemo objekat (koji bi mogao da sadrži hiljade
trouglova) do Direct3D za crtanje. Ovo štedi GPU od toga da se radi na nečijim računima na nevidljivoj
geometriji, po cenu a jeftin CPU test. Pod pretpostavkom da je kamera vidljiva u vidu od 90 ° i beskrajno
daleko daleko ravno, frustracija fotoaparata zauzima samo 1/6 svetskog zapremine, tako da je 5/6
svetski predmeti mogu biti frustrirani, pod pretpostavkom da su predmeti ravnomerno raspoređeni
scena. U praksi, kamere koriste manji ugao gledanja od 90 ° i konačni daleki avion, što znači da možemo
da odvrnemo čak i više od 5/6 objekata scene. Slika 16.10. Objekti ograničeni količinom A i D su potpuno
spolja frustrirani, i zato ga ne treba izvlačiti. Objekt odgovara zapremini C potpuno je unutar frustuma i
treba ga izvući. Objekti ograničeni zapremine B i E su delimično izvan frustrima i delimično unutar
frustuma; mi moraju crtati ove predmete i pustiti hardver i trouglove izvan frustum-a.
Slika 16.11. Snimak ekrana "Instancing and Culling" demo.
U našem demo-u, koristimo 5 × 5 × 5 mrežu lobanje (vidi Sliku 16.11) instancing. Izračunavamo AABB
mrežu lobanje u lokalnom prostoru. U Metod UpdateInstanceData, vršimo frustriranje na svim našim
primjerima. Ako instanca se preseca frustumom, a onda ga dodamo u sledeći dostupni slot u našem
strukturirani bafer koji sadrži podatke instance i uvećava visibleInstanceCount counter. Na taj način,
prednji deo strukturiranog bafera sadrži podatke za sve vidljive instance. (Naravno, strukturirani bafer je
veličine da odgovara broj slučajeva u slučaju da su sve instance vidljive.) Zato što je AABB lobanje
mreža je u lokalnom prostoru, moramo da pretvorimo pogled koji je frustriran u lokalni prostor svake od
njih primer da bi se izvršio test ukrštanja; mogli bismo koristiti alternativne prostore pretvoriti AABB u
svetski prostor i frustriraju na svetski prostor, na primer. The šifru ažuriranja frustum-a je data u
nastavku:
XMMATRIX view = mCamera.GetView();
XMMATRIX invView =
XMMatrixInverse(&XMMatrixDeterminant(view), view);
auto currInstanceBuffer = mCurrFrameResource>
InstanceBuffer.get();
for(auto& e : mAllRitems)
{
const auto& instanceData = e->Instances;
int visibleInstanceCount = 0;
for(UINT i = 0; i < (UINT)instanceData.size(); ++i)
{
XMMATRIX world =
XMLoadFloat4x4(&instanceData[i].World);
XMMATRIX texTransform =
XMLoadFloat4x4(&instanceData[i].TexTransform);
XMMATRIX invWorld =
XMMatrixInverse(&XMMatrixDeterminant(world), world);
// View space to the object’s local space.
XMMATRIX viewToLocal = XMMatrixMultiply(invView,
invWorld);
// Transform the camera frustum from view space to
the object’s local space.
BoundingFrustum localSpaceFrustum;
mCamFrustum.Transform(localSpaceFrustum,
viewToLocal);
// Perform the box/frustum intersection test in
local space.
if(localSpaceFrustum.Contains(e->Bounds) !=
DirectX::DISJOINT)
{
InstanceData data;
XMStoreFloat4x4(&data.World,
XMMatrixTranspose(world));
XMStoreFloat4x4(&data.TexTransform,
XMMatrixTranspose(texTransform));
data.MaterialIndex =
instanceData[i].MaterialIndex;
// Write the instance data to structured buffer
for the visible objects.
currInstanceBuffer-
>CopyData(visibleInstanceCount++, data);
}
}
e->InstanceCount = visibleInstanceCount;
// For informational purposes, output the number of
instances
// visible over the total number of instances.
std::wostringstream outs;
outs.precision(6);
outs << L”Instancing and Culling Demo” <<
L” ” << e->InstanceCount <<
L” objects visible out of ” << e-
>Instances.size();
mMainWndCaption = outs.str();
}
Iako naša instantna bafera ima prostor za svaku instancu, mi samo crtamoVidljivi primeri koji odgovaraju
instancama od 0 do visibleInstanceCount-1:
cmdList->DrawIndexedInstanced(ri->IndexCount,
ri->InstanceCount,
ri->StartIndexLocation,
ri->BaseVertexLocation, 0);
Na slici 16.12 prikazana je razlika u performansama između izbacivanja frustum omogućeno i ne. Sa
frustriranim ubijanjem, podnosimo samo jedanaest primeraka cevovod za obradu. Bez frustracijskog
ubojstva, podnosimo svih 125 instanci rendering pipeline za obradu. Iako je vidljiva scena ista, sa
frustumomodbacivanje je isključeno, trošimo računarsku snagu preko 100 mrežica čestica čiji
geometrija se eventualno odbacuje tokom faze klipovanja. Svaka lobanja ima oko 60K
trouglovi, tako da je puno vertikala za obradu i puno trouglova za spajanje po lobanju. Od strane
radimo jedan frustum / AABB test, možemo odbaciti 60K trokuta od čak i poslati na
grafički plinovod - ovo je prednost frustrijskog ubojstva i vidimo razliku u
frejmova u sekundi.
Chapter 17
U ovom poglavlju imamo problem određivanja 3D objekta (ili primitivnog) korisnik izabran pomoću
kursora miša (pogledajte sliku 17.1). Drugim rečima, s obzirom na 2D ekran koordinate kursora miša,
možemo li odrediti 3D objekat na koji je projektovan ta tačka? Da bismo rešili ovaj problem, u nekom
smislu moramo raditi unazad; odnosno, obično se transformišemo iz 3D prostora u prostor ekrana, ali
ovde se transformišemo sa ekrana prostor nazad u 3D prostor. Naravno, već imamo mali problem: 2D
tačka ekrana ne odgovara jedinstvenoj 3D tački (tj. moguće je projektovati više od jedne 3D tačke)
na istu tačku prozora 2D projekcije - videti sliku 17.2). Dakle, postoje neke nejasnoća u određivanju koji
objekat je stvarno izabran. Međutim, ovo nije tako veliko problem, jer je najbliži predmet kameri obično
onaj koji želimo.
Slika 17.1. Korisnik odabira dodecahedron.
Slika 17.2. Pogled sa strane na frustum. Obratite pažnju na nekoliko tačaka u 3D prostoru može
projicirati na tačku prozora projekcije. Razmotrimo sliku 17.3, koja prikazuje frustraciju gledanja. Ovde p
je tačka na projekcioni prozor koji odgovara kliknutoj tački ekrana s. Sada to vidimo, ako smo
pucajte iz bora, koji potiče iz položaja oka, kroz p, presečemo objekat
čija projekcija okružuje p, odnosno cilindar u ovom primeru. Stoga, naša strategija
je kako slijedi: Kad jednom izračunamo žljebanje, možemo se ponoviti kroz svaki objekat u
scenu i testira ako se zraka preseca. Objekat kojim se žljeb preseca je objekat koji je
je izabrao korisnik. Kao što je pomenuto, zraka može da se preseče nekoliko objekata scene (ili nijedno
uopšte - ništa nije izabrano), ako su objekti duž puta zraka, ali sa drugačijom dubinom
vrednosti, na primer. U ovom slučaju, možemo samo uzeti urezani objekat najbliži
kamera kao izabrani objekat.
Slika 17.3. Snimanje zraka kroz p će presečiti objekat čije projekcije okružuje str. Imajte na umu da
projektovana tačka p na projekciji projekcija odgovara tačka s ekranom kliknute s.
Ciljevi:
1. Da naučite kako da implementirate algoritam za odabir i da razumete kako to funkcioniše. Mi
rasklapanje u sledeće četiri koraka:
1. S obzirom na klikanu tačku s, pronađite odgovarajuću tačku na projekciji prozor i nazovite str.
2. Izračunajte izbirni zrak u prostoru za prikaz. To je zrak koji potiče od porekla, u prostoru pogleda, koji
puca kroz str.
3. Transformirati izbirni zrak i modele za ispitivanje sa zrakom u isto prostor.
4. Odredite objekat u kojem se selektovani žljeb preseca. Najbliži (iz kamere) Presečeni objekat odgovara
izabranom ekranu.
17.1 TRANZFORMIRAN PROZOR PROZORA
Prvi zadatak je pretvaranje tačke klikiranog ekrana na normalizovane koordinate uređaja
(vidi §5.4.3.3). Podsjetimo da matrica pogleda pretvara vertikale iz normalizovanog uređaja
koordiniše ekranski prostor; dato je u nastavku: Varijable matrice prikaza odnose se na one
D3D12_VIEVPORT struktura:
typedef struct D3D12_VIEWPORT
{
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D12_VIEWPORT;
Uopšteno gledano, za igru, prikazni prozor je čitavi bubanj i raspon dubine
je 0 do 1. Dakle, TopLeftKs = 0, TopLeftI = 0, MinDepth = 0, MakDepth = 1, Širina = v, i
Visina = h, gde su v i h, su širina i visina backbffera, respektivno.
Pod pretpostavkom da je ovo zaista slučaj, matrica pogleda pojednostavljuje se na:
Sada pustite pndc = (kndc, indc, zndc, 1) tačka u normalizovanom prostoru uređaja (tj. -1 ≤ kndc
≤ 1, -1 ≤ indc ≤ 1 i 0 ≤ zndc ≤ 1). Transformacija pndc-a u prostor ekrana daje:
Koordinatni zndc se upravo koristi od bafera za dubinu i mi se ne bavimo ni jednim
dubinske koordinate za biranje. Tačka 2D tačke ps = (ks, is) koja odgovara pndc je
samo transformisane k- i i-koordinate:
Gornja jednačina nam daje tačku ekrana ps u smislu normalizovanog uređaja
point pndc i dimenzije pogleda. Međutim, u našoj situaciji izbora, na početku smo
s obzirom na tačku ekrana ps i dimenzije prikaza, i želimo naći pndc. Rešavanje
gornje jednačine za pndc prinose:
Sada imamo klikiranu tačku u prostoru NDC. Ali da zapalimo birući zrak, stvarno
želite tačku ekrana u pogledu prostora. Podsjetimo iz §5.6.3.3 da smo mapirali projektovane
tačka iz prostora gledanja na NDC razmak deljenjem k-koordinate prema razmeru r:
Dakle, da bi se vratili na prostor, potrebno je samo pomnožiti k-koordinat u NDC
prostor po razmeru. Na taj način je kliknuta tačka u prostoru pogleda:
Projektovana i koordinata u pogledu prostora je ista u prostoru NDC. Ovo je
jer smo izabrali visinu projekcionog prozora u prostoru za prikaz kako bi pokrili
interval [-1, 1].
Sada se setimo iz §5.6.3.1 da prozor projekcija leži na daljini
od porekla, gde je a vertikalno polje ugla gledanja. Tako da možemo pucati na biranje
rai kroz tačku (kv, iv, d) na projekcionom prozoru. Međutim, to zahteva da mi
računati. Jednostavniji način je da posmatramo sa slike 17.4 da:
Dakle, možemo ubaciti naš odabrani zrak kroz tačku (k'v, i'v, 1) umesto toga. Imajte na umu da ovo daje
istu birući zrak kao i jedan snimak kroz tačku (kv, iv, d). Kod koji izračunava odbrojavanje zraka u
prikaznom prostoru dati je u daljem tekstu:
void PickingApp::Pick(int sx, int sy)
{
XMFLOAT4X4 P = mCamera.GetProj4x4f();
// Compute picking ray in view space.
float vx = (+2.0f*sx / mClientWidth - 1.0f) / P(0,
0);
float vy = (-2.0f*sy / mClientHeight + 1.0f) / P(1,
1);
// Ray definition in view space.
XMVECTOR rayOrigin = XMVectorSet(0.0f, 0.0f, 0.0f,
1.0f);
XMVECTOR rayDir = XMVectorSet(vx, vy, 1.0f, 0.0f);
Imajte na umu da zraka proizlazi iz porijekla u prostoru pogleda, pošto oko sedi na poreklo u pogledu
prostora.
17.2 SVIJET / LOKALNI PROSTORNI PROSTOR
Za sada imamo izbirni zrak u prostoru pogleda, ali ovo je korisno samo ako su naši objekti u pogledu
prostora, takođe. Zato što matrica prikaza pretvara geometriju iz svetskog prostora u pogledati prostor,
inverzna matrica prikaza pretvara geometriju iz prostora za gledanje na svet prostor. Ako je rv (t) = k + tu
je rai picking rai prostora, a V je matrica prikaza, onda je svetski prostor za sakupljanje prostora daje:
Imajte na umu da se izvor mrlja k transformiše kao tačka (tj., Kv = 1) i smjer zraka u se transformiše kao
vektor (tj., uv = 0). Svetski prostor za biranje svemira može biti koristan u nekim situacijama gde ih imate
objekti definisani u svetskom prostoru. Međutim, većina vremena je geometrija objekta definisani u
odnosu na sopstveni lokalni prostor objekta. Zbog toga, radi izvođenja zraka / objekta presek testa,
moramo pretvoriti zraku u lokalni prostor objekta. Ako je V svetska matrica objekta, matrica V-1
transformiše geometriju iz svetskog prostora na lokalnog prostora objekta. Dakle, lokalni prostor za
sakupljanje prostora je:
Generalno, svaki objekat na sceni ima svoj lokalni prostor. Zbog toga, zraka mora biti
transformisano u lokalni prostor svakog objekta iz scene da bi se izvršio test ukrštanja.
Možda se predlaže da se mreža pretvori u svetski prostor i raskrsi
testiraj tamo. Međutim, ovo je preskupo. Mrežica može sadržavati hiljade vertisa, i
sve te vertikale bi trebalo da se transformišu u svetski prostor. Mnogo je efikasnije
samo pretvoriti zraku u lokalne prostore objekata.
Sledeći kod pokazuje kako se picking rai pretvara iz prostora za prikaz na
lokalni prostor objekta:
// Assume nothing is picked to start, so the picked
render-item is invisible.
mPickedRitem->Visible = false;
// Check if we picked an opaque render item. A real
app might keep a separate
// “picking list” of objects that can be selected.
for(auto ri : mRitemLayer[(int)RenderLayer::Opaque])
{
auto geo = ri->Geo;
// Skip invisible render-items.
if(ri->Visible == false)
continue;
XMMATRIX V = mCamera.GetView();
XMMATRIX
invView = XMMatrixInverse(&XMMatrixDeterminant(V), V);
XMMATRIX W = XMLoadFloat4x4(&ri->World);
XMMATRIX
invWorld = XMMatrixInverse(&XMMatrixDeterminant(W), W);
// Tranform ray to vi space of Mesh.
XMMATRIX toLocal = XMMatrixMultiply(invView,
invWorld);
rayOrigin = XMVector3TransformCoord(rayOrigin,
toLocal);
rayDir = XMVector3TransformNormal(rayDir, toLocal);
// Make the ray direction unit length for the
intersection tests.
rayDir = XMVector3Normalize(rayDir);
KSMVector3TransformCoord i KSMVector3TransformNormal funkcija uzima 3D vektore kao parametre,
ali zapazite to sa Funkcija KSMVector3TransformCoord se shvata za v = 1 za četvrtu sastavni deo. S druge
strane, sa funkcijom KSMVector3TransformNormal za četvrtu komponentu se shvata v = 0. Tako
možemo koristiti KSMVector3TransformCoord za transformaciju poena i možemo ih koristiti
KSMVector3TransformNormalno da transformiše vektore.
17.3 INTERESKA RAI / MESH
Jednom kada imamo biran žare i mrežu u istom prostoru, možemo izvršiti presek testa da bi se videlo da
li se trag izbora preseče mrežom. Sledeći kod ponavlja kroz svaki trougao u mrežu i vrši test ukrštanja
zraka / trougla. Ako je zraka presecuje jedan od trouglova, onda mora da je pogodio mrežu na kojoj
trougao pripada. U suprotnom, zrac mu nedostaje. Uobičajeno, želimo najbližu krizu trougla, kao
moguće je da se zraka preseče nekoliko mrežastih trouglova ako se trouglovi preklapaju
poštovanje zraka.
// If we hit the bounding box of the Mesh, then we
might have
// picked a Mesh triangle, so do the ray/triangle
tests.
//
// If we did not hit the bounding box, then it is
impossible that we hit
// the Mesh, so do not waste effort doing ray/triangle
tests.
float tmin = 0.0f;
if(ri->Bounds.Intersects(rayOrigin, rayDir, tmin))
{
// NOTE: For the demo, we know what to cast the
vertex/index data to.
// If we were mixing formats, some metadata would be
needed to figure
// out what to cast it to.
auto vertices = (Vertex*)geo->VertexBufferCPU-
>GetBufferPointer();
auto indices = (std::uint32_t*)geo->IndexBufferCPU-
>GetBufferPointer();
UINT triCount = ri->IndexCount / 3;
// Find the nearest ray/triangle intersection.
tmin = MathHelper::Infinity;
for(UINT i = 0; i < triCount; ++i)
{
// Indices for this triangle.
UINT i0 = indices[i * 3 + 0];
UINT i1 = indices[i * 3 + 1];
UINT i2 = indices[i * 3 + 2];
// Vertices for this triangle.
XMVECTOR v0 = XMLoadFloat3(&vertices[i0].Pos);
XMVECTOR v1 = XMLoadFloat3(&vertices[i1].Pos);
XMVECTOR v2 = XMLoadFloat3(&vertices[i2].Pos);
// We have to iterate over all the triangles in
order to find
// the nearest intersection.
float t = 0.0f;
if(TriangleTests::Intersects(rayOrigin, rayDir,
v0, v1, v2, t))
{
if(t < tmin)
{
// This is the new nearest picked triangle.
tmin = t;
UINT pickedTriangle = i;
// Set a render item to the picked triangle so
that
// we can render it with a special “highlight”
material.
mPickedRitem->Visible = true;
mPickedRitem->IndexCount = 3;
mPickedRitem->BaseVertexLocation = 0;
// Picked render item needs same world matrix
as object picked.
mPickedRitem->World = ri->World;
mPickedRitem-
>NumFramesDirty = gNumFrameResources;
// Offset to the picked triangle in the mesh
index buffer.
mPickedRitem->StartIndexLocation = 3 *
pickedTriangle;
}
}
}
}
Obratite pažnju na to da koristimo kopiju sistemske memorije geometrije mreže uskladištene u klasi
MeshGeometri. To je zato što ne možemo pristupiti vertici / indeksu bafer za čitanje koji će biti nacrtan
od GPU-a. Uobičajeno je čuvati system memorijske kopije geometrije za stvari kao što su biranje i
otkrivanje sudara. Ponekad a pojednostavljena verzija mreže se čuva u ove svrhe kako bi se uštedela
memorija I računanje.
17.3.1 Presek Rai / AABB
Obratite pažnju da prvo koristimo funkciju biblioteke DirectKs collision BoundingBok ::
Preseče da vidi da li se zraka preseca graničnu kutiju mreže. Ovo je analogno
na optimizaciju izbacivanja frustracije u prethodnom poglavlju. Izvođenje raskrsnice zraka
test za svaki trougao u sceni doprinosi u računskom vremenu. Čak i za mrežu koja nije blizu
birački snop, ipak bi morao da se ponovimo nad svakim trouglom da bi se zaključilo da su mrlje u zraku
mreža; ovo je rasipno i neefikasno. Popularna strategija je približiti mrežu
sa jednostavnom ograničenom zapreminom, poput sfere ili kutije. Zatim, umesto da se presecaju zraci
sa mrežicom, prvo se presecamo zrakom s ograničenjem zapremine. Ako zraka propusti
ograničavajući volumen, tada zraka neobavezno nedostaje mrežnu mrežu trougla i tako nema
treba dalje procjene. Ako se zraka preseca ograničavajući volumen, onda ćemo to uraditi
preciznije testiranje zraka / mreža. Pod pretpostavkom da će zrakom nedostajati najviše ograničene
zapremine scena, ovo nas spasava mnogo testova intersekcije zraka / trougla. The
BoundingBok :: Intersects funkcija vraća true ako se zraka preseca polje i
false othervise; prototip je na sledeći način:
bool XM_CALLCONV
BoundingBox::Intersects(
FXMVECTOR Origin, // ray origin
FXMVECTOR Direction, // ray direction (must be unit
length)
float& Dist ); const // ray intersection parameter
S obzirom na zrak r (t) = k + tu, poslednji parametar izlazi na parametar zraka t0 koji daje stvarnu tačku
preseka p:
17.3.2 Presečak Rai / Sphere
Postoji i test ukrštanja zraka / sfere datog u biblioteci sudara za DirectKs:
There is also a ray/sphere intersection test given in the DirectX collision library:
bool XM_CALLCONV
BoundingSphere::Intersects(
FXMVECTOR Origin,
FXMVECTOR Direction,
float& Dist ); const
Da bi se napravio ukus ovih testova, pokazaćemo kako da izvedemo test ukrštanja zraka / sfere.
Tačke p na površini sfere s centrom c i radijusom r zadovoljavaju jednačinu:
Neka je r (t) = k + tu tu rai. Želimo da rešimo za t1 i t2 tako da r (t1) i r (t2) zadovoljavaju
sferna jednačina (tj. parametri t1 i t2 duž zraka koji daju raskrsnicu
bodova).
Ako je pravac zraka dužine jedinice, onda je a = u · u = 1. Ako je rešenje imaginarno
komponenti, zraka ne sija. Ako su dva prava rešenja ista, zraka
seka tačka tangentna ka sferi. Ako su dva stvarna rešenja različita, zraka pere
dve tačke sfere. Negativno rešenje označava tačku preseka "iza"
zrak. Najmanji pozitivno rešenje daje najbliži parametar preseka.
17.3.3 Presek Rai / Trougao
Za obavljanje testa ukrštanja zraka / trougla koristimo DirectKs collision biblioteku
funkcija TriangleTests :: Intersects:
bool XM_CALLCONV
TriangleTests::Intersects(
FXMVECTOR Origin, // ray origin
FXMVECTOR Direction, // ray direction (unit length)
FXMVECTOR V0, // triangle vertex v0
GXMVECTOR V1, // triangle vertex v1
HXMVECTOR V2, // triangle vertex v2
float& Dist ); // ray intersection parameter
Da bi se napravio ukus ovih testova, pokazaćemo kako da izvedemo test ukrštanja zraka / sfere.
Tačke p na površini sfere s centrom c i radijusom r zadovoljavaju jednačinu:
Neka je r (t) = k + tu tu rai. Želimo da rešimo za t1 i t2 tako da r (t1) i r (t2) zadovoljavaju
sferna jednačina (tj. parametri t1 i t2 duž zraka koji daju raskrsnicu
bodova).
Ako je pravac zraka dužine jedinice, onda je a = u · u = 1. Ako je rešenje imaginarno
komponenti, zraka ne sija. Ako su dva prava rešenja ista, zraka
seka tačka tangentna ka sferi. Ako su dva stvarna rešenja različita, zraka pere
dve tačke sfere. Negativno rešenje označava tačku preseka "iza"
zrak. Najmanji pozitivno rešenje daje najbliži parametar preseka.
17.3.3 Presek Rai / Trougao
Za obavljanje testa ukrštanja zraka / trougla koristimo DirectKs collision biblioteku
funkcija TriangleTests :: Intersects:
17.4 DEMO APLIKACIJA
Demo za ovo poglavlje čini automobilsku mrežu i omogućava korisniku da izabere trougao pritiskom na
desno dugme miša, a odabrani trougao se prikazuje pomoću "highlight" materijal (vidi sliku 17.6). Za
prikaz trougla sa isticanjem, potrebna nam je stavka renderera za to. Za razliku od prethodnih stavki u
ovoj knjizi gde smo ih definisali inicijalizacijsko vreme, ova renderna stavka može se samo delimično
popuniti u inicijalizacijskom vremenu. To je zato što još uvek ne znamo koji će trougao biti izabran, pa ne
znamo početnu lokaciju indeksa i svetsku matricu. Pored toga, trougao ne treba uvek se bavi. Zbog toga
smo dodali vidljivu osobinu na stavku renderera struktura. Nevidljiva reklamna stavka neće biti nacrtana.
Donji kod, koji je deo PickingApp :: Pick metod, pokazuje kako popunjavamo preostalu stavku renderera
svojstva na osnovu izabranog trougla:
// Cache a pointer to the render-item of the picked
// triangle in the PickingApp class.
RenderItem* mPickedRitem;
if(TriangleTests::Intersects(rayOrigin, rayDir, v0,
v1, v2, t))
{
if(t < tmin)
{
// This is the new nearest picked triangle.
tmin = t;
UINT pickedTriangle = i;
// Set a render item to the picked triangle so
that
// we can render it with a special “highlight”
material.
mPickedRitem->Visible = true;
mPickedRitem->IndexCount = 3;
mPickedRitem->BaseVertexLocation = 0;
// Picked render item needs same world matrix as
object picked.
mPickedRitem->World = ri->World;
mPickedRitem->NumFramesDirty = gNumFrameResources;
// Offset to the picked triangle in the mesh index
buffer.
mPickedRitem->StartIndexLocation = 3 *
pickedTriangle;
}
}
Ova renderna stavka se nacrta nakon što nacrtamo naše neprozirne stavke. Koristi poseban
istaknite PSO, koji koristi blende transparentnosti i postavlja funkciju poređenja testa dubine na
D3D12_COMPARISON_FUNC_LESS_EKUAL. Ovo je potrebno jer se izabrani trougao nacrta dvaput, drugi
put sa istaknutim materijalom. Drugi put kada se probudi trougao, test dubine bi bio neuspešan ako je
funkcija upoređivanja bila samo D3D12_COMPARISON_FUNC_LESS.
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Opaque]);
mCommandList-
>SetPipelineState(mPSOs[“highlight”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Highlight]);
Poglavlje 18 CUBE MAPPING
U ovom poglavlju istražujemo kockarske karte, koje su u osnovi nizovi od šest tekstura
interpretirani na poseban način. Sa mapiranjem kocke, lako možemo tekstirati nebo ili model
refleksije.
Ciljevi:
1. Da biste saznali šta su kocke karte i kako ih uzimati u HLSL kodu.
2. Da biste otkrili kako kreirati mape kockica sa DirectKs teksturnim alatima.
3. Da saznamo kako možemo koristiti kocke karte za model refleksije.
4. Da shvatimo kako možemo da teksturiramo sferu sa kockama za simulaciju neba i
daleke planine.
18.1
Ideja o mapiranju kocke je da se sačuva šest tekstura i da ih vizualizuje kao lica a
kocka-odakle je ime kocke mapirano na sredini i osa su poravnate oko neke koordinate
sistem. Budući da je kubna tekstura poravnata prema osovini, svako lice odgovara pravcu
duž tri glavne ose; stoga je prirodno za referencu određeno lice na kocki
mapu zasnovane na pravcu ose (± Ks, ± I, ± Z) koja seosko lice.
U Direct3D-u, kocka mapa predstavlja teksturni niz sa šest elemenata kao što je
1. indeks 0 odnosi se na lice + Ks
2. indeks 1 odnosi se na -Ks lice
3. indeks 2 odnosi se na lice + I
4. indeks 3 odnosi se na -I face
5. indeks 4 odnosi se na + Z lice
6. indeks 5 odnosi se na -Z lice
Za razliku od 2D teksture, više ne možemo identifikovati teksela sa 2D teksturom
koordinate. Za identifikaciju teksela u kocki karti koristimo koordinate 3D teksture, koje
definišite 3D vektor za traženje v poreklom iz porekla. Teksel kocke mape v
Preseke (vidi Sliku 18.1) je teksel koji odgovara 3D koordinatama v
Koncepti filtriranja teksture o kojima se govori u poglavlju 9 primenjuje se u slučaju da se vazi na jednu
tačku
između uzoraka tekela.
Slika 18.1. Ilustriramo u 2D za jednostavnost; u 3D kvadrat postaje kocka.
Kvadrat označava kocke mape i poravnate sa nekim koordinatama
sistem. Snimamo vektor v od porekla. Teksel V se preseca
tekel. Na ovoj ilustraciji, v seče se lice kocke koje odgovara osi + I.
Veličina vektora za pretragu je nebitna, samo u pravcu
pitanja. Dva vektora sa istim pravcem, ali različitih veličina
uzorku istu tačku na mapi kocke.
U HLSL-u, tekstura kocke je predstavljena tipom TektureCube. Sledeći
fragment koda ilustruje kako uzimamo uzorak mape kocke:
TextureCube gCubeMap;
SamplerState gsamLinearWrap : register(s2);
…
// in pixel shader
float3 v = float3(x,y,z); // some lookup vector
18.2 MAŠINE ZA ŽIVOTNU SREDINU
Primarna primena kocke mape je mapiranje okoline. Ideja je da položite kamera u centru nekog objekta
O u sceni sa ugao gledanja od 90 ° (oba vertikalno i horizontalno). Zatim će kamera gledati niz pozitivnu
k-osu, negativno k-ose, pozitivna i-osa, negativna i-osa, pozitivna z-osa, i negativna z-osa, i uzeti
slika scene (isključujući objekat O) iz svake od ovih šest tačaka. Jer polje ugla gledanja je 90 °, ove šest
slika će uhvatiti celokupno okruženje okruženje (pogledaj sliku 18.2) iz perspektive objekta O. Zatim ih
skladištimo šest slika okoline u kocki mapama, što vodi do imena mapa okoline. Drugim rečima, mapa sa
okruženjem je mapa kocke gde je kocka lica čuvaju okolne slike okruženja.
Slika 18.2. Primer mape okruženja nakon "razvijanja" kocke Mapa. Zamisli da preusmerite ova šest lica u
3D kutiju, a onda zamislite da ste na njemu centar kutije. Sa svakog pravca gledate, vidite okolinu
Životna sredina. Gornji opis sugeriše da je za svaki od njih potrebno kreirati mapu okoline objekat koji
treba da koristi mapiranje okruženja. Iako bi to bilo tačnije, to bi takođe bilo zahteva više teksturne
memorije. Kompromis bi bio da se koristi nekoliko mapa o okolini koji zauzimaju okruženje u ključnim
tačkama scene. Tada će objekti uzeti uzorak mapu okoline koja je najbliža njima. Ovo pojednostavljenje
obično dobro funkcioniše u praksi jer su sa zakrivljenim predmetima netačne refleksije teško primetiti.
Drugi pojednostavljenje koje se često uzima sa mapiranjem okoline jeste da se iz određenih objekata
izostavljaju scena. Na primer, mapa sa okruženjem na slici 18.2 samo snima udaljenu "Pozadinske"
informacije o nebu i planinama koje su veoma daleko. Lokalna scena objekti su izostavljeni. Iako je mapa
okruženja u pozadini, u nekom smislu, nepotpuna, u praksi dobro funkcioniše kako bi se stvorili zrcalni
refleksiji. Da bi se uhvatili lokalnih objekata, morali bismo da koristimo Direct3D da prikazujemo šest
slika našeg okruženja Mapa; o ovome se govori u § 18.5. U demo za ovo poglavlje (Slika 18.3), svi objekti
u sceni dele istu kartu okruženja prikazanu na slici 18.2. Slika 18.3. Snimak ekrana "demo kocke".
Ako su pravci osa kamera pogledala dole kako bi napravila slike mape okoline bile svetske kosmičke ose,
onda se navodi da je karta za okolinu generisana u odnosu na svetski prostor. Vi biste, naravno, mogli da
snimite okruženje iz drugačije orijentacije (recite lokalni prostor objekta). Međutim, koordinate za
pretraživanje vektora moraju biti u prostor kubne mape je relativan. Zbog toga što kocke karte samo
čuvaju podatke o tekstu, njihov sadržaj može biti unapred generisan od strane umetnik (baš kao i 2D
teksture koje smo koristili). Zbog toga, ne moramo da koristimo prikazivanje u realnom vremenu za
izračunavanje slike kocke mape. To jest, možemo napraviti scenu u 3D svetski urednik, a zatim unapred
stavite šest kocke mape na slike u uredniku. Za mape vanjskog okruženja, program Terragen
(http://vvv.planetside.co.uk/) je zajednički za korišćenje (besplatno za ličnu upotrebu) i mogu kreirati
fotorealističke scene na otvorenom. The mape životne sredine koje smo napravili za ovu knjigu, kao što
je prikazano na slici 18.2 napravljen sa Terragenom.
dijalog bok-a i podesite faktor zuma na 1.0 da biste postigli 90 ° vidno polje. Takođe budi sigurno
postaviti svoje dimenzije izlaznog snimka da budu jednake tako da je i vertikalna i horizontalni uglovi
vidnog polja su isti, naime 90 °. Postoji lep Terragen skript na internet
(https://developer.valvesoftvare.com/viki/Skibok_(2D)_vith_Terragen) koja će koristite trenutnu
poziciju kamere i izradite šest okolnih slika 90 ° vidno polje.
Jednom kada ste kreirali šest slika kocke mape koristeći neki program, moramo
stvoriti teksturu kocke mape, koja čuva svih šest. DDS formata slike koju imamo
koristeći lako podržava mape kockica, i možemo koristiti alatku za oblikovanje teksta za izgradnju a
kocka karta iz šest slika. Ispod je primer kako se kreira mapa kocke
tekassemble (preuzete iz dokumentacije za tekassemble):
texassemble -cube -w 256 -h 256 -o cubemap.dds
lobbyxposjpg lobbyxneg.jpg lobbyypos.jpg lobbyyneg.jpg
lobbyzpos.jpg lobbyzneg.jpg
NVIDIA obezbeđuje Photoshop plugine za uštedu .DDS i cubemaps u Photoshop; pogledajte
http://developer.nvidia.com/nvidia-tekture-tools-adobephotoshop. 18.2.1 Učitavanje i korišćenje
kockarskih mapa u Direct3D Kao što je pomenuto, mapa kocke je predstavljena u Direct3D teksturiranim
nizom sa šest elementi. Naš DDS kod za učitavanje tekstura (DDSTektureLoader.h / .cpp) već podržava
učitavanje kubnih mapa, i možemo da učitamo teksturu kao i svaka druga. Kod za učitavanje će
otkriti da DDS datoteka sadrži mapu kocke i da će kreirati teksturni niz i učitati ga
podatke o licima u svaki element.
auto skyTex = std::make_unique<Texture>();
skyTex->Name = “skyTex”;
skyTex->Filename = L”Textures/grasscube1024.dds”;
ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.
mCommandList.Get(), skyTex->Filename.c_str(),
skyTex->Resource, skyTex->UploadHeap));
Kad kreiramo SRV u izvorni teksture kubne mape, mi odredimo dimenziju
D3D12_SRV_DIMENSION_TEKSTURECUBE i koristite svojstvo TektureCube od opis SRV-a:
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping =
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.ViewDimension =
D3D12_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube.MostDetailedMip = 0;
srvDesc.TextureCube.MipLevels = skyTex-
>GetDesc().MipLevels;
srvDesc.TextureCube.ResourceMinLODClamp = 0.0f;
srvDesc.Format = skyTex->GetDesc().Format;
md3dDevice->CreateShaderResourceView(skyTex.Get(),
&srvDesc, hDescriptor);
18.3 TEKSTIRANJE SKI
Možemo koristiti mapu okruženja da tekstujete nebo. Mi kreiramo veliku sferu okružuje celu scenu. Da
napravimo iluziju dalekih planina daleko u horizontu i nebo, teksturamo sferu pomoću mape okruženja
metodom prikazanom na slici 18.4. Na taj način, mapa životne sredine se projektuje na površinu sfere.
Slika 18.4. Ilustriramo u 2D za jednostavnost; u 3D kvadrat postaje kocka a krug postaje sfera.
Pretpostavljamo da su mapa neba i okruženja usredsređen na isto poreklo. Zatim da teksturira tačku na
površini sfere, koristimo vektor od porekla do površine kao vektor za pretragu u kocka karta. Ovo
projektuje mapu kocke na sferu. Pretpostavljamo da je neba sfera beskrajno daleko (tj. Ona je
usredsređena na svetski prostor, ali ima beskonačni radijus), pa bez obzira kako se kamera kreće u svetu,
nikad se ne pojavljujemo bliže ili dalje od površine nebeske sfere. Implementirati ovo beskonačno
udaljeno nebo, jednostavno postavljamo nebesku sferu o kameri na svetu tako da je uvek u centru
pažnje oko fotoaparata. Shodno tome, dok se kamera pomera, ne bismo se približavali površini sfere.
Ako to nismo uradili, i pustili smo kamera se približava površini neba, čitava iluzija bi se srušila, kao trik
koristimo da simulišemo nebo bi bilo očigledno.
//*********************************************************************
// Sky.hlsl by Frank Luna (C) 2015 All Rights
Reserved.
//*********************************************************************
// Include common HLSL code.
#include “Common.hlsl”
struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosL : POSITION;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Use local vertex position as cubemap lookup
vector.
vout.PosL = vin.PosL;
// Transform to world space.
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
// Always center sky about camera.
posW.xyz += gEyePosW;
// Set z = w so that z/w = 1 (i.e., skydome always
on far plane).
vout.PosH = mul(posW, gViewProj).xyww;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return gCubeMap.Sample(gsamLinearWrap, pin.PosL);
}
Programi neba shader su značajno različiti od programa shader za crtanje naših objekata (Default.hlsl).
Međutim, on ima isti korenski potpis tako da mi ne morate da menjate root potpis u sredini crteža. Kod
koji je uobičajen na oba Default.hlsl i Ski.hlsl je presao u Common.hlsl, tako da kod nije duplirana. Za
referencu, Common.hlsl izgleda ovako:
//****************************************************************************
// Common.hlsl by Frank Luna (C) 2015 All Rights
Reserved.
//****************************************************************************
// Defaults for number of lights.
#ifndef NUM_DIR_LIGHTS
#define NUM_DIR_LIGHTS 3
#endif
#ifndef NUM_POINT_LIGHTS
#define NUM_POINT_LIGHTS 0
#endif
#ifndef NUM_SPOT_LIGHTS
#define NUM_SPOT_LIGHTS 0
#endif
// Include structures and functions for lighting.
#include “LightingUtil.hlsl”
struct MaterialData
{
float4 DiffuseAlbedo;
float3 FresnelR0;
float Roughness;
float4x4 MatTransform;
uint DiffuseMapIndex;
uint MatPad0;
uint MatPad1;
uint MatPad2;
};
TextureCube gCubeMap : register(t0);
// An array of textures, which is only supported in
shader model 5.1+. Unlike
// Texture2DArray, the textures in this array can be
different sizes and
// formats, making it more flexible than texture
arrays.
Texture2D gDiffuseMap[4] : register(t1);
// Put in space1, so the texture array does not
overlap with these resources.
// The texture array will occupy registers t0, t1, …,
t3 in space0.
StructuredBuffer<MaterialData> gMaterialData :
register(t0, space1);
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
// Constant data that varies per frame.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
uint gMaterialIndex;
uint gObjPad0;
uint gObjPad1;
uint gObjPad2;
};
// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
// Indices [0, NUM_DIR_LIGHTS) are directional
lights;
// indices [NUM_DIR_LIGHTS,
NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;
// indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS,
// NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)
// are spot lights for a maximum of MaxLights per
object.
Light gLights[MaxLights];
};
Crtež neba zahteva različite programe senatora, a time i novi PSO. Dakle, crtamo nebo kao poseban sloj
u našem crtežnom kodu:
// Draw opaque render-items.
mCommandList->SetPipelineState(mPSOs[“opaque”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Opaque]);
// Draw the sky render-item.
mCommandList->SetPipelineState(mPSOs[“sky”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Sky]);
Osim toga, renderovanje neba zahteva neka različita stanja renderovanja. Naročito, jer kamera leži
unutar sfere, potrebno je onemogućiti lijevanje lica (ili pravljenje takođe bi trebalo da funkcionišu
trikotažu unazad na satu, a mi treba da promenimo funkcija upoređivanja dubine na LESS_EKUAL tako
da će nebo proći test dubine:
D3D12_GRAPHICS_PIPELINE_STATE_DESC skyPsoDesc =
opaquePsoDesc;
// The camera is inside the sky sphere, so just turn
off culling.
skyPsoDesc.RasterizerState.CullMode =
D3D12_CULL_MODE_NONE;
// Make sure the depth function is LESS_EQUAL and not
just LESS.
// Otherwise, the normalized depth values at z = 1
(NDC) will
// fail the depth test if the depth buffer was cleared
to 1.
skyPsoDesc.DepthStencilState.DepthFunc =
D3D12_COMPARISON_FUNC_LESS_EQUAL;
skyPsoDesc.pRootSignature = mRootSignature.Get();
skyPsoDesc.VS =
{
reinterpret_cast<BYTE*>(mShaders[“skyVS”]-
>GetBufferPointer()),
mShaders[“skyVS”]->GetBufferSize()
};
skyPsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders[“skyPS”]-
>GetBufferPointer()),
mShaders[“skyPS”]->GetBufferSize()
};
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&skyPsoDesc, IID_PPV_ARGS(&mPSOs[“sky”])));
18.4 MODELING REFLECTIONS
U Poglavlju 8 smo saznali da spektralni naglasak dolazi iz svetlosnih izvora
emitovana svetlost udara površinu i može se odraziti u oko na osnovu Fresnelovog efekta i
hrapavost. Međutim, zbog rasipanja i odbijanja svetlosti, svetlost stvarno udara
površina iz svih pravaca iznad površine, a ne samo duž zraka od direktnog svetla
izvori. Imali smo indirektno difuzno svetlo sa našim ambijentalnim izrazom u našem osvetljenju
jednačina. U ovom odeljku prikazujemo kako da koristimo mape okoline za modeliranje zrna
refleksije koje dolaze iz okoline. Zrcalno razmišljanje, mislimo
da ćemo samo pogledati svetlost koja se reflektuje na površini zbog Fresnel-a
efekat. Napredna tema o kojoj ne govorimo koristi kockarske karte za izračunavanje difuznog osvetljenja
iz okoline (npr., videti
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter10.html).
Kada napravimo scenu o tački O za izgradnju mape okoline, mi smo
snimanja svjetlosnih vrijednosti koje dolaze iz svih pravaca oko tačke O. Drugim riječima,
mapa okoline čuva svetlosne vrednosti koje dolaze iz svakog pravca oko tačke O,
i možemo razmišljati o svakom tekselu na mapi o životnoj sredini kao izvoru svetlosti. Koristimo ovo
podaci približavaju zrcalne refleksije svetlosti iz okoline
Životna sredina. Da biste videli ovo, pogledajte Sliku 18.5. Svetlost iz okoline dolazi sa
pravac incidenta I i odražava površinu (zbog efekta Fresnel-a) i ulazi u
oko u pravcu v = E - p. Svetlost iz okoline se dobija uzimanjem uzorka
mapa kruga životne sredine sa vektorskim pregledom za pretragu r = reflektuju (-v, n). Ovo čini površinu
imaju svojstva ogledala kao što su: pogled na p i vidi se okruženje koje se odbija od strane p.
Slika 18.5. Ovde E je tačka oka, a n je površina normalna u tački p.
Teksel koji čuva svetlost koja odražava p p i ulazi u oko dobija se
uzorkovanje kocke mape sa vektorom r.
Računamo vektor refleksije po-pikselu i onda ga koristimo za uzorkovanje okruženja
Mapa:
const float shininess = 1.0f - roughness;
// Add in specular reflections.
float3 r = reflect(-toEyeW, pin.NormalW);
float4 reflectionColor =
gCubeMap.Sample(gsamLinearWrap, r);
float3 fresnelFactor = SchlickFresnel(fresnelR0,
pin.NormalW, r);
litColor.rgb += shininess * fresnelFactor *
reflectionColor.rgb;
Zato što govorimo o refleksijama, moramo primeniti efekat Fresnel-a, što je
određuje koliko se svetlost reflektuje iz okruženja u oko na osnovu
materijalne osobine površine i ugao između svetlosnog vektora (vektor refleksije)
i normalno. Osim toga, skaliramo količinu refleksije zasnovanu na shininess of the
Materijal - grubi materijal treba da ima malu količinu refleksije, ali ipak neki
refleksija.
Slika 18.6 pokazuje da refleksije kroz mapiranje okruženja ne funkcionišu dobro za ravno
površine.
Slika 18.6. Vektor refleksije koji odgovara dvema različitim tačkama p i p '
kada je oko na pozicijama E i E ', respektivno.
To je zato što vektor refleksije ne govori cijelu priču, kao što nije
inkorporira poziciju; zaista nam je potreban odraz zraka i da se ukrcamo sa zrakom
mapa okoline. Zrak ima položaj i smer, dok vektor ima samo pravac.
Iz slike vidimo da su dva reflekciona zraka, k (t) = p + tr i k '(t) = p' + tr,
presecaju različite teksele mape kocke, i stoga bi trebalo da budu različite boje. Međutim,
jer oba zraka imaju isti pravac vektora r, a vektor pravca r je isključivo
koristi se za pretraživanje kocke mape, isti tekel se mapira na p i p 'kada je oko na E
i E ', respektivno. Za ravne objekte ovaj nedostatak mapiranja okoline je veoma veliki
primetno. Za zakrivljene površine ovaj nedostatak mapiranja okoline ide uglavnom
neopaženo, jer zakrivljenost površine dovodi do promene vektora refleksije.
Jedno rešenje je povezivanje nekih proki geometrije sa mapom okruženja. Za
primer, pretpostavimo da imamo mapu okruženja za kvadratnu sobu. Možemo da se pridružimo
graničnik sa poravnavanjem sa osovinom sa mapom okruženja koja ima približno istu
dimenzije u sobi. Slika 18.7 pokazuje kako možemo izvršiti presek zraka sa
kutija za izračunavanje vektora v koji daje bolji vektor za pretragu od vektora refleksije
r. Ako granična kutija povezana sa mapom kocke ulazi u shader (npr. Preko a
konstantni bafer), onda se test za presek zraka / kutije može izvršiti u pikel shaderu, a mi
može izračunati poboljšani vektor za pretragu u pikselnom shaderu da bi uzorak mape kocke.
Slika 18.7. Koristeći vektor refleksije r za pretragu kocke mape,
koristimo tačku presecanja v = p + t0r između zraka i kutije. Imajte na umu da
tačka p se vrši relativno u odnosu na sredinu geometrije proksi limita, tako da je
tačka preseka se može koristiti kao vektor za pretragu za mapu kocke.
Sledeća funkcija pokazuje kako se kubna mapa vidi u vektoru može se izračunati.
float3 BoxCubeMapLookup(float3 rayOrigin, float3
unitRayDir,
float3 boxCenter, float3 boxExtents)
{
// Based on slab method as described in Real-Time
Rendering
// 16.7.1 (3rd edition).
// Make relative to the box center.
float3 p = rayOrigin - boxCenter;
// The ith slab ray/plane intersection formulas for
AABB are:
//
// t1 = (-dot(n_i, p) + h_i)/dot(n_i, d) = (-p_i +
h_i)/d_i
// t2 = (-dot(n_i, p) - h_i)/dot(n_i, d) = (-p_i -
h_i)/d_i
// Vectorize and do ray/plane formulas for every
slab together.
float3 t1 = (-p+boxExtents)/unitRayDir;
float3 t2 = (-p-boxExtents)/unitRayDir;
// Find max for each coordinate. Because we assume
the ray is inside
// the box, we only want the max intersection
parameter.
float3 tmax = max(t1, t2);
// Take minimum of all the tmax components:
float t = min(min(tmax.x, tmax.y), tmax.z);
// This is relative to the box center so it can be
used as a
// cube map lookup vector.
return p + t*unitRayDir;
}
18.5 DINAMIC CUBE MAPS
Do sada smo opisali statičke kocke karte, gde se slike čuvaju u mapi kocke
su premadne i fiksne. Ovo funkcioniše u mnogim situacijama i relativno je jeftino.
Međutim, pretpostavimo da želimo da se animirani glumci kreću na našoj sceni. Sa predgeneracijom
kocke mape, ne možete da snimite ove animirane predmete, što znači da ne možemo
odražava animirane predmete. Da bi prevazišli ovo ograničenje, možemo napraviti mapu kocke
runtime. To jest, svaki okvir postavljate kameru u scenu koja će biti poreklo
mape kocke, a zatim skeniranje scene šest puta u svaku mapu kocke
koordinatni pravac ose (vidi Sliku 18.8). Budući da je mapa kocke rekonstruisana svakim kadrom, to je
će snimati animirane objekte u okruženju, a refleksija će biti animirana kao
dobro (vidi sliku 18.9).
Slika 18.8. Kamera je postavljena na položaj O u sceni, usredsređena na
objekat na koji želimo generisati mapu dinamičke kocke u odnosu na. Izradili smo scenu šest
puta duž svakog smera koordinatne osi sa ugao gledanja od 90 ° tako da
slika celokupnog okruženja je zarobljena.
Slika 18.9. Snimak ekrana "Dinamic CubeMap" demo prikazuje dinamiku
refleksije. Lobanja kruži oko središnje sfere, i njen odraz u sferi
animira prema tome. Štaviše, jer mi crtamo kocku,
možemo takođe modelirati refleksije lokalnih objekata, kao što su kolone,
sfere i pod.
Renderiranje kocke mape dinamički je skupo. To zahteva izradu
scena do šest ciljeva! Zbog toga pokušajte da minimizirate broj dinamike
kocke potrebne mape u sceni. Na primer, možda samo koristite dinamiku
refleksije za ključne objekte u vašoj sceni koju želite pokazati ili naglasiti.
Zatim koristite statičke kocke karte za manje važne objekte koji su dinamični
refleksije će verovatno ostati neprimećene ili se ne mogu propustiti. Normalno, nisko
Rezolucije kocke mape se koriste za mape dinamičke kocke, kao što su 256 × 256, u
štedi pri obradi piksela (tj., stopu popunjavanja).
18.5.1 Klasifikator dinamičke kocke mape
Da bismo omogućili dinamičnu mapu kocke, kreiramo sledeće
CubeRenderTarget klasa, koja inkapsulira stvarni ID3D12Resource objekt
karte kocke, različite deskriptore resursa, i druge korisne podatke za
prikazivanje kocke mape:
class CubeRenderTarget
{ public:
CubeRenderTarget(ID3D12Device* device,
UINT width, UINT height,
DXGI_FORMAT format);
CubeRenderTarget(const CubeRenderTarget&
rhs)=delete;
CubeRenderTarget& operator=(const CubeRenderTarget&
rhs)=delete;
˜CubeRenderTarget()=default;
ID3D12Resource* Resource();
CD3DX12_GPU_DESCRIPTOR_HANDLE Srv();
CD3DX12_CPU_DESCRIPTOR_HANDLE Rtv(int faceIndex);
D3D12_VIEWPORT Viewport()const;
D3D12_RECT ScissorRect()const;
void BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuSrv,
CD3DX12_GPU_DESCRIPTOR_HANDLE hGpuSrv,
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuRtv[6]);
void OnResize(UINT newWidth, UINT newHeight);
private:
void BuildDescriptors();
void BuildResource();
private:
ID3D12Device* md3dDevice = nullptr;
D3D12_VIEWPORT mViewport;
D3D12_RECT mScissorRect;
UINT mWidth = 0;
UINT mHeight = 0;
DXGI_FORMAT mFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuSrv;
CD3DX12_GPU_DESCRIPTOR_HANDLE mhGpuSrv;
CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuRtv[6];
Microsoft::WRL::ComPtr<ID3D12Resource> mCubeMap =
nullptr;
};
18.5.2 Izgradnja kockarskog resursa
Stvaranje teksture kocke mape se vrši stvaranjem polja teksture sa šest elemenata (po jedan za svako
lice). Zato što ćemo se prikazati na mapi kocke, moramo postaviti
D3D12_RESOURCE_FLAG_ALLOV_RENDER_TARGET zastavu. Ispod je metoda koja gradi kouč kartu
resurs:
void CubeRenderTarget::BuildResource()
{
D3D12_RESOURCE_DESC texDesc;
ZeroMemory(&texDesc, sizeof(D3D12_RESOURCE_DESC));
texDesc.Dimension =
D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texDesc.Alignment = 0;
texDesc.Width = mWidth;
texDesc.Height = mHeight;
texDesc.DepthOrArraySize = 6;
texDesc.MipLevels = 1;
texDesc.Format = mFormat;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
texDesc.Flags =
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&texDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&mCubeMap)));
}
18.5.3 Prostran prostor s posebnim opisom
Za prikazivanje kocke mape potrebno je još šest dodatnih ciljnih prikaza za prikazivanje, po jedan za
svako lice, i jedan dodatni bafer za dubinu / šablon. Zbog toga moramo prevladati
D3DApp :: CreateRtvAndDsvDescriptorHeaps metod i dodelite ih dodatni deskriptori:
void
DynamicCubeMapApp::CreateRtvAndDsvDescriptorHeaps()
{
// Add +6 RTV for cube render target.
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount +
6;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&rtvHeapDesc,
IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
// Add +1 DSV for cube render target.
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 2;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&dsvHeapDesc,
IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
mCubeDSV = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mDsvHeap->GetCPUDescriptorHandleForHeapStart(),
1,
mDsvDescriptorSize);
}
Pored toga, trebat će nam i jedan dodatni SRV, tako da možemo vezati mapu kocke kao unos shadera
nakon što je generisan.Ručice deskriptora prenose se u CubeRenderTarget :: BuildDescriptors metod koji
čuva kopiju rukuje, a zatim stvarno stvara pogled:
To jest, ako je k cijeli broj u opsegu 0-255, onda je f -1 (k) broj sa plutajućom tačkom u
opseg [-1, 1].
Nećemo morati sami da vršimo proces kompresije, jer ćemo koristiti a
Photoshop plug-in za pretvaranje slika u normalne mape. Međutim, kada izmerimo normalu
mapu u piksel shaderu, morat ćemo da uradimo deo inverznog procesa da ga izjednačimo.
Kada uzorkujemo normalnu mapu u ovakvom sjaju:
float3 normalT = gNormalMap.Sample (gTriLinearSam,
pin.Tek);
Vektor normalnog tona boje ima normalizovane komponente (r, g, b) tako da je 0 ≤ r,
g, b ≤ 1.
Tako je metoda već učinila deo nekompresivnog posla za nas (naime
podelu sa 255, koja pretvara ceo broj u opsegu od 0-255 do plivajuće tačke
interval [0, 1]). Završimo transformaciju pomeranjem i skaliranjem svake komponente
[0, 1] do [-1, 1] sa funkcijom g: [0, 1] → [-1, 1] definisana pomoću:
U kodu primenjujemo ovu funkciju na svaku komponentu boje kao što je ova:
// Unkomprimati svaku komponentu od [0,1] do [-1,1].
normalT = 2.0f * normalT - 1.0f;
Ovo radi zato što je skalar 1.0 povećan u vektor (1, 1, 1), tako da je
izraz ima smisla i radi se komponentno.
Photoshop plug-in je dostupan na http://developer.nvidia.com/nvidiatekture-
alati-adobe-photoshop. Postoje i drugi alati za generisanje
normalne mape kao što su http://vvv.crazibump.com/ i
http://shadermap.com/home/. Takođe, postoje alati koji mogu generisati normalno
mape iz mreža visoke rezolucije (vidi
http://vvv.nvidia.com/object/melodi_home.html).
Ako želite da koristite format kompresovanog tekstura za čuvanje normalne mape, onda koristite BC7
(DKSGI_FORMAT_BC7_UNORM) format za najbolji kvalitet, jer značajno smanjuje
greške izazvane kompresijom normalnih mapa. Za BC6 i BC7 formate, DirectKs SDK
ima uzorak pod nazivom "BC6HBC7EncoderDecoder11". Ovaj program se može koristiti za
konvertovanje
vaše teksture do BC6 ili BC7.
19.3 TEKSTURA / TANGENT SPACE
Razmislite o trouglu mapiranim 3D strukturom. Da bi se raspravljalo, pretpostavite to
nije izobličenje mapiranja teksture; drugim rečima, mapirajući trougao teksture na
3D trougao zahteva samo krutu transformaciju tela (prevod i rotaciju). Sada,
Pretpostavimo da je tekstura kao decal. Izabrali smo decal, preveli ga i okrenuli
na 3D trougao. Sada Slika 19.4 prikazuje kako se osi teksture odnose na 3D
trougao: oni su tangentni prema trouglu i leže u ravnini trougla. Tekstura
Koordinate trougla su, naravno, u odnosu na koordinatni sistem teksturnog prostora.
Uključujući trougao sa licem N, dobijamo 3D TBN-bazu u ravnini
trougao koji nazivamo teksturnim prostorom ili tangentnim prostorom. Imajte na umu da tangentni
prostor generalno
varira od trougla do trougla (vidi sliku 19.5).
Sada, kao što pokazuje slika 19.3, normalni vektori na normalnoj mapi su definisani relativno
na teksturu. Ali naša svetla su definisana u svetskom prostoru. Da bi se osvetljava,
normalni vektori i svetla moraju biti u istom prostoru. Dakle naš prvi korak je da se povežemo
tangentni prostorni koordinatni sistem sa koordinatnim sistemom objektnog prostora trougao
tačke su relativne. Kada smo u objektnom prostoru, možemo da koristimo svetsku matricu
od objektnog prostora do svetskog prostora (detalji o tome su pokriveni u sledećem odeljku). Dozvoliti
v0, v1 i v2 definišu tri vertikala 3D trougla sa odgovarajućom teksturom
koordinate (u0, v0), (u1, v1) i (u2, v2) koje definišu trougao u ravni teksture u odnosu
na teksturne kosmičke ose (to jest, T i B). Neka je e0 = v1 - v0 i e1 = v2 - v0 biti dve ivice
vektori 3D trougla sa odgovarajućim ivicama vektora teksturnog trougla (Du0, Dv0) =
(u1 - u0, v1 - v0) i (Du1, Dv1) = (u2 - u0, v2 - v0). Sa slike 19.4, jasno je da
Predstavljajući vektore sa koordinatama u odnosu na objektni prostor, dobijamo matricu
jednačina:
Imajte na umu da znamo koordinate objektnog prostora vertikala trougla; stoga mi
znaju koordinate objekta objekta ivica vreća, tako da je matrica
poznat kao. Isto tako, znamo teksturu koordinate, pa matrica
poznat kao. Rešenje za koordinate objekata T i B dobijamo:
U gornjem tekstu koristili smo činjenicu da je inverzna matrica
daje:
Imajte na umu da vektori T i B uopšteno nisu dužine jedinice u objektnom prostoru, a ako postoji
je izobličenje teksture, niti će biti ortonormalni.
T, B i N vektori se obično nazivaju tangentni, binormalni (ili
bitangent) i normalni vektori, respektivno.
19.4 VERTEKS TANGENT SPACE
U prethodnom odeljku izvodili smo tangentni prostor po trouglu. Međutim, ako koristimo
ovaj teksturni prostor za normalno mapiranje, dobićemo triangulirani izgled od početka
tangentni prostor je konstantan preko lica trougla. Stoga, određujemo tangente
vektori po verteku, a mi činimo isti srednji trik koji smo uradili sa normama verteka
približiti glatku površinu:
1. Tangentni vektor T za proizvoljnu vertek v u mrežu se pronalazi sredinom
tangentnih vektora svakog trougla u mrežu koja deli verteks v.
2. Bitangentni vektor B za proizvoljnu vertek v u mrežu se nalazi usredsređujući se na
bitangentni vektori svakog trougla u mreži koja deli vertek v.
Uopšteno, nakon prosečenja, TBN-baze će generalno trebati oronormalizovati,
tako da su vektori međusobno ortogonalni i dužine jedinice. Ovo se obično vrši pomoću
procedura Gram-Schmidt. Kod je dostupan na vebu za izgradnju per-vertek-a
tangentni prostor za proizvoljnu mrežu trouglova: http://vvv.terathon.com/code/tangent.html.
U našem sistemu, bitangentni vektor B nećemo čuvati direktno u memoriji. Umesto toga,
mi ćemo izračunati B = N × T kada nam je potreban B, gde je N uobičajena usredna vertek
normalno. Stoga, naša vertikalna struktura izgleda ovako:
Sada, kao što pokazuje slika 19.3, normalni vektori na normalnoj mapi su definisani relativno
na teksturu. Ali naša svetla su definisana u svetskom prostoru. Da bi se osvetljava,
normalni vektori i svetla moraju biti u istom prostoru. Dakle naš prvi korak je da se povežemo
tangentni prostorni koordinatni sistem sa koordinatnim sistemom objektnog prostora trougao
tačke su relativne. Kada smo u objektnom prostoru, možemo da koristimo svetsku matricu
od objektnog prostora do svetskog prostora (detalji o tome su pokriveni u sledećem odeljku). Dozvoliti
v0, v1 i v2 definišu tri vertikala 3D trougla sa odgovarajućom teksturom
koordinate (u0, v0), (u1, v1) i (u2, v2) koje definišu trougao u ravni teksture u odnosu
na teksturne kosmičke ose (to jest, T i B). Neka je e0 = v1 - v0 i e1 = v2 - v0 biti dve ivice
vektori 3D trougla sa odgovarajućim ivicama vektora teksturnog trougla (Du0, Dv0) =
(u1 - u0, v1 - v0) i (Du1, Dv1) = (u2 - u0, v2 - v0). Sa slike 19.4, jasno je da
Predstavljajući vektore sa koordinatama u odnosu na objektni prostor, dobijamo matricu
jednačina:
Imajte na umu da znamo koordinate objektnog prostora vertikala trougla; stoga mi
znaju koordinate objekta objekta ivica vreća, tako da je matrica
poznat kao. Isto tako, znamo teksturu koordinate, pa matrica
poznat kao. Rešenje za koordinate objekata T i B dobijamo:
U gornjem tekstu koristili smo činjenicu da je inverzna matrica
daje:
Imajte na umu da vektori T i B uopšteno nisu dužine jedinice u objektnom prostoru, a ako postoji
je izobličenje teksture, niti će biti ortonormalni.
T, B i N vektori se obično nazivaju tangentni, binormalni (ili
bitangent) i normalni vektori, respektivno.
19.4 VERTEKS TANGENT SPACE
U prethodnom odeljku izvodili smo tangentni prostor po trouglu. Međutim, ako koristimo
ovaj teksturni prostor za normalno mapiranje, dobićemo triangulirani izgled od početka
tangentni prostor je konstantan preko lica trougla. Stoga, određujemo tangente
vektori po verteku, a mi činimo isti srednji trik koji smo uradili sa normama verteka
približiti glatku površinu:
1. Tangentni vektor T za proizvoljnu vertek v u mrežu se pronalazi sredinom
tangentnih vektora svakog trougla u mrežu koja deli verteks v.
2. Bitangentni vektor B za proizvoljnu vertek v u mrežu se nalazi usredsređujući se na
bitangentni vektori svakog trougla u mreži koja deli vertek v.
Uopšteno, nakon prosečenja, TBN-baze će generalno trebati oronormalizovati,
tako da su vektori međusobno ortogonalni i dužine jedinice. Ovo se obično vrši pomoću
procedura Gram-Schmidt. Kod je dostupan na vebu za izgradnju per-vertek-a
tangentni prostor za proizvoljnu mrežu trouglova: http://vvv.terathon.com/code/tangent.html.
U našem sistemu, bitangentni vektor B nećemo čuvati direktno u memoriji. Umesto toga,
mi ćemo izračunati B = N × T kada nam je potreban B, gde je N uobičajena usredna vertek
normalno. Stoga, naša vertikalna struktura izgleda ovako:
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT3 Normal;
XMFLOAT2 Tex;
XMFLOAT3 TangentU;
};
Podsjetimo da su naša proceduralno generirana mreža kreirana od strane
GeometriGenerator izračunava tangentni vektor T koji odgovara u-osu
teksturni prostor. Koordinate objektnog prostora tangentnog vektora T lako se preciziraju na
svaka verteka za kutije i mrežne mreže (vidi Sliku 19.5). Za cilindre i sfere,
tangentni vektor T u svakoj vertici može se naći formiranjem vektorske vrijednosti funkcije od dvije
varijable P (u, v) cilindra / sfere i izračunavanja ∂p / ∂u, gdje je parametar u
takođe se koristi kao koordinat u-teksture.
Slika 19.5. Prostor teksture je različit za svako lice kutije.
19.5 TRANSFORMACIJA IZMEĐU TANGENT PROSTORA I
OBJEKT PROSTOR
U ovom trenutku, imamo jednu ortonormalnu TBN bazu u svakoj vrsti vertikalne mreže. Štaviše,
imamo koordinate TBN vektora u odnosu na objektni prostor mreže. Tako
sada kada imamo koordinat TBN-osnove u odnosu na koordinat objekta objektnog prostora
sistem, možemo transformisati koordinate iz tangentnog prostora u objektni prostor sa matricom:
Pošto je ova matrica ortogonalna, njegova inverzna je njegova transpozicija. Dakle, promena
koordinatna matrica od objektnog prostora do tangentnog prostora je:
U našem programu shadera, zapravo ćemo želeti da pretvorimo normalni vektor iz
tangentni prostor svetskom prostoru za osvetljenje. Jedan od načina bi bio da se transformiše normalna
tangentni prostor za prijelazanje prostora prvo, a zatim koristiti svjetsku matricu za transformaciju iz
objekta
prostor svetskom prostoru:
Međutim, pošto je matrično množenje asocijativno, možemo to učiniti ovako:
. Tako da idemo od tangentnog prostora direktno do svetskog prostora, jednostavno moramo opisati
tangenciju
osnove u svetskim koordinatama, što se može učiniti pretvaranjem TBN-osnove iz objekta
koordinate svemira na koordinate svetskog prostora.
Samo će nas zanimati transformisati vektore (ne poene). Dakle, samo nam treba
3 × 3 matrica. Podsetimo se da je četvrti red afiniteta matrice za prevod, ali mi ne znamo
translate vectors.
19.6 NORMALNI MAPPING SHADER ŠIFRA
Ukratko sažemo opšti proces za normalno mapiranje:
1. Kreirajte željene normalne mape iz nekog umetničkog programa ili uslužnog programa i spremite
ih u datoteku slike. Kreirajte 2D teksture iz ovih datoteka kada je program
inicijalizovan.
2. Za svaki trougao izračunati tangentni vektor T. Dobiti per-verteks tangentni vektor
za svaku vertiksu v u mrežu, tako što uobličava tangentne vektore svakog trougla u
mreža koja deli verteks v. (U našoj demo, koristimo jednostavnu geometriju i sposobno
odrediti tangentne vektore direktno, ali ovaj proces prosecanja treba da se uradi
ako koristite proizvoljne mrežne triangle napravljene u 3D modelarskom programu.)
3. U vertek shaderu, transformišite vertikalni normalni i tangentni vektor u svetski prostor
i izlaze rezultate u piksel shader.
4. Korišćenjem interpoliranog tangentnog vektora i normalnog vektora, izgradimo bazu TBN-a na
svaka točka piksela na površini trougla. Koristimo ovu osnovu za transformaciju
uzorkovani normalni vektor sa normalne mape od tangentnog prostora do svetskog prostora.
Zatim imamo normalni vektor svetskog prostora od normalne mape koji ćemo koristiti za naše
uobičajene rasveta rasvete. Da bismo pomogli u implementaciji normalnog mapiranja, dodali smo
sljedeću funkciju Common.hlsl:
//––––––––––––––––––––––—
// Transforms a normal map sample to world space.
//––––––––––––––––––––––—
float3 NormalSampleToWorldSpace(float3
normalMapSample,
float3 unitNormalW,
float3 tangentW)
{
// Uncompress each component from [0,1] to [-1,1].
float3 normalT = 2.0f*normalMapSample - 1.0f;
// Build orthonormal basis.
float3 N = unitNormalW;
float3 T = normalize(tangentW - dot(tangentW,
N)*N);
float3 B = cross(N, T);
float3x3 TBN = float3x3(T, B, N);
// Transform from tangent space to world space.
float3 bumpedNormalW = mul(normalT, TBN);
return bumpedNormalW;
}
This function is used like this in the pixel shader:
float3 normalMapSample = gNormalMap.Sample(samLinear,
pin.Tex).rgb;
float3 bumpedNormalW = NormalSampleToWorldSpace(
normalMapSample,
pin.NormalW,
pin.TangentW);
Two lines that might not be clear are these:
float3 N = unitNormalW;
float3 T = normalize(tangentW - dot(tangentW, N)*N);
Nakon interpolacije, tangentni vektor i normalni vektor ne smeju biti ortormalni.
Ovaj kod obezbedjuje da je T ortonorman u N tako što oduzima bilo koju komponentu T zajedno
pravac N (vidi sliku 19.6). Imajte na umu da postoji pretpostavka da je unitNormalV
normalizovano.
Slika 19.6. Od || N || = 1, projN (T) = (T · N) N. Vektor T-projN (T) je
deo T ortogonalnog prema N.
Jednom kada imamo normalu sa normalne mape, koju nazivamo "bumped normal",
koristimo je za sve naknadne kalkulacije koje uključuju normalni vektor (npr. osvetljenje,
kubiranje mapiranja). Celokupan normalan efekat mapiranja je prikazan ispod za kompletnost, sa
delovi koji su bitni za normalno mapiranje su boldirani.
//*********************************************************************
// Default.hlsl by Frank Luna (C) 2015 All Rights
Reserved.
//*********************************************************************
// Defaults for number of lights.
#ifndef NUM_DIR_LIGHTS
#define NUM_DIR_LIGHTS 3
#endif
#ifndef NUM_POINT_LIGHTS
#define NUM_POINT_LIGHTS 0
#endif
#ifndef NUM_SPOT_LIGHTS
#define NUM_SPOT_LIGHTS 0
#endif
// Include common HLSL code.
#include “Common.hlsl”
struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
float3 TangentU : TANGENT;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float3 TangentW : TANGENT;
float2 TexC : TEXCOORD;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut)0.0f;
// Fetch the material data.
MaterialData matData =
gMaterialData[gMaterialIndex];
// Transform to world space.
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosW = posW.xyz;
// Assumes nonuniform scaling; otherwise, need to
use
// inverse-transpose of world matrix.
vout.NormalW = mul(vin.NormalL, (float3x3)gWorld);
vout.TangentW = mul(vin.TangentU, (float3x3)gWorld);
// Transform to homogeneous clip space.
vout.PosH = mul(posW, gViewProj);
// Output vertex attributes for interpolation across
triangle.
float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f),
gTexTransform);
vout.TexC = mul(texC, matData.MatTransform).xy;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
// Fetch the material data.
MaterialData matData =
gMaterialData[gMaterialIndex];
float4 diffuseAlbedo = matData.DiffuseAlbedo;
float3 fresnelR0 = matData.FresnelR0;
float roughness = matData.Roughness;
uint diffuseMapIndex = matData.DiffuseMapIndex;
uint normalMapIndex = matData.NormalMapIndex;
// Interpolating normal can unnormalize it, so
renormalize it.
pin.NormalW = normalize(pin.NormalW);
float4 normalMapSample =
gTextureMaps[normalMapIndex].Sample(
gsamAnisotropicWrap, pin.TexC);
float3 bumpedNormalW = NormalSampleToWorldSpace(
normalMapSample.rgb, pin.NormalW, pin.TangentW);
// Uncomment to turn off normal mapping.
//bumpedNormalW = pin.NormalW;
// Dynamically look up the texture in the array.
diffuseAlbedo *=
gTextureMaps[diffuseMapIndex].Sample(
gsamAnisotropicWrap, pin.TexC);
// Vector from point being lit to eye.
float3 toEyeW = normalize(gEyePosW - pin.PosW);
// Light terms.
float4 ambient = gAmbientLight*diffuseAlbedo;
// Alpha channel stores shininess at per-pixel
level.
const float shininess = (1.0f - roughness) *
normalMapSample.a;
Material mat = { diffuseAlbedo, fresnelR0, shininess
};
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat,
pin.PosW,
bumpedNormalW, toEyeW, shadowFactor);
float4 litColor = ambient + directLight;
// Add in specular reflections.
float3 r = reflect(-toEyeW, bumpedNormalW);
float4 reflectionColor =
gCubeMap.Sample(gsamLinearWrap, r);
float3 fresnelFactor = SchlickFresnel(fresnelR0,
bumpedNormalW, r);
litColor.rgb += shininess * fresnelFactor *
reflectionColor.rgb;
// Common convention to take alpha from diffuse
albedo.
litColor.a = diffuseAlbedo.a;
return litColor;
}
Obratite pažnju da je vektor "bumped normal" koristi u izračunavanju svetlosti, ali i u proračun refleksije
za modeliranje refleksija sa mape okoline. Pored toga, u alfa kanala normalne mape čuvamo masku za
sjaj, koja kontroliše shininess na nivou per-piksela (pogledajte Sliku 19.7).
Slika 19.7. Alfa kanala tile_nmap.dds slike iz knjige DVD. Alfa kanal označava sjajnost površine. Bele
vrednosti ukazuju na a vrednost sjajnosti 1,0 i crne vrednosti ukazuju na vrednost shininess od 0,0. To
daje nas per-piksel kontrole svojstva sjajnog materijala.
Poglavlje 20 SHADOV MAPPING
Senke ukazuju na posmatrača gde svetlost potiče i pomaže u prenošenju srodnika
lokacije objekata u sceni. Ovo poglavlje daje uvod u osnovnu sjenu
algoritam mapiranja, koji je popularan metod za modeliranje dinamičnih senki u igricama
i 3D aplikacije. Za uvodnu knjigu fokusiramo se samo na osnovnu senku
algoritam mapiranja; sofisticiranije tehnike snimanja, kao što je kaskadna senka
mape [Engel06] koje daju bolje rezultate kvaliteta, izgrađuju se proširenjem osnovne sjene
algoritam mapiranja.
Ciljevi:
1. Da otkrijemo osnovni algoritam mapiranja senki.
2. Da saznate kako funkcioniše projektivno teksturiranje.
3. Da saznate o ortografskim projekcijama.
4. Da biste razumeli probleme senjanja mape senki i zajedničke strategije za njihovo rešavanje.
20.1 RENDERING SCENE DEPTH
Algoritam mapiranja sjena oslanja se na prikazivanje dubine scene sa stanovišta gledišta
izvor svetlosti - ovo je u suštini varijanta renderinga-teksture, koja je bila prva
opisano u § 13.7.2. Podrazumevajući "rendering dubine scene" podrazumevamo izgradnju bafera dubine
pogled na svetlosni izvor. Tako, nakon što smo izložili scenu iz
Vidikovac izvora svetlosti, poznaćemo fragmente piksela najbliže izvorima svetlosti
-Svaki fragmenti ne mogu biti u senci. U ovom odeljku pregledamo klasu korisnosti koja se zove
ShadovMap koji nam pomaže da skladištimo dubinu scene iz perspektive izvora svetlosti. To
jednostavno enkapsulira bafer dubine / stencila, neophodne poglede i prikaz. Dubina / šablon
bafer koji se koristi za mapiranje senki se zove mapa senke.
class ShadowMap
{ public:
ShadowMap(ID3D12Device* device,
UINT width, UINT height);
ShadowMap(const ShadowMap& rhs)=delete;
ShadowMap& operator=(const ShadowMap& rhs)=delete;
˜ShadowMap()=default;
UINT Width()const;
UINT Height()const;
ID3D12Resource* Resource();
CD3DX12_GPU_DESCRIPTOR_HANDLE Srv()const;
CD3DX12_CPU_DESCRIPTOR_HANDLE Dsv()const;
D3D12_VIEWPORT Viewport()const;
D3D12_RECT ScissorRect()const;
void BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuSrv,
CD3DX12_GPU_DESCRIPTOR_HANDLE hGpuSrv,
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuDsv);
void OnResize(UINT newWidth, UINT newHeight);
private:
void BuildDescriptors();
void BuildResource();
private:
ID3D12Device* md3dDevice = nullptr;
D3D12_VIEWPORT mViewport;
D3D12_RECT mScissorRect;
UINT mWidth = 0;
UINT mHeight = 0;
DXGI_FORMAT mFormat = DXGI_FORMAT_R24G8_TYPELESS;
CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuSrv;
CD3DX12_GPU_DESCRIPTOR_HANDLE mhGpuSrv;
CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuDsv;
Microsoft::WRL::ComPtr<ID3D12Resource> mShadowMap =
nullptr;
};
Konstruktor stvara teksturu određenih dimenzija i pogleda. The Rezolucija karte senke utiče na kvalitet naših senki, ali u isto
vreme, a mapa senke visoke rezolucije je skuplja da bi se uključila i potrebna je više memorije.
ShadowMap::ShadowMap(ID3D12Device* device, UINT width,
UINT height)
{
md3dDevice = device;
mWidth = width;
mHeight = height;
mViewport = { 0.0f, 0.0f, (float)width,
(float)height, 0.0f, 1.0f };
mScissorRect = { 0, 0, (int)width, (int)height };
BuildResource();
}
void ShadowMap::BuildResource()
{
D3D12_RESOURCE_DESC texDesc;
ZeroMemory(&texDesc, sizeof(D3D12_RESOURCE_DESC));
texDesc.Dimension =
D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texDesc.Alignment = 0;
texDesc.Width = mWidth;
texDesc.Height = mHeight;
texDesc.DepthOrArraySize = 1;
texDesc.MipLevels = 1;
texDesc.Format = mFormat;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
texDesc.Flags =
D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optClear;
optClear.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&texDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
&optClear,
IID_PPV_ARGS(&mShadowMap)));
}
Kao što ćemo videti, algoritam mapiranja senki zahtijeva dva renderinga. U prvom jedan, dovodi dubinu
scene sa stanovišta svetlosti u mapu senke; u drugi pasus, činimo scenu kao normalnu u zadnjem baferu
iz naše "igračke" kamere, ali koristite mapu senke kao input shadera za implementaciju algoritma za
senčenje. Mi pružite metodu za pristup izvorima shadera i njegovim pogledima:
ID3D12Resource* ShadowMap::Resource()
{
return mShadowMap.Get();
}
CD3DX12_GPU_DESCRIPTOR_HANDLE ShadowMap::Srv()const
{
return mhGpuSrv;
}
CD3DX12_CPU_DESCRIPTOR_HANDLE ShadowMap::Dsv()const
{
return mhCpuDsv;
}
20.2 ORTHOGRAPHIC PROJECTIONS
Do sada u ovoj knjizi koristili smo perspektivnu projekciju. Ključno vlasništvo
perspektivna projekcija je da se objekti posmatraju kao da postaju manji kao njihova distanca
iz očiju se povećava. Ovo se slaže sa načinom na koji stvari u stvarnom životu shvatamo. Drugi tip
projekcije je ortografska projekcija. Takve projekcije primarno se koriste u 3D
nauke ili inženjerske aplikacije, gde je poželjno da postoje paralelne linije
paralelno nakon projekcije. Međutim, ortografske projekcije će nam omogućiti modeliranje
senke koje paralelno osvetljenje generiše. Sa ortografskom projekcijom, volumen gledanja
je osa kutije poravnata sa prostorom za prikaz sa širinom v, visinom h, blizu ravni n i daleko
ravninu f koji gleda niz pozitivnu z-osu prostora za prikaz (pogledajte Sliku 20.1). Ove
brojevi, definisani u odnosu na koordinatni sistem prikaza prostora, definišu prikaz okvira
zapremine.
Slika 20.1. Orthographic volume volume je kutija sa poravnavanjem osa
koordinatni sistem pogleda.
Sa ortografskom projekcijom, linije projekcije su paralelne sa prostorom za prikaz
z-ose (slika 20.2). I vidimo da je 2D projekcija verteka (k, i, z) samo (k, i).
Slika 20.2. Orthografska projekcija tačaka na ravan projekcije. The
linije projekcija su paralelne sa osovinom z-zona sa ortografskim prikazom
projekcija.
Kao i sa perspektivnom projekcijom, želimo održavati relativne dubinske informacije, i
želimo normirane koordinate uređaja. Da biste pretvorili volumen prikaza iz prostora za prikaz u
NDC prostor, potrebno je preseliti i prebaciti na mapiranje prikaza prostora za prikaz prostora
na NDC prostoru pogled prostora [-1, 1] × [-1, 1] × [0, 1]. Ovakvo mapiranje možemo odrediti
radom koordinate-po-koordinatama. Za prve dve koordinate, to je lako videti
intervali se razlikuju samo faktorima skaliranja:
Za treću koordinatnu mapu moramo mapirati [n, f] → [0, 1]. Pretpostavljamo da je mapiranje potrebno
oblik g (z) = az + b (tj. skaliranje i prevod). Imamo uslove g (n) = 0 i
g (f) = 1, što nam omogućava da rešimo a i b:
Matrica 4 × 4 u gornjoj jednačini je ortografska projekciona matrica.
Podsetimo se da smo sa perspektivnom projekcionom transformacijom morali podeliti na dva dela:
linearni deo opisan od projekcione matrice i nelinearni deo koji je opisao
podelite sa v. Nasuprot tome, transformacija ortografske projekcije je potpuno linearna -
nema razlike od strane v. Multiplikacija pomoću ortografske projekcione matrice vodi nas direktno
u NDC koordinate.
20.3 KOORDINATI PROJEKTIVE TEKSTURE
Projektivno teksturisanje je tzv. Zato što nam omogućava da projektujemo teksturu na proizvoljno
geometrija, slično projektoru za slajdove. Slika 20.3 prikazuje primjer projektivnog
teksturiranje.
Slika 20.3. Tekstura lobanje (desno) projektovana je na geometriji scene (levo).
Projektivno teksturisanje može biti korisno za modeliranje slajd projektorskih svetala, ali
kao što ćemo videti u §20.4, takođe se koristi kao srednji korak za mapiranje senki.
Ključ za projektivno teksturisanje je stvaranje koordinata teksture za svaki piksel
tako da izgleda da primenjena tekstura projektuje na geometriju. Mi
će takvu generisanu teksturu nazvati koordinatnim koordinatama projektivne teksture.
Na slici 20.4 vidimo da koordinate teksture (u, v) identifikuju teksel
treba projicirati na 3D tačku str. Međutim, koordinate (u, v) precizno identifikuju
projekcija p na projekcionom prozoru, u odnosu na koordinatni sistem teksturnog prostora
prozor projekcije. Dakle, strategija generisanja projektnih koordinata teksture je takva
u nastavku:
1. Proektirajte tačku p u prozor projekcije svetlosti i transformišite koordinate
u NDC prostor.
2. Transformišite projektovane koordinate iz NDC prostora u teksturni prostor, na taj način
efikasno ih pretvara u teksturne koordinate.
Slika 20.4. Teksel označen koordinatama (u, v) u odnosu na teksturu
prostor na projekcionom prozoru projicira se na tačku p prateći liniju
pogled od svetlosnog porekla do tačke p.
Korak 1 se može implementirati razmišljanjem o projektoru svetlosti kao kameri. Definišemo a
pogledati matricu V i projekcionu matricu P za svetlosni projektor. Zajedno ove matrice
u suštini definišu položaj, orijentaciju i frustraciju svetlosnog projektora na svetu.
Matrica V transformiše koordinate od svetskog prostora do koordinatnog sistema
light projektor. Kada su koordinate relativne prema svetlosnom koordinatnom sistemu,
projekciona matrica, zajedno sa homogenim podelom, koriste se za projektovanje vertikala
na projekcionu ravni svetlosti. Podsjetimo iz §5.6.3.5 da nakon homogenih
podeli, koordinate su u NDC prostoru.
Korak 2 se postiže pretvaranjem iz NDC prostora u teksturni prostor preko
nakon promene koordinatne transformacije:
Ovde, u, v ∈ [0, 1] daje k, i ∈ [-1, 1]. Uvećemo i-koordinat sa negativnim
invertirati osu jer pozitivna i-osa u NDC koordinatama ide u pravcu
nasuprot pozitivnoj v-osi u teksturnim koordinatama. Transformacije prostornih tekstura
može se napisati u obliku matrica (recimo Vježba 21 iz poglavlja 3):
Nazovimo gore navedenu matricu T za "teksturnu matricu" koja se transformiše iz NDC prostora u
teksturni prostor. Možemo da formiramo kompozitnu transformaciju VPT koja nas vodi od svetskog
prostora
direktno na teksturu. Nakon što se ovom transformacijom umnožimo, još uvek moramo da uradimo
perspektiva se deli kako bi završila transformaciju; pogledajte Poglavlje 5 Vježba 8 zašto mi
može učiniti perspektivu podijeliti nakon što se tekstura transformira.
20.3.1 Primena kodova
Kod za generisanje koordinata projektivne teksture prikazan je ispod:
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 TangentW : TANGENT;
float3 NormalW : NORMAL;
float2 Tex : TEXCOORD0;
float4 ProjTex : TEXCOORD1;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
[…]
// Transform to light’s projective space.
vout.ProjTex = mul(float4(vIn.posL, 1.0f),
gLightWorldViewProjTexture);
[…]
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
// Complete projection by doing division by w.
pin.ProjTex.xyz /= pin.ProjTex.w;
// Depth in NDC space.
float depth = pin.ProjTex.z;
// Sample the texture using the projective texcoords.
float4 c = gTextureMap.Sample(sampler,
pin.ProjTex.xy);
[…]
}
20.3.2 Pokazuje van frustuma
U cevovodu za rendering, geometrija izvana frustuma je obeležena. Međutim, kada smo
generišu projektivne koordinate teksture projiciranjem geometrije sa stanovišta gledišta
svetlih projektora, ne vrši se iscrtavanje - mi jednostavno projektujemo vertikale. Stoga,
geometrija izvan frustuma projektora prima koordinate projektivne teksture spolja
opseg [0, 1]. Koeficijent projektne strukture izvan funkcije [0, 1] kao i sl
Normalna tekstura koordiniše van opsega [0, 1] na osnovu omogućenog režima adresiranja
(videti (§9.6) koji se koristi pri uzorkovanju teksture.
Uopšteno, ne želimo da teksturiramo bilo kakvu geometriju izvan frustracije projektora
jer nema smisla (takva geometrija ne dobija svetlost od projektora).
Korišćenje režima adresa adrese boje sa nultom bojom je zajedničko rešenje. Drugi
strategija je povezivanje reflektora sa projektorom tako da bilo šta izvan
konveksno polje vidnog polja ne svetli (tj. površina ne dobija projektovano svetlo). The
prednost korišćenja reflektora je da je intenzitet svetlosti od projektora najjači
središte reflektujućeg konusa i može glatko da izbledi kao ugao ph između -L i
d povećava (gde je L svetlosni vektor na površinsku tačku i d je pravac
spotlight).
20.3.3 Orthografske projekcije
Do sada smo ilustrovali projektivno teksturisanje koristeći perspektivne projekcije (frustum
uobičajene količine). Međutim, umjesto korištenja projekcije perspektive za projekciju
proces, mogli smo da koristimo ortografsku projekciju. U ovom slučaju tekstura je
projektovano u pravcu z-ose svetlosti kroz kutiju.
Sve što smo razgovarali sa projektivnim teksturnim koordinatama takođe se primenjuje
kada koristite ortografsku projekciju, izuzev nekoliko stvari. Prvo, sa
ortografska projekcija, strategija reflektora koja se koristi za rukovanje tačkama izvan projektora
volumen ne radi. To je zato što je konus reflektora približan zapremini a
u određenoj meri frustrira, ali ne približava kutiju. Međutim, i dalje možemo da koristimo
tekstualne režime adresiranja za rukovanje tačkama izvan jačine projektora. To je zato što je
ortografska projekcija i dalje generiše NDC koordinate i tačka (k, i, z) je unutar
zapreminu ako i samo ako:
Drugo, sa ortografskom projekcijom, ne moramo raditi podjelu putem v; to je,
ne treba nam linija:
// Celokupna projekcija delimično podijeljenjem v.
pin.ProjTek.kiz / = pin.ProjTek.v;
To je zato što su nakon ortografske projekcije koordinate već u NDC
prostor. Ovo je brža, jer izbegava odjeljak po pikselu koji je potreban za perspektivu
projekcija. Sa druge strane, ostavljanje u diviziji ne boli jer se deli sa 1
(ortografska projekcija ne mijenja v-koordinate, pa v će biti 1). Ako odemo
deljenje po v u shader kodu, tada shader kod radi kako za perspektivu tako i za
ortografske projekcije ravnomerno. Ipak, kompromis za ovu uniformnost je to što radite
suvišna podjela sa ortografskom projekcijom.
20.4 SHAPE MAPPING
20.4.1 Opis algoritma
Ideja o algoritmu mapiranja senki je da izvede teksturu dubine scene
pogled na svetlost u bafer dubine pod nazivom mapa senke. Nakon što se to uradi,
mapa senke će sadržati vrednosti dubine svih vidljivih piksela iz perspektive
svetlo. (Pikseli koji su okruženi drugim pikselima neće biti na mapi u senci, jer će to biti
propusti ispitivanje dubine i bilo bi prepisano ili nikad napisano.)
Da bi se scena prikazala iz tačke gledišta, potrebno je definisati prikaz svetlosti
matrica koja transformiše koordinate iz svetskog prostora u prostor svetlosti i svetlosti
projekciona matrica, koja opisuje volumen koji svetlost emituje u svetu. Ovo
može biti ili frustumski volumen (perspektivna projekcija) ili zapremina kutije (ortografski
projekcija). Zapremina svetlosti svetlosti može da se koristi za model reflektora ugrađivanjem
konus reflektora unutar frustuma. Volumenska lampica se može koristiti za modeliranje paralelnih
svetala.
Međutim, paralelno svetlo je sada ograničeno i samo prolazi kroz količinu kutije;
stoga, može samo da udari podskup područja (pogledajte Sliku 20.5). Za izvor svetlosti
udara cijelu scenu (kao što je Sunce), možemo učiniti dovoljno jačinu svjetlosti
sadrže celu scenu.
Jednom kad izgradimo mapu senki, činimo scenu kao normalna perspektiva "igrača" kamere. Za svaki
pikel p rendered, takođe izračunamo njegovu dubinu iz izvora svetlosti, koje označavamo d (p). Pored
toga, koristeći projektivno teksturiranje, mi uzmite uzorak mape sjene duž linije vida od izvora svetlosti
do piksela p da biste dobili vrednost dubine s (p) spremljena u mapu senke; ova vrednost je dubina
piksela najbliža na svetlost duž linije vida od položaja svetlosti do p. Zatim, iz slike 20.6, vidimo da je
piksel p u senci ako i samo ako je d (p)> s (p). On nce piksel nije u senci ako i samo ako je d (p) ≤ s (p).
struct SkinnedModelInstance
{
SkinnedData* SkinnedInfo = nullptr;
// Storage for final transforms at the given time
position.
std::vector<DirectX::XMFLOAT4X4> FinalTransforms;
// Current animation clip.
std::string ClipName;
// Animation time position.
float TimePos = 0.0f;
// Call every frame to increment the animation.
void UpdateSkinnedAnimation(float dt)
{
TimePos += dt;
// Loop animation
if(TimePos > SkinnedInfo-
>GetClipEndTime(ClipName))
TimePos = 0.0f;
// Called every frame and increments the time
position,
// interpolates the animations for each bone based
on
// the current animation clip, and generates the
final
// transforms which are ultimately set to the
effect
// for processing in the vertex shader.
SkinnedInfo->GetFinalTransforms(ClipName, TimePos,
FinalTransforms);
}
};
Then we add the following data members to our render-item structure:
struct RenderItem
{
[…]
// Index to bone transformation constant buffer.
// Only applicable to skinned render-items.
UINT SkinnedCBIndex = -1;
// Pointer to the animation instance associated with
this render item.
// nullptr if this render-item is not animated by
skinned mesh.
SkinnedModelInstance* SkinnedModelInst =
nullptr; […]
};
Every frame we update the animated character instances (in our demo we only have
one):
void SkinnedMeshApp::UpdateSkinnedCBs(const GameTimer&
gt)
{
auto currSkinnedCB = mCurrFrameResource-
>SkinnedCB.get();
// We only have one skinned model being animated.
mSkinnedModelInst-
>UpdateSkinnedAnimation(gt.DeltaTime());
SkinnedConstants skinnedConstants;
std::copy(
std::begin(mSkinnedModelInst->FinalTransforms),
std::end(mSkinnedModelInst->FinalTransforms),
&skinnedConstants.BoneTransforms[0]);
currSkinnedCB->CopyData(0, skinnedConstants);
}A
nd when we draw the render-items, we bind the
associated final bone transforms if the render-item is
animated by a skinned mesh:
if(ri->SkinnedModelInst != nullptr)
{
D3D12_GPU_VIRTUAL_ADDRESS skinnedCBAddress =
skinnedCB->GetGPUVirtualAddress() +
ri->SkinnedCBIndex*skinnedCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(1,
skinnedCBAddress);
}e
lse
{
cmdList->SetGraphicsRootConstantBufferView(1, 0);
}
Slika 23.10 prikazuje snimak ekrana ili našu demo. Originalni animirani model i
teksture su uzete iz DirectKs SDK-a i konvertovane u .m3d format za demo-svrhe. Ovaj model uzorka ima
samo jedan animirani clip nazvan Take1.