You are on page 1of 462

Directx12 je high performance softvare koji I ma ulogu u gaiming industriji za programiranje I pravljenje

3d modela, koje se koristi na windows 10 platformama(windows 10 desktop, windows 10 mobile, xbox


360 I xbox one). Osnova directx 12 je directx3d koji tolazi u svaki novoinstalirani racunar, ali su potrebne
jake graficke karte za koriscenje directx3d 12. U ovoj knjizi su 3 dela:matematicka osnova, sama osnova
3d geometrije I koriscenje direct x 3d za pravljenje 3d animacija. DA bi napraqvio aplikaciju otvori visual
studio 2017 File->New->Project,nakon toga ici na Visual C++ > Win32 ili win64 I nakon toga otvoriti
projekat. Uncekiraj Create directory for solutions, ako je cekirano po defoult. U slucaju da se pojavi
windows application wizzard cekiraju windows application I empty project. Trazenjem najblizih fajlova
za source code u #pragmas sa lokacijom Common/d3dApp.h dobili smo:
// Link necessary d3d12 libraries.
#pragma comment(lib,“d3dcompiler.lib”)
#pragma comment(lib, “D3D12.lib”)
#pragma comment(lib, “dxgi.lib”)
Након што копирате датотеке, пратите ове кораке да бисте додали код вашem пројектu.
1.Klikni deno na deo ispod projekta pod nazivom Solution Explorer,nakon toga selektuj Add >
Existing Item iz dropdown menia dodaj BoxApp.cpp u projekat.
2. Klikni deno na deo ispod projekta pod nazivom Solution Explorer,nakon toga selektuj Add >
Existing Item iz dropdown menia odredi mesto gde ce se nalaziti knjigin Common directory code I dodaj
sve .h/.cpp fajlove iz te datoteke u projekat
3. Klikni deno na deo ispod projekta,selektuj Propertise iz context menu. Ispod
Configuration Properties > General, budi siguran da je Target Platform Version I postavi verziju u 10x u
odredjeni Windows 10.
4. Sada je source code deo projekta I onda udji u glavni meni I Debug-Debugging da bi kompailirao fajl I
ukljuciti demo.
Vektori
Prvi korak u karakterizaciji vektora matematicki je geometrijski. Mi graficki specifiramo vektore od
direktnog linijskog segmenta, Imajte na umu da lokacija u kojoj nacrtamo vektor nije bitna jer je
promenjena lokacija ne menja veličinu ili pravac (dve osobine koje vectori poseduju). Stoga kažemo da
su dva vektora jednaka ako i samo ako imaju iste dužine i ističu se u istom pravcu. Kao fizički primjer,
vektori u i v kažu mravima na dvije različite tačke A i B da se presele na deset metara od mjesta gdje se
nalaze. Ponovo imamo da je u = v. Sami vektori su nezavisni od položaja; oni jednostavno podučavaju
mrave kako se kreću odakle su. U ovom primjeru kažu mravima da krenu na sjever (smjer) deset metara
(dužinu).
Sada možemo definisati korisne geometrijske operacije na vektore, koje se zatim mogu koristiti za
rešavanje problema koji uključuju vektorske vriednosti. Međutim, pošto računar ne može raditi sa
vektorima geometrijski, potrebno je naći način za specifikaciju vektora numerički umesto toga. Dakle,
ono što radimo je uvođenje 3D koordinatnog sistema u svemir i prevođenje svih vektora tako da se
njihovi repovi poklapaju sa poreklom (Slika 1.2). Tada možemo identifikovati vektor specifičnim
koordinatama glave i napisati v = (k,y, z) kao što je prikazano na slici 1.3. Sada možemo predstaviti
vektor sa tri floats u računarskom programu. Mi prevodimo v tako da se njegov rep poklapa sa poreklom
koordinatnog sistema. Kada se rep vektora poklapa sa poreklom, mi kažemo da je u standardnoj pozicijji.
Isti vektor v ima različite koordinate kada je opisan različitim okvirima.
Voda kljuca na 100 ° C ili 212 ° Fahrenheit. Fizička temperatura kljucanje vode je ista bez obzira na
veličinu (tj., Ne možemo smanjiti tačku ključanja biranjem drugačije veličine), ali dodeljujemo drugačiji
skalarni broj temperaturi baziranoj na skali koju koristimo. Slično tome, za vektor, njegov pravac i
veličina, koji su ugrađeni u segment usmerene linije, ne menja se; samo se njegove koordinate menjaju
na osnovu referentnog okvira koji koristimo da ga opišemo. Ovo je važno jer to znači kad god
identifikujemo vektor po koordinatama, te koordinate su relativne sa nekim referentnim okvirom. Često
u 3D kompjuterskoj grafici koristićemo više od referenci i stoga ćemo morati da pratimo koji okvir je
vektorske koordinate relativne; Takođe, moramo znati kako pretvoriti vektorske koordinate iz jednog
kadra u drugi.
1.1.2 Sistemi koordinatnog sistema leve ruke u odnosu na kordinatni sistem desne ruke
Direct3d se naziva levoruki kordinatni system. Obratite pažnju na to da za desnorukii koordinatni
sistem, ako uzmete desnu ruku i usmerite prste dole na pozitivnu k-osu, a zatim uvijte prste ka pozitivnoj
y-osi, vaš palac pokazuje otprilike u pravcu pozitivnog z -akis.
1.1.3 Obicne vektorske operacije
Сада дефинишемо једнакост, додатак, скаларно множење и одузимање на векторе користећи
координатну представу. За ове четири дефиниције, пустимо у = (ук, уи, уз) и в = (вк, ви, вз).
1. Два вектора су једнака ако и само ако су њихове одговарајуће компоненте једнаке. То је, у = в
ако и само ако је ук = вк, уи = ви, и уз = вз.
2. Додамо векторе компонентно: у + в = (ук + вк, уи + ви, уз + вз). Обратите пажњу на то да има
смисла само додати векторе исте димензије.
3. Можемо множити скалар (тј., Прави број) и вектор, а резултат је вектор.
Нека к буде скалар, онда ку = (кук, куи, куз). Ово се зове скаларно множење.
4. Дефинишемо одузимање у смислу додавања вектора и скаларне множења. То јест, у- в = у + (-1 ·
в) = у + (-в) = (ук - вк, уи - ви, уз - вз).
Primer 1.1
Neka je u = (1, 2, 3), v = (1, 2, 3), v = (3, 0, -2) i k = 2. Zatim,
1. u + v = (1, 2, 3) + (3, 0, -2) = (4, 2, 1);
2. u = v;
3. u - v = u + (-v) = (1, 2, 3) + (-1, -2, -3) = (0, 0, 0) = 0;
4. kv = 2 (3, 0, -2) = (6, 0, -4)
Razlika u trećem metku ilustruje poseban vektor, nazvan nula-vektor, koji ima nule za sve svoje
komponente i označava se sa 0.
Primer 1.2
Primer ćemo ilustrovati sa 2D vektora da bi se crteži učinili jednostavnijim. The
ideje su isto kao u 3D; radimo samo sa manjom komponentom u 2D.
1. Neka v = (2, 1) Kako v I uporedite geometrijski? To zapazimo Grafikon oba v I (Slika 1.6a), to
primetimo je u pravcu suprotno od v i njegova dužina je 1/2 od v. Dakle, geometrijski, negiranje vektora
može se smatrati "flippingom" njenog pravca, I skalarno množenje se može smatrati kao skaliranje
dužine vektora.
2. Neka
i v = (1, 2). Onda Slika 1.6b pokazuje šta vektorsko dodavanje znači geometrijski: paralelno prevedemo
u tako da se njegov rep poklapa s glavom v. Zatim, suma je vektor koji potiče
na repu v i završava se na glavi prevoda u. (Dobićemo isti rezultat ako držimo se u fiksnom i prevodimo v
tako da se njegov rep poklapa sa glavom u. U ovo slučaj, u + v bi bio vektor koji potiče od repa u i
završava na glavi prevedeni v.) Obratite pažnju i na to da se naša pravila dodavanja vektora slažu sa onim
što mi intuitivno očekivati da će se dogoditi fizički kada dodamo snage zajedno proizvede netu sila: Ako
dodamo dve sile (vektori) u istom pravcu, dobijamo još veća neto sila (duži vektor) u tom pravcu. Ako
dodamo dve sile (vektori) u suprotnosti jedni sa drugima, onda dobijamo slabiju mrežnu sila (kraći
vektor).
Slika 1.7 ilustruje ove ideje.
3. Neka i v = (1, 2). Onda Slika 1.6c pokazuje koliko je vektorsko oduzimanje geometrijski. U suštini,
razlika v - u daje nam vektor ciljan od glave u do glave v. Ako mi umesto da interpretiramo u i v kao
tačke, onda v - u daje vektor koji je usmeren na tačku u do tačke v; ovo tumačenje je važno jer ćemo
često želeti vector ciljani od jedne do druge tačke. Obratite pažnju da je dužina v - u rastojanja od u do v,
kada mislite na u i v kao poene.
Geometrijski, veličina vektora je dužina segmenta usmerene linije. Mi
označava veličinu vektora dvostrukim vertikalnim šipkama (npr. || u || označava veličinu
of u). Sada, s obzirom na vektor u = (k, i, z), želimo da svojom veličinom izračunamo algebraički.
Veličnost 3D vektora može se izračunati primenom Pitagoreove teoreme dva puta; vidi sliku 1.8. Prvo,
pogledamo trougao u kz ravni sa strane k, z, i hipotenuza a. Iz teorema Pitagorea, imamo a2 = x2 + z2
. Sada pogledajte trougao sa stranama a, i i hipotenuzom || u ||. Od Pitagorejana
teorema ponovo dolazimo do sledeće velicne formule:

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:

const float Epsilon = 0.001f;


bool Equals(float lhs, float rhs)
{
// Is the distance between lhs and rhs less than
EPSILON?
return fabs(lhs - rhs) < Epsilon ? true : false;
}
The DirectX Math library provides the XMVector3NearEqual function when
testing the equality of vectors with an allowed tolerance Epsilon parameter:
// Returns
// abs(U.x – V.x) <= Epsilon.x &&
// abs(U.y – V.y) <= Epsilon.y &&
// abs(U.z – V.z) <= Epsilon.z
XMFINLINE bool XM_CALLCONV XMVector3NearEqual(
FXMVECTOR U,
FXMVECTOR V,
FXMVECTOR Epsilon);
1.7 Sazetak
1. Vektori se koriste za modeliranje fizičkih količina koje poseduju i veličinu i veličinu
pravac. Geometrijski, predstavljamo vektor s usmerenim linijskim segmentom. Vektor
je u standardnoj poziciji kada je prevedeno paralelno sa sobom tako da se njegov rep poklapa
sa poreklom koordinatnog sistema. Vektor u standardnoj poziciji može biti
opisuje se numerički tako što određuje koordinate glave u odnosu na a
koordinatni sistem.
2. Ako je u = (uk, ui, uz) i v = (vk, vi, vz), onda imamo sljedeće vektorske operacije:
1. Dodavanje: u + v = (uk + vk, ui + vi, uz + vz)
2. Odraz: u - v = (uk - vk, ui - vi, uz - vz)
3. Skalarno množenje: ku = (kuk, kui, kuz)
4. Dužina:
5. Normalizacija:
6. Dot Product:
7. Cross Product:
3. Mi koristimo DirectKs Math KSMVECTOR tip za efektivno opisivanje vektora u kodu
koristeći SIMD operacije. Za članove klase podataka koristimo KSMFLOAT2,
KSMFLOAT3 i KSMFLOAT4 klase, a zatim koristite metode učitavanja i skladištenja
da pretvara napred i nazad između KSMVECTOR i KSMFLOATn. Konstantni vektori
koji zahtevaju inicijalizaciju sintaksu treba koristiti KSMVECTORF32 tip.
4. U cilju efikasnosti, vrijednosti KSMVECTOR mogu se prenijeti kao argumenti za funkcije
u SSE / SSE2 registruje umesto u stacku. Da to učinite u nezavisnoj platformi
način, koristimo tipove FKSMVECTOR, GKSMVECTOR, HKSMVECTOR i CKSMVECTOR
za prenos KSMVECTOR parametara. Tada je pravilo za prenos KSMVECTOR parametara
je da prva tri KSMVECTOR parametra treba da budu tipa FKSMVECTOR; the
četvrti KSMVECTOR treba da bude tipa GKSMVECTOR; peti i šesti KSMVECTOR
parametar treba da bude tipa HKSMVECTOR; i bilo koji dodatni KSMVECTOR
parametri bi trebali biti tipa CKSMVECTOR.
5. Klasa KSMVECTOR preopterećuje aritmetičke operatere da izvrši vektorsko dodavanje,
oduzimanje i skalarno množenje. Štaviše, DirectKs Math biblioteka pruža
sledeće korisne funkcije za izračunavanje dužine vektora, kvadrat
XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVector3Dot(FXMVECTOR V1,
FXMVECTOR V2);
XMVECTOR XM_CALLCONV XMVector3Cross(FXMVECTOR V1,
FXMVECTOR V2);
XMVECTOR XM_CALLCONV XMVector3Normalize(FXMVECTOR V);
2.Matrix Algebra
U 3D računarskoj grafici koristimo matrice za kompaktno opisivanje geometrijskih transformacija kao što
su skaliranje, rotacija i prevođenje, a takođe i promena koordinata tačke ili vektora iz jednog kadra u
drugi. Ovo poglavlje istražuje matematiku matrica.
2.1.Definicija
M × n matrica M je pravougaoni niz realnih brojeva sa m redovima i n kolonama. Proizvod broja redova i
kolona daje dimenzije matrice. Brojevi u matrici se nazivaju elementi ili unosi. Identifikujemo matrični
element tako što specificiramo red i kolonu elementa koristeći dvostruku inscriptsku notaciju Mij, gde
prvi indeks unosi red, a drugi inscript identifikuje kolonu.
1. Matrica A je matrica 4 × 4; matrica B je matrica 3 × 2; matrica u je matrica 1 × 3; i matrica v je matrica
4 × 1.
2. Identifikujemo element u četvrtom redu i drugoj koloni matrice A pomoću A42 = -5. Identifikujemo
element u drugom redu i prvu kolonu matrice B prema B21.
3. Matrice u i v su specijalne matrice u smislu da sadrže jedan red ili kolonu, respektivno. Ponekad
zovemo ove vrste matrica redi vektore ili vektore kolona jer se koriste da predstavljaju vektor u
matričnoj formi (npr., Možemo slobodno da izmenimo vektorske oznake (k, i, z) i [k, i, z]). Obratite
pažnju na to da za vektore redova i kolona nije potrebno koristiti dvostruki indeks koji označava
elemente matrice - potreban vam je samo jedan indeks. Povremeno volimo da razmišljamo o redovima
matrice kao vektore.
gde A1, * = [A11, A12, A13], A2, * = [A21, A22, A23] i A3, * = [A31, A32, A33]. U ovom zapisu, prvi indeks
specificira red i stavljamo '*' u drugi indeks koji ukazuje na to da mislimo na ceo red vektora.
U ovoj notaciji, drugi indeks specificira kolonu i stavimo '*' u prvi indeks koji ukazuje na to da mislimo na
ceo vektor kolone.
Sada definišemo jednakost, dodatak, skalarno množenje i oduzimanje na matricama.
1. Dve matrice su jednake ako i samo ako su njihovi odgovarajući elementi jednaki; Kao takva, dve
matrice moraju imati isti broj redova i stupaca kako bi bili
U poređenju.
2. Dodamo dve matrice dodavanjem njihovih odgovarajućih elemenata; Kao takva, ima smisla samo
dodati matricu da je isti broj redova i kolona.
3. Umnožavamo skalar i matricu pomnožujući skalar sa svakim elementom u matrici.
4. Definišemo oduzimanje u smislu dodavanja matrice i skalarne množenja,tj. A- B = A + (-1 B) =A+(-B).
2.2.Umnozavanje matriksa
Ako je A amk n matrica, a B je matrica × p, tada je proizvod AB definisan i je am × p matrica C, gdje je ijth
ulaz proizvoda C dat sa uzimanjem tačnog proizvoda i-stog vektora u A sa jth kolonom vektorom u B, to
jest, Napominjemo da je za definisanje matričnog proizvoda AB potrebno da broj kolona u A bude
jednak broju redova u B, što znači da zahtijevamo da dimenzija vektora redova u A jednaka dimenziji
vektora kolona u B. Ako se ove dimenzije ne podudaraju, onda tački u Ekuation 2.1 ne bi imao smisla.
2.3.Objasnjenje
Proizvod AB nije definisan s obzirom da vektori redova u A imaju dimenziju 2, a vektori kolona u B imaju
dimenziju 3. Konkretno, ne možemo uzeti tačni proizvod prvog reda vektora u A sa prvim vektrom
kolone u B, jer mi ne može preuzeti tačan proizvod 2D vektora sa 3D vektorom.
U slucaju da AB I Ba nemaju ist broj rovova I koloni a imaju iste elemente AB=/BA
2.2.3.Asocijacije
Matrično množenje ima lepe algebarske osobine. Na primer, množenje matriksa se distribuira preko
dodatka: A (B + C) = AB + AC) i (A + B) C = AC + BC). Posebno, međutim, s vremena na vreme ćemo
koristiti asocijativni zakon matrične množenja, što nam omogućava da izaberemo red da množimo
matrice:
(AB) C = A (BC)
2.3. Prenos matriksa
Transponovanje matrice se pronalazi izmenjenjem redova i stupaca
matrica. Tako je transponovanje m × n matrice n × m matrica. Mi označavamo transponovanje matrice
M kao MT.
2.4.IDENTITEMT MATRIKSA
Postoji specijalna matrica koja se zove matrica identiteta. Matrica identiteta je kvadrat
matrica koja ima nule za sve elemente, osim duž glavne dijagonale; elementi duž glavne dijagonale su svi
oni. Na primer, dole su 2 × 2, 3 × 3 i 4 × 4 matrice identiteta. Matrica identiteta deluje kao multiplikativni
identitet; to jest, ako je A m × n matrica, B je n × p matrica, a I je n × n matrica identiteta, onda
AI = A i IB = B
Drugim rečima, množenje matrice matricom identiteta ne menja matricu. Matrica identiteta se može
smatrati kao broj 1 za matrice. Posebno, ako je M kvadratna matrica, onda je množenje sa matricom
identiteta komutativno:
MI = IM = M
2.5Deternimacija matriksa
Određivač je posebna funkcija koja ulazi u kvadratnu matricu i izlazi stvarni broj. Određivač kvadratne
matrice A obično se označava det A. Može se pokazati da determinant ima geometrijsko tumačenje koje
se odnosi na količine kutija i da determinant pruža informacije o tome kako se zapremine menjaju pod
linearnim transformacijama. Pored toga, determinante se koriste za rešavanje sistema linearnih
jednačina korišćenjem Cramerovog pravila. Međutim, za naše potrebe, mi smo uglavnom motivisani da
proučavamo determinantu jer nam daje eksplicitnu formulu za pronalaženje inverzne matrice (tema iz
§2.7). Pored toga, može se dokazati da: Kvadratna matrica A je obratno ako i samo ako je det A = 0. Ova
činjenica je korisna jer nam daje računski alat za određivanje da li je matrica obrnuta. Prije nego što
možemo odrediti determinantu, prvi put predstavljamo koncept maloletnih matrica.
2.5.1 Matrica mola
S obzirom na n × n matricu A, manja matrica je (n - 1) × (n - 1) matrica pronađena brisanjem I i Ks kolone
A.
2.5.2 Definicija
Determinanta matrice definisana je rekurzivno; Na primer, determinanta matrice 4 × 4 je definisana u
smislu determinante matrice 3 × 3, a determinanta matrice 3 × 3 je definisana u smislu determinante
matrice 2 × 2, a determinanta matrice 2 × 2 je definisana u smislu determinante matrice 1 × 1
(determinanta matrice 1 × 1 A = [A11] je trivialno definisana da bude det [A11] = A11). Neka je A n × n
matrica. Zatim za n> 1 definišemo: Podsećajući na definiciju manje matrice, za matrice 2 × 2, to daje
formulu:
2.7 Inverzija matriksa
Matrična algebra ne definiše operaciju podele, ali definiše multiplikativnu inverznu operaciju. Sledeća
lista daje sumu važnih informacija o inverzijama:
1. Samo kvadratne matrice imaju inverses; stoga, kada govorimo o inverzijama matrice, pretpostavljamo
da se bavimo kvadratnom matricom.
2. Inverzna n × n matrica M je matrica n × n označena sa M-1.
3. Ne svaka kvadratna matrica ima inverznu. Rečeno je da je matrica koja ima inverznu inverziju, a
matrica koja nema inverznu reč je da je singularna.
4. Inverzni je jedinstven kada postoji.
5. Množenje matrice sa svojim inverznim rezultatima u matrici identiteta:
MM-1 = M-1M = I. Imajte na umu da je množenje matrice sa sopstvenim inverznim slučajem kada je
umnožavanje matrica komutativno. Matrični inverzi su korisni kada se rešavaju za druge matrice u
matričnoj jednačini. Na primer, pretpostavimo da nam daje matričnu jednačinu p '= pM. Dalje,
pretpostavimo da su nam dati p 'i M, i želimo da rešimo za p. Pod pretpostavkom da je M invertabilan
(tj., M-1 postoji), možemo rešiti za p
2.8 DIRECTKS MATH MATRICES
Za transformaciju tačaka i vektora koristimo 1 × 4 red vektora i 4 × 4 matrice. Razlog za ovo će biti
objašnjen u sledećem poglavlju. Za sada se koncentrišemo samo na tipove DirectKs Math koje se koriste
za predstavljanje 4 × 4 matrica.
2.8.1 Tipovi matriksa
Da bi predstavili 4 × 4 matrice u matematici DirectKs, koristimo KSMMATRIKS klasu, koja je
definisani kako slijedi u fajlu zaglavlja DirectKSMath.h (sa nekim manjim podešavanjima mi
napravili su za jasnoću):
#if (defined(_M_IX86) || defined(_M_X64) ||
defined(_M_ARM)) && defined(_XM_NO_INTRINSICS_)
struct XMMATRIX
#else
__declspec(align(16)) struct XMMATRIX
#endif
{
// Use 4 XMVECTORs to represent the matrix for SIMD.
XMVECTOR r[4];
XMMATRIX() {}
// Initialize matrix by specifying 4 row vectors.
XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2,
CXMVECTOR R3)
{ r[0] = R0; r[1] = R1; r[2] = R2; r[3] = R3; }
// Initialize matrix by specifying 4 row vectors.
XMMATRIX(float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23,
float m30, float m31, float m32, float m33);
// Pass array of sixteen floats to construct matrix.
explicit XMMATRIX(_In_reads_(16) const float
*pArray);
XMMATRIX& operator= (const XMMATRIX& M)
{ r[0] = M.r[0]; r[1] = M.r[1]; r[2] = M.r[2];
r[3] = M.r[3]; return *this; }
XMMATRIX operator+ () const { return *this; }
XMMATRIX operator- () const;
XMMATRIX& XM_CALLCONV operator+= (FXMMATRIX M);
XMMATRIX& XM_CALLCONV operator-= (FXMMATRIX M);
XMMATRIX& XM_CALLCONV operator*= (FXMMATRIX M);
XMMATRIX& operator*= (float S);
XMMATRIX& operator/= (float S);
XMMATRIX XM_CALLCONV operator+ (FXMMATRIX M)
const;
XMMATRIX XM_CALLCONV operator- (FXMMATRIX M)
const;
XMMATRIX XM_CALLCONV operator* (FXMMATRIX M)
const;
XMMATRIX operator* (float S) const;
XMMATRIX operator/ (float S) const;
friend XMMATRIX XM_CALLCONV operator* (float S,
FXMMATRIX M);
};
Kao što vidite, KSMMATRIKS koristi četiri KSMVECTOR instance da koristi SIMD. Štaviše, KSMMATRIKS
obezbeđuje preopterećene operatere za matričnu aritmetiku.Pored korišćenja različitih konstruktora,
može se kreirati instanca KSMMATRIKS koristeći KSMMatrikSet funkciju:
XMMATRIX XM_CALLCONV XMMatrixSet(
float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23,
float m30, float m31, float m32, float m33);
Just as we use XMFLOAT2 (2D), XMFLOAT3 (3D), and XMFLOAT4 (4D) when storing
vectors in a class, it is recommended, by the DirectXMath documentation to use the
XMFLOAT4X4 type to store matrices as class data members.
struct XMFLOAT4X4
{
union
{
struct
{
float _11, _12, _13, _14;
float _21, _22, _23, _24;
float _31, _32, _33, _34;
float _41, _42, _43, _44;
};
float m[4][4];
};
XMFLOAT4X4() {}
XMFLOAT4X4(float m00, float m01, float m02, float
m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23,
float m30, float m31, float m32, float m33);
explicit XMFLOAT4X4(_In_reads_(16) const float
*pArray);
float operator() (size_t Row, size_t Column)
const { return m[Row][Column]; }
float& operator() (size_t Row, size_t Column) {
return m[Row][Column]; }
XMFLOAT4X4& operator= (const XMFLOAT4X4& Float4x4);
};
We use the following method to load data from XMFLOAT4X4 into XMMATRIX:
inline XMMATRIX XM_CALLCONV
XMLoadFloat4x4(const XMFLOAT4X4* pSource);
We use the following method to store data from XMMATRIX into XMFLOAT4X4:
inline void XM_CALLCONV
XMStoreFloat4x4(XMFLOAT4X4* pDestination, FXMMATRIX
M);
2.8.2.Matriks funkcije
XMMATRIX XM_CALLCONV XMMatrixIdentity(); // Returns
the identity matrix I
bool XM_CALLCONV XMMatrixIsIdentity( // Returns true
if M is the identity matrix
FXMMATRIX M); // Input M
XMMATRIX XM_CALLCONV XMMatrixMultiply( // Returns
the matrix product AB
FXMMATRIX A, // Input A
CXMMATRIX B); // Input B
XMMATRIX XM_CALLCONV XMMatrixTranspose( // Returns MT
FXMMATRIX M); // Input M
XMVECTOR XM_CALLCONV XMMatrixDeterminant( // Returns
(det M, det M, det M, det M)
FXMMATRIX M); // Input M
XMMATRIX XM_CALLCONV XMMatrixInverse( // Returns M−1
XMVECTOR* pDeterminant, // Input (det M, det
M, det M, det M)
FXMMATRIX M); // Input M
Kada proglašavamo parametar KSMMATRIKS funkciji, mi koristimo ista pravila koristi se prilikom prenosa
KSMVECTOR parametara osim što se KSMMATRIKS računa kao četiri KSMVECTOR parametra. Pod
pretpostavkom da nema više od dva dodatna FKSMVECTOR parametri u potpunosti za funkciju, prvi
KSMMATRIKS treba da bude tipa FKSMMATRIKS, a bilo koji drugi KSMMATRIKS treba da bude tipa
CKSMMATRIKS. Mi ilustrujemo kako su ti tipovi definirani na 32-bitnom Vindovsu sa kompajlerom koji
podržava konvenciju poziva __fastcall i kompajler koji podržava noviju __vectorcall pozivnu konvenciju:
// 32-bit Windows __fastcall passes first 3 XMVECTOR
arguments
// via registers, the remaining on the stack.
typedef const XMMATRIX& FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;
// 32-bit Windows __vectorcall passes first 6 XMVECTOR
arguments
// via registers, the remaining on the stack.
typedef const XMMATRIX FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;
Obratite pažnju na to da na 32-bitnom Vindovsu sa __fastcallom KSMMATRIKS ne može biti prenet
preko SSE / SSE2 registara jer su podržane samo tri KSMVECTOR argumenta preko registara, a
KSMMATRIKS zahteva četiri; tako da se matrica upravo prenese na stack referencom. Za detalje o tome
kako su ovi tipovi definisani za druge platforme, pogledajte "Pozivanje konvencija" u okviru "Librari
Internals" u DirectKSMath dokumentaciji [DirectKSMath]. Izuzetak od ovih pravila je sa metodama
konstruktora. [DirectKSMath] preporučuje uvek da koristi CKSMMATRIKS za konstruktore koji uzima
KSMMATRIKS parametre. Nadalje, nemojte koristiti anonimaciju KSM_CALLCONV za konstruktore.
2.8.3 Primer programa Matematicka Matrik Directx
#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 and XMMATRIX objects.
ostream& XM_CALLCONV operator << (ostream& os,
FXMVECTOR v)
{
XMFLOAT4 dest;
XMStoreFloat4(&dest, v);
os << “(” << dest.x << “, ” << dest.y << “, ” <<
dest.z << “, ” << dest.w << “)”;
return os;
}
ostream& XM_CALLCONV operator << (ostream& os,
FXMMATRIX m)
{
for (int i = 0; i < 4; ++i)
{
os << XMVectorGetX(m.r[i]) << ″\t”;
os << XMVectorGetY(m.r[i]) << “\t”;
os << XMVectorGetZ(m.r[i]) << “\t”;
os << XMVectorGetW(m.r[i]);
os << endl;
}
return os;
}
int main()
{
// Check support for SSE2 (Pentium4, AMD K8, and
above).
if (!XMVerifyCPUSupport())
{
cout << “directx math not supported” << endl;
return 0;
}
XMMATRIX A(1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, 4.0f, 0.0f,
1.0f, 2.0f, 3.0f, 1.0f);
XMMATRIX B = XMMatrixIdentity();
XMMATRIX C = A * B;
XMMATRIX D = XMMatrixTranspose(A);
XMVECTOR det = XMMatrixDeterminant(A);
XMMATRIX E = XMMatrixInverse(&det, A);
XMMATRIX F = A * E;
cout << “A = ” << endl << A << endl;
cout << “B = ” << endl << B << endl;
cout << “C = A*B = ” << endl << C << endl;
cout << “D = transpose(A) = ” << endl << D << endl;
cout << “det = determinant(A) = ” << det << endl <<
endl;
cout << “E = inverse(A) = ” << endl << E << endl;
cout << “F = A*E = ” << endl << F << endl;
return 0;
}
2.9 Sazetak
1. M × n matrica M je pravougaoni niz realnih brojeva sa m redovima i n
kolone. Dve matrice iste dimenzije su jednake ako i samo ako su njihovi
odgovarajuće komponente su jednake. Dodamo dve matrice iste dimenzije
dodajući njihove odgovarajuće elemente. Pomnožimo skalar i matricu
pomnožujući skalar sa svakim elementom u matrici.
2. Ako je A m × n matrica i B je n × p matrica, onda je proizvod AB definisan i je a
m × p matrica C, pri čemu se dobija ijth ulaz proizvoda C uzimajući tačku
proizvod i-stog vektora u A sa jth kolonom vektorom u B, to jest,
.
3. Multiplikacija matriksa nije komutativna (tj. AB = BA, općenito). Matrica
množenje je asocijativno: (AB) C = A (BC).
4. Transponovanje matrice se pronalazi izmenjenjem redova i stupaca
matrica. Tako je transponovanje m × n matrice n × m matrica. Mi označavamo
transponovanje matrice M kao MT.
5. Matrica identiteta je kvadratna matrica koja ima nule za sve elemente osim duž
glavna dijagonala, a elementi duž glavne dijagonale su svi oni.
6. Determinant, det A, je posebna funkcija koja ulazi u kvadratnu matricu i
izlazi pravi broj. Kvadratna matrica A je invertibilna ako i samo ako je det A = 0. The
determinanta se koristi u formuli za izračunavanje inverzne matrice.
7. Množenje matrice sa inverznim rezultatima u matrici identiteta: MM-1 = M-1M =
I. Inverzna matrica, ako postoji, je jedinstvena. Samo kvadratne matrice se inverziraju
pa i tada, kvadratna matrica ne može biti obrnuta. Inverzna matrica može biti
izračunato sa formulom:, gde je pridruženo (transponovanje kofaktorske matrice od
A).
8. Koristimo DirectKs Math KSMMATRIKS tip da efikasno opišemo matrice 4 × 4
kod koristeći SIMD operacije. Za članove klase podataka koristimo KSMFLOAT4Ks4
klase, a zatim koristite učitavanje (KSMLoadFloat4k4) i spremište
(KSMStoreFloat4k4) metode za pretvaranje napred i nazad između KSMMATRIKS i
KSMFLOAT4Ks4. Klasa KSMMATRIKS preopterećuje aritmetičke operatere da rade matricu
dodavanje, oduzimanje, množenje matriksa i skalarno množenje. Štaviše,
DirectKs Math biblioteka nudi sledeće korisne matrične funkcije za računarstvo
matrica identiteta, proizvod, transponovanje, odrednica i inverzna:
XMMATRIX XM_CALLCONV XMMatrixIdentity();
XMMATRIX XM_CALLCONV XMMatrixMultiply(FXMMATRIX A,
CXMMATRIX B);
XMMATRIX XM_CALLCONV XMMatrixTranspose(FXMMATRIX M);
XMVECTOR XM_CALLCONV XMMatrixDeterminant(FXMMATRIX
M);
XMMATRIX XM_CALLCONV XMMatrixInverse(XMVECTOR*
pDeterminant, FXMMATRIX M);
Oblast 3 Transformacija
3.1.3. Scaling
Definišemo transformaciju skaliranja pomoću:
S (k, i, z) = (skk, sii, szz)
Ovo skalira vektor sk jedinicama na k-osama, si jedinicama na i-osi i sz jedinicama na z-osu, u odnosu na
poreklo radnog koordinatnog sistema. Sada pokazujemo da je S zaista linearna transformacija.
3.1.4 Rotacija
U ovom odeljku opisujemo rotaciju vektora v oko nize n za ugao th? videti sliku 3.3. Imajte na umu da
izmerimo ugao u smjeru kazaljke na satu kada gledamo niz nju; Osim toga, pretpostavljamo || n || = 1.
Prvo razložite v na dva dela: jedan deo paralelan sa n, a drugi deo ortogonalan na n. Paralelni deo je
samo projn (v) (opoziva Primer 1.5); ortogonalni deo daje v⊥ = perpn (v) = v - projn (v). (Podsjetimo,
takođe iz Primera 1.5, da pošto je n jedinični vektor, imamo projn (v) = (n · v) n.) Ključno zapažanje je da
dio projn (v) koji je paralelan sa n je invariantan rotaciju, pa samo treba da shvatimo kako rotirati
ortogonalni deo. To jest, rotirani vektor Rn (v) = projn (v) + Rn (v⊥), prema slici 3.3. Da bi smo pronašli
Rn (v⊥), postavili smo 2D koordinatni sistem u ravni rotacije. Koristićemo v⊥ kao jedan referentni
vektor. Da dobijemo drugi referentni vektor ortogonalni za v⊥ i n, uzmemo krstni proizvod n × v (pravilo
ljevog ruka).
3.3 i Vežba 14 iz Poglavlja 1, to vidimo
gde je a ugao između n i n. Oba referentna vektora imaju istu dužinu i leže na kružiću rotacije. Sada kada
smo postavili ova dva referentna vektora, vidimo iz trigonometrije da: Ovo nam daje sledeću formulu
rotacije:
3.2 AFFINE TRANSFORMACIJE
3.2.1 Homogene koordinate
U sledećem odjeljku ćemo videti da je affine transformacija linearna transformacija
u kombinaciji sa prevodom. Međutim, prevod nema smisla za vektore
jer vektor samo opisuje pravac i veličinu, nezavisno od lokacije; u drugim
reči, vektori treba da budu nepromenjeni u prevodu. Prevodi bi trebali biti samo
primenjene na tačke (tj. vektore položaja). Homogene koordinate pružaju pogodan
notacioni mehanizam koji nam omogućava ravnomerno rukovanje tačkama i vektori. Sa
homogene koordinate, povećavamo 4-tukle i ono što stavljamo u četvrti vcoordinate
zavisi od toga da li opisujemo tačku ili vektor. Konkretno, pišemo:
1. (k, i, z, 0) za vektore
2. (k, i, z, 1) za bodove
Videćemo kasnije da podešavanje v = 1 za tačke omogućava prevod bodova na posao
ispravno, i postavljanje v = 0 za vektore sprečava koordinate vektora
modifikovanim prevođenjem (ne želimo prevesti koordinate vektora, kao što je to
bi promenio svoj pravac i veličina - prevodi ne bi trebalo da menjaju svojstva
vektori).
Notacija homogenih koordinata je u skladu sa prikazanim idejama
na slici 1.17. To jest, razlika između dvije tačke k - p = (kk, ki, kz, 1) -
(pk, pi, pz, 1) = (kk - pk, ki - pi, kz - pz, 0) rezultira vektorom, a tačka plus
vektor p + v = (pk, pi, pz, 1) + (vk, vi, vz, 1) = (pk + vk, pi + vi, pz + vz, 1)
u tački.
Matrica 4 × 4 u jednačini 3.6 se naziva matrična reprezentacija affine transformacija. Obratite pažnju da
je dodavanje pomoću b u suštini prevod (tj. Promena u položaju). Ne želimo ovo primijeniti na vektore
jer vektori nemaju položaj. Ipak, ipak želimo da primenimo linearni deo afinskog transformacije na
vektore. Ako postavimo v = 0 u četvrtoj komponenti za vektore, onda se prevod b ne primjenjuje
(verificira se pomoću množenja matrice).
3.2.3 Prevod
Transformacija identiteta je linearna transformacija koja jednostavno vraća svoj argument;
to jest, I (u) = u. Može se pokazati da je matrična reprezentacija ove linearne transformacije matrica
identiteta. Sada, transformacionu transformaciju definišemo kao afinsku transformaciju čija je linearna
transformacija transformacija identiteta; to jest, Kao što vidite, ovo jednostavno prevodi (ili pomiče)
tačku u od strane b. Slika 3.5 ilustruje kako se ovo može iskoristiti za izmještanje objekata - svaku tačku
na objektu prevesti isti vektor b za pomjeranje.
3.2.4 Matične matrice za skaliranje i rotaciju
Obratite pažnju na to da ako je b = 0, affine transformacija se smanjuje na linearnu transformaciju.
Tako možemo da izrazimo bilo kakvu linearnu transformaciju kao afinsku transformaciju sa b = 0.
Ovo, pak, znači da možemo predstaviti bilo koju linearnu transformaciju pomoću matrice 4 × 4 affine.
Na primer, matrice za skaliranje i rotaciju, napisane pomoću 4 × 4 matrica, date su kao
u nastavku:
Na ovaj način možemo sve svoje transformacije izraziti konzistentno koristeći 4 × 4 matrice
i tačke i vektore koristeći 1 × 4 homogene redove vektora.
3.2.5 Geometrijsko tumačenje matrice transformacije afine
U ovom odeljku razvijamo neku intuiciju onoga što su brojevi unutar afine
matrica transformacije znači geometrijski. Prvo, razmotrimo kruto telo
transformacija, koja je u suštini oblik očuvanja transformacije. Pravi svet
primer rigidne transformacije tijela možda bi izabrao knjigu sa stola i postavljanja
na polici za knjige; tokom ovog procesa prevođujete knjigu sa svog stola na
knjižara, ali i veoma verovatne promene orijentacije knjige u procesu
(rotacija). Neka je t rotacija transformacija koja opisuje kako želimo da rotirate objekat
i neka b definiše vektor pomjeranja koji opisuje kako želimo da prevučemo objekat. Ovo
kruta transformacija tela može se opisati afinskom transformacijom:
U matričnoj notaciji, koristeći homogene koordinate (v = 1 za tačke i v = 0 za
vektori tako da prevod nije primenjen na vektore), ovo je napisano kao:
Sada, da vidimo koja ova jednačina radi geometrijski, sve što treba da uradimo je da grafiramo
red vektora u matrici (pogledajte sliku 3.7). Pošto je t transformacija rotacije
čuva dužine i uglove; naročito, vidimo da t rotira standardnu bazu
vektorima i, j i k u novu orijentaciju t (i), t (j) i t (k). Vektor b je samo pozicija
vektor označava pomeranje iz porekla. Sada Slika 3.7 prikazuje kako
transformirana tačka se dobija geometrijski kada je a (k, i, z) = kt (i) + it (j) + zt (k) + b
računato.
Ista ideja se odnosi na skaliranje ili skeve transformacije. Razmotrimo linearnu transformaciju t koja
upadne kvadrat u paralelogram kao što je prikazano na slici 3.8. Izgrebana tačka je jednostavno linearna
kombinacija vektora zagađenih osnova.Slika 3.8. Za linearnu transformaciju koja upadne kvadrat u
paralelogram, transformirana tačka t (p) = (k, i) daje se kao linearna kombinacija transformisane
bazni vektori t (i), t (j)
3.3 САСТАВ ТРАНСФОРМАЦИЈЕ
Pretpostavimo da je S matrica za skaliranje, R je rotaciona matrica, a T je matrica prevođenja.
Pretpostavimo da imamo kocku sačinjenu od osam vertikala vi za i = 0, 1, ..., 7, i želimo da ove tri
transformacije primenimo na svaki od njih vertikalno. Očigledan način za to je korak po korak:
Međutim, pošto je matrično množenje asocijativno, možemo to napisati ekvivalentno kao: Možemo
zamisliti matricu C = SRT kao matricu koja inkapsuliše sve tri transformacije u jednu matricu neto
transformacije. Drugim rečima, množenje matričnih matrica omogućava nam da spojimo transformacije.
To ima uticaj na performanse. Da vidimo ovo, pretpostavimo da je 3D objekt sastavljen od 20.000 poena
i da želimo da primenimo ove tri uzastopne geometrijske transformacije u objekt. Koristeći korak-po-
korak pristup, tražimo 20,000 × 3 vektor-matrične množenja. S druge strane, korišćenje kombinovanog
matričkog pristupa zahteva 20.000 multiplikacija vektorskog matriksa i 2 matrične matrične množenja.
Jasno je da su dva dodatna matrično-matrična množenja jeftina cena za plaćanje velikih ušteda u
multiplikacijama vektorskih matrica. Opet ističemo da množenje matriksa nije komutativno. Ovo se čak i
vidi geometrijski. Na primer, rotacija nakon čega sledi prevod, koji možemo opisati pomoću matričnog
proizvoda RT, ne rezultira istom transformacijom kao isti prevod, praćen istom rotacijom, odnosno TR.
3.4 PROMENA KOORDINATSKIH TRANSFORMACIJA
Skalar 100 ° C predstavlja temperaturu vode koja je ključala u odnosu na Celzijevu skalu. Kako da
opišemo istu temperaturu vode koja je ključala u odnosu na Fahrenheit skalu? Drugim rečima, šta je
skalar, u odnosu na Fahrenheitsku skalu, koja predstavlja temperaturu vode koja je ključala? Da bismo
napravili ovu konverziju (ili promenu okvira), moramo znati kako se relativne vage Celsius i Fahrenheit
odnose. Oni su povezani na sledeći način:. Zato je temperatura vrele vode u odnosu na Fahrenheit skalu
data Tf=9/5Tc+32
Ovaj primer ilustruje da možemo pretvoriti skalar k koji opisuje određenu količinu u odnosu na okvir A u
novi skalar k 'koji opisuje istu količinu u odnosu na različiti okvir B, pod uslovom da znamo kako su okviri
A i B povezani. U sledećim pododeljima gledamo na sličan problem, ali umjesto skalara, zanimaju nas
kako da pretvorimo koordinate tačke / vektora u odnosu na jedan okvir u koordinate u odnosu na drugi
okvir (pogledajte Sliku 3.10). Mi nazivamo transformaciju koja pretvara koordinate iz jednog kadra u
koordinate drugog kadra, promjenu koordinatne transformacije.
Vredi naglasiti da u promeni koordinatne transformacije ne
razmišljajte o geometriji kao promenljivom; umjesto toga, mi mijenjamo referentni okvir, koji na taj
način mijenja koordinatnu reprezentaciju geometrije. To je suprotno tome kako obično razmišljamo o
rotacijama, prevodima i skaliranju, gde mislimo da se fizički kreće ili deformiše geometrija. U 3D
kompjuterskoj grafici, zapošljavamo više koordinatnih sistema, tako da moramo znati kako se pretvarati
iz jedne u drugu. Pošto je lokacija osobina tačaka, ali ne i vektora, promena koordinatne transformacije
je različita za tačke i vektore.
3.4.2 Poeni
Promena koordinatne transformacije za tačke je nešto drugačija nego što je za vektore; ovo je zato što je
lokacija važna za bodove, tako da ne možemo prevesti tačke dok smo preveli vektore
3.6 FUNKCIJE TRANSFORMACIJE DIRECTKS MATH TRANSFORMACIJE
// Constructs a scaling matrix:
XMMATRIX XM_CALLCONV XMMatrixScaling(
float ScaleX,
float ScaleY,
float ScaleZ); // Scaling factors
// Constructs a scaling matrix from components in
vector:
XMMATRIX XM_CALLCONV XMMatrixScalingFromVector(
FXMVECTOR Scale); // Scaling factors (sx, sy,
sz)
// Constructs a x-axis rotation matrix Rx:
XMMATRIX XM_CALLCONV XMMatrixRotationX(
float Angle); // Clockwise angle θ to
rotate
// Constructs a y-axis rotation matrix Ry:
XMMATRIX XM_CALLCONV XMMatrixRotationY(
float Angle); // Clockwise angle θ to
rotate
// Constructs a z-axis rotation matrix Rz:
XMMATRIX XM_CALLCONV XMMatrixRotationZ(
float Angle); // Clockwise angle θ to
rotate
// Constructs an arbitrary axis rotation matrix Rn:
XMMATRIX XM_CALLCONV XMMatrixRotationAxis(
FXMVECTOR Axis, // Axis n to rotate about
float Angle); // Clockwise angle θ to
rotate
Constructs a translation matrix:
XMMATRIX XM_CALLCONV XMMatrixTranslation(
float OffsetX,
float OffsetY,
float OffsetZ); // Translation factors
Constructs a translation matrix from components in a
vector:
XMMATRIX XM_CALLCONV XMMatrixTranslationFromVector(
FXMVECTOR Offset); // Translation factors (tx,
ty, tz)
// Computes the vector-matrix product vM where vw = 1
for transforming points:
XMVECTOR XM_CALLCONV XMVector3TransformCoord(
FXMVECTOR V, // Input v
CXMMATRIX M); // Input M
// Computes the vector-matrix product vM where vw = 0
for transforming vectors:
XMVECTOR XM_CALLCONV XMVector3TransformNormal(
FXMVECTOR V, // Input v
CXMMATRIX M); // Input M
For the last two functions XMVector3TransformCoord and
XMVector3TransformNormal, you do not need to explicitly set the w coordinate.
The functions will always use vw = 1 and vw = 0 for XMVector3TransformCoord
and XMVector3TransformNormal, respectively.

Drugi deo Direct3d Osnove


Proces inicijalizacije Direct3D zahteva da budemo upoznati sa nekim osnovnim Direct3D tipovima i
osnovnim grafičkim konceptima; prvi i drugi deo ovog poglavlja adresiraju ove zahteve. Zatim detaljno
objašnjavamo potrebne korake za inicijalizaciju Direct3D-a. Zatim se preduzima mala putovanja za
uvođenje tačnog vremena i vremenskih merenja potrebnih za grafičke aplikacije u realnom vremenu.
Konačno, istražujemo okvir za kodiranje uzorka, koji se koristi da obezbedi konzistentan interfejs.
4.1.1 Direct3D 12 Pregled
Direct3D je grafički API za nizak nivo (interfejs za programiranje aplikacija) koji se koristi za kontrolu i
programiranje GPU-a (grafičke procesne jedinice) iz naše aplikacije, čime nam omogućavamo da pravimo
virtuelne 3D svetove pomoću hardverskog ubrzanja. Na primer, da biste poslali naredbu GPU-u da biste
obrisali ciljnu liniju (npr. Ekran), pozivali smo Direct3D metod
ID3D12CommandList::ClearRenderTargetViev. Nivo Direct3D sloja i hardverski drajveri će prevoditi
Direct3D komande u matične instrukcije koje su razumljive u GPU sistema; tako da ne moramo brinuti o
specifičnostima GPU-a, sve dok podržava Direct3D verziju koju koristimo. Da bi napravili ovaj posao,
proizvođači grafičkih kartica kao što su NVIDIA i AMD moraju raditi sa Direct3D timom i pružiti
kompatibilne Direct3D drajvere. Direct3D 12 dodaje neke nove funkcije renderinga, ali glavno
poboljšanje u odnosu na prethodnu verziju je da je redizajnirano da značajno smanjuje troškove CPU-a i
poboljša podršku za više threadova. Da bi postigli ove performanse, Direct3D12 je postao mnogo manji
nivo API-ja nego Direct3D 11; ona ima manje apstrakcije, zahteva dodatnu ručno "knjigovodstvo" od
programera, a bliže odražava moderne GPU arhitekture. Poboljšana performansa je, naravno, nagrada
za korišćenje ovog težeg API-ja.
4.1.2 COM
Komponentni model objekta (COM) je tehnologija koja omogućava DirectKs-u da bude nezavisan od
programskog jezika i ima kompatibilnost sa kompatibilnošću unazad. Većina detalja COM-a su nam
skriveni prilikom programiranja DirectKs-a sa C ++-om. Jedina stvar koju moramo znati jeste da dobijemo
pokazivače COM interfejsa putem posebnih funkcija ili metodima drugog COM interfejsa - Pored toga,
objekti COM su referentni brojevi; kada završimo sa interfejsom, nazovemo njegovu metodu izdavanja
(svi COM interfejsi nasleđuju funkcionalnost iz IUnknovn COM interfejsa, koji obezbeđuje metod izdanja)
umjesto da ga obrišu-COM objekti će osloboditi svoju memoriju kada njihova referentna brojka prelazi
na 0. Da pomoću upravljanja životnim vekom COM objekata, Vindovs Runtime Librari (WRL) pruža grupu
Microsoft :: WRL :: ComPtr (#include <vrl.h>), koja se može smatrati pametnim pokazivačem za COM
objekte. Kada instanca ComPtr izađe iz opsega, ona će automatski pozvati Izdanje na osnovnom COM
objektu, čime će nas spasiti da nećemo ručno pozvati izdanje. Tri glavne metode ComPtr koje koristimo
u ovoj knjizi su:
1. Get:Return pokazivač na osnovni COM interfejs. Ovo se često koristi za prenošenje argumenata na
funkcije koje uzimaju neobičan pokazivač interfejsa COM-a. Na primer:
ComPtr<ID3D12RootSignature> mRootSignature;

// SetGraphicsRootSignature expects
ID3D12RootSignature* argument.
mCommandList-
>SetGraphicsRootSignature(mRootSignature.Get());
2. GetAddressOf: Vraća adresu pokazivača na osnovni COM pristup. Ovo se često koristi za povratak
pokazivača COM-interfejsa kroz funkciju parametar. Na primer:
ComPtr<ID3D12CommandAllocator>
mDirectCmdListAlloc;

ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.GetAddressOf()));
1. DXGI_FORMAT_R32G32B32_FLOAT: Svaki element ima 3 floatingpoint komponente
2. DXGI_FORMAT_R16G16B16A16_UNORM: Svaki element ima 4 16 bit komponente mapirane u
[0, 1] range.
3. DXGI_FORMAT_R32G32_UINT: Svaki elemnt ima 2 32 bit komponente neprijavljene integrisane.
4. DXGI_FORMAT_R8G8B8A8_UNORM: Each element has four 8-bit unsigned
components mapped to the [0, 1] range.
5. DXGI_FORMAT_R8G8B8A8_SNORM: Each element has four 8-bit signed
components mapped to the [-1, 1] range.
6. DXGI_FORMAT_R8G8B8A8_SINT: Each element has four 8-bit signed integer
components mapped to the [-128, 127] range.
7. DXGI_FORMAT_R8G8B8A8_UINT: Each element has four 8-bit unsigned integer components
mapped to the [0, 255] range
Imajte na umu da se slova R, G, B, A koriste za postojanje crvene, zelene, plave i alfa, respektivno. Boje
se formiraju kao kombinacije osnovnih boja crvene, zelene i plave (npr. Jednake crvene i jednake zelene
čine žutom bojom). Alfa kanal ili alfa komponenta se generalno koristi za kontrolu transparentnosti.
Međutim, kao što smo ranije rekli, teksture ne trebaju čuvati informacije o boji, iako imena formata
ukazuju na to što rade; na primer, format DXGI_FORMAT_R32G32B32_FLOAT
4.1.4 Lanac zamene i promena stranica
Da bi se izbeglo treperenje u animaciji, najbolje je crtati čitav okvir animacije u teksturu van ekrana
zvanog bafera. Kada se cela scena povuče u zadnji bafer za datu animaciju, ona se prikazuje ekranu kao
jedan kompletan okvir; na ovaj način, gledalac ne gleda kako se okvir iscrtava - gledalac vidi samo
potpune okvire. Da bi se ovo implementiralo, hardverski uređaji održavaju dva bufera teksture, jedan se
naziva prednji bafer, a drugi se naziva "back buffer". Prednji bafer čuva podatke o slici koji se trenutno
prikazuju na ekranu, dok se sledeći okvir animacije vuče u zadnji bafer. Nakon što je okvir okrenuo u
zadnje pufer, uloge zadnjeg pufera i prednjeg bafera su obrnuti: zadnji bafer postaje prednji bafer, a
prednji bafer postaje zadnji bafer za sledeći okvir animacije. Zamena uloga zadnjeg i prednjeg odbojnika
se zove prezentacija. Predstavljanje je efikasna operacija, pošto se pokazivač na trenutni prednji bafer i
pokazivač na trenutni bafer za leđa treba samo zameniti. Slika 4.1 ilustruje proces.

4.1.5 Dubinsko Poliranje


Bufer dubine je primer teksture koji ne sadrži podatke o slici, već dubine informacija o određenom
pikselu. Moguće vrednosti dubine se kreću od 0.0 do 1.0, pri čemu 0.0 označava najbliži objekat u
prikazu frustum može biti gledaocu, a 1.0 označava najdalji objekat u frustumu koji se može videti od
gledatelja. Postoji jedna-na-jedna korespondencija između svakog elementa u baferu dubine i svakom
pikslu u zadnjem puferu (tj., Ijth element u zadnjem puferu odgovara ijth elementu u dubinskom
baferu). Dakle, ako je zadnji odbojnik imao rezoluciju od 1280 × 1024, bilo bi 1280 × 1024 unosa dubine.

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):

typedef struct D3D12_FEATURE_DATA_FEATURE_LEVELS {


UINT NumFeatureLevels;
const D3D_FEATURE_LEVEL *pFeatureLevelsRequested;
D3D_FEATURE_LEVEL MaxSupportedFeatureLevel;
} D3D12_FEATURE_DATA_FEATURE_LEVELS;
D3D_FEATURE_LEVEL featureLevels[3] =
{
D3D_FEATURE_LEVEL_11_0, // First check D3D 11
support
D3D_FEATURE_LEVEL_10_0, // Next, check D3D 10
support
D3D_FEATURE_LEVEL_9_3 // Finally, check D3D 9.3
support
};
D3D12_FEATURE_DATA_FEATURE_LEVELS featureLevelsInfo;
featureLevelsInfo.NumFeatureLevels = 3;
featureLevelsInfo.pFeatureLevelsRequested =
featureLevels;
md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_FEATURE_LEVELS,
&featureLevelsInfo,
sizeof(featureLevelsInfo));
Imajte na umu da je drugi parametar i ulazni i izlazni parametar. Za ulaz unesemo broj elemenata
(NumFeatureLevels) u nizu funkcija i pokazivač na niz nivoa funkcija (pFeatureLevelsRekuested) koji
sadrži listu nivoa nivoa za koje želimo da proverimo hardversku podršku. Funkcija izlazi maksimalno
podržani nivo funkcije pomoću polja MakSupportedFeatureLevel.
4.1.12 Prebivalište
Složena igra će koristiti puno resursa kao što su teksture i 3D mreže, ali mnoge od ovih resursa neće biti
potrebno GPU-u sve vreme. Na primer, ako zamislimo igru sa otvorenom šumom koja ima veliku pećinu
u njemu, pećinski resursi neće biti potrebni dok igrač ne uđe u pećinu, a kada igrač uđe u pećinu, šumski
resursi više neće biti potrebni . U Direct3D 12, aplikacije upravljaju rezidencijom resursa (u suštini, da li
je resurs u GPU memoriji) iseljavanjem resursa iz GPU memorije i zatim ih ponovo učini na GPU-u po
potrebi. Osnovna ideja je da minimizirate koliko GPU memorije aplikacija koristi, jer možda ne postoji
dovoljno za čuvanje svakog resursa za celu igru, ili ako korisnik ima druge aplikacije koje pokreću to
zahtevaju GPU memoriju.
Kao napomena o performansi, aplikacija bi trebalo da izbegava situaciju zamene istih resursa u i iz GPU
memorije u kratkom vremenskom okviru, jer postoji opterećenje za ovo. U idealnom slučaju, ako
nameravate iseliti resurs, taj resurs ne bi trebao neko vrijeme. Izmene nivoa igre / područja su dobri
primeri vremena za promjenu rezidencije resursa. Podrazumevano, kada se kreira resurs, on je stvoren i
isteruje se kada je uništen. Međutim, aplikacija može ručno da kontroliše boravak sa sledećim
metodama:
HRESULT ID3D12Device::MakeResident(
UINT NumObjects,
ID3D12Pageable *const *ppObjects);
HRESULT ID3D12Device::Evict(
UINT NumObjects,
ID3D12Pageable *const *ppObjects);

4.2 INTERACTION CPU / GPU


Moramo da razumemo da sa grafičkim programiranjem imamo dva procesora na poslu: CPU i GPU. Oni
rade paralelno i ponekad trebaju biti sinhronizovani. Za optimalne performanse, cilj je da se obojica
zadrže što duže i da se minimiziraju sinhronizacije. Sinhronizacije su nepoželjne, jer to znači da jedna
jedinica za obradu radi u stanju mirovanja dok čeka na drugu da završi neki posao; Drugim riječima, on
ruši paralelizam.
4.2.1 Command Kueue i komandna lista
GPU ima komandni red. CPU dostavlja komande u red preko Direct3D API pomoću komandnih lista
(pogledajte sliku 4.6). Važno je shvatiti da kada skup komandi bude upućen u komandni red, GPU ih ne
izvršava odmah. Sede u redu dok GPU nije spreman da ih obradi, jer je GPU verovatno zauzet
procesiranje prethodno ubačenih komandi.
Ako redosled zadataka postane prazan, GPU će biti u stanju mirovanja jer nema nikakvog posla; s druge
strane, ako se komandni red prelazi, CPU će u nekom trenutku morati da radi u stanju mirovanja dok
GPU uhvati [Cravfis12]. Obe ove situacije su nepoželjne; za aplikacije visokih performansi kao što su igre,
cilj je da i CPU i GPU budu zauzeti kako bi u potpunosti iskoristili dostupne hardverske resurse.
U Direct3D 12, komandni red predstavlja ID3D12CommandKueue pristup. Kreiran je popunjavanjem
strukture D3D12_COMMAND_KUEUE_DESC opisujući red i pozivanje ID3D12Device ::
CreateCommandKueue. Način na koji kreiramo red u ovoj knjizi je sledeći:
ommandQueue;
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)));
The IID_PPV_ARGS helper macro is defined as:
#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)),
IID_PPV_ARGS_Helper(ppType)
Gde __uuidof (** (ppTipe)) ocenjuje ID interfejsa COM (**(ppTipe)), što je u gore navedenom kodu
ID3D12CommandKueue. TheIID_PPV_ARGS_Helper funkcija u suštini baca ppTipe u prazan **. Koristimo
ovo makro tokom čitave knjige, pošto mnogi Direct3D 12 API pozivi imaju parametar koji zahteva COM
ID interfejsa koji kreiramo i uzimamo prazninu **. Jedna od primarnih metoda ovog interfejsa je
EkecuteCommandLists metod koji dodaje komande u komandne liste u red:
void ID3D12CommandQueue::ExecuteCommandLists(
// Number of commands lists in the array
UINT Count,
// Pointer to the first element in an array of
command lists
ID3D12CommandList *const *ppCommandLists);
Komandne liste se izvršavaju u početku s prvim elementom arrai. Kao što ukazuju na gore navedene
deklaracije o metodu, komandna lista za grafiku predstavlja ID3D12GraphicsCommandList interfejs koji
nasljeđuje iz interfejsa ID3D12CommandList. Interfejs ID3D12GraphicsCommandList ima brojne metode
za dodavanje komandi u komandnu listu. Na primjer, sledeći kod dodaje komande koje postavljaju
prikazni prozor, obrišu prikaz cilja rendera i izdaju poziv za izvlačenje:
// mCommandList pointer to ID3D12CommandList
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->ClearRenderTargetView(mBackBufferView,
Colors::LightSteelBlue, 0, nullptr);
mCommandList->DrawIndexedInstanced(36, 1, 0, 0, 0);
Imena ovih metoda ukazuju na to da se komande izvršavaju odmah, ali nisu. Gore navedeni kod samo
dodaje komande na komandnu listu. Metod EkecuteCommandLists dodaje komande u red naredbe, a
GPU obrađuje komande iz reda. Saznaćemo o različitim komandama ID3D12GraphicsCommandList
podržava kako napredujemo kroz ovu knjigu. Kada završimo dodavanje komandi na komandnu listu,
moramo naznačiti da smo završili snimanje komandi tako što smo pozvali
ID3D12GraphicsCommandList :: Close metod:
// Done recording commands.
mCommandList->Close();
Lista komandi mora biti zatvorena pre nego što se pređe na ID3D12CommandKueue ::
EkecuteCommandLists. Povezana sa komandnom listom je klasa podsjećanja memorije koja se zove
ID3D12CommandAllocator. Kako se komande snimaju na komandnu listu, one će zapravo biti sačuvane u
pridruženom raspoređivaču naredbe. Kada se komandna lista izvrši preko ID3D12CommandKueue ::
EkecuteCommandLists, naredbeni red će referencirati komande u alokatoru. Rasporednik komande je
kreiran iz ID3D12Device:
HRESULT ID3D12Device::CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE type,
REFIID riid,
void **ppCommandAllocator);
1.tip: tip komandnih lista koje se mogu povezati sa ovim raspoređivačem. Dva najčešća tipa koja
koristimo u ovoj knjizi su:
1. D3D12_COMMAND_LIST_TIPE_DIRECT: Skladišti spisak komandi koje direktno izvršava GPU (vrsta
komandne liste koju smo opisali do sada).
2. D3D12_COMMAND_LIST_TIPE_BUNDLE: Određuje komandnu listu predstavlja skup. Postoji neki
trošak CPU-a u izgradnji komandne liste, tako da Direct3D 12 pruža optimizaciju koja nam omogućava
snimanje sekvence komandi u takozvani paket. Nakon snimanja paketa, vozač će preprodati komande da
optimizuje njihovo izvršenje tokom renderinga. Zbog toga, paketi treba da budu zabeleženi u vreme
inicijalizacije. Upotreba snopova treba smatrati optimizacijom koja se koristi ako profilisanje pokazuje
izgradnju određenih komandnih lista koje uzimaju značajno vrijeme. Direct3D 12 crtež API je već veoma
efikasan, tako da ne morate često koristiti pakete, a samo ih koristite ako možete da pokažete dobitak
performansi od njih; to jest, ne koristite ih podrazumevano. U ovoj knjizi ne koristimo pakete; pogledajte
dokumentaciju DirectKs 12 za više detalja.
2. riid: COM ID interfejsa ID3D12CommandAllocator koji želimo
stvoriti.
3. ppCommandAllocator: daje pokazivač stvorenom komandnom alocatoru.
Komandne liste su takođe kreirane iz ID3D12Device:
HRESULT ID3D12Device::CreateCommandList(
UINT nodeMask,
D3D12_COMMAND_LIST_TYPE type,
ID3D12CommandAllocator *pCommandAllocator,
ID3D12PipelineState *pInitialState,
REFIID riid,
void **ppCommandList);

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);
}

4.3.8 Kreirajte Buffer za Dubin / Stencil i Pregled


Sada moramo da kreiramo bafer dubine / stencila. Kao što je opisano u §4.1.5, dubina
bafer je samo 2D tekstura koja čuva informacije o dubini najbližih vidljivih objekata (i informacije o
šabloni ako koriste šablone). Tekstura je neka vrsta grafičkog resursa, tako da je kreiramo tako što
popunjavamo D3D12_RESOURCE_DESC strukturu koja opisuje resurs teksture, a zatim poziva metodu
ID3D12Device :: CreateCommittedResource. Struktura D3D12_RESOURCE_DESC definisana je na sledeći
način:
typedef struct D3D12_RESOURCE_DESC
{
D3D12_RESOURCE_DIMENSION Dimension;
UINT64 Alignment;
UINT64 Width;
UINT Height;
UINT16 DepthOrArraySize;
UINT16 MipLevels;
DXGI_FORMAT Format;
DXGI_SAMPLE_DESC SampleDesc;
D3D12_TEXTURE_LAYOUT Layout;
D3D12_RESOURCE_MISC_FLAG MiscFlags;
} D3D12_RESOURCE_DESC;
1. Dimension: The dimension of the resource, which is one of the following
enumerated types:
enum D3D12_RESOURCE_DIMENSION
{
D3D12_RESOURCE_DIMENSION_UNKNOWN = 0,
D3D12_RESOURCE_DIMENSION_BUFFER = 1,
D3D12_RESOURCE_DIMENSION_TEXTURE1D = 2,
D3D12_RESOURCE_DIMENSION_TEXTURE2D = 3,
D3D12_RESOURCE_DIMENSION_TEXTURE3D = 4
} D3D12_RESOURCE_DIMENSION;
2. Širina: širina teksture u tekselima. Za resurse bafera, ovo je broj bajtova u puferu.
3. Visina: visina teksture u tekselima.
4. DepthOrArraiSize: dubina teksture u tekselima ili veličina teksture (za 1D i 2D teksture). Imajte na umu
da ne možete imati teksturni niz 3D tekstura.
5. MipLevels: Broj mipmap nivoa. Mipmaps su pokriveni u Poglavlju 9 teksturiranje. Za stvaranje bafera
za dubinu / stencil, našoj teksturi potreban je samo jedan mipmap nivo.
6. Format: Član popisnog tipa DKSGI_FORMAT koji navodi format od teksela. Za bafer dubine / stencila,
ovo mora biti jedan od prikazanih formata §4.1.5.
7. SampleDesc: Broj multisamplesa i nivoa kvaliteta; vidi §4.1.7 i §4.1.8. Podsjetimo da 4Ks MSAA koristi
buffer i buffer dubine 4Ks veći od ekrana rezolucije, kako bi se sačuvale informacije o boji i dubini /
šabloni za podpiksel. Prema tome, podešavanja multisamplinga koja se koriste za bafer dubine / stencila
moraju se podudarati sa podešavanja koja se koriste za ciljeve renderiranja.
8. Laiout: Član je D3D12_TEKSTURE_LAIOUT navedenog tipa
određuje raspored teksture. Za sada, ne moramo da brinemo o izgledu i
može odrediti D3D12_TEKSTURE_LAIOUT_UNKNOVN.
9. MiscFlags: Razni resursi zastavice. Za resursni resurs za dubinu / stencil,
navesti D3D12_RESOURCE_MISC_DEPTH_STENCIL.
GPU resursi žive u grudima, koji su u suštini blokovi GPU memorije sa
određena svojstva. ID3D12Device :: CreateCommittedResource metoda
kreira i obrađuje resurs za određeni kup s karakteristikama koje mi odredimo.
HRESULT ID3D12Device::CreateCommittedResource(
const D3D12_HEAP_PROPERTIES *pHeapProperties,
D3D12_HEAP_MISC_FLAG HeapMiscFlags,
const D3D12_RESOURCE_DESC *pResourceDesc,
D3D12_RESOURCE_USAGE InitialResourceState,
const D3D12_CLEAR_VALUE *pOptimizedClearValue,
REFIID riidResource,
void **ppvResource);
typedef struct D3D12_HEAP_PROPERTIES {
D3D12_HEAP_TYPE Type;
D3D12_CPU_PAGE_PROPERTIES CPUPageProperties;
D3D12_MEMORY_POOL MemoryPoolPreference;
UINT CreationNodeMask;
UINT VisibleNodeMask;
} D3D12_HEAP_PROPERTIES;
1. pHeapProperties: Karakteristike kupea na koju želimo da izvrsimo resurs.
Neke od ovih osobina su za naprednu upotrebu. Za sada, glavna imovina koja nam je potrebna
zabrinuti je D3D12_HEAP_TIPE, koji može biti jedan od sledećih
članovi D3D12_HEAP_PROPERTIES nabrojanih tipova:
1. D3D12_HEAP_TIPE_DEFAULT: Podrazumevani heap. Ovdje smo počinili
resursa kojima GPU pristupa isključivo. Uzmi bafer dubine / stencila
kao primer: GPU čita i piše u bafer dubine / stencila. CPU
nikada nije potreban pristup njemu, tako da bi se bufer dubine / šablona stavio u
default heap.
2. D3D12_HEAP_TIPE_UPLOAD: Upload šapu. Ovdje smo počinili
resursa gdje je potrebno da otpremamo podatke sa CPU-a na GPU resurs.
3. D3D12_HEAP_TIPE_READBACK: Štrajk za čitanje. Ovdje smo počinili
resurse koje treba pročitati CPU.
4. D3D12_HEAP_TIPE_CUSTOM: Za napredne scenarije upotrebe - pogledajte
MSDN dokumentaciju za više informacija.
2. HeapMiscFlags: Dodatne zastave o kupu koju želimo da izvršimo
do. Ovo će obično biti D3D12_HEAP_MISC_NONE.
3. pResourceDesc: Pokazivač na D3D12_RESOURCE_DESC instancu opisujući
resurs koji želimo da napravimo.
4. InitialResourceState: Podsetite iz §4.2.3 da sredstva imaju trenutnu upotrebu
država. Koristite ovaj parametar da biste podesili početno stanje resursa kada se kreira. Za
bafer dubine / šablona, početno stanje će biti
D3D12_RESOURCE_USAGE_INITIAL, a onda ćemo želeti da pređemo na
D3D12_RESOURCE_USAGE_DEPTH tako da se može povezati sa gasovodom kao a
dubina / stencil bafer.
5. pOptimizedClearValue: Pokazivač na objekat D3D12_CLEAR_VALUE koji
opisuje optimizovanu vrednost za čišćenje resursa. Obrišite pozive koji odgovaraju istom
optimizirana jasna vrednost može potencijalno biti brža od jasnih poziva koji se ne podudaraju sa
optimizirana jasna vrednost. Null takođe može biti specificiran za ovu vrijednost da ne odredi
optimizirana jasna vrednost.
struct D3D12_CLEAR_VALUE
{
DXGI_FORMAT Format;
union
{
FLOAT Color[ 4 ];
D3D12_DEPTH_STENCIL_VALUE DepthStencil;
};
} D3D12_CLEAR_VALUE;
6. riidResource: COM ID interfejsa ID3D12Resource koji želimonabavite pokazivač na.
7. ppvResource: vraća pokazivač na ID3D12Resource koji predstavlja novostvoreni resurs.
Pored toga, pre korišćenja bafera dubine / stencila, moramo da kreiramo povezani prikaz dubine /
šablona koji se vezuje za cevovod. Ovo se radi slično stvaranju prikaza cilja renderera. Sledeći primer kod
pokazuje kako kreiramo teksturu dubine / šablona i njegov odgovarajući prikaz dubine / šablona:
// Create the depth/stencil buffer and view.
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension =
D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.Format = mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 :
1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ?
(m4xMsaaQuality - 1) : 0;
depthStencilDesc.Layout =
D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags =
D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));
// Create descriptor to mip level 0 of entire resource
using the
// format of the resource.
md3dDevice->CreateDepthStencilView(
mDepthStencilBuffer.Get(),
nullptr,
DepthStencilView());
// Transition the resource from its initial state to
be used as a depth buffer.
mCommandList->ResourceBarrier(
1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_DEPTH_WRITE));
Note that we use the CD3DX12_HEAP_PROPERTIES helper constructor to create
the heap properties structure, which is implemented like so:
explicit CD3DX12_HEAP_PROPERTIES(
D3D12_HEAP_TYPE type,
UINT creationNodeMask = 1,
UINT nodeMask = 1 )
{
Type = type;
CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
CreationNodeMask = creationNodeMask;
VisibleNodeMask = nodeMask;
}
Drugi parametar CreateDepthStencilViev je pokazivač na D3D12_DEPTH_STENCIL_VIEV_DESC. Između
ostalog, ova struktura opisuje tip podataka (format) elemenata u resursu. Ako je resurs kreiran sa a
ukucani format (tj. ne tipičan), onda ovaj parametar može biti null, što ukazuje na stvaranje
pogled na prvi mipmap nivo ovog resursa (bafer dubine / stencila je kreiran sa
samo jedan nivo mipmap-a) sa formatom sa kojim je kreiran resurs. (Mipmaps su
o kojima smo razgovarali u Poglavlju 9.) Zato što smo odredili vrstu našeg bafera za dubinu / šablon, mi
navedite null za ovaj parametar.
4.3.9 Podesite Vievport
Obično volimo da nacrtamo 3D scenu u čitav zadnji rezervni prag, gde se nalazi bafer za pozadinu
veličina odgovara celom ekranu (režim celog ekrana) ili celokupnom klijentskom području a
prozor. Međutim, ponekad samo želimo da 3D scenu nacrtamo u pravougaonik
Back buffer; pogledajte sliku 4.9.
Slika 4.9. Modifikovanjem prikaza, 3D scenu možemo nacrtati u a
pravougaonik zadnjeg pufera. Back buffer se zatim prezentira na klijentskoj oblasti prozora. Podredeni
pravougaoni bafera u koji se izvlačimo naziva se pogleda i opisuje se sledećom strukturom:
typedef struct D3D12_VIEWPORT {
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D12_VIEWPORT;

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);
}
}

Naš demo okvir kreira instancu GameTimer-a za merenje ukupnog vremena


pošto je aplikacija započela, a vreme proteklo između okvira; Međutim, takođe možete
kreirajte dodatne instance i koristite ih kao generičke "štoperice". Na primjer, kada a
bomba se zapalila, mogli ste da započnete novi GameTimer i kada je TotalTime postigao
5 sekundi, mogli biste podići događaj koji je bomba eksplodirala.
4.5 OKVIR ZA PRIMENA DEMO
Demos u ovoj knjizi koristi kod iz d3dUtil.h, d3dUtil.cpp, d3dApp.h i
d3dApp.cpp datoteke, koje se mogu preuzeti sa veb stranice knjige. The d3dUtil.h i
d3dUtil.cpp datoteke sadrže korisne korisničke kodove, a d3dApp.h i d3dApp.cpp datoteke
sadrže kod klase aplikacija Direct3D koji se koristi za encapsulaciju Direct3D-a
primjer primjene. Čitač se ohrabruje da proučava ove datoteke nakon čitanja ovog poglavlja,
pošto ne pokrivamo svaku liniju koda u ovim datotekama (npr., mi ne pokazujemo kako da kreirate
prozor, kao osnovno programiranje Vin32 je preduslov ove knjige). Cilj ovoga
okvir je bio da se sakrije kod za kreiranje prozora i inicijalizacijski kod Direct3D-a; od strane
sakrivajući ovaj kod, osećamo da to čini demo manje odvraćanjem, jer se možete fokusirati samo na
specifični detalji pokušavaju da ilustruju primerni kod.
4.5.1 D3DApp
Klasa D3DApp je osnovna klasa aplikacije Direct3D, koja pruža funkcije
za kreiranje glavnog prozora aplikacije, pokretanje petlje aplikacije aplikacija, rukovanje
prozorske poruke i inicijalizaciju Direct3D. Štaviše, klasa definira okvir
funkcije za demo aplikacije. Klijenti treba da potiču iz D3DApp, preklapaju
virtuelne funkcije okvira i instancirati samo jednu instancu izvedenog D3DApp
klasa. D3DApp klasa je definisana na sledeći način:
#include “d3dUtil.h”
#include “GameTimer.h”
// Link necessary d3d12 libraries.
#pragma comment(lib,“d3dcompiler.lib”)
#pragma comment(lib, “D3D12.lib”)
#pragma comment(lib, “dxgi.lib”)
class D3DApp
{ protected:
D3DApp(HINSTANCE hInstance);
D3DApp(const D3DApp& rhs) = delete;
D3DApp& operator=(const D3DApp& rhs) = delete;
virtual ˜D3DApp();
public:
static D3DApp* GetApp();
HINSTANCE AppInst()const;
HWND MainWnd()const;
float AspectRatio()const;
bool Get4xMsaaState()const;
void Set4xMsaaState(bool value);
int Run();
virtual bool Initialize();
virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM
wParam, LPARAM lParam);
protected:
virtual void CreateRtvAndDsvDescriptorHeaps();
virtual void OnResize();
virtual void Update(const GameTimer& gt)=0;
virtual void Draw(const GameTimer& gt)=0;
// Convenience overrides for handling mouse input.
virtual void OnMouseDown(WPARAM btnState, int x, int
y){ }
virtual void OnMouseUp(WPARAM btnState, int x, int
y) { }
virtual void OnMouseMove(WPARAM btnState, int x, int
y){ }
protected:
bool InitMainWindow();
bool InitDirect3D();
void CreateCommandObjects();
void CreateSwapChain();
void FlushCommandQueue();
ID3D12Resource* CurrentBackBuffer()const
{
return mSwapChainBuffer[mCurrBackBuffer].Get();
}
D3D12_CPU_DESCRIPTOR_HANDLE
CurrentBackBufferView()const
{
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),
mCurrBackBuffer,
mRtvDescriptorSize);
}
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView()const
{
return mDsvHeap-
>GetCPUDescriptorHandleForHeapStart();
}
void CalculateFrameStats();
void LogAdapters();
void LogAdapterOutputs(IDXGIAdapter* adapter);
void LogOutputDisplayModes(IDXGIOutput* output,
DXGI_FORMAT format);
protected:
static D3DApp* mApp;
HINSTANCE mhAppInst = nullptr; // application
instance handle
HWND mhMainWnd = nullptr; // main window handle
bool mAppPaused = false; // is the application
paused?
bool mMinimized = false; // is the application
minimized?
bool mMaximized = false; // is the application
maximized?
bool mResizing = false; // are the resize bars
being dragged?
bool mFullscreenState = false;// fullscreen
enabled
// Set true to use 4X MSAA (§4.1.8). The default is
false.
bool m4xMsaaState = false; // 4X MSAA enabled
UINT m4xMsaaQuality = 0; // quality level of 4X
MSAA
// Used to keep track of the “delta-time” and game
time (§4.4).
GameTimer mTimer;
Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;
Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
Microsoft::WRL::ComPtr<ID3D12Device> md3dDevice;
Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
UINT64 mCurrentFence = 0;
Microsoft::WRL::ComPtr<ID3D12CommandQueue>
mCommandQueue;
Microsoft::WRL::ComPtr<ID3D12CommandAllocator>
mDirectCmdListAlloc;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList>
mCommandList;
static const int SwapChainBufferCount = 2;
int mCurrBackBuffer = 0;
Microsoft::WRL::ComPtr<ID3D12Resource>
mSwapChainBuffer[SwapChainBufferCount];
Microsoft::WRL::ComPtr<ID3D12Resource>
mDepthStencilBuffer;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>
mRtvHeap;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>
mDsvHeap;
D3D12_VIEWPORT mScreenViewport;
D3D12_RECT mScissorRect;
UINT mRtvDescriptorSize = 0;
UINT mDsvDescriptorSize = 0;
UINT mCbvSrvDescriptorSize = 0;
// Derived class should set these in derived
constructor to customize
// starting values.
std::wstring mMainWndCaption = L”d3d App”;
D3D_DRIVER_TYPE md3dDriverType =
D3D_DRIVER_TYPE_HARDWARE;
DXGI_FORMAT mBackBufferFormat =
DXGI_FORMAT_R8G8B8A8_UNORM;
DXGI_FORMAT mDepthStencilFormat =
DXGI_FORMAT_D24_UNORM_S8_UINT;
int mClientWidth = 800;
int mClientHeight = 600;
};
Koristili smo komentare u gorenavedenom kodu kako bismo opisali neke od članova podataka; the
metode se razmatraju u narednim odeljcima.
4.5.2 Ne-okvirni metodi
1. D3DApp: Konstruktor jednostavno inicijalizuje članove podataka na podrazumevane vrednosti.
2. ~D3DApp: destruktor oslobađa COM interfejse koje D3DApp stiče, i
ispira redosled komandi. Razlog zašto moramo da ispunimo komandni red u
destructor je da moramo čekati dok se GPU ne obrađuje komande u
red pre nego što uništimo resurse na koje se GPU i dalje pominje. Inače,
GPU se može srušiti kada aplikacija izađe.
D3DApp :: ~D3DApp ()
{
ako (md3dDevice! = nullptr)
FlushCommandKueue ();
}
3. AppInst: funkcija Trivial access vraća kopiju instance drške instance.
4. MainVnd: funkcija Trivial access vraća kopiju glavne ručice prozora.
5. AspectRatio: odnos aspekta je definisan kao odnos širine bafera unazad do
visinu. Odnos prosjeka će se koristiti u sljedećem poglavlju. Trije se primenjuje
kao:
float D3DApp::AspectRatio()const
{
return static_cast<float>(mClientWidth) /
mClientHeight;
}
6. Get4kMsaaState: Povratak istinit je 4Ks MSAA je omogućen i pogrešan u suprotnom.
7. Set4kMsaaState: Omogućava / onemogućava 4Ks MSAA.
8. Pokreni: Ovaj metod obnavlja petlju aplikacije. Koristi Vin32
PeekMessage funkciju tako da može obraditi našu logiku igre kada nijedne poruke nisu
poklon. Implementacija ove funkcije je prikazana u §4.4.3.
9. InitMainVindov: Inicijalizuje glavni prozor aplikacije; pretpostavljamo čitaoca
je upoznat sa osnovnom inicijalizacijom Vin32 prozora.
10. InitDirect3D: inicijalizira Direct3D primenom koraka opisanih u §4.3.
11. CreateSvapChain: Kreira svap lanac (§4.3.5.).
12. CreateCommandObjects: Kreira komandni red, komandnu listu
alokator i komandnu listu, kako je opisano u §4.3.4.
13. FlushCommandKueue: Napadi CPU da sačeka dok GPU nije završio
obradu svih komandi u redu (vidi §4.2.2).
14. CurrentBackBuffer: Vraća ID3D12Resource u trenutni bafer za bafer
u lancu zamene.
15. CurrentBackBufferViev: Vraća RTV (prikaz ciljanja) na trenutnu
Back buffer.
16. DepthStencilViev: Vraća DSV (prikaz dubine / šablona) u glavnu
dubina / stencil bafer.
17. CalculateFrameStats: Izračunava prosečne okvire u sekundi i
prosečne milisekunde po kadru. O implementaciji ove metode govori se u
§4.4.4.
18. LogAdapters: Numeriše sve adaptere u sistemu (§4.1.10).
19. LogAdapterOutputs: Numeriše sve izlaze povezane sa adapterom
(§4.1.10).
20. LogOutputDisplaiModes: Numeriše sve modove prikaza koji podržava izlaz
za dati format (§4.1.10).
4.5.3 Okvirne metode
Za svaku aplikaciju uzorka u ovoj knjizi, dosledno preuzimamo šest virtuelnih funkcija
D3DApp. Ove šest funkcija se koriste za implementaciju specifičnog koda
uzorak. Prednost ove postavke je da je inicijalizacijski kod, rukovanje porukama, itd
implementiran u D3DApp klasi, tako da se izvedena klasa mora samo fokusirati na
poseban kod demo aplikacije. Evo opisa okvirnih metoda:
1. Inicijalizirajte: Koristite ovaj metod da postavite inicijalizacijski kod za aplikaciju kao što je
dodeljivanje resursa, inicijalizacija objekata i postavljanje 3D scene. D3DApp
implementacija ovog metoda poziva InitMainVindov i InitDirect3D;
Zbog toga, trebalo bi da nazovete D3DApp verziju ove metode u svojoj izvedenoj verziji
implementacija najpre ovako:
bool TestApp::Init()
{
if(!D3DApp::Init())
return false;
/* Rest of initialization code goes here */
}
so that your initialization code can access the initialized members of D3DApp.
2. MsgProc: Ovaj metod implementira funkciju procedure prozora za glavni
prozor aplikacije. Uopšteno govoreći, potrebno je samo da preglasite ovaj metod ako postoji
poruku koju trebate da rešite da D3DApp :: MsgProc ne radi (ili ne
rukuje se svojim željama). D3DApp implementacija ove metode je istražena u
§4.5.5. Ako preokrenete ovaj metod, svaka poruka sa kojom ne radite treba da bude
prosledjen D3DApp :: MsgProc.
3. CreateRtvAndDsvDescriptorHeaps: Virtuelna funkcija gde kreirate
RTV i DSV deskriptor dopušta vašoj aplikaciji potrebe. Podrazumevana implementacija
stvara RTV kup s SvapChainBufferCount mnogim deskriptorima (za
bafer u svap lancu) i DSV kup s jednim opisom (za dubinu / šablon
pufer). Podrazumevana primena će biti dovoljna za mnoge naše demo grupe; više
napredne tehnike renderinga koje koriste višestruke ciljeve renderiranja, moraćemo
zamenite ovaj metod.
4. OnResize: Ova metoda poziva D3DApp :: MsgProc kada je VM_SIZE
poruka je primljena. Kada se prozor promeni veličine, neke Direct3D osobine trebaju
da se menjaju, pošto zavise od dimenzija klijentskog područja. Posebno, pozadi
bafera i dubina / šablona za šablone treba ponovo napraviti da se podudaraju sa novom klijentskom
površinom
prozor. Back buffer se može mijenjati pozivanjem
IDKSGISvapChain :: ResizeBuffers metoda. Trebalo bi da se koristi dubina / matrica
uništiti, a zatim popraviti na osnovu nove dimenzije. Osim toga, render
cilj i dubina / šablon pogleda treba ponovo napraviti. D3DApp implementacija
OnResize rukuje kodom neophodnim za promjenu veličine bafera za nazad i dubinu / šablone;
pogledajte izvorni kod za direktne detalje. Pored bafera, drugi
svojstva zavise od veličine područja klijenta (npr. matrice projekcije), tako da je ovo
metoda je deo okvira jer klijentski kod možda treba da izvrši neke od
sopstveni kod kada se prozor menja.
5. Ažuriranje: Ovaj apstraktni metod naziva se svaki okvir i treba ga koristiti za ažuriranje
3D aplikacija tokom vremena (npr. izvodite animacije, pomerite fotoaparat, izvršite sudar
otkrivanje, proveru korisničkog unosa i sl.).
6. Nacrt: Ovaj apstraktni metod se poziva na svaki okvir i gde se izdaje rendering
komande da zapravo nacrtaju naš trenutni okvir u zadnje pufer. Kad završimo
crtajući naš okvir, pozivamo metodu IDKSGISvapChain :: Present za prezentaciju
zadnji bafer na ekran.
Pored gore navedenih šest okvirnih metoda, pružamo još tri
virtuelne funkcije za lakše rukovanje događajima kada je dugme miša
pritisnuti, osloboditi i kada se miš pomera
virtual void OnMouseDown(WPARAM btnState, int x, int
y){ }
virtual void OnMouseUp(WPARAM btnState, int x, int
y) { }
virtual void OnMouseMove(WPARAM btnState, int x, int
y){ }
4.5.4 Okvirna statistika
Uobičajena je aplikacija za igrice i grafiku za merenje broja okvira
koji se vrši u sekundi (FPS). Da bismo to uradili, jednostavno računamo broj okvira
obrađeni (i skladištiti u promenljivoj n) u određenom vremenskom periodu t. Zatim
prosečna FPS tokom vremenskog perioda t je fpsavg = n / t = n. Ako postavimo t = 1, onda fpsavg = n / 1
= n.
U našem kodu koristimo t = 1 (drugo), jer se izbjegava podjela i, s druge strane, jedna sekunda
daje prilično dobar prosek - nije previše dug i nije suviše kratak. Kod za izračunavanje
FPS je obezbeđen metodom D3DApp :: CalculateFrameStats:
void D3DApp::CalculateFrameStats()
{
// Code computes the average frames per second, and
also the
// average time it takes to render one frame. These
stats
// are appended to the window caption bar.
static int frameCnt = 0;
static float timeElapsed = 0.0f;
frameCnt++;
// Compute averages over one second period.
if( (mTimer.TotalTime() - timeElapsed) >= 1.0f )
{
float fps = (float)frameCnt; // fps = frameCnt /
1
float mspf = 1000.0f / fps;
wstring fpsStr = to_wstring(fps);
wstring mspfStr = to_wstring(mspf);
wstring windowText = mMainWndCaption +
L” fps: ” + fpsStr +
L” mspf: ” + mspfStr;
SetWindowText(mhMainWnd, windowText.c_str());
// Reset for next average.
frameCnt = 0;
timeElapsed += 1.0f;
}
}
Ovaj metod bi se nazvao svakim okvirom kako bi se računalo okvir.
Pored računanja FPS-a, gore navedeni kod takođe izračunava broj
milisekundi u proseku treba procesirati okvir:
float mspf = 1000.0f / fps;
Sekundi po okvira su samo recipročni FPS, ali se množimo za 1000 ms /
1 s za pretvaranje od sekundi do milisekundi (opoziv je 1000 ms u sekundi).
Ideja iza ove linije je da izračuna vreme, u milisekundama, potrebno je da se a
Ram; ovo je različita količina od FPS-a (ali posmatrajte ovu vrijednost može se izvući iz
FPS). U stvarnosti, vrijeme koje je potrebno da se okvir okrene više koristi od FPS-a, kao i mi
može direktno videti povećanje / smanjenje vremena koje je potrebno da bi se napravio okvir dok mi
modifikujemo
scena. Sa druge strane, FPS ne nam odmah govori o povećanju / smanjenju
vreme dok mi modifikujemo našu scenu. Štaviše, kako [Dunlop03] ističe u svom članku FPS nasuprot
Frame Time, zbog nelinearnosti FPS krivulje, koristeći FPS može dati pogrešno
rezultate. Na primer, razmotrite situaciju (1): Pretpostavimo da se naša aplikacija pokreće na 1000
FPS, uzimajući 1 ms (milisekunde) da izvede okvir. Ako brzina kadra pada na 250 FPS, onda
potrebno je 4 ms da biste napravili okvir. Sada razmislite o situaciji (2): Pretpostavimo da je naša
aplikacija
trčanje pri 100 FPS, uzimajući 10 ms za prikaz okvira. Ako brzina kadra pada na oko 76.9
FPS, onda je potrebno oko 13 ms da bi se prikazao okvir. U obe situacije, rendering per
okvir je povećan za 3 ms, i time oba predstavljaju isto povećanje u vremenu koje je potrebno
napraviti okvir. Čitanje FPS-a nije tako jednostavno. Pad od 1000 FPS do 250
FPS izgleda mnogo drastičnije od padova od 100 FPS do 76.9 FPS; međutim, kao i mi
upravo su pokazali, oni zapravo predstavljaju isto povećanje u vremenu koje je potrebno za
renderovanje
Ram.
4.5.5 Message Handler
Procedura prozora koju implementiramo za naš okvir aplikacija čini golem
minimum. Generalno, u svakom slučaju nećemo mnogo raditi sa Vin32 porukama. In
činjenica, jezgro našeg aplikacijskog koda se izvršava tokom obrade u toku rada (tj. kada nema
prozorska poruka je prisutna). Ipak, postoje neke važne poruke koje trebamo
proces. Međutim, zbog dužine procedure prozora, ne uključujemo sve
kod ovde; već samo objašnjavamo motivaciju iza svake poruke sa kojom se bavimo. Mi
podstaknite čitaoca da preuzme datoteke izvornog koda i provede neko vreme
upoznati sa okvirnim kodom aplikacije, jer je osnova svakog uzorka
ova knjiga.
Prva poruka sa kojom se bavimo je VM_ACTIVATE poruka. Ova poruka je poslata
kada se aplikacija aktivira ili deaktivira. Mi ga implementiramo tako:
case WM_ACTIVATE:
if( LOWORD(wParam) == WA_INACTIVE )
{
mAppPaused = true;
mTimer.Stop();
}
else
{
mAppPaused = false;
mTimer.Start();
}
return 0;
Kao što vidite, kada se naša aplikacija deaktivira, postavili smo podatke za podatke
mAppPaused to true, i kada naša aplikacija postane aktivna, podesili smo podatke
member mAppPaused to false. Pored toga, kada se aplikacija zaustavi, mi zaustavljamo
tajmer, a zatim nastavite tajmer kada se aplikacija ponovo aktivira. Ako pogledamo
nazad na implementaciju na D3DApp :: Run (§4.4.3), smatramo da ako je naša aplikacija
pauzirana, onda ne ažuriramo našu aplikacionu šifru, već umesto oslobađanja nekih CPU ciklusa
nazad na OS; na ovaj način, naša aplikacija ne smanjuje CPU ciklusa kada je neaktivna.
Sledeća poruka sa kojom se bavimo je VM_SIZE poruka. Podsjetimo da je ova poruka
kada se promeni prozor. Glavni razlog za rukovanje ovom porukom je da mi
žele da dimenzije zadnjeg pufera i dimenzije dubine / šablona budu u skladu sa dimenzijama klijenta
površina pravougaonika (tako da ne dođe do istezanja). Stoga, svaki put kad prozor promenimo, želimo
da promenite dimenzije pufera. Kod za promenu veličine bafera se implementira
D3DApp :: OnResize. Kao što je već rečeno, zadnji bafer može biti promenjen tako što se pozove
IDKSGISvapChain :: ResizeBuffers metoda. Trebalo bi da bude bafer dubine / šablona
uništavali, a zatim remadirali na osnovu novih dimenzija. Pored toga, cilja i
Potrebno je ponovo napraviti pogled na dubinu / šablon. Ako korisnik prevlači veličine barova, moramo
biti pazite zato što vuče veličine barova šalje kontinuirane VM_SIZE poruke, a mi to radimo
ne žele da kontinuirano menjaju razmake. Stoga, ako utvrdimo da je korisnik
menjajući veličinu povlačenjem, mi zapravo ne radimo ništa (osim pauze aplikacije) do korisnika
se vrši prevlačenjem velikih barova. To možemo učiniti rukovanjem VM_EKSITSIZEMOVE
poruka. Ova poruka se šalje kada korisnik izda veličinu barova.
// WM_ENTERSIZEMOVE is sent when the user grabs the
resize bars.
case WM_ENTERSIZEMOVE:
mAppPaused = true;
mResizing = true;
mTimer.Stop();
return 0;
// WM_EXITSIZEMOVE is sent when the user releases the
resize bars.
// Here we reset everything based on the new window
dimensions.
case WM_EXITSIZEMOVE:
mAppPaused = false;
mResizing = false;
mTimer.Start();
OnResize();
return 0;
The next three messages we handle are trivially implemented and so we just show the
code:
// WM_DESTROY is sent when the window is being
destroyed.
case WM_DESTROY:
PostQuitMessage(0);
return 0;
// The WM_MENUCHAR message is sent when a menu is
active and the user
// presses a key that does not correspond to any
mnemonic or
// accelerator key.
case WM_MENUCHAR:
// Don’t beep when we alt-enter.
return MAKELRESULT(0, MNC_CLOSE);
// Catch this message to prevent the window from
becoming too small.
case WM_GETMINMAXINFO:
((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
return 0;
Finally, to support our mouse input virtual functions, we handle the following
messages as follows:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
OnMouseDown(wParam, GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
return 0;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
OnMouseUp(wParam, GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
return 0;
case WM_MOUSEMOVE:
OnMouseMove(wParam, GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
return 0;
Moramo #include <Vindovsk.h> za GET_Ks_LPARAM I Makroi GET_I_LPARAM.
4.5.6 Demo "Init Direct3D" Sada kada smo razgovarali o okvirima aplikacije, napravimo malo
aplikaciju koja ga koristi. Program ne zahteva skoro nikakav pravi posao od naseg roditelja
klasa D3DApp radi većinu posla koji je potreban za ovaj demo. Najvažnije je napomenuti
kako izvodimo klase iz D3DApp i implementiramo okvirne funkcije, gdje smo
napišemo naš specifičan kod uzorka. Svi programi iz ove knjige će se slijediti istim
template.
#include “../../Common/d3dApp.h”
#include <DirectXColors.h>
using namespace DirectX;
class InitDirect3DApp : public D3DApp
{ public:
InitDirect3DApp(HINSTANCE hInstance);
˜InitDirect3DApp();
virtual bool Initialize()override;
private:
virtual void OnResize()override;
virtual void Update(const GameTimer& gt)override;
virtual void Draw(const GameTimer& gt)override;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE
prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF |
_CRTDBG_LEAK_CHECK_DF );
#endif
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;
}
}
InitDirect3DApp::InitDirect3DApp(HINSTANCE hInstance)
: D3DApp(hInstance)
{} InitDirect3DApp::˜
InitDirect3DApp()
{}
bool InitDirect3DApp::Initialize()
{
if(!D3DApp::Initialize())
return false;
return true;
}
void InitDirect3DApp::OnResize()
{
D3DApp::OnResize();
}
void InitDirect3DApp::Update(const GameTimer& gt)
{
}
void InitDirect3DApp::Draw(const GameTimer& gt)
{
// Reuse the memory associated with command
recording.
// We can only reset when the associated command
lists have finished
// execution on the GPU.
ThrowIfFailed(mDirectCmdListAlloc->Reset());
// A command list can be reset after it has been
added to the
// command queue via ExecuteCommandList. Reusing the
command list reuses memory.
ThrowIfFailed(mCommandList->Reset(
mDirectCmdListAlloc.Get(), nullptr));
// 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));
// Set the viewport and scissor rect. This needs to
be reset
// whenever the command list is reset.
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);
// 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());
// 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;
// Wait until frame commands are complete. This
waiting is
// inefficient and is done for simplicity. Later we
will show how to
// organize our rendering code so we do not have to
wait per frame.
FlushCommandQueue();
}
Postoje neke metode o kojima još nismo raspravljali. The
Metod ClearRenderTargetViev obriše određeni cilj rendera u datu boju,
i ClearDepthStencilViev metod clears određeni buffer za dubinu / stencil.
Uvek ćemo očistiti ciljni odbojnik za odbojnik i dupleks / šablon za šablon svaki ranije
crtanje za pokretanje slike sveže. Ove metode se deklarišu na sledeći način:
void
ID3D12GraphicsCommandList::ClearRenderTargetView(
D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetView,
const FLOAT ColorRGBA[ 4 ],
UINT NumRects,
const D3D12_RECT *pRects);
1. RenderTargetViev: RTV na resurs koji želimo da obrišemo.
2. ColorRGBA: Definira boju da bi se oslobodila ciljna linija.
3. NumRects: Broj elemenata u nizu pRects. Ovo može biti 0.
4. pRects: niz D3D12_RECTs koji identifikuju oblasti pravougaonika na rendering cilj da se očisti. Ovo
može biti nullptr koji ukazuje na očistiti celokupnu ciljnu granicu.
void
ID3D12GraphicsCommandList::ClearDepthStencilView(
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView,
D3D12_CLEAR_FLAGS ClearFlags,
FLOAT Depth,
UINT8 Stencil,
UINT NumRects,
const D3D12_RECT *pRects);

1.DepthStencilViev: DSV u buffer za dubinu / šablon za brisanje.


2. ClearFlags: Zastave označavaju koji deo dubine / stencil bafera da se očisti. Ovo
može biti ili D3D12_CLEAR_FLAG_DEPTH, D3D12_CLEAR_FLAG_STENCIL,
ili oboje bitkani zajedno.
3. Dubina: Definira vrednost da se vrednosti dubine očiste na.
4. Matrica: Definira vrednost da bi se vrednosti štampe obrisale.
5. NumRects: Broj elemenata u nizu pRects. Ovo može biti 0.
6. pRects: niz D3D12_RECTs koji identifikuju oblasti pravougaonika na renderu
cilj da se očisti. Ovo može biti nullptr koji ukazuje na očistiti celokupnu ciljnu granicu.
Još jedna nova metoda je
ID3D12GraphicsCommandList :: Metoda OMSetRenderTargets. Ovaj metod
postavlja ciljnu granicu i dubinu / stencil bafer koji želimo koristiti za gasovod. Za sada, mi
žele da koriste trenutni bafer za bafer kao ciljnu crtu i naš osnovni bafer dubine / stencila.
Kasnije u ovoj knjizi ćemo pogledati tehnike koje koriste višestruke ciljeve. Ovaj metod
ima sledeći prototip:
void ID3D12GraphicsCommandList::OMSetRenderTargets(
UINT NumRenderTargetDescriptors,
const D3D12_CPU_DESCRIPTOR_HANDLE
*pRenderTargetDescriptors,
BOOL RTsSingleHandleToDescriptorRange,
const D3D12_CPU_DESCRIPTOR_HANDLE
*pDepthStencilDescriptor);
1. NumRenderTargetDescriptors: Određuje broj RTV-a koje idemo
da veže. Korišćenje višestrukih ciljeva rendera istovremeno se koristi za neke napredne
tehnike. Za sada, uvek koristimo jedan RTV.
2. pRenderTargetDescriptors: pokazivač na niz RTV-ova koji određuju
napravimo ciljeve koje želimo vezati za gasovod.
3. RTsSingleHandleToDescriptorRange: Navesti true ako su svi RTV-ovi u
prethodni niz su susedni u skripcu deskriptora. U suprotnom, navesti pogrešno.
4. pDepthStencilDescriptor: Pokazivač na DSV koji određuje dubinu / šablon
bafer koji želimo da se vezujemo za gasovod.
Na kraju, metod IDKSGISvapChain :: Present zamenjuje nazad i napred
buffers. Kada predstavimo zamenljivi lanac da zamenimo prednji i zadnji odbojnik, imamo
da ažurirate indeks u trenutni bafer za bafer tako da ćemo se vratiti na novu leđa
bafer na sledećem okviru:
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) %
SwapChainBufferCount;
4.6 ODREĐIVANJE DIRECT3D APLIKACIJA
Mnoge Direct3D funkcije vraćaju HRESULT greške kodova. Za naše programe za uzorke, mi
koristite jednostavan sistem za obradu grešaka gde proveravamo vraćeni HRESULT, a ako ne uspije,
bacimo izuzetak koji čuva kod greške, ime funkcije, ime datoteke i liniju
broj uvredljivog poziva. To se radi s sledećim kodom u d3dUtil.h:

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.

Poglavlje 6 Crtanje U DIRECT3D


U prethodnom poglavlju, uglavnom smo se fokusirali na konceptualne i matematičke aspekti renderinga.
Ovo poglavlje, s druge strane, fokusira se na Direct3D API interfejs i metode potrebne za konfiguraciju
linije za rendering, definišu vertek i piksel shaderi i predaju geometriju na rendering plin za crtanje. Do
kraja ovoga poglavlje, moći ćete da nacrtate 3D kutiju sa solidnim bojama ili u režimu žične slike.
Ciljevi:
1. Da biste otkrili metode Direct3D interfejsa za definisanje, čuvanje i crtanje
geometrijski podaci.
2. Naučiti kako pisati osnovne vertek i piksel shadere.
3. Da biste saznali kako da konfigurišete liniju za rendering s objektima stanja gasovoda.
4. Da biste razumeli kako kreirati i povezati konstantne podatke pufera sa gasovodom i da upoznajte se
sa korenom potpisa.
6.1 VERTIKI I ULAZI ULAZA
Pozovite iz §5.5.1 da se verzija u Direct3D može porediti sa dodatnim podacima prostorna lokacija. Da
biste kreirali prilagođeni format vertek-a, mi prvo napravimo strukturu koja drži vertek podaci koje
bismo odabrali. Na primer, sledeće ilustruje dve različite vrste verteka formati; jedan se sastoji od
položaja i boje, a drugi sastoji se od položaja, normalne
vektor i dva seta koordinata 2D teksture.
struct Vertex1
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
struct Vertex2
{
XMFLOAT3 Pos;
XMFLOAT3 Normal;
XMFLOAT2 Tex0;
XMFLOAT2 Tex1;
};
Jednom kada smo definisali strukturu verteksa, moramo da obezbedimo Direct3D sa opis naše vertikalne
strukture tako da ona zna šta da radi sa svakom komponentom. Ovo opis se daje Direct3D u obliku opisa
unosa podataka koji je predstavljena strukturom D3D12_INPUT_LAIOUT_DESC:
typedef struct D3D12_INPUT_LAYOUT_DESC
{
const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
UINT NumElements;
} D3D12_INPUT_LAYOUT_DESC;
typedef struct D3D12_INPUT_LAYOUT_DESC
{
const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
UINT NumElements;
} D3D12_INPUT_LAYOUT_DESC;
Opis unosa teksta je jednostavno niz D3D12_INPUT_ELEMENT_DESC
elemente i broj elemenata u nizu. Svaki element u D3D12_INPUT_ELEMENT_DESC nizu opisuje i
odgovara jednoj komponenti u strukturi verteka. Dakle, ako struktura verteksa ima dve komponente,
onda će odgovarajući D3D12_INPUT_ELEMENT_DESC niz imati
dva elementa. Struktura D3D12_INPUT_ELEMENT_DESC definisana je kao
typedef struct D3D12_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D12_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;
1.SemanticName: string koji se povezuje sa elementom. Ovo može biti svako validno
promenljivo ime. Semantika se koristi za mapiranje elemenata u strukturi verteksa
elementi u potpisu ulaznog shadera; pogledajte sliku 6.1.
Slika 6.1. Svaki element u strukturi verteka opisuje a
odgovarajući element u D3D12_INPUT_ELEMENT_DESC nizu. The
semantičko ime i indeks obezbeđuje način mapiranja elemenata verteka na
odgovarajući parametri vertek shadera.
2. SemanticIndek: Indeks koji se povezuje sa semantikom. Motivacija za ovo jeste
prikazano na slici 6.1, gde, na primer, vertek struktura može imati više od
jedan skup teksturnih koordinata; pa pre nego što uvodimo novo semantičko ime, mi
može samo dodati indeks do kraja da razlikuje dva skupa koordinata teksture. A
semantičnost bez indeksa navedenog u shader kodu podrazumevano da indeksira nulu; za
primer, POSITION je ekvivalentan POSITION0 na slici 6.1.
3. Format: Član popisnog tipa DKSGI_FORMAT koji navodi format
(to jest, tip podataka) ovog elementa verteka za Direct3D; Ovde su neke uobičajene
primjeri korištenih formata:
scalar
DXGI_FORMAT_R32G32_FLOAT // 2D 32-bit float
vector
DXGI_FORMAT_R32G32B32_FLOAT // 3D 32-bit float
vector
DXGI_FORMAT_R32G32B32A32_FLOAT // 4D 32-bit float
vector
DXGI_FORMAT_R8_UINT // 1D 8-bit unsigned
integer scalar
DXGI_FORMAT_R16G16_SINT // 2D 16-bit signed
integer vector
DXGI_FORMAT_R32G32B32_UINT // 3D 32-bit unsigned
integer vector
DXGI_FORMAT_R8G8B8A8_SINT // 4D 8-bit signed
integer vector
DXGI_FORMAT_R8G8B8A8_UINT // 4D 8-bit unsigned
integer vector
4. InputSlot: Određuje ulazni slot od kojeg će ovaj element doći. Direct3D
podržava šesnaest ulaznih slotova (indeksiranih od 0-15) preko kojih možete hraniti vertikale
podaci. Za sada ćemo koristiti samo ulazni slot 0 (tj. Svi elementi vertek-a dolaze iz
isti ulazni ulaz); Vježba 2 vas pita da eksperimentišete sa više ulaznih slotova.
5. AlignedBiteOffset: Ofset, u bajtovima, od početka C ++ vertek-a
struktura specificiranog ulaznog slota na početak komponente verteka. Na primer, u sledećoj strukturi
verteka, element Pos ima 0-bite offset od početka poklapa se sa početkom strukture verteksa; element
Normal ima 12-bajtni offset jer moramo preskočiti po bajtovima Posa da dođemo do početka
Normalnog; element Tek0 ima 24-bajtni ofset jer moramo preskočiti po bajtovima Pos
i Normal za početak Tek0; element Tek1 ima 32-bajtni offset jer moramo preskočiti bajtove Pos, Normal
i Tek0 da bi došli do početka
Tek1.
struct Vertex2
{
XMFLOAT3 Pos; // 0-byte offset
XMFLOAT3 Normal; // 12-byte offset
XMFLOAT2 Tex0; // 24-byte offset
XMFLOAT2 Tex1; // 32-byte offset
};
6. InputSlotClass: Specifikujte D3D12_INPUT_PER_VERTEKS_DATA za sada; the druga opcija se koristi za
naprednu tehniku instanciranja.
7. InstanceDataStepRate: Specifikujte 0 za sada; ostale vrednosti se koriste samo za napredna tehnika
instanciranja. Za prethodna dva primera vertek struktura, Vertek1 i Vertek2, the
odgovarajući opis unosa izgleda bi bio:
D3D12_INPUT_ELEMENT_DESC desc1[] =
{
{“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_PER_VERTEX_DATA, 0},
{“COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D12_INPUT_PER_VERTEX_DATA, 0}
};
D3D12_INPUT_ELEMENT_DESC desc2[] =
{
{“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_PER_VERTEX_DATA, 0},
{“NORMAL”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D12_INPUT_PER_VERTEX_DATA, 0},
{“TEXCOORD”, 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
D3D12_INPUT_PER_VERTEX_DATA, 0}
{“TEXCOORD”, 1, DXGI_FORMAT_R32G32_FLOAT, 0, 32,
D3D12_INPUT_PER_VERTEX_DATA, 0}
};
6.2 VERTEKS BUFFERS
Da bi GPU pristupio nizu vertikala, oni moraju biti smešteni u GPU-u resurs (ID3D12Resource) naziva se
baferom. Mi zovemo pufer koji čuva vertikale a vertek buffer. Bufferi su jednostavniji resursi nego
teksture; oni nisu multidimenzionalni, i nemaju mipmaps, filtere ili multisampling podršku. Mi ćemo
koristiti bafera kad god moramo obezbediti GPU nizom elemenata podataka kao što su vertices.
Kao što smo uradili u §4.3.8, kreiramo ID3D12Resource objekt popunjavanjem a
D3D12_RESOURCE_DESC struktura koja opisuje baferov resurs, a zatim poziva na ID3D12Device ::
CreateCommittedResource metod. Vidi §4.3.8 za a opis svih članova strukture D3D12_RESOURCE_DESC.
Direct3D 12 pruža C ++ omotač klase CD3DKS12_RESOURCE_DESC, koji potiče od
D3D12_RESOURCE_DESC i pruža pogodnosti konstruktore i metode. In Posebno, on pruža sledeći metod
koji pojednostavljuje izgradnju a
static inline CD3DX12_RESOURCE_DESC Buffer(
UINT64 width,
D3D12_RESOURCE_FLAGS flags =
D3D12_RESOURCE_FLAG_NONE,
UINT64 alignment = 0 )
{
return CD3DX12_RESOURCE_DESC(
D3D12_RESOURCE_DIMENSION_BUFFER,
alignment, width, 1, 1, 1,
DXGI_FORMAT_UNKNOWN, 1, 0,
D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags );
}
Za statičku geometriju (tj., Geometrija koja se ne menja po per-frame), stavljamo
vertek baferi u defaultu (D3D12_HEAP_TIPE_DEFAULT) za optimalno
performanse. Generalno, većina geometrije u igri će biti ovakva (npr. Drveće, zgrade,
teren, likovi). Nakon inicijalizacije baze vertek-a, potrebno je samo GPU
pročitajte iz bafera verteka da izvučete geometriju, tako da podrazumevani kupac ima smisla.
Međutim, ako CPU ne može pisati u bafer verteka u podrazumevanom kupu, kako ćemo
inicijalizovati bafer verteka?
Pored stvaranja stvarnog resursa bafera, potrebno je kreirati
sredstvo za puštanje bafera sa sapunom tipa D3D12_HEAP_TIPE_UPLOAD.
Podsjetimo iz §4.3.8 da uložimo resurs u upload heap kad treba da kopiramo
podaci sa CPU-a na GPU memoriju. Kada kreiramo bafer za otpremanje, mi kopiramo našu vertek
podatke iz sistemske memorije u bafer za otpremanje, a zatim kopiramo podatke verteka iz
upload buffer za stvarni bafer verteka.
Zbog toga što je za inicijalizaciju podataka podrazumevanog potreban bafer za učitavanje
buffer (buffer vith heap tipe D3D12_HEAP_TIPE_DEFAULT), mi gradimo sledeće
koristite funkciju u d3dUtil.h / .cpp kako biste izbegli ponavljanje ovog rada svaki put kada nam trebamo
podrazumevano buffer:
Microsoft::WRL::ComPtr<ID3D12Resource>
d3dUtil::CreateDefaultBuffer(
ID3D12Device* device,
ID3D12GraphicsCommandList* cmdList,
const void* initData,
UINT64 byteSize,
Microsoft::WRL::ComPtr<ID3D12Resource>&
uploadBuffer)
{
ComPtr<ID3D12Resource> defaultBuffer;
// Create the actual default buffer resource.
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultBuffer.GetAddressOf())));
// In order to copy CPU memory data into our default
buffer, we need
// to create an intermediate upload heap.
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(uploadBuffer.GetAddressOf())));
// Describe the data we want to copy into the
default buffer.
D3D12_SUBRESOURCE_DATA subResourceData = {};
subResourceData.pData = initData;
subResourceData.RowPitch = byteSize;
subResourceData.SlicePitch =
subResourceData.RowPitch;
// Schedule to copy the data to the default buffer
resource.
// At a high level, the helper function
UpdateSubresources
// will copy the CPU memory into the intermediate
upload heap.
// Then, using
ID3D12CommandList::CopySubresourceRegion,
// the intermediate upload heap data will be copied
to mBuffer.
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COPY_DEST));
UpdateSubresources<1>(cmdList,
defaultBuffer.Get(), uploadBuffer.Get(),
0, 0, 1, &subResourceData);
cmdList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_GENERIC_READ));
// Note: uploadBuffer has to be kept alive after the
above function
// calls because the command list has not been
executed yet that
// performs the actual copy.
// The caller can Release the uploadBuffer after it
knows the copy
// has been executed.
return defaultBuffer;
}
The D3D12_SUBRESOURCE_DATA structure is defined as follows:
typedef struct D3D12_SUBRESOURCE_DATA
{
const void *pData;
LONG_PTR RowPitch;
LONG_PTR SlicePitch;
} D3D12_SUBRESOURCE_DATA;
1.pData: pokazivač na niz sistemske memorije koji sadrži podatke za inicijalizaciju
buffer vith. Ako bafer može čuvati n vertikala, tada sistemski niz mora sadržavati
najmanje n vertices, tako da ceo bafer može biti inicijalizovan.
2. RovPitch: Za bafera, veličina podataka koju kopiramo u bajta.
3. SlicePitch: Za bafera, veličina podataka koju kopiramo u bajtovima.
Sledeći kod pokazuje kako bi se ova klasa koristila za kreiranje podrazumevanog bafera koji je čuvao 8
vertikala kocke, gde je svaki vertek imao drugačiju boju:
Vertex vertices[] =
{
{ XMFLOAT3(-1.0f, -1.0f, -1.0f),
XMFLOAT4(Colors::White) },
{ XMFLOAT3(-1.0f, +1.0f, -1.0f),
XMFLOAT4(Colors::Black) },
{ XMFLOAT3(+1.0f, +1.0f, -1.0f),
XMFLOAT4(Colors::Red) },
{ XMFLOAT3(+1.0f, -1.0f, -1.0f),
XMFLOAT4(Colors::Green) },
{ XMFLOAT3(-1.0f, -1.0f, +1.0f),
XMFLOAT4(Colors::Blue) },
{ XMFLOAT3(-1.0f, +1.0f, +1.0f),
XMFLOAT4(Colors::Yellow) },
{ XMFLOAT3(+1.0f, +1.0f, +1.0f),
XMFLOAT4(Colors::Cyan) },
{ XMFLOAT3(+1.0f, -1.0f, +1.0f),
XMFLOAT4(Colors::Magenta) }
};
const UINT64 vbByteSize = 8 * sizeof(Vertex);
ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
VertexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices, vbByteSize,
VertexBufferUploader);
where the Vertex type and colors are defined as follows:
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
Metoda IASetVertekBuffers može izgledati malo komplikovana zbog toga podržava postavljanje nizova
vertek bafera u različite ulazne slotove. Međutim, koristićemo samo jedan ulazni ulaz. Vježba na kraju
poglavlja daje vam malo iskustva u radu sa dva ulazna slota. Bufer verteksa će ostati vezan za ulazno
mesto dok ga ne promenite. Tako možete strukturirati svoj kod ovako, ako koristite više od jednog
bafera
ID3D12Resource* mVB1; // stores vertices of type
Vertex1
ID3D12Resource* mVB2; // stores vertices of type
Vertex2
D3D12_VERTEX_BUFFER_VIEW_DESC mVBView1; // view to
mVB1
D3D12_VERTEX_BUFFER_VIEW_DESC mVBView2; // view to
mVB2
/*…Create the vertex buffers and views…*/
mCommandList->IASetVertexBuffers(0, 1, &VBView1);
/* …draw objects using vertex buffer 1… */
mCommandList->IASetVertexBuffers(0, 1, &mVBView2);
/* …draw objects using vertex buffer 2… */
Setting a vertex buffer to an input slot does not draw them; it only makes the vertices
ready to be fed into the pipeline. The final step to actually draw the vertices is done with
the ID3D12GraphicsCommandList::DrawInstanced method:
void ID3D12CommandList::DrawInstanced(
UINT VertexCountPerInstance,
UINT InstanceCount,
UINT StartVertexLocation,
UINT StartInstanceLocation);
1.VertekCountPerInstance: Broj vertisa za crtanje (po instanci).
2. InstanceCount: koristi se za naprednu tehniku pod nazivom instancing; za sada, postavite
ovo na 1, jer samo crtamo samo jednu instancu.
3. StartVertekLocation: podrazumeva indeks (baziran na nuli) prvog verteka u
vertek bafer da započne crtanje.
4. StartInstanceLocation: koristi se za naprednu tehniku pod nazivom instancing;
za sada, podesite ovo na 0.
Dva parametra VertekCountPerInstance i StartVertekLocation
definiše susedni podskup tačaka u baferu verteksa za crtanje; pogledajte sliku 6.2.
Slika 6.2. StartVertekLocation specificira indeks (baziran na nuli) od
prva vertek u baferu verteka za pocetak crtanja. VertekCountPerInstance
određuje broj vertisa za crtanje.
Slika 6.2. StartVertekLocation određuje indeks (na osnovu nula) od prvog
vertek u baferu verteksa da započne crtanje. Specifikuje VertekCountPerInstance
broj vertisa za crtanje.
DravInstanced metoda ne određuje koja vrsta primitivnih vertikala
definisati. Da li se trebaju izvući kao tačke, linijske liste ili trougao liste? Podsjetimo iz §5.5.2
primitivna topološka država je postavljena sa
ID3D12GraphicsCommandList :: IASetPrimitiveTopologi metoda. Ovde je
primer poziva:
cmdList-
> IASetPrimitiveTopologi (D3D_PRIMITIVE_TOPOLOGI_TRIANGLELIST);
6.3 INDEKSI I INDEKSNI BUFFERS
Slično vertici, kako bi GPU pristupio nizu indeksa, one moraju biti
postavljen u bafer grafički resurs (ID3D12Resource). Mi zovemo pufer koji čuva
indeksira indeksni bafer. Zato što je naša funkcija d3dUtil :: CreateDefaultBuffer
radi sa generičkim podacima preko praznine *, možemo koristiti tu istu funkciju da kreiramo indeks
buffer (ili bilo koji podrazumevani bafer).
Da bismo vezali indeksni bafer na gasovod, moramo kreirati indeksni bafer
pogledajte izvorni bafer indeksa. Kao i kod prikaza bafera verteka, ne treba nam
descriptor heap za prikaz indeksa bafera. Pregled bafera indeksa predstavlja
Struktura D3D12_INDEKS_BUFFER_VIEV:
typedef struct D3D12_INDEX_BUFFER_VIEW
{
D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
UINT SizeInBytes;
DXGI_FORMAT Format;
} D3D12_INDEX_BUFFER_VIEW;
1.BufferLocation: Virtuelna adresa resursnog bafera koji želimo
kreiraj pogled. Možemo koristiti metod ID3D12Resource :: GetGPUVirtualAddress da biste dobili ovo.
2. SizeInBites: Broj bajtova koji će se prikazati u indeksnom baferu počev od
BufferLocation.
3. Format: Format indikatora koji mora biti ili
DKSGI_FORMAT_R16_UINT za 16-bitne indekse ili DKSGI_FORMAT_R32_UINT za
32-bitni indeksi. Trebali biste koristiti 16-bitne indekse kako biste smanjili memoriju i propusni opseg, i
koristite samo 32-bitne indekse ako imate indeksne vrijednosti kojima je potreban dodatni 32-bitni
opseg.
Kao što je sa baferima verteka i drugim Direct3D resursom u tom smislu, pre nego što to možemo
koristiti, moramo ga povezati sa gasovodom. Indeksni bafer je vezan za fazu asembler ulaza pomoću
ID3D12CommandList :: SetIndekBuffer metoda. Sledeći kod pokazuje kako kreirati indeksni bafer koji
definiše trouglove kocke, stvoriti pogled prema njemu i povezati ga s pipetom:
std::uint16_t indices[] = {
// front face
0, 1, 2,
0, 2, 3,
// back face
4, 6, 5,
4, 7, 6,
// left face
4, 5, 1,
4, 1, 0,
// right face
3, 2, 6,
3, 6, 7,
// top face
1, 5, 6,
1, 6, 2,
// bottom face
4, 0, 3,
4, 3, 7
};
const UINT ibByteSize = 36 * sizeof(std::uint16_t);
ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
IndexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices), ibByteSize,
IndexBufferUploader);
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU-
>GetGPUVirtualAddress();
ibv.Format = DXGI_FORMAT_R16_UINT;
ibv.SizeInBytes = ibByteSize;
mCommandList->IASetIndexBuffer(&ibv);
Finally, when using indices, we must use the
ID3D12GraphicsCommandList::DrawIndexedInstanced method instead of
DrawInstanced:
void ID3D12GraphicsCommandList::DrawIndexedInstanced(
UINT IndexCountPerInstance,
UINT InstanceCount,
UINT StartIndexLocation,
INT BaseVertexLocation,
UINT StartInstanceLocation);
1.IndekCountPerInstance: Broj indeksa koji se crta (po instanci).
2. InstanceCount: koristi se za naprednu tehniku pod nazivom instancing; za sada, postavite
ovo na 1, jer samo crtamo samo jednu instancu.
3. StartIndekLocation: Indeks na element u indeksnom baferu koji označava
polazna tačka za početak čitanja indeksa.
4. BaseVertekLocation: Celobrojna vrednost koja se dodava indeksima korišćenim u ovom
nacrtati poziv pre nego što se vrate vertikale.
5. StartInstanceLocation: koristi se za naprednu tehniku pod nazivom instancing;
za sada, podesite ovo na 0.
Da bismo ilustrovali ove parametre, razmotrite sledeću situaciju. Pretpostavimo da imamo tri
predmeti: sfera, kutija i cilindar. U početku, svaki objekat ima sopstveni bafer i njegov
vlastiti indeksni bafer. Indeksi u svakom lokalnom indeksu bafera su relativni sa odgovarajućim
lokalni bafer verteka. Sada pretpostavimo da smo ukrštali vertikale i indekse
sfere, kutije i cilindra u jedan globalni vertek i indeksni bafer, kao što je prikazano na slici 6.3.
(Može se povezati vertek i indeksni baferi, jer postoji više API troškova
prilikom promene verteksa i indeksnih bafera. Najverovatnije ovo neće biti usko grlo, ali
ako imate mnogo malih verteka i indeksnih bafera koji se lako mogu spojiti, to može biti
vrijedi to iz razloga performansi.) Nakon ove koncenacije, indeksi su ne
više su tačni, pošto čuvaju indeksne lokacije u odnosu na njihovu odgovarajuću lokalnu vertek
baferi, a ne globalni; tako da se indeksi moraju ponovo preporučiti da se ispravno indeksiraju
globalni bafer verteka. Indeksi originalne kutije su izračunati sa pretpostavkom da
vertikale kutije su prolazile kroz indekse
Slika 6.3. Konceniranje nekoliko bafera verteka u jedan veliki bafer verteka, i
prikupljanje nekoliko indeksnih bafera u jedan veliki indeksni bafer.
0, 1, ..., numBokVertices-1
Ali nakon spajanja, one vode
firstBokVertekPos,
firstBokVertekPos + 1,
...,
firstBokVertekPos + numBokVertices-1
Zbog toga, za ažuriranje indeksa, moramo dodati firstBokVertekPos svakom
bok indek. Isto tako, treba dodati firstCilVertekPos u svaki indeks cilindra.
Imajte na umu da indekse sfere ne treba menjati (od prvog sferskog polja)
pozicija je nula). Pozovimo poziciju prvog korita objekta u odnosu na globalnu
vertek buffer njegova osnovna vertek lokacija. Generalno, novi indeksi objekta su
izračunato dodavanjem svoje bazne vertek lokacije prema svakom indeksu. Umesto da računate
nove indekse sami, možemo da dozvolimo Direct3D da to uradi prosleđivanjem bazne vertek lokacije
četvrti parametar DravIndekedInstanced.
Zatim možemo sipati sferu, kutiju i cilindar jedan po jedan sa sledeća tri
poziva:
mCmdList->DrawIndexedInstanced(
numSphereIndices, 1, 0, 0, 0);
mCmdList->DrawIndexedInstanced(
numBoxIndices, 1, firstBoxIndex, firstBoxVertexPos,
0);
mCmdList->DrawIndexedInstanced(
numCylIndices, 1, firstCylIndex, firstCylVertexPos,
0);
Demo projekat "Shapes" u sledećem poglavlju koristi ovu tehniku.
6.4 PRIMER VERTEKS SHADER
Ispod implementacije jednostavnog vertek shadera (recall §5.6):
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
};
void VS(float3 iPosL : POSITION,
float4 iColor : COLOR,
out float4 oPosH : SV_POSITION,
out float4 oColor : COLOR)
{
// Transform to homogeneous clip space.
oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
oColor = iColor;
}

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):

cbuffer cbPerObject : register(b0)


{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.PosL, 1.0f),
gWorldViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
6.4.1 Opis ulaznog rasporeda i povezivanje ulaznog potpisa
Napomena sa slike 6.4 pokazuje da postoji veza između atributa vertikala
dovodi se u cevovod, što je definisano opisom unosa teksta. Ako se hranite
vertices koji ne isporučuju sve ulazne elemente očekuje vertek shader, rezultiraće greška. Za
primer, sledeći signat za unos vertek shadera i podaci verteka su nekompatibilni:
//––––—
// C++ app code
//––––—
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
D3D12_INPUT_ELEMENT_DESC desc[] =
{
{“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_PER_VERTEX_DATA, 0},
{“COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D12_INPUT_PER_VERTEX_DATA, 0}
};
//––––—
// Vertex shader
//––––—
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
float3 Normal : NORMAL;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin) { … }
Kao što ćemo videti u §6.9, kada kreiramo objekat ID3D12PipelineState, mi
mora definisati i opis unosa teksta i šetač vertek-a. Direct3D će zatim potvrditi da su opis unosa izgleda i
vertek shader kompatibilni. Podaci o verteku i ulazni potpis ne moraju tačno da se podudaraju. Ono što
je potrebno jeste za podatke vertek-a da obezbede sve podatke o kojima vertek shader očekuje. Stoga
jeste dozvoljeno je da podaci o vertikali daju dodatne podatke koje ne koriste vertek shader. To je
sledeće:
//––––—
// C++ app code
//––––—
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
XMFLOAT3 Normal;
};
D3D12_INPUT_ELEMENT_DESC desc[] =
{
{“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_PER_VERTEX_DATA, 0},
{“COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D12_INPUT_PER_VERTEX_DATA, 0},
{ “NORMAL”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 28,
D3D12_INPUT_PER_VERTEX_DATA, 0 }
};
//––––—
// Vertex shader
//––––—
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin) { … }
Now consider the case where the vertex structure and input signature have matching
vertex elements, but the types are different for the color attribute:
//––––—
// C++ app code
//––––—
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
D3D12_INPUT_ELEMENT_DESC desc[] =
{
{“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_PER_VERTEX_DATA, 0},
{“COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D12_INPUT_PER_VERTEX_DATA, 0}
};
//––––—
// Vertex shader
//––––—
struct VertexIn
{
float3 PosL : POSITION;
int4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin) { … }
This is actually legal because Direct3D allows the bits in the input registers to be
reinterpreted. However, the VC++ debug output window gives the following warning:
D3D12 WARNING: ID3D11Device::CreateInputLayout: The
provided input signature expects to read an element with
SemanticName/Index: ‘COLOR’/0 and component(s) of the
type ‘int32’. However, the matching entry in the Input
Layout declaration, element[1], specifies mismatched
format: ‘R32G32B32A32_FLOAT’. This is not an error, since
behavior is well defined: The element format determines
what data conversion algorithm gets applied before it
shows up in a shader register. Independently, the shader
input signature defines how the shader will interpret the
data that has been placed in its input registers, with no
change in the bits stored. It is valid for the
application to reinterpret data as a different type once
it is in the vertex shader, so this warning is issued
just in case reinterpretation was not intended by the author.
6.5 PRIMER PIKSEL SHADER
Kao što je rečeno u §5.10.3, tokom verifikacije rasterskih atributa pripisuje izlaz iz verteka
Shader (ili geometrijski shader) se interpoliraju preko piksela trougla. The
Interpolirane vrednosti se onda unose u piksel shader kao ulaz (§5.11). Pod pretpostavkom da postoji
nema geometrijskog shadera, Slika 6.5 ilustruje podatke o vertek staze do sada.
Slika 6.5. 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. Isto tako, svaki izlaz iz vertek shadera ima
priloženi semantički, i svaki ulazni parametar piksel shadera ima priloženu semantiku.
Ove semantike se koriste za mapiranje izlaza vertek shadera u ulaz piksel shadera
parametri.
Piksel shader je kao vertek shader u tome što je funkcija izvršena za svaki piksel
fragment. S obzirom na unos pikselnog shadera, zadatak pikselnog shadera je izračunavanje boje
vrednost za fragment piksela. Napominjemo da fragment piksela možda neće preživjeti i uspeti
na bafer za leđa; na primer, može biti ispisano u pikel shaderu (HLSL
uključuje klip funkciju koja može odbaciti fragment piksela od dalje obrade),
okružen drugim fragmentom piksela sa manjom vrijednošću dubine ili fragmentom piksela
biti odbačen kasnijim testom na gasovodu kao što je ispitivanje pufera. Dakle, piksel na
Back buffer može imati nekoliko pikselskih fragmenta; ovo je razlika između
šta se podrazumeva "pikselski fragment" i "piksel", mada se ponekad koriste termini
međusobno, ali kontekst obično jasno pokazuje šta se podrazumeva.
Kao optimizacija hardvera, moguće je da se fragment piksela odbije
cevovoda pre nego što je napravite u piksel shaderu (npr. rano-z odbacivanje). Ovo je
gde se prvi put vrši ispitivanje dubine i ako je određen fragment piksela
okružen testom dubine, onda je piksel shader preskočen. Međutim, postoje
neki slučajevi koji mogu onemogućiti optimizaciju odbacivanja rano-z. Na primer, ako
pikel shader modifikuje dubinu piksela, tada mora biti piksel shader
izvršeni zato što ne znamo šta je dubina piksela pre
pikel shader ako je shader piksela menja.
Ispod je jednostavan piksel shader koji odgovara vertikalnom shaderu datom u §6.4.
Za kompletnost, vertek shader se ponovo prikazuje.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
};
void VS(float3 iPos : POSITION, float4 iColor :
COLOR,
out float4 oPosH : SV_POSITION,
out float4 oColor : COLOR)
{
// Transform to homogeneous clip space.
oPosH = mul(float4(iPos, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
oColor = iColor;
}
float4 PS(float4 posH : SV_POSITION, float4 color :
COLOR) : SV_Target
{
return pin.Color;
}
U ovom primeru, piksel shader jednostavno vraća interpoliranu vrednost boje. Objava da se ulaz piksela
shadera tačno poklapa sa izlazom vertek shadera; ovo je uslov. Piksel shader vraća vrijednost 4D boje, a
SV_TARGET semantik slijedi Lista funkcija parametara ukazuje na to da se tip povratne vrijednosti
podudara sa ciljnom oznakom formatu. Mi možemo ekvivalentno prepisati gornje vertikale i piksel
shadere koristeći ulaz / izlaz strukture. Pojava se razlikuje po tome što se članovima grupe prikrade
semantika ulazne / izlazne strukture i da koristimo povratnu izjavu za izlaz umesto izlaza
parametri.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 Pos : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.Pos, 1.0f),
gWorldViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
6.6 Konstantni BUFFERi
6.6.1 Stvaranje konstantnih pufera
Konstantni bafer je primer grafičkog resursa (ID3D12Resource) čiji podaci
sadržaj se može referisati u programima shadera. Kao što ćemo naučiti tokom čitave ove knjige, teksture
i druge vrste baferskih resursa takođe se mogu referencirati u shader programe. Primjer vertek shader u
§6.4 imao je kod:
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
};
Ovaj kod se odnosi na cbuffer objekat (konstantni bafer) koji se zove cbPerObject. U ovo
primer, konstantni bafer čuva jednu matricu 4 × 4 koja se zove gVorldVievProj,
koji predstavljaju kombinovani svet, pogled i matrice projekcija koji se koriste za transformaciju tačke
od lokalnog prostora do homogenog prostora za snimanje. U HLSL-u, matrica 4 × 4 deklariše
ugrađeni float4k4 tip; da deklarišemo matricu 3 × 4 i matricu 2 × 4, na primer, vi
bi koristio float3k4 i float2k2 tipove, respektivno.
Za razliku od verteka i indeksnih bafera, konstantni baferi se obično ažuriraju jednom po kadru
od strane CPU-a. Na primer, ako se fotoaparat pomera u svakom kadru, konstantni bafer bi mogao
treba da se ažuriraju sa novom matricom prikaza svakog kadra. Stoga stvaramo stalno
bafera u kupovini za otpremu, a ne podrazumevano, tako da možemo ažurirati sadržaj
iz CPU-a.
Konstantni baferi takođe imaju poseban hardverski zahtev da njihova veličina mora biti a
više od minimalne veličine alokacije hardvera (256 bajtova).
Često nam je potrebno više konstantnih bafera istog tipa. Na primjer,
iznad konstantnog pufera cbPerObject čuva konstante koje se razlikuju po objektu, pa ako imamo
n objekata, onda će nam biti potrebni n konstantni baferi ovog tipa. Sledeći kod pokazuje
kako stvaramo bafer koji čuva NumElements puno konstantnih bafera:
struct ObjectConstants
{
DirectX::XMFLOAT4X4 WorldViewProj =
MathHelper::Identity4x4();
};
UINT elementByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
ComPtr<ID3D12Resource> mUploadCBuffer;
device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize *
NumElements),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&mUploadCBuffer));
Možemo zamisliti mUploadCBuffer kao spremište niz konstantnih bafera tipa ObjectConstants (sa
paddingom da bi napravio više od 256 bajtova). Kada dođe vreme da nacrtamo objekat, jednostavno se
vezuje konstantni prikaz bafera (CBV) u podregion bafer koji čuva konstante za taj objekat. Imajte na
umu da ćemo često nazvati puffer mUploadCBuffer konstantni bafer jer čuva niz konstantnih bafera.
Funkcija korisnosti d3dUtil :: CalcConstantBufferBiteSize radi na aritmetika da okruži veličinu bajtova
bafera da bude višestruko od minimalnog hardvera Veličina alokacije (256 bajtova):
UINT d3dUtil::CalcConstantBufferByteSize(UINT
byteSize)
{
// Constant buffers must be a multiple of the
minimum hardware
// allocation size (usually 256 bytes). So round up
to nearest
// multiple of 256. We do this by adding 255 and
then masking off
// the lower 2 bytes which store all bits < 256.
// Example: Suppose byteSize = 300.
// (300 + 255) & ˜255
// 555 & ˜255
// 0x022B & ˜0x00ff
// 0x022B & 0xff00
// 0x0200
// 512
return (byteSize + 255) & ˜255;
}
Iako mi dodeljujemo konstantne podatke u višestrukim 256, to nije neophodno je eksplicitno podesiti
odgovarajuće konstantne podatke u HLSL struktura jer se implicitno radi:
// Implicitly padded to 256 bytes.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
};
// Explicitly padded to 256 bytes.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
float4x4 Pad0;
float4x4 Pad1;
float4x4 Pad1;
};
Da biste izbegli postupanje sa zaokruživanjem konstantnih pufernih elemenata na više od 256 bites,
možete eksplicitno podesiti sve vaše konstantne strukture pufera da budu uvek a više od 256 bajtova.
Direct3D 12 predstavio shader model 5.1. Shader model 5.1 je predstavio alternativna HLSL sintaksa za
definisanje konstantnog bafera koji izgleda ovako:
struct ObjectConstants
{
float4x4 gWorldViewProj;
uint matIndex;
};
ConstantBuffer<ObjectConstants> gObjConstants :
register(b0);
Ovde su elementi podataka konstantnog pufera upravo definisani u posebnoj strukturi,
a zatim se kreira konstantni bafer iz te strukture. Polja konstantnog pufera su
zatim se pristupa u shaderu koristeći sintaksu člana podataka:
uint indek = gObjConstants.matIndek;
6.6.2 Ažuriranje konstantnih pufera
Budući da je konstantni bafer kreiran s tipom kupaca
D3D12_HEAP_TIPE_UPLOAD, možemo preneti podatke sa CPU u konstantni bafer
resurs. Da bi to uradili, prvo moramo dobiti pokazivač na podatke o resursima, što se može uraditi
sa metodom Mape:
ComPtr <ID3D12Resource> mUploadBuffer;
BITE * mMappedData = nullptr;
mUploadBuffer-> Karta (0, nullptr,
reinterpret_cast <void **> (& mMappedData));
Prvi parametar je indeks subresursa koji identifikuje potresno središte za mapiranje. Za
bafer, jedino podresno sredstvo je sam bafer, tako da smo to postavili na 0. Drugi
parametar je opcioni pokazivač na D3D12_RANGE strukturu koja opisuje opseg od
memorija za mapiranje; navodeći nultne mape celokupnog resursa. Drugi parametar vraća a
pokazivač na mapirane podatke. Da kopiramo podatke iz sistemske memorije u konstantni bafer, mi
može samo učiniti memcpi:
memcpi (mMappedData, & data, dataSizeInBites);
Kad završimo sa konstantnim baferom, trebali bi ga Unmap pre nego što oslobodimo
memorija:
ako (mUploadBuffer! = nullptr)
mUploadBuffer-> Unmap (0, nullptr);
mMappedData = nullptr;
Prvi parametar Unmap-a je indeks subresursa koji identificira pod-izvore
mapu, koja će biti 0 za pufer. Drugi parametar za Unmap je opcioni pokazivač
u strukturu D3D12_RANGE koja opisuje opseg memorije za unmap; specifiing
null unmaps cijeli resurs.
6.6.3
Pogodno je napraviti lagan omotač oko bafera za otpremanje. Mi definišemo sledeću klasu u
UploadBuffer.h olakšati rad sa baferima za dodavanje. To se rukuje izgradnja i uništavanje bafera za
otpremanje za nas, rukovodi mapiranjem I unmapping resursa, i daje metodu CopiData da ažurira
određeni element u puferu. Mi koristimo metodu CopiData kada treba da promenimo sadržaj bafera za
otpremanje iz CPU-a (npr. kada se promeni matrica prikaza). Napomenuti da ova klasa se može koristiti
za bilo koji bafer za učitavanje, ne obavezno konstantni bafer. Ako koristimo to je za konstantni bafer,
međutim, potrebno je to pokazati preko isConstantBuffer-a parametar konstruktora. Ako čuva
konstantni bafer, onda će automatski podesiti memoriju da bi svaki konstantni bafer bio više od 256
bajtova.
template<typename T>
class UploadBuffer
{p
ublic:
UploadBuffer(ID3D12Device* device, UINT
elementCount, bool isConstantBuffer) :
mIsConstantBuffer(isConstantBuffer)
{
mElementByteSize = sizeof(T);
// Constant buffer elements need to be multiples
of 256 bytes.
// This is because the hardware can only view
constant data
// at m*256 byte offsets and of n*256 byte
lengths.
// typedef struct D3D12_CONSTANT_BUFFER_VIEW_DESC
{
// UINT64 OffsetInBytes; // multiple of 256
// UINT SizeInBytes; // multiple of 256
// } D3D12_CONSTANT_BUFFER_VIEW_DESC;
if(isConstantBuffer)
mElementByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(T));
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize*elementCount),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&mUploadBuffer)));
ThrowIfFailed(mUploadBuffer->Map(0, nullptr,
reinterpret_cast<void**>(&mMappedData)));
// We do not need to unmap until we are done with
the resource.
// However, we must not write to the resource
while it is in use by
// the GPU (so we must use synchronization
techniques).
}
UploadBuffer(const UploadBuffer& rhs) = delete;
UploadBuffer& operator=(const UploadBuffer& rhs) =
delete;
˜UploadBuffer()
{
if(mUploadBuffer != nullptr)
mUploadBuffer->Unmap(0, nullptr);
mMappedData = nullptr;
}
ID3D12Resource* Resource()const
{
return mUploadBuffer.Get();
}
void CopyData(int elementIndex, const T& data)
{
memcpy(&mMappedData[elementIndex*mElementByteSize],
&data, sizeof(T));
}
private:
Microsoft::WRL::ComPtr<ID3D12Resource>
mUploadBuffer;
BYTE* mMappedData = nullptr;
UINT mElementByteSize = 0;
bool mIsConstantBuffer = false;
};
Tipično, svetska matrica objekta će se menjati kada se pomera / rotira / skali, a
pregledati matrične promene kada se kamera pomera / rotira, a matrica projekcije mijenja
kada je prozor promenjen. U našoj demo za ovo poglavlje, dozvoljavamo korisniku da rotira i
pomerite kameru mišem i ažuriramo kombinovanu projekciju svetskog prikaza
matrica sa novom matricom prikaza svakog kadra u funkciji Update:
void BoxApp::OnMouseMove(WPARAM btnState, int x, int
y)
{
if((btnState & MK_LBUTTON) != 0)
{
// Make each pixel correspond to a quarter of a
degree.
float dx =
XMConvertToRadians(0.25f*static_cast<float> (x -
mLastMousePos.x));
float dy =
XMConvertToRadians(0.25f*static_cast<float> (y -
mLastMousePos.y));
// Update angles based on input to orbit camera
around box.
mTheta += dx;
mPhi += dy;
// Restrict the angle mPhi.
mPhi = MathHelper::Clamp(mPhi, 0.1f,
MathHelper::Pi - 0.1f);
}
else if((btnState & MK_RBUTTON) != 0)
{
// Make each pixel correspond to 0.005 unit in the
scene.
float dx = 0.005f*static_cast<float>(x -
mLastMousePos.x);
float dy = 0.005f*static_cast<float>(y -
mLastMousePos.y);
// Update the camera radius based on input.
mRadius += dx - dy;
// Restrict the radius.
mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
}
mLastMousePos.x = x;
mLastMousePos.y = y;
}
void BoxApp::Update(const GameTimer& gt)
{
// Convert Spherical to Cartesian coordinates.
float x = mRadius*sinf(mPhi)*cosf(mTheta);
float z = mRadius*sinf(mPhi)*sinf(mTheta);
float y = mRadius*cosf(mPhi);
// Build the view matrix.
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, view);
XMMATRIX world = XMLoadFloat4x4(&mWorld);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX worldViewProj = world*view*proj;
// Update the constant buffer with the latest
worldViewProj matrix.
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(mObjectCB->CopyData(0,
objConstants);
}
6.6.4 Deskriptori sa konstantnim buferima
Podsjetimo iz §4.1.6 da vezujemo izvor na rendering pipeline kroz a
deskriptor objekta. Do sada smo koristili deskriptore / stavove za prikazivanje meta, dubine / šablona
bafera i bafera indeksa i indeksa. Takođe su nam potrebni deskriptori za vezivanje konstantnih bafera
cevovod. Konstantni deskriptori bafera žive u tipu tipkinja
D3D12_DESCRIPTOR_HEAP_TIPE_CBV_SRV_UAV. Ovakva kupina može čuvati smešu
konstantnog bafera, resursa shadera i neuređenih deskriptora pristupa. Za skladištenje ovih novih
tipovi deskriptora morat ćemo napraviti novi tip skripte ovakvog tipa:
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = 1;
cbvHeapDesc.Type =
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags =
D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;
ComPtr<ID3D12DescriptorHeap> mCbvHeap
md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
IID_PPV_ARGS(&mCbvHeap));
Ovaj kod je sličan onom kako smo kreirali ciljni cilj i dubinu / stencil-pufer descriptor heaps. Međutim,
jedna važna razlika je u tome što mi odredimo D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE za
označavanje da su ove deskriptori će se pristupiti programima shadera. U demo za njegovo poglavlje, mi
nemamo SRV ili UAV deskriptori, a mi ćemo samo izvući jedan objekat; stoga, mi samo
potreban je 1 deskriptor u ovoj kupi za čuvanje 1 CBV. Konstantni prikaz pufera kreira se popunjavanjem
a Primer D3D12_CONSTANT_BUFFER_VIEV_DESC i pozivanje ID3D12Device :: CreateConstantBufferViev:
// Constant data per-object.
struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj =
MathHelper::Identity4x4();
};
// Constant buffer to store the constants of n object.
std::unique_ptr<UploadBuffer<ObjectConstants>>
mObjectCB = nullptr;
mObjectCB =
std::make_unique<UploadBuffer<ObjectConstants>>(
md3dDevice.Get(), n, true);
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
// Address to start of the buffer (0th constant
buffer).
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB-
>Resource()->GetGPUVirtualAddress();
// Offset to the ith object constant buffer in the
buffer.
int boxCBufIndex = i;
cbAddress += boxCBufIndex*objCBByteSize;
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
md3dDevice->CreateConstantBufferView(
&cbvDesc,
mCbvHeap->GetCPUDescriptorHandleForHeapStart());
Struktura D3D12_CONSTANT_BUFFER_VIEV_DESC opisuje podskup od
konstantni puferski resurs koji se vezuje za HLSL konstantnu pufersku strukturu. Kao što je pomenuto,
obično konstantni bafer čuva niz konstanta po objektu za n objekte, ali možemo
dobijate pogled na konstantne podatke ith objekta koristeći BufferLocation i
SizeInBites. D3D12_CONSTANT_BUFFER_VIEV_DESC :: SizeInBites
i D3D12_CONSTANT_BUFFER_VIEV_DESC :: Članovi OffsetInBites moraju
sa više od 256 bajtova zbog hardverskih zahteva. Na primer, ako ste odredili
64, onda biste dobili sledeće greške otklanjanja grešaka:
D3D12 ERROR: ID3D12Device::CreateConstantBufferView:
SizeInBytes of 64 is invalid. Device requires SizeInBytes
be a multiple of 256.
D3D12 ERROR: ID3D12Device:: CreateConstantBufferView:
OffsetInBytes of 64 is invalid. Device requires
OffsetInBytes be a multiple of 256.
6.6.5 Root potpis i deskriptori tabele
Uopšteno govoreći, različiti programi shadera će očekivati da se različiti resursi vezuju za
izvršava se izvođenje plinovoda pre poziva. Resursi su posebno vezani
registrujte slotove, gde se njima mogu pristupiti programi shader-a. Na primer, prethodni
vertek i shader piksela očekivali su samo konstantni bafer da bi se registrovao b0. Još
napredni skup vertek-a i piksel-shadera koji kasnije koristimo u ovoj knjizi očekujemo nekoliko
konstantni baferi, teksture i sempleri koji se vezuju za različite registarske slotove:
// Texture resource bound to texture register slot 0.
Texture2D gDiffuseMap : register(t0);
// Sampler resources bound to sampler register slots
0-5.
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
// cbuffer resource bound to cbuffer register slots 0-2
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
};
// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gProj;
[…] // Other fields omitted for brevity.
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
Root potpis definiše koje resurse aplikacija će se vezati za rendering cevovod prije poziva za izvlačenje
može se izvršiti i gdje se ti resursi mapiraju registratori ulaza shadera. Root potpis mora biti kompatibilan
sa shaderima koji će biti Koristi se sa (tj. root potpis mora obezbediti sve resurse za koje shaderi očekuju
vezani za rendgensku cevovode pre nego što se izvrši poziv za izvlačenje); ovo će biti potvrđeno
kada se kreira objekat stanja gasovoda (§6.9). Različiti pozivi poziva mogu da koriste drugačije
skup programa shadera, koji će zahtevati drugačiji root potpis. Ako pomislimo na programe sidere kao
funkciju i resurse za unos shaderi očekuju kao parametre funkcije, onda se moze smatrati korenskim
potpisom kao definisanje potpisa funkcije (dakle ime root potpis). Vezivanjem različiti resursi kao
argumenti, izlaz shadera će biti drugačiji. Pa, za primer, vertek shader zavisi od stvarne vertek-a koji se
unosi u shader, kao i vezane resurse. Root potpis je predstavljen u Direct3D od strane
ID3D12RootSignature pristup. Definisan je nizom parametara root-a koji opisuju resurse
Shaderi očekuju poziv za poziv. Korijenski parametar može biti konstanta korena, deskriptor korena ili
descriptor table. U sledećem poglavlju ćemo diskutovati o konstantama korena i korijenskim
deskriptorima; in ovo poglavlje, mi ćemo koristiti samo deskriptivne tabele. Tabela deskriptora određuje
susednu raspon deskriptora u skripcu deskriptora. Sledeći kod ispod donosi korijenski potpis koji ima
jedan root parametar koji je deskriptorska tabela dovoljno velika da čuva jedan CBV (konstantni prikaz
bafera):
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
// Create a single descriptor table of CBVs.
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(
D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
1, // Number of descriptors in table
0);// base shader register arguments are bound to
for this root parameter
slotRootParameter[0].InitAsDescriptorTable(
1, // Number of ranges
&cbvTable); // Pointer to array of ranges
// 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);
// 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());
ThrowIfFailed(md3dDevice->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(&mRootSignature)));
Root potpis samo definiše koje resurse aplikacija će se vezati za liniju za rendering; ona ustvari ne vrši
vezu sa resursima. Kada je korijenski potpis postavljen sa komandnom listom, koristimo
ID3D12GraphicsCommandList :: SetGraphicsRootDescriptorTable to vezati tablicu deskriptora na
gasovod:
void
ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable(
UINT RootParameterIndex,
D3D12_GPU_DESCRIPTOR_HANDLE BaseDescriptor);
1.RootParameterIndek: Indeks root parametra koji postavljamo.
2. BaseDescriptor: rukovanje deskriptorom u grupi koja određuje prvu
deskriptor u tabeli koja je postavljena. Na primer, ako je korenski potpis to odredio
tabela je imala pet deskriptora, zatim BaseDescriptor i sledeća četiri deskriptora
kupi se postavljaju na ovaj korijenski sto. Sledeći kod postavlja korijenski potpis i CBV heap na komandnu
listu i postavlja tabela deskriptora koja identifikuje resurs koji želimo vezati za gasovod:
mCommandList-
>SetGraphicsRootSignature(mRootSignature.Get());
ID3D12DescriptorHeap* descriptorHeaps[] = {
mCbvHeap.Get() };
mCommandList-
>SetDescriptorHeaps(_countof(descriptorHeaps),
descriptorHeaps);
// Offset the CBV we want to use for this draw call.
CD3DX12_GPU_DESCRIPTOR_HANDLE cbv(mCbvHeap -
>GetGPUDescriptorHandleForHeapStart());
cbv.Offset(cbvIndex, mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(0, cbv);
6.7 kompajliranje sejdera
U Direct3D-u, shader programi moraju prvo biti kompajlirani na prenosivu bajtodu. The grafički drajver
će potom uzeti ovaj bajtod i ponovo ga kompajlirati u optimalni izvor uputstva za GPU sistema [ATI1].
Prilikom izvršavanja, možemo sakupiti shader sa sledećom funkcijom:
HRESULT D3DCompileFromFile(
LPCWSTR pFileName,
const D3D_SHADER_MACRO *pDefines,
ID3DInclude *pInclude,
LPCSTR pEntrypoint,
LPCSTR pTarget,
UINT Flags1,
UINT Flags2,
ID3DBlob **ppCode,
ID3DBlob **ppErrorMsgs);
1.pFileName: Ime datoteke .hlsl koja sadrži HLSL izvorni kod mi želite da kompajlirate.
2. pDefines: Napredna opcija koju ne koristimo; pogledajte SDK dokumentaciju. Mi uvek navesti null u
ovoj knjizi.
3. pInclude: Napredna opcija koju ne koristimo; pogledajte SDK dokumentaciju. Mi uvek navesti null u
ovoj knjizi.
4. pEntripoint: Naziv funkcije tačke ulaza shadera. A. Hlsl može sadržavati programi višestrukih shadera
(npr., jedan vertek shader i jedan piksel shader), tako da nam je potrebno da odredimo ulaznu tačku
određenog shadera koju želimo sakrivati.
5. pTarget: string koji određuje tip programa i verziju shadera koji koristimo. In
ovu knjigu, mi ciljamo verzije 5.0 i 5.1.
a) vs_5_0 i vs_5_1: Vertek shader 5.0 i 5.1, respektivno.
b) hs_5_0 i hs_5_1: Hull shader 5.0 i 5.1, respektivno.
c) ds_5_0 i ds_5_1: Shader domena 5.0 i 5.1, respektivno.
d) gs_5_0 i gs_5_1: Geometrijski shader 5.0 i 5.1, respektivno.
e) ps_5_0 i ps_5_1: piksel shader 5.0 i 5.1, respektivno.
f) cs_5_0 i cs_5_1: izračunajte shader 5.0 i 5.1, respektivno.
6. Zastave1: Zastave za određivanje načina na koji se šalje sidarski kod. Postoji prilično a
nekoliko od ovih zastava navedenih u SDK dokumentaciji, ali samo dve koje koristimo u ovom
knjiga su:
a) D3DCOMPILE_DEBUG: Kompajlira shadere u režimu debagovanja.
b) D3DCOMPILE_SKIP_OPTIMIZATION: Upućuje kompajler da preskoči optimizacije (koristan za
debagovanje).
7. Zastave2: Napredne mogućnosti kompajliranja efekata koje ne koristimo; pogledajte SDK
dokumentacija.
8. ppCode: Vraća pokazivač na ID3DBlob strukturu podataka koja čuva sastavljene shader object
bitecode.
9. ppErrorMsgs: Vraća pokazivač na ID3DBlob strukturu podataka koja čuva string
koji sadrže greške u kompilaciji, ako ih ima. Tip ID3DBlob je samo generički deo memorije koji ima dva
načina:
1. LPVOID GetBufferPointer: Vraća prazninu * u podatke, tako da mora biti izbačen do odgovarajućeg
tipa pre upotrebe (pogledajte primer ispod).
2. SIZE_T GetBufferSize: Vraća veličinu bajtova bafera. Da podržimo izlazne greške, implementiramo
sljedeću pomoćnu funkciju za kompajliranje
shaderi u radnom vremenu u d3dUtil.h / .cpp:
ComPtr<ID3DBlob> d3dUtil::CompileShader(
const std::wstring& filename,
const D3D_SHADER_MACRO* defines,
const std::string& entrypoint,
const std::string& target)
{
// Use debug flags in debug mode.
UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
compileFlags = D3DCOMPILE_DEBUG |
D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
HRESULT hr = S_OK;
ComPtr<ID3DBlob> byteCode = nullptr;
ComPtr<ID3DBlob> errors;
hr = D3DCompileFromFile(filename.c_str(), defines,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
entrypoint.c_str(), target.c_str(), compileFlags,
0, &byteCode, &errors);
// Output errors to debug window.
if(errors != nullptr)
OutputDebugStringA((char*)errors-
>GetBufferPointer());
ThrowIfFailed(hr);
return byteCode;
}H
ere is an example of calling this function:
ComPtr<ID3DBlob> mvsByteCode = nullptr;
ComPtr<ID3DBlob> mpsByteCode = nullptr;
mvsByteCode =
d3dUtil::CompileShader(L”Shaders\color.hlsl”,
nullptr, “VS”, “vs_5_0”);
mpsByteCode =
d3dUtil::CompileShader(L”Shaders\color.hlsl”,
nullptr, “PS”, “ps_5_0”);
HLSL greške i upozorenja će se vratiti kroz parametar ppErrorMsgs.
Na primer, ako smo pogrešno napisali mul funkciju, onda ćemo dobiti sledeću grešku
prozor debagiranja:
Shaders \ color.hlsl (29,14-55): greška Ks3004: neobjavljeno
identifikator 'mu'
Sastavljanje shadera ne povezuje ga sa linijom za rendering radi upotrebe. Videćemo kako
da to uradi u § 6.9.
6.7.1 Offline kompilacija
Umjesto kompajliranja shadera u vrijeme izvršavanja, možemo ih kompilirati offline
korak (npr. stepen izgradnje, ili kao deo procesa gasovodnog sadržaja). Ima ih nekoliko
razloge za to:
1. Za komplikovane shadere, kompilacija može potrajati dugo. Zbog toga, kompajliranje
offline će učiniti Vaše vrijeme učitavanja brže.
2. Pogodno je videti greške u kompilaciji shadera ranije u procesu izgradnje
nego u radnom vremenu.
3. Aplikacije za Vindovs 8 Store moraju da koriste offline kompilaciju.
Uobičajena je praksa za korištenje .cso (složenog shader objekta) ekstenzija za
kompajlirani shaderi.
Za kompilaciju shadera na mreži koristimo FKSC alat koji dolazi sa DirectKs-om. Ovo je
alat komandne linije. Da sastavite vertek i piksel shader sačuvan u color.hlsl sa unosom
tačke VS i PS, respektivno, sa debagovanjem koje bi napisali:
fxc “color.hlsl” /Od /Zi /T vs_5_0 /E “VS” /Fo
“color_vs.cso” /Fc “color_vs.asm”
fxc “color.hlsl” /Od /Zi /T ps_5_0 /E “PS” /Fo
“color_ps.cso” /Fc “color_ps.asm”
To compile a vertex and pixel shader stored in color.hlsl with entry points VS and PS,
respectively, for release we would write:
fxc “color.hlsl” /T vs_5_0 /E “VS” /Fo “color_vs.cso”
/Fc “color_vs.asm”
fxc “color.hlsl” /T ps_5_0 /E “PS” /Fo “color_ps.cso”
/Fc “color_ps.asm”
If you try to compile a shader with a syntax error, FXC will output the error/warning
to the command window. For example, if we misname a variable in the color.hlsl effect
file:
// Should be gWorldViewProj, not worldViewProj!
vout.PosH = mul(float4(vin.Pos, 1.0f), worldViewProj);
Then we get quite a few errors from this one mistake (the top error being the key one
to fix) listed in the debut output window:
color.hlsl(29,42-54): error X3004: undeclared
identifier ‘worldViewProj’
color.hlsl(29,14-55): error X3013: ‘mul’: no matching
2 parameter intrinsic function
color.hlsl(29,14-55): error X3013: Possible intrinsic
functions are:
color.hlsl(29,14-55): error X3013: mul(float|half…
HLSL greške i upozorenja će se vratiti kroz parametar ppErrorMsgs. Na primer, ako smo pogrešno
napisali mul funkciju, onda ćemo dobiti sledeću grešku prozor debagiranja:
Shaders \ color.hlsl (29,14-55): greška Ks3004: neobjavljeno identifikator 'mu' Sastavljanje shadera ne
povezuje ga sa linijom za rendering radi upotrebe. Videćemo kako da to uradi u § 6.9.
6.7.1 Offline kompilacija
Umjesto kompajliranja shadera u vrijeme izvršavanja, možemo ih kompilirati offline
korak (npr. stepen izgradnje, ili kao deo procesa gasovodnog sadržaja). Ima ih nekoliko
razloge za to:
1. Za komplikovane shadere, kompilacija može potrajati dugo. Zbog toga, kompajliranje
offline će učiniti Vaše vrijeme učitavanja brže.
2. Pogodno je videti greške u kompilaciji shadera ranije u procesu izgradnje
nego u radnom vremenu.
3. Aplikacije za Vindovs 8 Store moraju da koriste offline kompilaciju.
Uobičajena je praksa za korištenje .cso (složenog shader objekta) ekstenzija za
kompajlirani shaderi.
Za kompilaciju shadera na mreži koristimo FKSC alat koji dolazi sa DirectKs-om. Ovo je
alat komandne linije. Da sastavite vertek i piksel shader sačuvan u color.hlsl sa unosom
tačke VS i PS, respektivno, sa debagovanjem koje bi napisali:
// Should be gWorldViewProj, not worldViewProj!
vout.PosH = mul(float4(vin.Pos, 1.0f), worldViewProj);
Zatim dobijemo nekoliko grešaka iz ove greške (najveća greška koja je ključna za ispravljanje) koja je
navedena u debitnom izlaznom prozoru:
color.hlsl(29,42-54): error X3004: undeclared
identifier ‘worldViewProj’
color.hlsl(29,14-55): error X3013: ‘mul’: no matching
2 parameter intrinsic function
color.hlsl(29,14-55): error X3013: Possible intrinsic
functions are:
color.hlsl(29,14-55): error X3013: mul(float|half…
Dobijanje poruka o grešci u vrijeme kompajliranja je mnogo pogodnije od vremena izvršavanja.
Pokazali smo kako da kompajliramo naše vertek i piksel shadere u offline .cso datoteke.
Stoga, mi više ne moramo to raditi u toku rada (tj. Ne moramo da pozivamo
D3DCompileFromFile). Međutim, i dalje moramo učitati kompajlirani objekat shadera
bitecode iz. cso datoteka u našu aplikaciju. Ovo se može uraditi koristeći standardne C ++ datoteke za
unos datoteke:
ComPtr<ID3DBlob> d3dUtil::LoadBinary(const
std::wstring& filename)
{
std::ifstream fin(filename, std::ios::binary);
fin.seekg(0, std::ios_base::end);
std::ifstream::pos_type size = (int)fin.tellg();
fin.seekg(0, std::ios_base::beg);
ComPtr<ID3DBlob> blob;
ThrowIfFailed(D3DCreateBlob(size,
blob.GetAddressOf()));
fin.read((char*)blob->GetBufferPointer(), size);
fin.close();
return blob;
}…C
omPtr<ID3DBlob> mvsByteCode =
d3dUtil::LoadBinary(L”Shaders\color_vs.cso”);
ComPtr<ID3DBlob> mpsByteCode =
d3dUtil::LoadBinary(L”Shaders\color_ps.cso”);
6.7.2 Generisana skupština
Opcioni parametar / Fc za FKSC generiše generirani prenosivi kod skupštine. Gledanje skupa vaših
shadera s vremena na vreme je korisno za proveru shadera broj uputstava i da vidimo kakav se kod
generira - ponekad to može drugačije od onoga što očekujete. Na primer, ako imate uslovnu izjavu u vaš
HLSL kod, onda možete očekivati da postoji instrukcija za grananje u montažni kod. U ranim danima
programabilnih GPU-a, nekada je bilo grananje u shaderima skupo, i tako ponekad kompajler će
izjednačiti uslovnu izjavu ocenjuju obe granice, a zatim interpoliraju između dva da biste izabrali pravi
odgovor.
To znači da će sljedeći kodovi dati isti odgovor: Dakle, ravnati metoda nam daje isti rezultat bez
grananja, ali bez gledajući skupštinski kod, nećemo znati da li se istezanje dešava ili ako je istina
generisana je instrukcija. Poenta je da ponekad želite da pogledate da vidimo šta se zapravo dešava.
Sledi primer skupa generiše se za shader vertek u color.hlsl:
//
// Generated by Microsoft (R) HLSL Shader Compiler
6.4.9844.0
//
//
// Buffer Definitions:
//
// cbuffer cbPerObject
// {
//
// float4x4 gWorldViewProj; // Offset: 0
Size: 64
//
// }
//
//
// Resource Bindings:
//
// Name Type Format Dim Slot
Elements
// –––––––––– –––- ––- –––— –- –––
// cbPerObject cbuffer NA NA 0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue
Format Used
// ––––––— –— –– ––— ––— ––- ––
// POSITION 0 xyz 0 NONE float xyz
// COLOR 0 xyzw 1 NONE float xyzw
//
//
// Output signature:
//
// Name Index Mask Register SysValue
Format Used
// ––––––— –— –– ––— ––— ––- ––
// SV_POSITION 0 xyzw 0 POS float xyzw
// COLOR 0 xyzw 1 NONE float xyzw
//
vs_5_0
dcl_globalFlags refactoringAllowed | skipOptimization
dcl_constantbuffer cb0[4], immediateIndexed
dcl_input v0.xyz
dcl_input v1.xyzw
dcl_output_siv o0.xyzw, position
dcl_output o1.xyzw
dcl_temps 2
//
// Initial variable locations:
// v0.x <- vin.PosL.x; v0.y <- vin.PosL.y; v0.z <-
vin.PosL.z;
// v1.x <- vin.Color.x; v1.y <- vin.Color.y; v1.z <-
vin.Color.z; v1.w <- vin.Color.w;
// o1.x <- <VS return value>.Color.x;
// o1.y <- <VS return value>.Color.y;
// o1.z <- <VS return value>.Color.z;
// o1.w <- <VS return value>.Color.w;
// o0.x <- <VS return value>.PosH.x;
// o0.y <- <VS return value>.PosH.y;
// o0.z <- <VS return value>.PosH.z;
// o0.w <- <VS return value>.PosH.w
//
#line 29 “color.hlsl”
mov r0.xyz, v0.xyzx
mov r0.w, l(1.000000)
dp4 r1.x, r0.xyzw, cb0[0].xyzw // r1.x <- vout.PosH.x
dp4 r1.y, r0.xyzw, cb0[1].xyzw // r1.y <- vout.PosH.y
dp4 r1.z, r0.xyzw, cb0[2].xyzw // r1.z <- vout.PosH.z
dp4 r1.w, r0.xyzw, cb0[3].xyzw // r1.w <- vout.PosH.w
#line 32
mov r0.xyzw, v1.xyzw // r0.x <- vout.Color.x; r0.y <-
vout.Color.y;
// r0.z <- vout.Color.z; r0.w <-
vout.Color.w
mov o0.xyzw, r1.xyzw
mov o1.xyzw, r0.xyzw
ret
// Approximately 10 instruction slots used
6.7.3 Korišćenje Visual Studio-a za kompajliranje Shadera na mreži
Visual Studio 2013 ima neku integrisanu podršku za kompajliranje programa shadera. ti
može dodati .hlsl fajlove u svoj projekat, a Visual Studio (VS) ih prepozna i obezbedi
opcije kompajliranja (pogledajte sliku 6.6). Ove opcije pružaju korisnički interfejs za FKSC parametre.
Kada dodate HLSL datoteku u vaš VS projekat, ona će postati deo procesa izgradnje,
a shader će se sastaviti sa FKSC.
Slika 6.6. Dodavanje prilagođenog alata za izgradnju u projekat.
Jedna nedostatak korišćenja VS integrisane HLSL podrške jeste da podržava samo jednu
shader program po datoteki. Zbog toga ne možete da sačuvate i vertikalni i pikselski shader u jednom
file. Štaviše, ponekad želimo da sačuvamo isti program shadera sa drugačijim
pretprocesorske direktive da bi dobili različite varijante shadera. Opet, ovo neće biti
moguće je koristiti integrisanu VS podršku, pošto je to jedan .cso izlaz po. hlsl ulazu.
6.8 RASTERIZER STATE
Dok se mnogi delovi plinovoda za rendering programiraju, neki delovi su samo
konfigurirati. Državna grupa rasterizer, koju predstavlja
D3D12_RASTERIZER_DESC struktura, koristi se za konfiguraciju faze rasterizacije
rendering pipeline:
typedef struct D3D12_RASTERIZER_DESC {
D3D12_FILL_MODE FillMode; // Default:
D3D12_FILL_SOLID
D3D12_CULL_MODE CullMode; // Default:
D3D12_CULL_BACK
BOOL FrontCounterClockwise; // Default: false
INT DepthBias; // Default: 0
FLOAT DepthBiasClamp; // Default: 0.0f
FLOAT SlopeScaledDepthBias; // Default: 0.0f
BOOL DepthClipEnable; // Default: true
BOOL ScissorEnable; // Default: false
BOOL MultisampleEnable; // Default: false
BOOL AntialiasedLineEnable; // Default: false
UINT ForcedSampleCount; // Default: 0
// Default:
D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF
D3D12_CONSERVATIVE_RASTERIZATION_MODE
ConservativeRaster;
} D3D12_RASTERIZER_DESC;
Većina ovih članova je napredovala ili se ne koristi veoma često; Stoga vas pozivamo
SDK dokumentaciju za opis svakog člana. Ovde opisujemo samo četiri.
1. FillMode: Specificirajte D3D12_FILL_VIREFRAME za renderiranje žica ili D3D12_FILL_SOLID za čvrstu
rendering. Solidno prikazivanje je podrazumevano.
2. CullMode: Specificirajte D3D12_CULL_NONE da biste onemogućili ubiranje, D3D12_CULL_BACK za
otklanjanje trouglova okrenutih unazad, ili D3D12_CULL_FRONT do izbaciti napredne trouglove.
Trouglovi koji su okrenuti unazad su podrazumevani.
3. FrontCounterClockvise: Specificirajte lažno ako želite naručiti trouglove u smeru kazaljke na satu (u
odnosu na kameru) koji se tretira kao prednji i trouglovi naručeno u smeru suprotnom od kazaljke na
satu (u odnosu na kameru) koja se tretira kao okrenuta unazad. Navedite tačno ako želite trokutke
naručene u suprotnom smeru kazaljke na satu (u odnosu na kameru) koja se tretira kao okrenuta prema
frontu i trouglovi naručeni u smeru kazaljke na satu (u odnosu na fotoaparat) koji se tretira kao okrenuti
nazad. Ovo stanje je podrazumevano lažno.
4. ScissorEnable: Specificirajte true da biste omogućili test makaze (§4.3.10) i false na onemogući.
Podrazumevana vrednost je lažna. Sledeći kod pokazuje kako da kreirate rasterizovano stanje koje se
uključuje u režim žične slike i onemogućava odbacivanje povreda:
CD3DX12_RASTERIZER_DESC rsDesc(D3D12_DEFAULT);
rsDesc.FillMode = D3D12_FILL_WIREFRAME;
rsDesc.CullMode = D3D12_CULL_NONE;
CD3DKS12_RASTERIZER_DESC je klasa pogodnosti koja se prostire
D3D12_RASTERIZER_DESC i dodaje neke pomoćne konstruktore. Konkretno, ima a
konstruktor koji preuzima objekat tipa CD3D12_DEFAULT, što je samo lažni tip
koji se koristi za preopterećenje da bi se naznačili članovi države rasterizer treba inicijalizirati na
default vrednosti. CD3D12_DEFAULT i D3D12_DEFAULT su definisani kao:
struct CD3D12_DEFAULT {};
extern const DECLSPEC_SELECTANY CD3D12_DEFAULT
D3D12_DEFAULT;
D3D12_DEFAULT is used in several of the Direct3D convenience classes.
6.9
Pokazali smo, na primer, kako opisati opis unosa teksta, kako
kreirajte vertek i piksel shadere i kako konfigurirati grupu stanja rasterizer. Međutim,
još uvek nismo pokazali kako vezati bilo koji od ovih objekata na grafičku liniju za stvarno
koristite. Većina objekata koji kontrolišu stanje grafičkog cjevovoda naznačeni su kao
agregat koji se naziva objekt državnog objekta (PSO), koji je predstavljen od strane
ID3D12PipelineState interfejs. Da napravimo PSO, prvo ga opišemo popunjavanjem
primer D3D12_GRAPHICS_PIPELINE_STATE_DESC:
typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC
{ ID3D12RootSignature *pRootSignature;
D3D12_SHADER_BYTECODE VS;
D3D12_SHADER_BYTECODE PS;
D3D12_SHADER_BYTECODE DS;
D3D12_SHADER_BYTECODE HS;
D3D12_SHADER_BYTECODE GS;
D3D12_STREAM_OUTPUT_DESC StreamOutput;
D3D12_BLEND_DESC BlendState;
UINT SampleMask;
D3D12_RASTERIZER_DESC RasterizerState;
D3D12_DEPTH_STENCIL_DESC DepthStencilState;
D3D12_INPUT_LAYOUT_DESC InputLayout;
D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType;
UINT NumRenderTargets;
DXGI_FORMAT RTVFormats[8];
DXGI_FORMAT DSVFormat;
DXGI_SAMPLE_DESC SampleDesc;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;

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.

// Defines a subrange of geometry in a MeshGeometry.


This is for when
// multiple geometries are stored in one vertex and
index buffer. It
// provides the offsets and data needed to draw a
subset of geometry
// stores in the vertex and index buffers so that we
can implement the
// technique described by Figure 6.3.
struct SubmeshGeometry
{
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
INT BaseVertexLocation = 0;
// Bounding box of the geometry defined by this
submesh.
// This is used in later chapters of the book.
DirectX::BoundingBox Bounds;
};
struct MeshGeometry
{
// Give it a name so we can look it up by name.
std::string Name;
// System memory copies. Use Blobs because the
vertex/index format can
// be generic.
// It is up to the client to cast appropriately.
Microsoft::WRL::ComPtr<ID3DBlob> VertexBufferCPU =
nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> IndexBufferCPU =
nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource>
VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource>
IndexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource>
VertexBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource>
IndexBufferUploader = nullptr;
// Data about the buffers.
UINT VertexByteStride = 0;
UINT VertexBufferByteSize = 0;
DXGI_FORMAT IndexFormat = DXGI_FORMAT_R16_UINT;
UINT IndexBufferByteSize = 0;
// A MeshGeometry may store multiple geometries in
one vertex/index
// buffer.
// Use this container to define the Submesh
geometries so we can draw
// the Submeshes individually.
std::unordered_map<std::string, SubmeshGeometry>
DrawArgs;
D3D12_VERTEX_BUFFER_VIEW VertexBufferView()const
{
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU-
>GetGPUVirtualAddress();
vbv.StrideInBytes = VertexByteStride;
vbv.SizeInBytes = VertexBufferByteSize;
return vbv;
}
D3D12_INDEX_BUFFER_VIEW IndexBufferView()const
{
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU-
>GetGPUVirtualAddress();
ibv.Format = IndexFormat;
ibv.SizeInBytes = IndexBufferByteSize;
return ibv;
}
// We can free this memory after we finish upload to
the GPU.
void DisposeUploaders()
{
VertexBufferUploader = nullptr;
IndexBufferUploader = nullptr;
}
};
6.11 BOKS DEMO
Konačno, pokrivali smo dovoljno materijala da predstavimo jednostavan demo, što čini a
obojena kutija. Ovaj primjer u suštini stavlja do sada sve o čemu smo razgovarali u ovom poglavlju u
jedan program. Čitač treba proučiti kod i vratiti se na prethodne odeljke ovog poglavlja sve dok se ne
shvati svaka linija. Imajte na umu da program koristi
the Shaders\color.hlsl, which was shown at the end of §6.5.
//*********************************************************************
// BoxApp.cpp by Frank Luna (C) 2015 All Rights
Reserved.
//
// Shows how to draw a box in Direct3D 12.
//
// Controls:
// Hold the left mouse button down and move the mouse
to rotate.
// Hold the right mouse button down and move the
mouse to zoom in and
// out.
//*********************************************************************
#include “../../Common/d3dApp.h”
#include “../../Common/MathHelper.h”
#include “../../Common/UploadBuffer.h”
using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj =
MathHelper::Identity4x4();
};
class BoxApp : public D3DApp
{p
ublic:
BoxApp(HINSTANCE hInstance);
BoxApp(const BoxApp& rhs) = delete;
BoxApp& operator=(const BoxApp& rhs) = delete;
˜BoxApp();
virtual bool Initialize()override;
private:
virtual void OnResize()override;
virtual void Update(const GameTimer& gt)override;
virtual void Draw(const GameTimer& gt)override;
virtual void OnMouseDown(WPARAM btnState, int x, int
y)override;
virtual void OnMouseUp(WPARAM btnState, int x, int
y)override;
virtual void OnMouseMove(WPARAM btnState, int x, int
y)override;
void BuildDescriptorHeaps();
void BuildConstantBuffers();
void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildBoxGeometry();
void BuildPSO();
private:
ComPtr<ID3D12RootSignature> mRootSignature =
nullptr;
ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;
std::unique_ptr<UploadBuffer<ObjectConstants>>
mObjectCB = nullptr;
std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;
ComPtr<ID3DBlob> mvsByteCode = nullptr;
ComPtr<ID3DBlob> mpsByteCode = nullptr;
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;
ComPtr<ID3D12PipelineState> mPSO = nullptr;
XMFLOAT4X4 mWorld = MathHelper::Identity4x4();
XMFLOAT4X4 mView = MathHelper::Identity4x4();
XMFLOAT4X4 mProj = MathHelper::Identity4x4();
float mTheta = 1.5f*XM_PI;
float mPhi = XM_PIDIV4;
float mRadius = 5.0f;
POINT mLastMousePos;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE
prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF |
_CRTDBG_LEAK_CHECK_DF );
#endif
try
{
BoxApp 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;
}
}
BoxApp::BoxApp(HINSTANCE hInstance)
: D3DApp(hInstance)
{} B
oxApp::˜BoxApp()
{} b
ool BoxApp::Initialize()
{
if(!D3DApp::Initialize())
return false;
// Reset the command list to prep for initialization
commands.
ThrowIfFailed(mCommandList-
>Reset(mDirectCmdListAlloc.Get(), nullptr));
BuildDescriptorHeaps();
BuildConstantBuffers();
BuildRootSignature();
BuildShadersAndInputLayout();
BuildBoxGeometry();
BuildPSO();
// Execute the initialization commands.
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = {
mCommandList.Get() };
mCommandQueue-
>ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// Wait until initialization is complete.
FlushCommandQueue();
return true;
}
void BoxApp::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);
}
void BoxApp::Update(const GameTimer& gt)
{
// Convert Spherical to Cartesian coordinates.
float x = mRadius*sinf(mPhi)*cosf(mTheta);
float z = mRadius*sinf(mPhi)*sinf(mTheta);
float y = mRadius*cosf(mPhi);
// Build the view matrix.
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, view);
XMMATRIX world = XMLoadFloat4x4(&mWorld);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX worldViewProj = world*view*proj;
// Update the constant buffer with the latest
worldViewProj matrix.
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.WorldViewProj,
XMMatrixTranspose(worldViewProj));
mObjectCB->CopyData(0, objConstants);
}
void BoxApp::Draw(const GameTimer& gt)
{
// Reuse the memory associated with command
recording.
// We can only reset when the associated command
lists have finished
// execution on the GPU.
ThrowIfFailed(mDirectCmdListAlloc->Reset());
// A command list can be reset after it has been
added to the
// command queue via ExecuteCommandList. Reusing the
command
// list reuses memory.
ThrowIfFailed(mCommandList-
>Reset(mDirectCmdListAlloc.Get(), mPSO.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());
mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo-
>VertexBufferView());
mCommandList->IASetIndexBuffer(&mBoxGeo-
>IndexBufferView());
mCommandList-
>IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
mCommandList->SetGraphicsRootDescriptorTable(
0, mCbvHeap-
>GetGPUDescriptorHandleForHeapStart());
mCommandList->DrawIndexedInstanced(
mBoxGeo->DrawArgs[“box”].IndexCount,
1, 0, 0, 0);
// 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;
// Wait until frame commands are complete. This
waiting is
// inefficient and is done for simplicity. Later we
will show how to
// organize our rendering code so we do not have to
wait per frame.
FlushCommandQueue();
}
void BoxApp::OnMouseDown(WPARAM btnState, int x, int
y)
{
mLastMousePos.x = x;
mLastMousePos.y = y;
SetCapture(mhMainWnd);
}
void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{
ReleaseCapture();
}
void BoxApp::OnMouseMove(WPARAM btnState, int x, int
y)
{
if((btnState & MK_LBUTTON) != 0)
{
// Make each pixel correspond to a quarter of a
degree.
float dx =
XMConvertToRadians(0.25f*static_cast<float>(x -
mLastMousePos.x));
float dy =
XMConvertToRadians(0.25f*static_cast<float>(y -
mLastMousePos.y));
// Update angles based on input to orbit camera
around box.
mTheta += dx;
mPhi += dy;
// Restrict the angle mPhi.
mPhi = MathHelper::Clamp(mPhi, 0.1f,
MathHelper::Pi - 0.1f);
}
else if((btnState & MK_RBUTTON) != 0)
{
// Make each pixel correspond to 0.005 unit in the
scene.
float dx = 0.005f*static_cast<float>(x -
mLastMousePos.x);
float dy = 0.005f*static_cast<float>(y -
mLastMousePos.y);
// Update the camera radius based on input.
mRadius += dx - dy;
// Restrict the radius.
mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
}
mLastMousePos.x = x;
mLastMousePos.y = y;
}
void BoxApp::BuildDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = 1;
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)));
}
void BoxApp::BuildConstantBuffers()
{
mObjectCB =
std::make_unique<UploadBuffer<ObjectConstants>>
(md3dDevice.Get(), 1, true);
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB-
>Resource()->GetGPUVirtualAddress();
// Offset to the ith object constant buffer in the
buffer.
// Here our i = 0.
int boxCBufIndex = 0;
cbAddress += boxCBufIndex*objCBByteSize;
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
md3dDevice->CreateConstantBufferView(
&cbvDesc,
mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}
void BoxApp::BuildRootSignature()
{
// Shader programs typically require resources as
input (constant
// buffers, textures, samplers). The root signature
defines the
// resources the shader programs expect. If we think
of the shader
// programs as a function, and the input resources
as function
// parameters, then the root signature can be
thought of as defining
// the function signature.
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
// Create a single descriptor table of CBVs.
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1,
0);
slotRootParameter[0].InitAsDescriptorTable(1,
&cbvTable);
// 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);
// 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(&mRootSignature)));
}
void BoxApp::BuildShadersAndInputLayout()
{
HRESULT hr = S_OK;
mvsByteCode =
d3dUtil::CompileShader(L”Shaders\color.hlsl”, nullptr,
“VS”, “vs_5_0”);
mpsByteCode =
d3dUtil::CompileShader(L”Shaders\color.hlsl”, nullptr,
“PS”, “ps_5_0”);
mInputLayout =
{
{ “POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,
0,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0,
12,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
}
void BoxApp::BuildBoxGeometry()
{
std::array<Vertex, 8> vertices =
{
Vertex({ XMFLOAT3(-1.0f, -1.0f, -1.0f),
XMFLOAT4(Colors::White) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, -1.0f),
XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, -1.0f),
XMFLOAT4(Colors::Red) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, -1.0f),
XMFLOAT4(Colors::Green) }),
Vertex({ XMFLOAT3(-1.0f, -1.0f, +1.0f),
XMFLOAT4(Colors::Blue) }),
Vertex({ XMFLOAT3(-1.0f, +1.0f, +1.0f),
XMFLOAT4(Colors::Yellow) }),
Vertex({ XMFLOAT3(+1.0f, +1.0f, +1.0f),
XMFLOAT4(Colors::Cyan) }),
Vertex({ XMFLOAT3(+1.0f, -1.0f, +1.0f),
XMFLOAT4(Colors::Magenta) })
};
std::array<std::uint16_t, 36> indices =
{
// front face
0, 1, 2,
0, 2, 3,
// back face
4, 6, 5,
4, 7, 6,
// left face
4, 5, 1,
4, 1, 0,
// right face
3, 2, 6,
3, 6, 7,
// top face
1, 5, 6,
1, 6, 2,
// bottom face
4, 0, 3,
4, 3, 7
};
const UINT vbByteSize = (UINT)vertices.size() *
sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() *
sizeof(std::uint16_t);
mBoxGeo = std::make_unique<MeshGeometry>();
mBoxGeo->Name = “boxGeo”;
ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo-
>VertexBufferCPU));
CopyMemory(mBoxGeo->VertexBufferCPU-
>GetBufferPointer(),
vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo-
>IndexBufferCPU));
CopyMemory(mBoxGeo->IndexBufferCPU-
>GetBufferPointer(),
indices.data(), ibByteSize);
mBoxGeo->VertexBufferGPU =
d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(), mCommandList.Get(),
vertices.data(), vbByteSize,
mBoxGeo->VertexBufferUploader);
mBoxGeo->IndexBufferGPU =
d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(), mCommandList.Get(),
indices.data(), ibByteSize,
mBoxGeo->IndexBufferUploader);
mBoxGeo->VertexByteStride = sizeof(Vertex);
mBoxGeo->VertexBufferByteSize = vbByteSize;
mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
mBoxGeo->IndexBufferByteSize = ibByteSize;
SubmeshGeometry submesh;
submesh.IndexCount = (UINT)indices.size();
submesh.StartIndexLocation = 0;
submesh.BaseVertexLocation = 0;
mBoxGeo->DrawArgs[“box”] = submesh;
}
void BoxApp::BuildPSO()
{
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 =
CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState =
CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState =
CD3DX12_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;
ThrowIfFailed(md3dDevice-
>CreateGraphicsPipelineState(&psoDesc,
IID_PPV_ARGS(&mPSO)));
}
Poglavlje 7 RISANJE U DIRECT3D DEO II
Ovo poglavlje uvodi određeni broj šema crtanja koje ćemo koristiti tokom celog vremena
ostatak ove knjige. Poglavlje počinje uvođenjem optimizacije crtanja, što mi
nazivaju se kao "okvirni resursi". Sa resursima rama, mi modifikujemo našu petlju za render, tako da mi
nemojte ispustiti komandni red u svakom okviru; ovo poboljšava CPU i GPU
upotreba. Zatim upoznajemo koncept stavke i objasnimo kako se delimo
naši konstantni podaci zasnovani na frekvenciji ažuriranja. Pored toga, pregledamo i root potpisa
više detalja i upoznajte druge tipove root parametara: root deskriptore i root
konstante. Na kraju, pokazujemo kako nacrtati neke komplikovanije objekte; do kraja
ovo poglavlje, moći ćete da nacrtate površinu koja podseća na brdove i doline, cilindre,
sfere i simulacije animiranog talasa.
Ciljevi:
1. Da biste razumeli modifikaciju našeg procesa renderinga koji nas ne traži
ispraznite komandni red u svakom okviru, čime se poboljšava performanse.
2. Da biste saznali o dve druge vrste tipova parametara root podpisa: root descriptors
i root konstante.
3. Da biste otkrili kako proceduralno generirati i crtati zajedničke geometrijske oblike poput
rešetke, cilindri i sfere.
4. Da biste saznali kako možemo animirati vertikale na CPU-u i otpremiti novu vertek
položaja na GPU pomoću dinamičkih bafera verteka.
7.1 FRAME RESOURCES
Podsjetimo iz §4.2 da CPU i GPU rade paralelno. CPU gradi i dostavlja
komandne liste (pored ostalih radova CPU-a) i naredbe za procesiranje GPU-a
komandni red. Cilj je da i CPU i GPU budu zauzeti kako bi se u potpunosti iskoristili
hardverske resurse dostupne na sistemu. Do sada smo u našim demo-centri
sinhronizaciju CPU-a i GPU-a jednom po kadru. Dva primera zašto je to neophodno
su:
1. Raspored komandi ne može se resetovati dok GPU nije završen
komande. Pretpostavimo da nismo sinhronizovali tako da bi CPU mogao nastaviti dalje
sledeći kadar n + 1 pre nego što GPU završi obradu tekućeg kadra n: Ako je
CPU poništava raspodjelu naredbe u okviru n + 1, ali GPU i dalje obrađuje
komande iz okvira n, onda ćemo očistiti komande koje je GPU još uvijek
radi na.
2. CPU ne može ažurirati konstantni bafer dok GPU ne završi izvršavanje komandi crteža koji se odnose
na konstantni bafer. Ovaj primer odgovara situaciji opisanoj u §4.2.2 i Slika 4.7. Pretpostavimo da nismo
sinhronizovati tako da bi CPU mogao nastaviti sa sledećim kadrom n + 1 pre GPU je završio obradu
tekućeg okvira n: Ako CPU prepisuje konstantu podaci u baferu u okviru n + 1, ali GPU još nije izvršio
poziv za izvlačenje referenca konstantnog bafera u okviru n, onda konstantni bafer sadrži pogrešno
podaci za kada GPU izvršava poziv izvlačenja za okvir n. Tako smo pozvali D3DApp ::
FlushCommandKueue na kraju svakog okvir kako bi se osiguralo da GPU završi izvršavanje svih komandi
za okvir. Ovo rešenje funkcioniše, ali je neefikasno iz sledećih razloga:
1. Na početku okvira, GPU neće imati nikakve komande za obradu od kada
čekali smo da ispraznimo komandni red. Moraće da čeka dok CPU ne gradi i
podnosi neke naredbe za izvršenje. 2. Na kraju okvira, CPU čeka GPU da završi obradu komande. Dakle,
svaki ram, CPU i GPU u nekom trenutku rade u praznom hodu.
Jedno rešenje za ovaj problem je kreiranje kružnog niza resursa na CPU-u treba modifikovati svaki okvir.
Mi takve resurse zovemo kao resurse, a mi obično koristimo a kružni niz elemenata resursa sa tri rama.
Ideja je da će za okvir n, CPU to učiniti kružite kroz ramski resurs rama kako biste dobili sljedeći dostupni
(tj. koji GPU ne koristi) frame resource. CPU će onda izvršiti bilo kakve izvore informacija o resursima, i
graditi i dostavljati komandne liste za okvir n dok GPU radi na prethodnim okvirima. Tada će CPU
Nastavite da rasterete n + 1 i ponovite. Ako okvir resursa rama ima tri elementa, ovo omogućuje CPU-u
da postane dva rama ispred GPU-a, osiguravajući da GPU bude zauzeta. Ispod je primer klase resursa
kadra koji koristimo za demonstraciju "Shapes" u ovom poglavlje. Pošto CPU samo treba da modifikuje
konstantne odbojnike u ovom demo, okvir klasa resursa sadrži samo konstantne bafere.
// Stores the resources needed for the CPU to build
the command lists
// for a frame. The contents here will vary from app
to app based on
// the needed resources.
struct FrameResource
{
public:
FrameResource(ID3D12Device* device, UINT
passCount, UINT objectCount);
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<PassConstants>> PassCB
= nullptr;
std::unique_ptr<UploadBuffer<ObjectConstants>>
ObjectCB = 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
objectCount)
{
ThrowIfFailed(device->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(CmdListAlloc.GetAddressOf())));
PassCB =
std::make_unique<UploadBuffer<PassConstants>>(device,
passCount, true);
ObjectCB =
std::make_unique<UploadBuffer<ObjectConstants>>(device,
objectCount, true);
}F
rameResource::˜FrameResource() { }
Naša klasa aplikacija će zatim instancirati vektor od tri frejma resursa i zadržati promenljive članova da
prati trenutni resurs fonda:
static const int NumFrameResources = 3;
std::vector<std::unique_ptr<FrameResource>>
mFrameResources;
FrameResource* mCurrFrameResource = nullptr;
int mCurrFrameResourceIndex = 0;
void ShapesApp::BuildFrameResources()
{
for(int i = 0; i < gNumFrameResources; ++i)
{
mFrameResources.push_back(std::make_unique<FrameResource>
(
md3dDevice.Get(), 1, (UINT)mAllRitems.size()));
}
}
Now, for CPU frame n, the algorithm works like so:
void ShapesApp::Update(const GameTimer& gt)
{
// Cycle through the circular frame resource array.
mCurrFrameResourceIndex = (mCurrFrameResourceIndex +
1) % NumFrameResources;
mCurrFrameResource =
mFrameResources[mCurrFrameResourceIndex];
// Has the GPU finished processing the commands of
the current frame
// resource. If not, wait until the GPU has
completed commands up to
// this fence point.
if(mCurrFrameResource->Fence != 0 &&
mCommandQueue->GetLastCompletedFence() <
mCurrFrameResource->Fence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false,
false, EVENT_ALL_ACCESS);
ThrowIfFailed(mCommandQueue-
>SetEventOnFenceCompletion(
mCurrFrameResource->Fence, eventHandle));
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
// […] Update resources in mCurrFrameResource (like
cbuffers).
}
void ShapesApp::Draw(const GameTimer& gt)
{
// […] Build and submit command lists for this
frame.
// 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);
// Note that GPU could still be working on commands
from previous
// frames, but that is okay, because we are not
touching any frame
// resources associated with those frames.
}
Imajte na umu da ovo rešenje ne sprečava čekanje. Ako jedan procesor obrađuje okvire
mnogo brže od drugog, jedan procesor će na kraju morati čekati drugu
uhvatite se, jer ne možemo dozvoliti da se predaleko ispred druge. Ako GPU obrađuje
komande brže od CPU-a mogu da podnesu posao, onda GPU ne radi. Uopšte, ako smo
pokušavaju da pritisnu grafičku granicu, želimo da izbegnemo ovu situaciju, kao što ne uzimamo
potpunu prednost GPU-a. Sa druge strane, ako je CPU uvek okruženje za obradu
brže od GPU-a, onda će CPU morati čekati u nekom trenutku. Ovo je željeno
situaciju, jer GPU se u potpunosti iskorištava; za dodatne CPU cikle se uvek mogu koristiti
druge delove igre kao što su AI, fizika i logika igre.
Dakle, ako viri višestrukog rama ne sprečavaju bilo kakvo čekanje, kako nam to pomaže? To
pomaže nam da zadržimo grafičku grafiku. Dok GPU obrađuje komande iz frame n, to je
omogućava procesoru da nastavi da gradi i predaje komande za okvire n + 1 i n + 2.
Ovo pomaže da redosled naredbe ne bude prazan, tako da GPU uvek ima posla.
7.2 RENDER ITEMS
Crtežom objekta potrebno je postaviti više parametara, kao što je veza vertek i
indeksni baferi, konstanti vezivanja objekata, postavljanje primitivnog tipa i navođenje
DravIndekedInstanced parametri. Kako počinjemo da crtamo više objekata u našem
scene, korisno je stvoriti laganu strukturu koja čuva podatke potrebne za crtanje
object; ovi podaci će se razlikovati od aplikacije do aplikacije pošto dodamo nove funkcije koje će vam
biti potrebne različiti podaci o crtežu. Mi zovemo skup podataka koji su potrebni da podnesete pun poziv
renderiranje plinovoda stavke. Za ovu demo, izgleda naša RenderItem struktura
ovo:
// Lightweight structure stores parameters to draw a
shape. This will
// vary from app-to-app.
struct RenderItem
{
RenderItem() = default;
// World matrix of the shape that describes the
object’s local space
// relative to the world space, which defines the
position,
// orientation, and scale of the object in the
world.
XMFLOAT4X4 World = MathHelper::Identity4x4();
// Dirty flag indicating the object data has changed
and we need
// to update the constant buffer. Because we have an
object
// cbuffer for each FrameResource, we have to apply
the
// update to each FrameResource. Thus, when we
modify obect data we
// should set
// NumFramesDirty = gNumFrameResources so that each
frame resource
// gets the update.
int NumFramesDirty = gNumFrameResources;
// Index into GPU constant buffer corresponding to
the ObjectCB
// for this render item.
UINT ObjCBIndex = -1;
// Geometry associated with this render-item. Note
that multiple
// render-items can share the same geometry.
MeshGeometry* Geo = nullptr;
// Primitive topology.
D3D12_PRIMITIVE_TOPOLOGY PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
// DrawIndexedInstanced parameters.
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
int BaseVertexLocation = 0;
};
Our application will maintain lists of render items based on how they need to be
drawn; that is, render items that need different PSOs will be kept in different lists.
// List of all the render items.
std::vector<std::unique_ptr<RenderItem>> mAllRitems;
// Render items divided by PSO.
std::vector<RenderItem*> mOpaqueRitems;
std::vector<RenderItem*> mTransparentRitems;
7.3 PASS CONSTANTS
U prethodnom odeljku uvodimo novi konstantni bafer u našem
FrameResource klasa:
std :: unikue_ptr <UploadBuffer <PassConstants >> PassCB =
nullptr;
U demosu koji ide napred, ovaj bafer čuva konstantne podatke koji su fiksirani preko datog
rendering prolaz, kao što je pozicija očiju, matrica prikaza i projekcija i informacije
o dimenzijama ekrana (prikazati cilj); ona takođe uključuje informacije o vremenu igre,
što je korisni podatak za pristup u programima shadera. Imajte na umu da naši demo neće
nužno koristiti sve ove konstantne podatke, ali je pogodno imati dostupno, i postoji
mali troškovi koji pružaju dodatne podatke. Na primer, dok nam ne treba ciljna meta
veličine sada, kada idemo da primenimo neki efekat postprocesa, imajući tu informaciju
biće potreban.
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;
};
Takođe smo promenili svoj konstantni pufer za svaki objekat samo da čuvamo konstante koje su
povezane sa objektom. Do sada smo jedini konstantni podaci koje povezujemo sa nekim objektom
crtež je njegova svetska matrica:
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
};
Ideja ovih promena je da grupišete konstante na osnovu frekvencije ažuriranja. Per prolazne konstante
treba samo da se ažuriraju jednom po rendering prolazu, a objektne konstante samo treba da se
promeni kada se svetska matrica objekta promeni. Da smo imali statički objekat scena, kao drvo,
moramo samo jednom postaviti svoju svetsku matricu u konstantni bafer I onda nikada ponovo ne
ažurirajte konstantni bafer. U našim demo-sama, implementiramo sledeće metode za obradu ažuriranja
per passa i konstantnih bafera objekta. Ove metode se pozivaju jednom po okvirima u metodu
ažuriranja.
void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
{
auto currObjectCB = mCurrFrameResource-
>ObjectCB.get();
for(auto& e : mAllRitems)
{
// Only update the cbuffer data if the constants
have changed.
// This needs to be tracked per frame resource.
if(e->NumFramesDirty > 0)
{
XMMATRIX world = XMLoadFloat4x4(&e->World);
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.World,
XMMatrixTranspose(world));
currObjectCB->CopyData(e->ObjCBIndex,
objConstants);
// Next FrameResource need to be updated too.
e->NumFramesDirty—;
}
}
}
void ShapesApp::UpdateMainPassCB(const GameTimer& gt)
{
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX viewProj = XMMatrixMultiply(view, proj);
XMMATRIX invView =
XMMatrixInverse(&XMMatrixDeterminant(view), view);
XMMATRIX invProj =
XMMatrixInverse(&XMMatrixDeterminant(proj), proj);
XMMATRIX invViewProj =
XMMatrixInverse(&XMMatrixDeterminant(viewProj),
viewProj);
XMStoreFloat4x4(&mMainPassCB.View,
XMMatrixTranspose(view));
XMStoreFloat4x4(&mMainPassCB.InvView,
XMMatrixTranspose(invView));
XMStoreFloat4x4(&mMainPassCB.Proj,
XMMatrixTranspose(proj));
XMStoreFloat4x4(&mMainPassCB.InvProj,
XMMatrixTranspose(invProj));
XMStoreFloat4x4(&mMainPassCB.ViewProj,
XMMatrixTranspose(viewProj));
XMStoreFloat4x4(&mMainPassCB.InvViewProj,
XMMatrixTranspose(invViewProj));
mMainPassCB.EyePosW = mEyePos;
mMainPassCB.RenderTargetSize =
XMFLOAT2((float)mClientWidth, (float)mClientHeight);
mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f /
mClientWidth, 1.0f / mClientHeight);
mMainPassCB.NearZ = 1.0f;
mMainPassCB.FarZ = 1000.0f;
mMainPassCB.TotalTime = gt.TotalTime();
mMainPassCB.DeltaTime = gt.DeltaTime();
auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(0, mMainPassCB);
}
We update our vertex shader accordingly to support these constant buffer changes:
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosH = mul(posW, gViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
Dodatno vektorsko-matrično množenje po verteku ovo podešavanje daje zanemarljivo
moderni GPU-ovi, koji imaju dovoljno računske snage. Resursi koje su naši shaderi očekivali promijenili
su se; stoga, moramo da ažuriramo Root potpis tako da uzmemo dve deskriptivne tabele (trebaju nam
dve tabele jer je CBVs će biti podešeni na različite frekvencije - za svaki CBV klip treba samo jednom
postaviti rendering prolaz dok je za objekat CBV potrebno postaviti po objektu):
CD3DX12_DESCRIPTOR_RANGE cbvTable0;
cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
CD3DX12_DESCRIPTOR_RANGE cbvTable1;
cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[2];
// Create root CBVs.
slotRootParameter[0].InitAsDescriptorTable(1,
&cbvTable0);
slotRootParameter[1].InitAsDescriptorTable(1,
&cbvTable1);
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2,
slotRootParameter, 0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_
7.4 OBJEKTIVNA GEOMETRIJA
U ovom delu prikazujemo kako da napravimo geometriju elipsoida, sfera, cilindara
i čunjeva. Ovi oblici su korisni za crtanje nebeskih kupola, otklanjanje grešaka, vizualizaciju
otkrivanje sudara i odloženo rendering. Na primer, možda ćete želeti da ponovite sve
svoje karaktere igre kao sfere za debug test.
Stavili smo kod za proizvodnju geometrije u GeometriGenerator klasa (GeometriGenerator.h / .cpp).
GeometriGenerator je korisna klasa za generiranje jednostavnih geometrijskih oblika poput mreža, sfere,
cilindara i kutija koje koristimo kroz ovu knjigu za naše demo programe. Ova klasa generiše podatke u
sistemu memoriju, a zatim ćemo kopirati podatke koje želimo našim vertikama i indeksnim baferima.
GeometriGenerator kreira nekoliko vertek podataka koji će se koristiti u kasnijim poglavljima. Ne
trebamo ove podatke na našim trenutnim demo-snimcima, tako da mi ne kopiramo ove podatke u naše
bafere vertek-a. Struktura MeshData je jednostavna struktura ugrađena unutar
GeometriGenerator koji čuva listu verteksa i indeksa:
class GeometryGenerator
{p
ublic:
using uint16 = std::uint16_t;
using uint32 = std::uint32_t;
struct Vertex
{
Vertex(){}
Vertex(
const DirectX::XMFLOAT3& p,
const DirectX::XMFLOAT3& n,
const DirectX::XMFLOAT3& t,
const DirectX::XMFLOAT2& uv) :
Position(p),
Normal(n),
TangentU(t),
TexC(uv){}
Vertex(
float px, float py, float pz,
float nx, float ny, float nz,
float tx, float ty, float tz,
float u, float v) :
Position(px,py,pz),
Normal(nx,ny,nz),
TangentU(tx, ty, tz),
TexC(u,v){}
DirectX::XMFLOAT3 Position;
DirectX::XMFLOAT3 Normal;
DirectX::XMFLOAT3 TangentU;
DirectX::XMFLOAT2 TexC;
};
struct MeshData
{
std::vector<Vertex> Vertices;
std::vector<uint32> Indices32;
std::vector<uint16>& GetIndices16()
{
if(mIndices16.empty())
{
mIndices16.resize(Indices32.size());
for(size_t i = 0; i < Indices32.size(); ++i)
mIndices16[i] = static_cast<uint16>
(Indices32[i]);
}
return mIndices16;
}
private:
std::vector<uint16> mIndices16;
};

};
7.4.1 Generisanje cilindrične mreže
Cilindar definišemo tako što odredjujemo donji i gornji radijus, njegovu visinu i brojke i brojke, kao što je
prikazano na slici 7.1. Razbijamo cilindar na tri dela: 1) bočnu geometriju, 2) geometriju gornjeg
poklopca i 3) geometriju donjeg poklopca.
7.4.1 Generisanje cilindrične mreže
Definišemo cilindar tako što određujemo donji i gornji radijus, njegovu visinu i deo i brojanje stada, kao
što je prikazano na slici 7.1. Prebacujemo cilindar na tri dela: 1) stranu geometrija, 2) geometrija gornje
kapice i 3) geometrija donjeg poklopca. Slika 7.1. Na ovoj ilustraciji, cilindar sa leve strane ima osam
komada i četiri štuku i cilindar sa desne strane ima šesnaest komada i osam štapova. Rezine I stackovi
kontrolišu gustinu trougla. Imajte na umu da se gornji i donji radijus može razlikovati da možemo
napraviti konusne objekte, a ne samo "čiste" cilindre.
7.4.1.1 Bočna geometrija cilindra
Generišemo cilindar usredsređen na poreklo, paralelno sa i-osom. Iz slike 7.1, sve vertikale leže na
"prstenovima" cilindra, gde postoje stackCount + 1 prstenovi, I svaki prsten ima slikovne jedinstvene
vertikale. Razlika u radijusu između uzastopnih prstenovi su Dr = (topRadius - bottomRadius) /
stackCount. Ako počnemo sa donjim prstenom indeks 0, onda je poluprečnik i-tog prstena ri =
bottomRadius + iDr i visina s-I prsten je hi= -h2+I delta h
gde je Dh visina stega i h je visina cilindra. Dakle, osnovna ideja je da pređete
svaki prsten i generišu vertikale koji leže na tom prstenu. Ovo daje sledeće
implementacija:
GeometryGenerator::MeshData
GeometryGenerator::CreateCylinder(
float bottomRadius, float topRadius,
float height, uint32 sliceCount, uint32 stackCount)
{
MeshData meshData;
//
// Build Stacks.
//
float stackHeight = height / stackCount;
// Amount to increment radius as we move up each
stack level from
// bottom to top.
float radiusStep = (topRadius - bottomRadius) /
stackCount;
uint32 ringCount = stackCount+1;
// Compute vertices for each stack ring starting at
the bottom and
// moving up.
for(uint32 i = 0; i < ringCount; ++i)
{
float y = -0.5f*height + i*stackHeight;
float r = bottomRadius + i*radiusStep;
// vertices of ring
float dTheta = 2.0f*XM_PI/sliceCount;
for(uint32 j = 0; j <= sliceCount; ++j)
{
Vertex vertex;
float c = cosf(j*dTheta);
float s = sinf(j*dTheta);
vertex.Position = XMFLOAT3(r*c, y, r*s);
vertex.TexC.x = (float)j/sliceCount;
vertex.TexC.y = 1.0f - (float)i/stackCount;
// Cylinder can be parameterized as follows,
where we introduce v
// parameter that goes in the same direction as
the v tex-coord
// so that the bitangent goes in the same
direction as the
// v tex-coord.
// Let r0 be the bottom radius and let r1 be
the top radius.
// y(v) = h - hv for v in [0,1].
// r(v) = r1 + (r0-r1)v
//
// x(t, v) = r(v)*cos(t)
// y(t, v) = h - hv
// z(t, v) = r(v)*sin(t)
//
// dx/dt = -r(v)*sin(t)
// dy/dt = 0
// dz/dt = +r(v)*cos(t)
//
// dx/dv = (r0-r1)*cos(t)
// dy/dv = -h
// dz/dv = (r0-r1)*sin(t)
// This is unit length.
vertex.TangentU = XMFLOAT3(-s, 0.0f, c);
float dr = bottomRadius-topRadius;
XMFLOAT3 bitangent(dr*c, -height, dr*s);
XMVECTOR T = XMLoadFloat3(&vertex.TangentU);
XMVECTOR B = XMLoadFloat3(&bitangent);
XMVECTOR N =
XMVector3Normalize(XMVector3Cross(T, B));
XMStoreFloat3(&vertex.Normal, N);
meshData.Vertices.push_back(vertex);
}
}
Na slici 7.2 posmatrajte da postoji četvoro (dva trougla) za svaki deo u svakoj
stack. Slika 7.2 pokazuje da su indeksi za I stack i jth rezinu dati:
gde je n broj vertisa po prstenu. Dakle, ključna ideja je da se prekrižete na svakom segmentu
u svakom stacku i primijenite gore navedene formule.
// Add one because we duplicate the first and last
vertex per ring
// since the texture coordinates are different.
uint32 ringVertexCount = sliceCount+1;
// Compute indices for each stack.
for(uint32 i = 0; i < stackCount; ++i)
{
for(uint32 j = 0; j < sliceCount; ++j)
{
meshData.Indices32.push_back(i*ringVertexCount +
j);
meshData.Indices32.push_back((i+1)*ringVertexCount
+ j);
meshData.Indices32.push_back((i+1)*ringVertexCount
+ j+1);
meshData.Indices32.push_back(i*ringVertexCount +
j);
meshData.Indices32.push_back((i+1)*ringVertexCount
+ j+1);
meshData.Indices32.push_back(i*ringVertexCount +
j+1);
}
}
BuildCylinderTopCap(bottomRadius, topRadius,
height,
sliceCount, stackCount, meshData);
BuildCylinderBottomCap(bottomRadius, topRadius,
height,
sliceCount, stackCount, meshData);
return meshData;
}
7.4.1.2 Geometrija poklopca
Generisanje geometrije poklopaca iznosi stvaranje trouglova reza gornje i donje prstenje kako bi se
približio krug:
void GeometryGenerator::BuildCylinderTopCap(
float bottomRadius, float topRadius, float height,
uint32 sliceCount, uint32 stackCount, MeshData&
meshData)
{
uint32 baseIndex = (uint32)meshData.Vertices.size();
float y = 0.5f*height;
float dTheta = 2.0f*XM_PI/sliceCount;
// Duplicate cap ring vertices because the texture
coordinates and
// normals differ.
for(uint32 i = 0; i <= sliceCount; ++i)
{
float x = topRadius*cosf(i*dTheta);
float z = topRadius*sinf(i*dTheta);
// Scale down by the height to try and make top
cap texture coord
// area proportional to base.
float u = x/height + 0.5f;
float v = z/height + 0.5f;
meshData.Vertices.push_back(
Vertex(x, y, z, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.0f, u, v) );
}
// Cap center vertex.
meshData.Vertices.push_back(
Vertex(0.0f, y, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.5f, 0.5f) );
// Index of center vertex.
uint32 centerIndex =
(uint32)meshData.Vertices.size()-1;
for(uint32 i = 0; i < sliceCount; ++i)
{
meshData.Indices32.push_back(centerIndex);
meshData.Indices32.push_back(baseIndex + i+1);
meshData.Indices32.push_back(baseIndex + i);
}
}
Kod donjeg poklopca je analogan.

7.4.2 Generisanje Mreže sfere


Definišemo sferu tako što određujemo njegov radijus i brojčinu i brojanje stada, kao što je prikazano na
slici
Slika 7.3. Algoritam za stvaranje sfere je veoma sličan onom u cilindru,
osim što je radijus perverzne promene nelinearni način zasnovan na trigonometrijskom
funkcije. Ostavićemo ga čitatelju da proučava
GeometriGenerator :: CreateSphere kod. Imajte na umu da možemo primijeniti neuniformat
skaliranje svetske transformacije da transformiše sferu u elipsoid.
Slika 7.3. Ideja o rezovima i stackovima odnosi se i na sferu koja kontroliše
nivo tezelacije.
7.4.3 Generisanje geosfere mreže
Na slici 7.3 posmatramo da trouglovi sfere nemaju jednake površine. Ovo
mogu biti nepoželjni u nekim situacijama. Geosfera približava sferu pomoću trouglova
sa skoro jednakim površinama, kao i jednakim dužinama strane (vidi sliku 7.4).
Slika 7.4. Približavanje geosfere ponovljenom podjeljenjem i ponovnim prikazom
na sferu.
Da generišemo geosferu, počinjemo sa ikosaederom, podijelimo trouglove i
zatim projicirati nove vertike na sferu sa datim radijusom. To možemo ponoviti
proces za poboljšanje tezelacije.
Slika 7.5 prikazuje kako se trougao može podijeliti na četiri ravnopravne trouglove.
Nove vertikale se mogu naći samo uzimanjem srednjih tačaka duž ivica originalnog
trougao. Novi vertici se onda mogu projektovati na sferu radijusa r projektiranjem
vrha na jedinicu sfere, a zatim skalarno množenje r/v’=r v/IvI
Kod je dat kao:
GeometryGenerator::MeshData
GeometryGenerator::CreateGeosphere(float radius,
uint32 numSubdivisions)
{
MeshData meshData;
// Put a cap on the number of subdivisions.
numSubdivisions = std::min<uint32>(numSubdivisions,
6u);
// Approximate a sphere by tessellating an
icosahedron.
const float X = 0.525731f;
const float Z = 0.850651f;
XMFLOAT3 pos[12] =
{
XMFLOAT3(-X, 0.0f, Z), XMFLOAT3(X, 0.0f, Z),
XMFLOAT3(-X, 0.0f, -Z), XMFLOAT3(X, 0.0f, -Z),
XMFLOAT3(0.0f, Z, X), XMFLOAT3(0.0f, Z, -X),
XMFLOAT3(0.0f, -Z, X), XMFLOAT3(0.0f, -Z, -X),
XMFLOAT3(Z, X, 0.0f), XMFLOAT3(-Z, X, 0.0f),
XMFLOAT3(Z, -X, 0.0f), XMFLOAT3(-Z, -X, 0.0f)
};
uint32 k[60] =
{
1,4,0, 4,9,0, 4,5,9, 8,5,4, 1,8,4,
1,10,8, 10,3,8, 8,3,5, 3,2,5, 3,7,2,
3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0,
10,1,6, 11,0,9, 2,11,9, 5,2,9, 11,2,7
};
meshData.Vertices.resize(12);
meshData.Indices32.assign(&k[0], &k[60]);
for(uint32 i = 0; i < 12; ++i)
meshData.Vertices[i].Position = pos[i];
for(uint32 i = 0; i < numSubdivisions; ++i)
Subdivide(meshData);
// Project vertices onto sphere and scale.
for(uint32 i = 0; i < meshData.Vertices.size(); ++i)
{
// Project onto unit sphere.
XMVECTOR n =
XMVector3Normalize(XMLoadFloat3(&meshData.Vertices[i].Position));
// Project onto sphere.
XMVECTOR p = radius*n;
XMStoreFloat3(&meshData.Vertices[i].Position, p);
XMStoreFloat3(&meshData.Vertices[i].Normal, n);
// Derive texture coordinates from spherical
coordinates.
float theta =
atan2f(meshData.Vertices[i].Position.z,
meshData.Vertices[i].Position.x);
// Put in [0, 2pi].
if(theta < 0.0f)
theta += XM_2PI;
float phi = acosf(meshData.Vertices[i].Position.y
/ radius);
meshData.Vertices[i].TexC.x = theta/XM_2PI;
meshData.Vertices[i].TexC.y = phi/XM_PI;
// Partial derivative of P with respect to theta
meshData.Vertices[i].TangentU.x = -
radius*sinf(phi)*sinf(theta);
meshData.Vertices[i].TangentU.y = 0.0f;
meshData.Vertices[i].TangentU.z =
+radius*sinf(phi)*cosf(theta);
XMVECTOR T =
XMLoadFloat3(&meshData.Vertices[i].TangentU);
XMStoreFloat3(&meshData.Vertices[i].TangentU,
XMVector3Normalize(T));
}
return meshData;
}

7.5 SHAPES DEMO


Da bismo demonstrirali naš kod generacije sfere i cilindara, implementiramo "Oblici"
demo prikazano na slici 7.6. Osim toga, dobićete i iskustvo pozicioniranja i
crtanje više objekata u sceni (tj. stvaranje više matrica transformacije svetova).
Štaviše, postavljamo svu geometriju scene u jedan veliki vertek i indeksni bafer. Onda
mi ćemo koristiti DravIndekedInstanced metod da nacrtamo jedan objekat odjednom (kao što je
svetska matrica mora biti promenjena između objekata); pa ćete videti primer korišćenja
parametri StartIndekLocation i BaseVertekLocation
DravIndekedInstanced.
Slika 7.6. Snimak ekrana "Shapes" demo.
7.5.1 Vertek i Indeks Buffers
Kao što je prikazano na slici 7.6, u ovom demou nacrtamo kutiju, mrežu, cilindre i sferu. Čak
iako u ovom demou crtamo više sfere i cilindre, potrebna nam je samo jedna kopija
sfera i geometrija cilindara. Jednostavno prelistavamo istu sferu i cilindričnu mrežu
više puta, ali sa različitim svetskim matricama; ovo je primer instanciranja
geometrija, koja štedi memoriju.
Spakujemo sve vertikale mreže i indekse u jedan verteks i indeksni bafer. Ovo je
obavlja se povezivanjem verteka i indeksnih nizova. To znači da kada crtamo
objekat, samo crtamo podskup od verteksa i indeksnih bafera. Postoje tri
količine koje trebamo znati da bi se izvukli samo podskup geometrije
ID3D12CommandList :: DravIndekedInstanced (opozvati Sliku 6.3 i
diskusija o tome iz Glave 6). Moramo znati početni indeks u objektu
koncenirani indeksni bafer, broj indeksa, i moramo znati osnovni vertek
lokacija - indeks prve vertek objekta u odnosu na kaskatorovani bafer verteka.
Podsjetimo da je osnovna lokacija verteksa cijela vrijednost dodata indeksima u pozivu za izvlačenje
pre preuzimanja vertikala, tako da se indeksi odnose na odgovarajuću podgrupu u
koncenirani bafer verteka. (Pogledajte i Vežbu 2 u Poglavlju 5.)
Kod ispod pokazuje kako se stvaraju baferi geometrije, kako je neophodno
crteži se cached, i kako se nacrtaju objekti.
void ShapesApp::BuildShapeGeometry()
{
GeometryGenerator geoGen;
GeometryGenerator::MeshData box =
geoGen.CreateBox(1.5f, 0.5f, 1.5f, 3);
GeometryGenerator::MeshData grid =
geoGen.CreateGrid(20.0f, 30.0f, 60, 40);
GeometryGenerator::MeshData sphere =
geoGen.CreateSphere(0.5f, 20, 20);
GeometryGenerator::MeshData cylinder =
geoGen.CreateCylinder(0.5f, 0.3f, 3.0f, 20, 20);
//
// We are concatenating all the geometry into one
big vertex/index
// buffer. So define the regions in the buffer each
submesh covers.
//
// Cache the vertex offsets to each object in the
concatenated vertex
// buffer.
UINT boxVertexOffset = 0;
UINT gridVertexOffset = (UINT)box.Vertices.size();
UINT sphereVertexOffset = gridVertexOffset +
(UINT)grid.Vertices.size();
UINT cylinderVertexOffset = sphereVertexOffset +
(UINT)sphere.Vertices.size();
// Cache the starting index for each object in the
concatenated index
// buffer.
UINT boxIndexOffset = 0;
UINT gridIndexOffset = (UINT)box.Indices32.size();
UINT sphereIndexOffset = gridIndexOffset +
(UINT)grid.Indices32.size();
UINT cylinderIndexOffset = sphereIndexOffset +
(UINT)sphere.Indices32.size();
// Define the SubmeshGeometry that cover different
// regions of the vertex/index buffers.
SubmeshGeometry boxSubmesh;
boxSubmesh.IndexCount = (UINT)box.Indices32.size();
boxSubmesh.StartIndexLocation = boxIndexOffset;
boxSubmesh.BaseVertexLocation = boxVertexOffset;
SubmeshGeometry gridSubmesh;
gridSubmesh.IndexCount =
(UINT)grid.Indices32.size();
gridSubmesh.StartIndexLocation = gridIndexOffset;
gridSubmesh.BaseVertexLocation = gridVertexOffset;
SubmeshGeometry sphereSubmesh;
sphereSubmesh.IndexCount =
(UINT)sphere.Indices32.size();
sphereSubmesh.StartIndexLocation =
sphereIndexOffset;
sphereSubmesh.BaseVertexLocation =
sphereVertexOffset;
SubmeshGeometry cylinderSubmesh;
cylinderSubmesh.IndexCount =
(UINT)cylinder.Indices32.size();
cylinderSubmesh.StartIndexLocation =
cylinderIndexOffset;
cylinderSubmesh.BaseVertexLocation =
cylinderVertexOffset;
//
// Extract the vertex elements we are interested in
and pack the
// vertices of all the meshes into one vertex
buffer.
//
auto totalVertexCount =
box.Vertices.size() +
grid.Vertices.size() +
sphere.Vertices.size() +
cylinder.Vertices.size();
std::vector<Vertex> vertices(totalVertexCount);
UINT k = 0;
for(size_t i = 0; i < box.Vertices.size(); ++i, ++k)
{
vertices[k].Pos = box.Vertices[i].Position;
vertices[k].Color =
XMFLOAT4(DirectX::Colors::DarkGreen);
}
for(size_t i = 0; i < grid.Vertices.size(); ++i,
++k)
{
vertices[k].Pos = grid.Vertices[i].Position;
vertices[k].Color =
XMFLOAT4(DirectX::Colors::ForestGreen);
}
for(size_t i = 0; i < sphere.Vertices.size(); ++i,
++k)
{
vertices[k].Pos = sphere.Vertices[i].Position;
vertices[k].Color =
XMFLOAT4(DirectX::Colors::Crimson);
}
for(size_t i = 0; i < cylinder.Vertices.size(); ++i,
++k)
{
vertices[k].Pos = cylinder.Vertices[i].Position;
vertices[k].Color =
XMFLOAT4(DirectX::Colors::SteelBlue);
}
std::vector<std::uint16_t> indices;
indices.insert(indices.end(),
std::begin(box.GetIndices16()),
std::end(box.GetIndices16()));
indices.insert(indices.end(),
std::begin(grid.GetIndices16()),
std::end(grid.GetIndices16()));
indices.insert(indices.end(),
std::begin(sphere.GetIndices16()),
std::end(sphere.GetIndices16()));
indices.insert(indices.end(),
std::begin(cylinder.GetIndices16()),
std::end(cylinder.GetIndices16()));
const UINT vbByteSize = (UINT)vertices.size() *
sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() *
sizeof(std::uint16_t);
auto geo = std::make_unique<MeshGeometry>();
geo->Name = “shapeGeo”;
ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo-
>VertexBufferCPU));
CopyMemory(geo->VertexBufferCPU->GetBufferPointer(),
vertices.data(),
vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo-
>IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(),
indices.data(),
ibByteSize);
geo->VertexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize,
geo->VertexBufferUploader);
geo->IndexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize,
geo->IndexBufferUploader);
geo->VertexByteStride = sizeof(Vertex);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;
geo->DrawArgs[“box”] = boxSubmesh;
geo->DrawArgs[“grid”] = gridSubmesh;
geo->DrawArgs[“sphere”] = sphereSubmesh;
geo->DrawArgs[“cylinder”] = cylinderSubmesh;
mGeometries[geo->Name] = std::move(geo);
}
The mGeometries variable used in the last line of the above method is defined like
so:
std::unordered_map<std::string,
std::unique_ptr<MeshGeometry>> mGeometries;
Ovo je glomazno kreirajte novi naziv promenljive za svaku geometriju, PSO, teksturu, shader itd., tako da
koristimo neuređene mape za konstantno traženje vremena i upućujemo naše objekte po imenu. Evo još
nekoliko primera:
std::unordered_map<std::string,
std::unique_ptr<MeshGeometry>> mGeometries;
std::unordered_map<std::string, ComPtr<ID3DBlob>>
mShaders;
std::unordered_map<std::string,
ComPtr<ID3D12PipelineState>> mPSOs;

7.5.2 Renderovanje objekata


Sada definišemo stavke za prikazivanje scene. Obratite pažnju na to kako sve stavke prikazuju
istu MeshGeometri, a mi koristimo DravArgs da dobijemo DravIndekedInstanced parametre da
nacrtamo podregiju bafera verteka / indeksa.
// ShapesApp member variable.
std::vector<std::unique_ptr<RenderItem>> mAllRitems;
std::vector<RenderItem*> mOpaqueRitems;
void ShapesApp::BuildRenderItems()
{
auto boxRitem = std::make_unique<RenderItem>();
XMStoreFloat4x4(&boxRitem->World,
XMMatrixScaling(2.0f, 2.0f,
2.0f)*XMMatrixTranslation(0.0f, 0.5f, 0.0f));
boxRitem->ObjCBIndex = 0;
boxRitem->Geo = mGeometries[“shapeGeo”].get();
boxRitem->PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
boxRitem->IndexCount = boxRitem->Geo-
>DrawArgs[“box”].IndexCount;
boxRitem->StartIndexLocation = boxRitem->Geo>
DrawArgs[“box”].
StartIndexLocation;
boxRitem->BaseVertexLocation = boxRitem->Geo-
>DrawArgs[“box”].
BaseVertexLocation;
mAllRitems.push_back(std::move(boxRitem));
auto gridRitem = std::make_unique<RenderItem>();
gridRitem->World = MathHelper::Identity4x4();
gridRitem->ObjCBIndex = 1;
gridRitem->Geo = mGeometries[“shapeGeo”].get();
gridRitem->PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
gridRitem->IndexCount = gridRitem->Geo-
>DrawArgs[“grid”].IndexCount;
gridRitem->StartIndexLocation = gridRitem->Geo-
>DrawArgs[“grid”].
StartIndexLocation;
gridRitem->BaseVertexLocation = gridRitem->Geo-
>DrawArgs[“grid”].
BaseVertexLocation;
mAllRitems.push_back(std::move(gridRitem));
// Build the columns and spheres in rows as in
Figure 7.6.
UINT objCBIndex = 2;
for(int i = 0; i < 5; ++i)
{
auto leftCylRitem = std::make_unique<RenderItem>
();
auto rightCylRitem = std::make_unique<RenderItem>
();
auto leftSphereRitem =
std::make_unique<RenderItem>();
auto rightSphereRitem =
std::make_unique<RenderItem>();
XMMATRIX leftCylWorld = XMMatrixTranslation(-5.0f,
1.5f, -10.0f + i*5.0f);
XMMATRIX rightCylWorld =
XMMatrixTranslation(+5.0f, 1.5f, -10.0f + i*5.0f);
XMMATRIX leftSphereWorld =
XMMatrixTranslation(-5.0f, 3.5f, -10.0f + i*5.0f);
XMMATRIX rightSphereWorld =
XMMatrixTranslation(+5.0f, 3.5f, -10.0f
+ i*5.0f);
XMStoreFloat4x4(&leftCylRitem->World,
rightCylWorld);
leftCylRitem->ObjCBIndex = objCBIndex++;
leftCylRitem->Geo = mGeometries[“shapeGeo”].get();
leftCylRitem->PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
leftCylRitem->IndexCount = leftCylRitem->Geo-
>DrawArgs[“cylinder”].
IndexCount;
leftCylRitem->StartIndexLocation =
leftCylRitem->Geo-
>DrawArgs[“cylinder”].StartIndexLocation;
leftCylRitem->BaseVertexLocation =
leftCylRitem->Geo-
>DrawArgs[“cylinder”].BaseVertexLocation;
XMStoreFloat4x4(&rightCylRitem->World,
leftCylWorld);
rightCylRitem->ObjCBIndex = objCBIndex++;
rightCylRitem->Geo =
mGeometries[“shapeGeo”].get();
rightCylRitem->PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
rightCylRitem->IndexCount = rightCylRitem-> Geo-
>DrawArgs[“cylinder”].
IndexCount;
rightCylRitem->StartIndexLocation =
rightCylRitem->Geo-
>DrawArgs[“cylinder”].StartIndexLocation;
rightCylRitem->BaseVertexLocation =
rightCylRitem->Geo-
>DrawArgs[“cylinder”].BaseVertexLocation;
XMStoreFloat4x4(&leftSphereRitem->World,
leftSphereWorld);
leftSphereRitem->ObjCBIndex = objCBIndex++;
leftSphereRitem->Geo =
mGeometries[“shapeGeo”].get();
leftSphereRitem->PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
leftSphereRitem->IndexCount = leftSphereRitem-
>Geo->DrawArgs[“sphere”].
IndexCount;
leftSphereRitem->StartIndexLocation =
leftSphereRitem->Geo-
>DrawArgs[“sphere”].StartIndexLocation;
leftSphereRitem->BaseVertexLocation =
leftSphereRitem->Geo-
>DrawArgs[“sphere”].BaseVertexLocation;
XMStoreFloat4x4(&rightSphereRitem->World,
rightSphereWorld);
rightSphereRitem->ObjCBIndex = objCBIndex++;
rightSphereRitem->Geo =
mGeometries[“shapeGeo”].get();
rightSphereRitem->PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
rightSphereRitem->IndexCount = rightSphereRitem-
>Geo->DrawArgs[“sphere”].
IndexCount;
rightSphereRitem->StartIndexLocation =
rightSphereRitem->Geo-
>DrawArgs[“sphere”].StartIndexLocation;
rightSphereRitem->BaseVertexLocation =
rightSphereRitem->Geo-
>DrawArgs[“sphere”].BaseVertexLocation;
mAllRitems.push_back(std::move(leftCylRitem));
mAllRitems.push_back(std::move(rightCylRitem));
mAllRitems.push_back(std::move(leftSphereRitem));
mAllRitems.push_back(std::move(rightSphereRitem));
}
// All the render items are opaque in this demo.
for(auto& e : mAllRitems)
mOpaqueRitems.push_back(e.get());
}
7.5.3 Frame Resources i Constant Buffer Vievs
Podsjetimo da imamo vektor FrameResources, a svaki FrameResource ima
bafer za otpremu za skladištenje konstante pasusa i konstantnih bafera za svaku stavku za prikazivanje u
sceni.

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:

Texture2D gDiffuseMap : register(t0);


cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
};
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;
Light gLights[MaxLights];
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
The root signature for this shader would be described as follows:
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(
D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
1, // number of descriptors
0); // register t0
// 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].InitAsDescriptorTable(1,
&texTable, D3D12_SHADER_VISIBILITY_PIXEL);
slotRootParameter[1].InitAsConstantBufferView(0); //
register b0
slotRootParameter[2].InitAsConstantBufferView(1); //
register b1
slotRootParameter[3].InitAsConstantBufferView(2); //
register b2
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4,
slotRootParameter,
0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INP
7.6.6 Verifikacija parametara korena
Root argumenti odnose se na stvarne vrednosti koje prosledimo na root parametre. Smatra da je
sledeći kod gde ćemo promeniti korijenske argumente (samo tabela deskriptora u ovom slučaju) između
poziva poziva:
for(size_t i = 0; i < mRitems.size(); ++i)
{
const auto& ri = mRitems[i];

// Offset to the CBV for this frame and this render
item.
int cbvOffset = mCurrFrameResourceIndex*
(int)mRitems.size();
cbvOffset += ri.CbIndex;
cbvHandle.Offset(cbvOffset, mCbvSrvDescriptorSize);
// Identify descriptors to use for this draw call.
cmdList->SetGraphicsRootDescriptorTable(0,
cbvHandle);
cmdList->DrawIndexedInstanced(
ri.IndexCount, 1,
ri.StartIndexLocation,
ri.BaseVertexLocation, 0);
}
Svaki poziv za izvlačenje će biti izvršen sa trenutno postavljenim korijenskim argumentima na
vreme pozivnog poziva. Ovo funkcioniše jer hardver automatski čuva snimak
trenutno stanje korenskih argumenata za svaki poziv za poziv. Drugim rečima, root
argumenti se automatski verifikuju za svaki poziv poziva.
Imajte na umu da root potpis može obezbediti više polja nego što koristi shader. Na primer, ako
Root potpis označava root CBV u root parametru 2, ali shader ne koristi
taj konstantni pufer, onda je ova kombinacija validna sve dok root-potpis ne radi
navedite sve resurse koje shader koristi.
Za performanse, cilj bi trebalo da bude mali. Jedan od razloga za ovo
je automatsko verovanje osnovnih argumenata po pozivu. Što je veći koren
potpis, veći će biti snimci korijenskih argumenata. Pored toga, SDK
dokumentacija savetuje da root parametre treba poručiti u korenskom potpisu
najčešće se menjaju najčešće promenjeni. Dokumentacija Direct3D 12 takođe
savetuje da izbegne prebacivanje root signala kada je to moguće, pa je dobra ideja da se podeli
isti korenski potpis u mnogim PSO-ovi koje kreirate. Konkretno, može biti korisno
imaju "super" korijenski potpis koji radi sa nekoliko programa shadera, iako nisu svi
shaderi koriste sve parametre koje korenski potpis definiše. Sa druge strane, takođe
ovisi koliko je veliki ovaj "super" korenski potpis za to da radi. Ako je prevelika, to je
može otkazati dobitke da ne prebacuje root potpis.
7.7 ZEMLJI I VALOVI DEMO
U ovom odeljku prikazujemo kako napraviti demonstraciju "Land and Vaves" prikazana na slici
7.7. Ova demo konstruiše mrežnu mrežu trouglasta proceduralno i pomera visine vrhova
da napravite teren. Pored toga, koristi drugu trouglastu mrežu da predstavlja vodu, i
animira vertikalne visine da stvara talase. Ova demo se takođe prebacuje na koren
deskriptori za konstantne bafere, što nam omogućava da ispustimo podršku za skripte deskriptora
CBVs.
Slika 7.7. Snimak ekrana "Zemlje i talasi" demo. Zato što nemamo
još uvek nije teško vidjeti oblik talasa. Držite taster "1"
pogledajte scenu u režim žične slike da biste bolje videli talase.
Grafikon "lijepe" stvarne funkcije i = f (k, z) je površina. Mi Možemo
približiti površinu konstrukcijom mreže u kz-ravnini, gde je svaki kuad izgrađen
iz dva trougla, a zatim primjenjuje funkciju na svaku mrežnu tačku; vidi sliku 7.8.
Slika 7.8. (Vrh) Postavite mrežu u kz-ravni. (Dno) Za svaku mrežnu tačku,
primenite funkciju f (k, z) da biste dobili i-koordinat. Parametar tačaka (k, f (k, z),
z) daje grafikon površine.
7.7.1 Generisanje vertikalnih mreža
Dakle, glavni zadatak je kako izgraditi mrežu u kz-ravni. Mreža m × n vertices
indukuje (m - 1) × (n - 1) četverice (ili ćelije), kao što je prikazano na slici 7.9. Svaka ćelija će biti
pokriveni sa dva trougla, tako da postoje ukupno 2 (m - 1) × (n - 1) trouglovi. Ako mreža ima
širina v i dubina d, razmak ćelija duž k-ose je dk = v / (n-1) i ćelija
razmak duž z-ose je dz = d / (m-1). Da generišemo vertikale, počinjemo na gornjoj strani
ugao i inkrementalno izračunati vertikalni koordinatni red-po-redu. Koordinate
Ijth grid verteka u kz-ravnini daju:
Slika 7.9. Izgradnja mreže.
Sledeći

The following code generates the grid vertices:


GeometryGenerator::MeshData
GeometryGenerator::CreateGrid(float width, float
depth, uint32 m, uint32 n)
{
MeshData meshData;
uint32 vertexCount = m*n;
uint32 faceCount = (m-1)*(n-1)*2;
float halfWidth = 0.5f*width;
float halfDepth = 0.5f*depth;
float dx = width / (n-1);
float dz = depth / (m-1);
float du = 1.0f / (n-1);
float dv = 1.0f / (m-1);
meshData.Vertices.resize(vertexCount);
for(uint32 i = 0; i < m; ++i)
{
float z = halfDepth - i*dz;
for(uint32 j = 0; j < n; ++j)
{
float x = -halfWidth + j*dx;
meshData.Vertices[i*n+j].Position = XMFLOAT3(x,
0.0f, z);
meshData.Vertices[i*n+j].Normal =
XMFLOAT3(0.0f, 1.0f, 0.0f);
meshData.Vertices[i*n+j].TangentU =
XMFLOAT3(1.0f, 0.0f, 0.0f);
// Stretch texture over grid.
meshData.Vertices[i*n+j].TexC.x = j*du;
meshData.Vertices[i*n+j].TexC.y = i*dv;
}
}

7.7.2 Generisanje Indeksa mreže


Nakon što smo izračunali vertikale, potrebno je definisati trouglove mreže pomoću navodeći indekse. Da
bismo to uradili, ponavljamo se iznad svakog kvadrata, ponovo po redosledu od početka gornji levi i
izračunati indekse da definišu dva trougla kvadrata; koji se odnosi na Slika 7.10, za m × n vertek mrežu,
indeksi linearne matrice dva trougla su izračunato na sledeći način:
Slika 7.10. Indeksi vertikala ijth kuad-a.
Odgovarajući kod:
meshData.Indices32.resize(faceCount*3); // 3 indices
per face
// Iterate over each quad and compute indices.
uint32 k = 0;
for(uint32 i = 0; i < m-1; ++i)
{
for(uint32 j = 0; j < n-1; ++j)
{
meshData.Indices32[k] = i*n+j;
meshData.Indices32[k+1] = i*n+j+1;
meshData.Indices32[k+2] = (i+1)*n+j;
meshData.Indices32[k+3] = (i+1)*n+j;
meshData.Indices32[k+4] = i*n+j+1;
meshData.Indices32[k+5] = (i+1)*n+j+1;
k += 6; // next quad
}
}
return meshData;
}
7.7.3 Primena funkcije visine
Nakon što smo kreirali mrežu, možemo izvući elemente verteka koje želimo od MeshData mrežu,
pretvorite ravnu mrežu u površinu koja predstavlja brdove i generišite boju za svaku vertiksu na osnovu
vertikalne nadmorske visine (i-koordinata).
// Not to be confused with GeometryGenerator::Vertex.
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
void LandAndWavesApp::BuildLandGeometry()
{
GeometryGenerator geoGen;
GeometryGenerator::MeshData grid =
geoGen.CreateGrid(160.0f, 160.0f, 50, 50);
//
// Extract the vertex elements we are interested and
apply the height
// function to each vertex. In addition, color the
vertices based on
// their height so we have sandy looking beaches,
grassy low hills,
// and snow mountain peaks.
//
std::vector<Vertex> vertices(grid.Vertices.size());
for(size_t i = 0; i < grid.Vertices.size(); ++i)
{
auto& p = grid.Vertices[i].Position;
vertices[i].Pos = p;
vertices[i].Pos.y = GetHillsHeight(p.x, p.z);
// Color the vertex based on its height.
if(vertices[i].Pos.y < -10.0f)
{
// Sandy beach color.
vertices[i].Color = XMFLOAT4(1.0f, 0.96f, 0.62f,
1.0f);
}
else if(vertices[i].Pos.y < 5.0f)
{
// Light yellow-green.
vertices[i].Color = XMFLOAT4(0.48f, 0.77f,
0.46f, 1.0f);
}
else if(vertices[i].Pos.y < 12.0f)
{
// Dark yellow-green.
vertices[i].Color = XMFLOAT4(0.1f, 0.48f, 0.19f,
1.0f);
}
else if(vertices[i].Pos.y < 20.0f)
{
// Dark brown.
vertices[i].Color = XMFLOAT4(0.45f, 0.39f,
0.34f, 1.0f);
}
else
{
// White snow.
vertices[i].Color = XMFLOAT4(1.0f, 1.0f, 1.0f,
1.0f);
}
}
const UINT vbByteSize = (UINT)vertices.size() *
sizeof(Vertex);
std::vector<std::uint16_t> indices =
grid.GetIndices16();
const UINT ibByteSize = (UINT)indices.size() *
sizeof(std::uint16_t);
auto geo = std::make_unique<MeshGeometry>();
geo->Name = “landGeo”;
ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo-
>VertexBufferCPU));
CopyMemory(geo->VertexBufferCPU->GetBufferPointer(),
vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo-
>IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(),
indices.data(), ibByteSize);
geo->VertexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize,
geo->VertexBufferUploader);
geo->IndexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(),
ibByteSize, geo->IndexBufferUploader);
geo->VertexByteStride = sizeof(Vertex);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;
SubmeshGeometry submesh;
submesh.IndexCount = (UINT)indices.size();
submesh.StartIndexLocation = 0;
submesh.BaseVertexLocation = 0;
geo->DrawArgs[“grid”] = submesh;
mGeometries[“landGeo”] = std::move(geo);
}
The function f(x, z) we have used in this demo is given by:
float LandAndWavesApp::GetHeight(float x, float
z)const
{
return 0.3f*(z*sinf(0.1f*x) + x*cosf(0.1f*z));
}
Grafikon izgleda donekle kao teren sa brdima i dolinama (vidi sliku 7.7).
7.7.4 Root CBVs
Druga promena koju smo napravili demo-u "Land and Vaves" iz prethodne demone "Shape" -a je da
koristimo root deskriptore tako da možemo vezati CBV-ove direktno bez korištenja skripte deskriptora.
Evo izmena koje treba uraditi da bi se ovo uradilo:
1. Root potpis mora biti promenjen tako da uzima dva root CBV umesto dva
descriptor tables.
2. Ne treba kupiti CBV, niti je potrebno popuniti deskriptorima.
3. Postoji nova sintaksa za vezivanje root deskriptora.
Novi korenski potpis je definisan kao:
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[2];
// Create root CBV.
slotRootParameter[0].InitAsConstantBufferView(0); //
per-object CBV
slotRootParameter[1].InitAsConstantBufferView(1); //
per-pass CBV
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2,
slotRootParameter, 0,
nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYO
Grafikon izgleda donekle kao teren sa brdima i dolinama (vidi sliku 7.7).
7.7.4 Root CBVs
Druga promena koju smo napravili demo-u "Land and Vaves" iz prethodne demone "Shape" -a je da
koristimo root deskriptore tako da možemo vezati CBV-ove direktno bez korištenja skripte deskriptora.
Evo izmena koje treba uraditi da bi se ovo uradilo:
1. Root potpis mora biti promenjen tako da uzima dva root CBV umesto dva
descriptor tables.
2. Ne treba kupiti CBV, niti je potrebno popuniti deskriptorima.
3. Postoji nova sintaksa za vezivanje root deskriptora.
Novi korenski potpis je definisan kao:
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[2];
// Create root CBV.
slotRootParameter[0].InitAsConstantBufferView(0); //
per-object CBV
slotRootParameter[1].InitAsConstantBufferView(1); //
per-pass CBV
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2,
slotRootParameter, 0,
nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
Obratite pažnju da koristimo metod InitAsConstantBufferViev helper za kreiranje
root CBV; parametar specificira registar shadera za koji je ovaj parametar vezan (u
iznad šifre, shader konstantni buffer registar "b0" i "b1").
Sada, vezujemo CBV kao argument korijenskom deskriptoru koristeći sledeći metod:
void
ID3D12GraphicsCommandList::SetGraphicsRootConstantBufferView(
UINT RootParameterIndex,
D3D12_GPU_VIRTUAL_ADDRESS BufferLocation);
1. RootParameterIndex: The index of the root parameter we are binding a CBV
to.
2. BufferLocation: The virtual address to the resource that contains the constant
buffer data.
With this change, our drawing code now looks like this:
void LandAndWavesApp::Draw(const GameTimer& gt)
{
[…]
// Bind per-pass constant buffer. We only need to do
this once per-
// pass.
auto passCB = mCurrFrameResource->PassCB-
>Resource();
mCommandList->SetGraphicsRootConstantBufferView(1,
passCB-
>GetGPUVirtualAddress());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Opaque]);
[…]
}
void LandAndWavesApp::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);
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress
= objectCB->GetGPUVirtualAddress();
objCBAddress += ri->ObjCBIndex*objCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(0,
objCBAddress);
cmdList->DrawIndexedInstanced(ri->IndexCount, 1,
ri->StartIndexLocation, ri->BaseVertexLocation,
0);
}
}
7.7.5 Dinamički Bufferi Vertek-a
Do sada smo pohranili naše vertikale u podrazumevani izvor bafera. Koristimo ovu vrstu
resurs kada želimo da sačuva statičku geometriju. To jest, geometrija koju ne menjamo
- podesili smo podatke, a GPU čita i crta podatke. Dinamički bafer vertek je
gde često menjamo podatke o vertici, recimo per-frame. Na primer, pretpostavimo da jesmo
radi simulaciju talasa i rešavamo jednačinu talasa za funkciju rešenja f (k, z,
t). Ova funkcija predstavlja visinu talasa u svakoj tački u kz ravni u trenutku t. Ako smo mi
trebali smo koristiti ovu funkciju da nacrtamo talase, koristićemo mrežnu mrežu trouglasta kao i mi
sa vrhovima i dolinama, i primijeniti f (k, z, t) u svaku mrežnu tačku kako bi dobili
visine talasa na tačkama mreže. Budući da ova funkcija zavisi i od vremena t (tj
površina talasa se menja sa vremenom), moramo ponovo da ponovimo ovu funkciju na tačke mreže
kratko vreme kasnije (recimo svaka 1/30 sekunde) da biste dobili glatku animaciju. Tako nam je
potrebno
dinamički bafer verteka kako bi se ažurirali visine vertikalnih mrežnih mreža kao što su
vreme prolazi. Druga situacija koja dovodi do dinamičkih bafera verteka su sistemi sa česticama
kompleksna fizika i otkrivanje sudara. Svaki okvir će raditi fiziku i sudar
otkrivanje na CPU-u da bi pronašao novu poziciju čestica. Zbog čestice
pozicije mijenjaju svaki okvir, potreban nam je dinamički bafer bafera kako bismo ažurirali
pozicije čestica za crtanje svakog okvira.
Već smo videli primer prenošenja podataka sa CPU-a u GPU perframe
kada smo koristili bafere za učitavanje da ažuriramo naše konstantne podatke o puferima. Možemo da
primenimo
istu tehniku i koristite našu UploadBuffer klasu, ali umjesto skladištenja niza
konstantni puferi, čuvamo niz vertisa:
std :: unikue_ptr <UploadBuffer <Vertek >> VavesVB =
nullptr;
VavesVB = std :: make_unikue <UploadBuffer <Vertek >> (
uređaj, vaveVertCount, false);
Zato što moramo da otpremimo novi sadržaj sa CPU-a na dinamiku talasa
bafer vertek svakog kadra, dinamički vertikalni bafer mora biti resurs frejma.
U suprotnom bi mogli prepisati memoriju pre nego što GPU završi sa procesiranjem poslednjeg
Ram.
Svaki frejm, pokrećemo simulaciju talasa i ažuriramo bafer vertek-a:
void LandAndVavesApp :: UpdateVaves (const GameTimer & gt)
{
// Svakog kvartala sekunde, generišite slučajni talas.
statički float t_base = 0.0f;
ako ((mTimer.TotalTime () - t_base)> = 0,25f)
{
t_base + = 0,25f;
int i = MathHelper :: Rand (4, mVaves-> RovCount () -
5);
int j = MathHelper :: Rand (4, mVaves-> ColumnCount ()
- 5);
float r = MathHelper :: RandF (0.2f, 0.5f);
mVaves-> Disturb (i, j, r);
}
// Ažurirajte simulaciju talasa.
mVaves-> Update (gt.DeltaTime ());
// Ažurirajte bafer vertek talasa sa novim
rešenje.
auto currVavesVB = mCurrFrameResource-
> VavesVB.get ();
za (int i = 0; i <mVaves-> VertekCount (); ++ i)
{
Vertek v;
v.Pos = mVaves-> Pozicija (i);
v.Color = KSMFLOAT4 (DirectKs :: Boje :: Plava);
currVavesVB-> CopiData (i, v);
}
// Postavite dinamički VB talasa renderimema na
trenutni okvir VB.
mVavesRitem-> Geo-> VertekBufferGPU = valVavesVB-
> Resource ();
}
Postoje neki troškovi prilikom korišćenja dinamičkih bafera, pošto novi podaci moraju biti
prebačen iz memorije CPU-a do GPU memorije. Prema tome, statički baferi trebaju
biti poželjni do dinamičkih bafera, pod uslovom da statički baferi rade. Nove verzije
Direct3D je uveo nove funkcije kako bi se smanjila potreba za dinamičkim baferima. Za
primer:
1. Jednostavne animacije se mogu uraditi u vertikalnom shaderu.
2. Moguće je, kroz renderiranje teksture ili izračunavanje shadera i vertek teksture
funkcionalnost, da bi se implementirala talasna simulacija kao što je gore opisano koje se pokreće
potpuno na GPU-u.
3. Geometrijski shader daje mogućnost GPU-u da stvori ili uništi primitive,
zadatak koji bi obično trebao biti obavljen na CPU bez geometrijskog shadera.
4. Faze tezelacije mogu dodati geometriju tessellate na GPU-u, zadatak koji bi bio
obično je potrebno uraditi na CPU-u bez hardverske tezelacije.
Indeksni baferi takođe mogu biti dinamični. Međutim, u demo "Land and Vaves",
topologija trougla ostaje konstantna i samo promene visine verteksa; stoga, samo
Vertek buffer mora biti dinamičan.
Demonstracija "Vaves" za ovo poglavlje koristi dinamički bafer verteka za implementaciju a
simulacija jednostavnog talasa kao što je opisana na početku ovog odeljka. Za ovo
knjiga, mi se ne bavimo stvarnim detaljima algoritma za simulaciju talasa (videti
[Lengiel02] za to), ali više sa procesom kako bi ilustrovao dinamičke bafere: ažuriranje
simulaciju na CPU-u, a zatim ažurirajte podatke verteka koristeći bafer za otpremanje.
7.8 REZIME
1. Čekajući GPU da završi izvršavanje svih komandi u redu u svakom okviru
je neefikasan jer uzrokuje kako CPU tako i GPU u nekom trenutku uđe u stanje mirovanja. Još
efikasna tehnika je kreiranje okvirnih resursa - kružni niz resursa
CPU treba da modifikuje svaki okvir. Na ovaj način, CPU ne mora da čeka
GPU da završi pre nego što pređe na sledeći okvir; CPU će raditi samo sa
sledeći dostupni (tj., ne koristi se u GPU-u) kadrovski resurs. Ako je CPU uvek
procesiranje okvira brže od GPU-a, onda će procesor na kraju morati čekati
neka tačka za GPU da uhvati, ali ovo je željena situacija, kao što je grafički procesor
potpuno iskorišćeni; dodatni CPU ciklusi se uvek mogu koristiti za druge delove
igra kao što su AI, fizika i logika igre.
2. Možemo dobiti ručicu prvom deskriptoru u gomili sa
ID3D12DescriptorHeap :: GetCPUDescriptorHandleForHeapStart
metoda. Možemo dobiti veličinu deskriptora (ovisi o hardveru i tipu deskriptora)
sa
ID3D12Device :: GetDescriptorHandleIncrementSize (DeskriptoriHeapTipe
tip). Jednom kada znamo veličinu inkrementa deskriptora, možemo da koristimo jednu od
dva CD3DKS12_CPU_DESCRIPTOR_HANDLE :: Offset metode za ofsetovanje
rukovanje n deskriptorima:
// Specify the number of descriptors to offset
times the descriptor
// increment size:
D3D12_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:
D3D12_CPU_DESCRIPTOR_HANDLE handle = mCbvHeap-
>GetCPUDescriptorHandleForHeapStart();
handle.Offset(n, mCbvSrvDescriptorSize);
The CD3DX12_GPU_DESCRIPTOR_HANDLE type has the same
Offset methods.
3. Root potpis definiše koji resursi moraju biti vezani za gasovod prije
izdavanje pozivnog poziva i kako se ti resursi mapiraju u registre ulaza shadera.
Koji resursi treba da budu vezani zavisi od toga koji su resursi vezani za shader
programi očekuju. Kada je PSO kreiran, root potpis i programi shadera
kombinacija će biti validirana. Korijenski potpis je naveden kao niz root-a
parametri. Korijenski parametar može biti deskriptorska tablica, korijenski deskriptor ili root
konstantno. Tabela deskriptora određuje susedni raspon deskriptora u kupu. A
Rootni deskriptor se koristi za vezivanje deskriptora direktno u korijenskom potpisu (ne
moraju biti u gomilu). Konstanti korena se koriste za vezivanje konstantnih vrednosti direktno u
root signature. Za performanse postoji limit od 64 DVORD-ova koji mogu biti
stavite korijenski potpis. Tabele deskriptora košta jedan DVORD, koeficijent deskriptora košta
svaki DVORDs svaki, a konstantne konstante koštaju jedan DVORD za svaku 32-bitnu konstantu.
Hardver automatski čuva snimak osnovnih argumenata za svaki žreb
pozovi. Tako smo sigurni da promenimo root argumente po pozivu, ali ipak treba
takođe pokušajte da zadržite potpis korena, tako da je manje memorije za kopiranje.
4. Dinamički baferi vertek-a se koriste kada treba sadržati sadržaj bafera
ažurirano često u toku izvršavanja (npr., svaki okvir ili svakih 1/30 sekunde). Mi Možemo
koristite UploadBuffer za implementaciju dinamičkih bafera verteka, ali umesto skladištenja
niz konstantnih bafera, čuvamo niz vertisa. Jer moramo da otpremimo
novi sadržaj od CPU-a do dinamičkog bafera talasa u svakom kadru, na
dinamički bafer verteka mora biti resurs frejma. Postoji nešto iznad glave kada
koristeći dinamičke bafere verteka, pošto novi podaci moraju biti preneti iz CPU memorije
Vratite se na GPU memoriju. Zbog toga bi trebali biti poželjni statični bajtovi verteka
dinamički vertikalni baferi, pod uslovom da statički buferovi verteka funkcionišu. Nove verzije
Direct3D je uveo nove funkcije kako bi se smanjila potreba za dinamičkim baferima.
8.Svetlo
Razmotrite Sliku 8.1. S leve strane imamo neosvetljenu sferu, a desno, upalimo
sfera. Kao što vidite, sfera sa leve strane izgleda prilično ravna - možda čak i nije
sfera uopšte, ali samo krug 2D! S druge strane, sfera sa desne strane izgleda 3D
- pomoć pri osvetljavanju i zatamnjenju u našoj percepciji čvrstog oblika i zapremine
objekat. Zapravo, naša vizuelna percepcija sveta zavisi od svetlosti i njegove interakcije
materijali i samim tim i veliki dio problema stvaranja fotorealističkih scena
u vezi sa fizičkim tačnim modelima osvetljenja.
Slika 8.1. (a) Neosvetljena sfera izgleda 2D. (b) Sjajna sfera izgleda 3D.
Naravno, uopšteno, što je tačniji model, to je računalnije
skupo je; tako da se mora ostvariti balans između realizma i brzine. Na primer,
3D posebne FKS scene za filmove mogu biti mnogo složenije i iskoristiti vrlo realistične
modeli osvetljenja od igre, jer su okviri za film unapred izrađeni, tako da mogu
priuštiti da traje satima ili danima za obradu okvira. Igre, s druge strane, su u stvarnom vremenu
aplikacije, a samim tim i okviri moraju biti izvučeni po stopi od najmanje 30 frejmova po
drugo.
Imajte na umu da je model osvetljenja objašnjen i implementiran u ovoj knjizi u velikoj mjeri zasnovan
od onog opisanog u [Moller08].
Ciljevi:
1. Da biste stekli osnovno razumevanje interakcije između svetla i materijala
2. Da razumeju razlike između lokalnog osvetljenja i globalnog osvetljenja
3. Da saznamo kako možemo matematički opisati pravac tačka na površini
"Okrenuti" tako da možemo utvrditi ugao u kojem dolazno svetlo udari
površina
4. Da naučite kako ispravno pretvarati normalne vektore
5. Da biste mogli razlikovati ambijentalno, difuzno i zrno svetlo
6. Da biste naučili kako da primenite usmerena svetla, tačkasta svetla i reflektori
7. Da biste razumeli kako da promenite intenzitet svetlosti kao funkciju dubine kontrolišući
parametri slabljenja
8.1 INTERAKCIJA SVETLOSTI I MATERIJALA
Kada koristimo osvetljenje, mi više ne određujemo boje vertek-a direktno; već smo precizirali
materijala i svetla, a zatim primeniti jednačinu osvetljenja, koja izračunava boje verteka
za nas zasnovane na interakciji sa svetlom / materijalom. To dovodi do mnogo realnijeg bojenja
objekat (uporedi sliku 8.1a i 8.1b).
Materijali se mogu smatrati osobinama koja određuju kako svetlost komunicira sa a
površina objekta. Primeri takvih osobina su boje svetlosti koja se reflektuje na površini
i upija, indeks refrakcije materijala ispod površine, koliko je glatka
površina je i koliko je površina transparentna. Specificiramo svojstva materijala
model različitih vrsta realnih površina poput drveta, kamena, stakla, metala i vode.
U našem modelu, izvor svetlosti može emitovati različite intenzitete crvene, zelene i plave svetlosti;
na ovaj način možemo simulirati mnoge svetle boje. Kada svetlost putuje izvan iz izvora
i sudari sa nekim predmetom, neko od tog svetla može se apsorbovati, a neki se mogu reflektovati
(za prozirne predmete, kao staklo, neko svetlo prolazi kroz medijum, ali
ovde ne razmatramo transparentnost). Reflektirano svetlo sada putuje duž novog puta
i mogu udarati druge predmete gde se neko svetlo ponovo apsorbuje i odražava. Svetlost zraka
može štrajkati mnoge predmete pre nego što se potpuno apsorbuju. Verovatno, neki svetlosni zraci
na kraju putuju u oko (vidi sliku 8.2) i udari ćelije receptora ćelija (imenovano
zglobova i šipki) na mrežnjači.
Prema trihromatskoj teoriji (videti [Santrock03]), retina sadrži tri
vrste svetlosnih receptora, svaka osetljiva na crveno, zeleno i plavo svetlo (sa nekim
preklapanje). Dolazno RGB svetlo stimuliše odgovarajuće svetlosne receptore na različite načine
intenziteta na osnovu jačine svetlosti. Kako su stimulisani receptori svetlosti (ili ne),
Neuralni impulsi se šalju prema optičkom nervu prema mozgu, gde mozak stvara
sliku u glavi na osnovu stimulusa svetlosnih receptora. (Naravno, ako ste
zatvorite / pokrivate oči, receptorske ćelije ne dobijaju stimulans i mozak to registruje kao
crn.)
Na primer, ponovo pogledajte Slika 8.2. Pretpostavimo da je materijal cilindra
odražava 75% crvene svetlosti, 75% zeleno svetlo i apsorbuje ostalo, a sfera odražava 25%
crveno svetlo i apsorbira ostatak. Takođe pretpostavimo da se čisto belo svetlo emituje iz
izvor svetlosti. Kako svetlosni zraci udaraju u cilindar, sve plavo svetlo se apsorbuje i samo
75% crveno i zeleno svetlo se reflektuje (to jest, žuto srednjeg intenziteta). Ovo je svetlo
zatim raspršeni - neki od njih putuju u oko i neki od njih putuju ka sferi.
Deo koji putuje u oko prevashodno stimuliše crvene i zelene ćelije konusa u a
polu visok stepen; prema tome, gledaoc vidi valjak kao polujajnu nijansu žute boje.
Sada, drugi svetlosni zraci putuju ka sferi i udare. Sfera odražava 25%
crveno svetlo i apsorbira ostalo; na taj način, razblaženo dolazno crveno svetlo (srednje visoki intenzitet
crvena) razređuje se dalje i reflektuje, a sve dolazno zeleno svetlo se apsorbuje. Ovo
preostalo crveno svetlo potom putuje u oko i pre svega stimuliše crvene ćelije konoplje
nizak stepen. Tako gledalac vidi sferu kao tamnu nijansu crvene boje.
Modeli osvetljenja koje mi (i većina aplikacija u realnom vremenu) prihvatamo u ovoj knjizi su
nazvani lokalni modeli osvetljenja. Sa lokalnim modelom, svaki objekat je osvetljen nezavisno od
drugi objekt, a uzima se u obzir samo svetlost koja se direktno emituje iz svetlosnih izvora
u procesu osvetljavanja (tj., svetlost koja je odbila druge predmete scene da udari u njega
objekt koji se trenutno osvetli je ignorisan). Slika 8.3 prikazuje posledicu ovog modela.
Slika 8.3. Fizički, zid blokira svetlosne zrake koje emituje sijalica
a sfera je u senci zida. Međutim, u lokalnom modelu osvetljenja,
sfera je osvetljena kao da zid nije bio tamo.
Sa druge strane, globalno osvetljenje modelira lake objekte uzimajući u obzir
uzimajući u obzir ne samo svetlost koja se direktno emituje iz svetlosnih izvora, već i indirektna
svetlost koja je odbila druge objekte u sceni. Ovo se naziva globalno osvetljenje
modeli zato što uzimaju u obzir sve na globalnoj sceni pri osvetljavanju
objekat. Globalni modeli osvetljenja uglavnom su previše skupi u realnom vremenu
igre (ali su prilično blizu stvaranju fotorealističkih scena). Pronalaženje realnog vremena
metode aproksimacije globalnog osvetljenja su područje tekućih istraživanja; vidi, za
primer, vokel globalno osvetljenje
[http: // ondemand. gputechconf.com/gtc/2014/presentations/S4552-rt-vokel-based-globalillumination-
gpus.pdf].
Druge popularne metode su prekompaktno indirektno osvetljenje statičke objekte (npr. zidove, statue),
a zatim koristite taj rezultat da biste približili indirektno osvetljenje
za dinamične objekte (npr., pokretanje karaktera igre).
8.2 NORMALNI VEKTORI
Normalno lice je jedinični vektor koji opisuje pravac koji je okrenut prema mnogougonu (tj
ortogonalno za sve tačke na poligonu); vidi sliku 8.4a. Normalna površina je jedinični vektor
to je ortogonalno prema tangentnoj ravni tačke na površini; vidi sliku 8.4b. Posmatrajte
da su površinski normali odredili smer u kome je tačka na površini "okrenuta".
Slika 8.4. (a) Normalno lice je ortogonalno za sve tačke na licu. (b)
Normalna površina je vektor koji je ortogonalan tangentnoj ravni tačke na a
površina.
Slika 8.5. Normali verteka n0 i n1 su definisani u vertikalnom segmentu
tačke p0 i p1. Normalni vektor n za tačku p u unutrašnjosti linijskog segmenta je
pronađeno linearno interpoliranjem (ponderisani prosjek) između vertek normala; to
je, n = n0 + t (n1 - n0) gde je t takav da p = p0 + t (p1 - p0) Iako smo ilustrovali
normalna interpolacija preko linijskog segmenta za jednostavnost, ideja direktno
generalizuje da interpolira preko 3D trougla.
Za izračunavanje rasvete potrebna nam je površina u svakoj tački na površini a
triangle mrežu, tako da možemo odrediti ugao u kojem svetlost udara tačku na
mrežna površina. Da bi dobili normalne površine, određujemo površinske normale samo na tački
tačke (tzv. vertek normali). Zatim, kako bi se dobila aproksimacija površinske normalnosti
u svakoj tački na površini mreže trougla, ovi normali verteka će biti interpolirani
preko trougla tokom rasterizacije (podsjetiti §5.10.3 i videti sliku 8.5).
Zovu se interpolacija normalne i radne kalkulacije osvetljenja po pikselu
osvetljenje piksela ili osvetljenje fonga. Jednostavnije, ali manje precizno, metod je
radi izračunavanja rasvete po verteksu. Zatim rezultat per verteksa
Izračunavanje osvetljenja izlazi iz vertek shadera i interpolira preko njega
piksela trougla. Kretanje kalkulacija iz pikselskog shadera do vrha
shader je zajednička optimizacija performansi radi kvaliteta i
Ponekad je vizuelna razlika veoma suptilna i čini veoma optimističan
atraktivno.
8.2.1 Računanje normalnih vektora
Da bi se nalazilo normalno lice trougla Dp0, p1, p2 prvo izračunavamo dva vektora koji leže
na ivicama trougla:
u = p1 - p0
v = p2 - p0
Tada je normalno lice:
𝑢.𝑣
n=uXV/ Iu.vI
Ispod je funkcija koja izračunava normalno lice prednje strane (§5.10.2) a
trougao iz tri tačke verteka trougla.
XMVECTOR ComputeNormal(FXMVECTOR p0,
FXMVECTOR p1,
FXMVECTOR p2)
{
XMVECTOR u = p1 - p0;
XMVECTOR v = p2 - p0;
return XMVector3Normalize(
XMVector3Cross(u,v));
}
Za diferencibilnu površinu, možemo koristiti računar da pronađemo normale tačaka na
površina. Nažalost, mreža trougla nije diferencibilna. Tehnika koja je
generalno primenjeno na mrežu trougla se zove normalno usredsređivanje verteka. Vertek normalno
n ili proizvoljna vertek v u mesu se pronalazi prosekom normalnih linija svakog
poligon u mrežu koja deli vertek v. Na primer, na slici 8.6, četiri poligona u
mreža dijeli verteks v tako da je veranda normalna za v data sa:

U gornjim primjerima, mi ne moramo da se podijelimo sa 4, kao što bi bilo u tipičnom proseku,


jer normalizujemo rezultat. Imajte na umu da mogu biti i sofisticiranije šeme prosecanja
izgrađen; na primer, ponderisani prosek može se koristiti tamo gde su težine
određeni površinama poligona (npr., poligoni sa većim površinama imaju više
težina nego poligoni sa manjim površinama).
Sledeći pseudokod pokazuje kako se ovo proračunsko sredstvo može implementirati s obzirom na to
vertek i indeksna lista triangle mreže:
// Input:
// 1. An array of vertices (mVertices). Each vertex
has a
// position component (pos) and a normal component
(normal).
// 2. An array of indices (mIndices).
// For each triangle in the mesh:
for(UINT i = 0; i < mNumTriangles; ++i)
{
// indices of the ith triangle
UINT i0 = mIndices[i*3+0];
UINT i1 = mIndices[i*3+1];
UINT i2 = mIndices[i*3+2];
// vertices of ith triangle
Vertex v0 = mVertices[i0];
Vertex v1 = mVertices[i1];
Vertex v2 = mVertices[i2];
// compute face normal
Vector3 e0 = v1.pos - v0.pos;
Vector3 e1 = v2.pos - v0.pos;
Vector3 faceNormal = Cross(e0, e1);
// This triangle shares the following three
vertices,
// so add this face normal into the average of
these
// vertex normals.
mVertices[i0].normal += faceNormal;
mVertices[i1].normal += faceNormal;
mVertices[i2].normal += faceNormal;
}
// For each vertex v, we have summed the face normals
of all
// the triangles that share v, so now we just need to
normalize.
for(UINT i = 0; i < mNumVertices; ++i)
mVertices[i].normal =
Normalize(&mVertices[i].normal));
8.2.2 Transformacija normalnih vektora
Razmotrimo sliku 8.7a gde imamo tangentni vektor u = v1 - v0 ortogonalan a
normalni vektor n. Ako primenimo neuniformantnu transformaciju skaliranja A, koju vidimo na slici
8.7b da transformirani tangentni vektor uA = v1A - v0A ne ostane ortogonalan
transformirani normalni vektor nA.
Slika 8.7. (a) Površina normalna pre transformacije. (b) Nakon skaliranja za 2
jedinice na k-osi normalna nije više ortogonalna za površinu. (c) površina
normalno ispravno transformisana inverse-transponovanjem skaliranja transformacije.
Dakle naš problem je ovo: s obzirom na matricu transformacije A koja pretvara tačke i
vektori (ne-normalni), želimo pronaći transformacionu matricu B koja pretvara normalno
vektorima tako da je transformirani tangentni vektor ortogonalan za transformisanu normalnost
vektor (tj. uA · nB = 0). Da uradimo ovo, prvo započinjemo sa nečim što znamo: znamo
da je normalni vektor n ortogonalan za tangentni vektor u:

Tako B = (A-1) T (inverzna transponacija A) radi posao u transformaciji normalnog


vektori, tako da su okomitljivi njegovom povezanom transformisanom tangentnom vektoru uA.
Imajte na umu da ako je matrica ortogonalna (AT = A-1), onda B = (A-1) T = (AT) T = A; to je,
ne treba izračunati inverznu transponaciju, jer A u ovom slučaju radi posao. In
rezime, pri transformaciji normalnog vektora neuniformom ili transformacijom smicanja,
koristite inverznu transponaciju.
Implementiramo pomoćnu funkciju u MathHelper.h za izračunavanje inversetransfera:
static XMMATRIX InverseTranspose(CXMMATRIX M)
{
XMMATRIX A = M;
A.r[3] = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
XMVECTOR det = XMMatrixDeterminant(A);
return XMMatrixTranspose(XMMatrixInverse(&det, A));
}
Obrišemo svaki prevod iz matrice jer koristimo inverznu transponaciju
transformisati vektore, a prevodi se odnose samo na tačke. Međutim, iz §3.2.1 mi znamo
da postavljanje v = 0 za vektore (koristeći homogene koordinate) sprečava vektore
koji su modifikovani prevodima. Stoga, ne bi trebalo da trebamo nulti prevod
u matrici. Problem je ukoliko želimo da konceninimo inverznu transponaciju i drugu
matrica koja ne sadrži nejednačeno skaliranje, recimo matrica prikaza (A-1) T V, the
transponovan prevod u četvrtu kolonu (A-1) T "curenja" u matricu proizvoda
uzrokuju greške. Zbog toga, mi prevodimo nulu kao mera predostrožnosti kako bismo izbegli ovu grešku.
The
Pravi način bi bio da transformišemo normalu pomoću: ((AV) -1) T. Ispod je primer a
matricu za skaliranje i prevođenje i kako izgleda inverzna transponacija sa četvrtom
kolona nije [0, 0, 0, 1] T:
Čak i sa transformacijom inverse-transpose, normalni vektori mogu izgubiti
dužina njihove jedinice; Stoga, možda će morati da se renormalizuju posle
transformacija.
8.3 VAŽNI VEKTORI U OSIGURANJU
U ovom odeljku sumira se nekoliko važnih vektora uključenih u osvjetljenje.
Pozivajući se na sliku 8.8, E je položaj očiju, a mi razmatramo tačku p oka
vidi duž linije mesta definisane jediničnim vektorom v. U tački p ima površinu
normalno n, a tačka je pogođena žarkom svetlosti koja putuje sa smerom incidenta. Svetlost
vektor L je jedinični vektor koji ima za cilj suprotan pravac svjetlosnog zraka
površinska tačka. Iako je možda intuitivnije raditi sa pravcem svetlosti
putuje I, za rasvjetu rasvete radimo sa svetlosnim vekom L; naročito, za
računajući Lambertov zakon kosina, vektor L se koristi za vrednovanje L · n = cos thi, gde je thi
je ugao između L i n. Vektor refleksije r je odraz incidentnog svetla
vektor oko normalne površine n. Vektor pogleda (ili vektor za vek) v = normalizovati (E - p)
je jedinični vektor od površine tačke p do tačke očiju E koji definiše liniju lokacije
od oka do tačke na površini koja se vidi. Ponekad treba da koristimo vektor
-V, što je jedinični vektor od oka do tačke na površini koju ocenjujemo
osvetljenje.
Slika 8.8. Važni vektori uključeni u izračunavanje rasvete.
Vektor refleksije dat je: r = I - 2 (n · I) n; vidi sliku 8.9. (Pretpostavlja se da je n
je jedinični vektor.) Međutim, zapravo možemo koristiti funkciju refleksije HLSL-a
računamo za nas u programu shadera.
Slika 8.9. Geometrija refleksije.
8.4 LAMBERT & RSKUO; S COSINE LAV
Mi možemo misliti na svetlost kao zbir fotona koji putuju kroz prostor u izvesnom
pravac. Svaki foton ima neku (laganu) energiju. Količina emitovane (lake) energije
u sekundi se zove sijentni fluks. Gustina fluksa zračenja po području (nazvana zračenjem) je
važno jer će to utvrditi koliko svjetlost područje na površini prima (i
na taj način kako će se sjajno pojaviti na oku). Looseli, možemo razmišljati o zračenju kao
količinu svetlosti koja udara površinu na površini, ili količinu svetlosti koja prolazi kroz
imaginarno područje u prostoru.
Svetlost koja udara površinsku glavu (to jest, svetlosni vektor L je jednak normalnom vektoru
n) je intenzivnija od svetlosti koja gleda na površinu pod uglom. Razmislite o malom svetlu
zraka s poprečnim presekom A1 sa zračenjem fluksa P koji prolazi kroz njega. Ako nameravamo ovo
svetlosni snop na površinskoj glavi (Slika 8.10a), onda svetlosni snop udari područje A1 na
površina i zračenje na A1 je E1 = P / A1. Pretpostavimo da tako rotiraju svetlosni snop
da udari površinu pod uglom (Slika 8.10b), onda svetlosni zrak pokriva veći
područje A2 i zračenje koje udara u ovo područje je E2 = P / A2. Sa trigonometrijom, A1 i A2 su
povezano sa:
Drugim riječima, površina upadanja zračenja površine A2 je jednaka zracenju na području A1
perpendikularno prema smeru svetlosti skaliranim od strane n · L = cos th. Ovo se zove Lambertov
kosinus
Zakon. Da se nosi sa slučajem gde svetlost udara na zadnju stranu površine (što rezultira u
dot proizvod je negativan), prikačimo rezultat maksimalnom funkcijom:
f (th) = mak (cos th, 0) = mak (L · n, 0)
Na slici 8.11 prikazan je grafikon f (th) kako bi se videlo kako se intenzitet, u rasponu od 0.0 do 1.0 (tj.
0% do 100%), varira sa th.
Slika 8.11. Veličina funkcije f (th) = mak (costh, 0) = mak (L · n, 0) za -2 ≤ th ≤
2. Imajte na umu da p / 2 ≈ 1.57.
8.5 DIFFUSE LIGHTING
Razmotrimo površinu neprozirnog objekta, kao na slici 8.12. Kada svetlost udari poentu
na površini, neka svetlost ulazi u unutrašnjost objekta i interaguje sa
materija blizu površine. Svetlost će se okretati u unutrašnjosti, gde će i neke od njih
biti apsorbovan i preostali deo raspršen sa površine u svakom pravcu; ovo je
nazvana difuznom refleksijom. Za pojednostavljenje, pretpostavljamo da je svetlost rasuta na
istu tačku ušlo je i svetlo. Količina apsorpcije i rasipanja zavisi od
materijal; na primer, drvo, prljavština, cigla, pločice i štukature bi upijali / rasuli svetlost
drugačije (zbog čega materijali izgledaju drugačije). U našoj aproksimaciji za modeliranje
ovakva vrsta interakcije svetlosti / materijala, mi predviđamo da se svetlost jednako rascepi u svima
pravci iznad površine; Zbog toga reflektirano svetlo će doći do očiju bez obzira na to
gledište (položaj očiju). Stoga, ne moramo da idemo u stanovište
razmatranje (tj. proračun difuznog osvetljenja nezavisno od tačke gledišta) i boje
tačke na površini uvek će izgledati isto bez obzira na stav.
Slika 8.12. Ulazna svetlost se raspršuje jednako u svakom smeru prilikom udaranja a
difuzna površina. Ideja je da svetlost ulazi u unutrašnjost medija i raspršuje
oko pod površinom. Nekoliko svetlosti će biti apsorbovano, a preostali će biti
rastereti sa površine. Zato što je teško modelirati ovu podzemnu površinu
raspršivanjem, pretpostavljamo da se ponovo emitovano svetlo razdvaja jednako u svim pravcima gore
površina oko tačke u koju je ulazio svetlost.
Proračunamo difuzno osvetljenje na dva dela. Za prvi deo, mi
navedite svetle boje i difuznu albedo boju. Difuzni albedo određuje količinu
dolazna svetlost da se površina odražava zbog difuzne refleksije (očuvanjem energije,
iznos koji se ne reflektuje apsorbuje materijal). Ovo se rukuje komponentom
množenje boja (zato što svetlo može biti obojeno). Na primer, pretpostavimo neku tačku
na površini odražava 50% dolazne crvene svetlosti, 100% zelene svetlosti i 75% plave svetlosti, i
boja dolazećeg svetla je 80% intenziteta belog svetla. To znači, količina
dolazno svetlo daje BL = (0,8, 0,8, 0,8), a difuzni albedo daje md =
(0,5, 1,0, 0,75); onda se količina svetlosti reflektuje od tačke:
cd = BL ⊗ md = (0,8, 0,8, 0,8) ⊗ (0,5, 1,0, 0,75) = (0,4, 0,8, 0,6)
Imajte na umu da komponente difuzne albedo moraju biti u opsegu 0.0 do 1.0, tako da oni
opisati frakciju reflektovane svetlosti.
Međutim, gornja formula nije sasvim tačna. Još uvek moramo uključiti Lambertovu
kosinus zakon (koji kontroliše koliko originalnog svetla na površini prima na bazi
ugao između normalnog i svetlosnog vektora). Neka BL predstavlja količinu
dolazno svetlo, md je difuzna boja albedo, L je svetlosni vektor, a n je površina
normalno. Zatim se količina difuzne svetlosti reflektuje od tačke:
cd = mak (L · n, 0) · BL ⊗ md (ek 8.1)
8.6 AMBIENT LIGHTING
Kao što je ranije rečeno, naš model osvetljenja ne uzima u obzir indirektno svetlo to
odbio je druge objekte u sceni. Međutim, puno svetlosti vidimo u stvarnom svetu
je indirektan. Na primer, hodnik povezan sa sobom možda nije u neposrednoj liniji
mesto sa izvorima svetlosti u sobi, ali se svetlost odbija od zidova u prostoriji i
neki od njih mogu da uđu u hodnik, čime ga malo osvjetljavaju. Kao sekund
primer, pretpostavimo da sedimo u sobi sa čajnikom na stolu i postoji jedno svetlo
izvor u sobi. Samo jedna strana čajnika nalazi se u direktnoj liniji mjesta svetlosnog izvora;
ipak, zadnja strana čajnika ne bi bila potpuno crna. To je zato
neka svetlost raste sa zidova ili drugih predmeta u prostoriji i na kraju udari
zadnja strana čajnika.
Kako bi hakali ovu indirektno svetlo, uvodimo ambijentni termin za osvetljenje
jednačina:
ca = AL ⊗ md (ekv. 8.2)
AL boja određuje ukupnu količinu indirektnog (ambijentalnog) svetla na površini prima,
što može biti različito od svetlosti emitovane iz izvora zbog apsorpcije
došlo je kada je svetlost odbila na drugim površinama. Difuzni albedo md navodi
količina dolazećeg svetla da se površina odražava zbog difuzne refleksije. Mi koristimo
ista vrednost za određivanje količine dolaznog ambijentalnog svetla na površini odražava; to
je za ambijentalno osvetljenje modeliranje difuzne refleksije indirektnog (ambijentalnog)
svetlo. Sva ambijentalna svetlost je jednako malo rasla objekat - nema stvarnog
obračun fizike. Ideja je da se indirektno svetlo raspršilo i odbacilo
oko scene tako mnogo puta da udara objekat jednako u svakom pravcu.
8.7 SPECULARNO OSVETLJENJE
Mi smo koristili difuzno osvetljenje za modeliranje difuzne refleksije, gde svetlost ulazi u sredinu,
okreće se oko sebe, neko svetlo se apsorbuje, a preostalo svetlo se razbacuje
srednji u svakom smeru. Druga vrsta refleksije se dešava zbog efekta Fresnel-a,
što je fizički fenomen. Kada svetlost dostigne interfejs između dva medija
sa različitim indeksima refrakcije, reflektuje se deo svetlosti i preostalo svetlo
(vidi sliku 8.13). Indeks refrakcije je fizička svojina medija koji je
je odnos brzine svetlosti u vakuumu do brzine svetlosti u datom mediju. Mi
obratite se ovom procesu refleksije svetlosti kao uzorak refleksije i reflektovano svetlo kao
zrcalno svetlo. Spektralno svetlo je ilustrovano na slici 8.14a.
Slika 8.13. (a) Fresnelov efekt za savršeno ravno ogledalo sa normalnim n. The
udesno svetlo I je podeljeno tamo gde se neke odražavaju u pravcu refleksije r i
preostalo svetlo prelazi u medijum u pravcu refrakcije t. Sve ovo
vektori su u istoj ravni. Ugao između vektora refleksije i normalnog je
uvek thi, što je isto kao i ugao između svetlosnog vektora L = -I i normalnog
n. Ugao tht između vektora refrakcije i -n zavisi od indeksa od
refrakcija između dva medija i određena je Snellovim zakonom. (b) Većina objekata
nisu savršeno ravne ogledalice, već imaju mikroskopsku hrapavost. Ovo uzrokuje
reflektovane i refraktovane svetlosti koje se šire oko reflektujućih i refrakcionih vektora.
Ako prekriveni vektor izlazi iz medija (sa druge strane) i ulazi u oko,
objekat je transparentan. To jest, svetlost prolazi kroz providne predmete. Realnom vremenu
Grafika obično koristi efekat alfa ili post-efektni efekat u približnu refrakciju
prozirne objekte, koje ćemo kasnije objasniti u ovoj knjizi. Za sada, mi smatramo samo
neprozirni objekti.
Za neprovidne predmete, refraktirana svetlost ulazi u medijum i prolazi kroz difuzno
refleksija. Dakle, na slici 8.14b možemo vidjeti nepropustne predmete, količinu svetlosti
koji odražava površinu i čini ga u očima je kombinacija reflektovanog tela
(difuzne) svetlosti i zrnaste refleksije. Za razliku od difuzne svetlosti, zrcaljenje svetlosti nije moguće
putovati u oko jer se odražava u određenom pravcu; to jest, zrcaljenje
proračun rasvete zavisi od stanovišta gledišta. To znači da kada se oko pomera oko
scene, količina zrcalnog svetla koju prima će se promeniti.
Slika 8.14. (a) Specularno svetlo grube površine širi se oko refleksije
vektor r. (b) Reflektirano svetlo koje ga čini očima je kombinacija zrna
refleksije i difuzne refleksije.
8.7.1 Fresnel efekat
Razmotrimo ravnu površinu sa normalnom n, koja razdvaja dva medija sa drugačijim
indeksi refrakcije. Zbog indeksa prekidanja refrakcije na površini, kada
dolazno svetlo udara na površinu, a neke odražavaju dalje od površine i neke refraktuju
na površinu (vidi sliku 8.13). Fresnelove jednačine matematički opisuju
procenat dolazećeg svetla koji se reflektuje, 0 ≤ RF ≤ 1. Zbog očuvanja energije, ako je RF
je količina reflektiranog svetla onda (1 - RF) je količina refraktirane svetlosti. Vrednost
RF je RGB vektor jer količina refleksije zavisi od svetlosne boje.
Koliko se reflektuje svetlost zavisi od medija (neki materijali će biti više
reflektujuće od drugih), kao i na uglu thi između normalnog vektora n i svetlosti
vektor L. Zbog njihove složenosti, pune Fresnelove jednačine se obično ne koriste u realnom vremenu
rendering; Umjesto toga, koristi se Schlickova aproksimacija:

RF (0 °) je svojstvo medija; dole su neke vrednosti za uobičajene materijale


[Moller08]:

Na slici 8.15 prikazan je grafikon Schlickove aproksimacije za nekoliko različitih RF (0 °).


Ključno je zapažanje da se količina refleksije povećava kao thi → 90 °. Da pogledamo
primjer iz stvarnog svijeta. Razmotrimo sliku 8.16. Pretpostavimo da stojimo nekoliko metara duboko
mirno jezero relativno bistre vode. Ako pogledamo dole, uglavnom vidimo donji pesak i
stene ribnjaka. To je zato što se svetlost spušta iz okoline
odražava u naše oči formira mali ugao thi blizu 0.0 °; tako je količina refleksije niska,
i, sa očuvanjem energije, količina refrakcije je visoka. Sa druge strane, ako pogledamo
prema horizontu, videćemo jaku refleksiju u vodotoku jezera. To je zato što
svetlost koja se spušta iz okoline koja ga čini našim očima čini ugao thi
bliže 90,0 °, čime se povećava količina refleksije. Ovo ponašanje se često pominje
kao efekat Fresnel-a. Da kratko sumi efekat Fresnel-a: količina reflektovanog svetla
zavisi od materijala (RF (0 °)) i ugla između normalnog i svetlosnog vektora.
Slika 8.15. Šlikovska aproksimacija je pripremljena za različite materijale: vodu,
rubin i gvožđe.
Metali apsorbuju prenošeno svetlo [Moller08], što znači da neće imati telo
refleksija. Međutim, metali se ne pojavljuju u crnoj boji, jer imaju visoke RF (0 °) vrednosti koje su
znači da odražavaju fer vrednost zrcalnog svetla čak i pri malim incidentnim uglovima blizu 0 °.
8.7.2 Hrapavost
Reflektivni objekti u stvarnom svetu nisu tendencija da budu savršena ogledala. Čak i ako je objekat
površina se pojavljuje ravnomerno, na mikroskopskom nivou može se smatrati da ima grubost.
Pozivajući se na Slika 8.17, možemo zamisliti savršeno ogledalo kao da nema hrapavosti i njenih
mikro-normali sve imaju isti cilj u istom pravcu kao i makro-normalna. Kao hrapavost
povećava se, pravac mikro normala se razdvaja od makro-normalne, što uzrokuje
reflektovano svetlo da se rasprostori u zglobni režanj.
Slika 8.16. (a): Gledanje dole u jezero, refleksija je niska i refrakcija visoka
jer je ugao između L i n mali. (b) pogledati prema horizontu i
Refleksija je visoka i refrakcija je niska jer je ugao između L i n bliži
90 °.
Slika 8.17. (a) crna horizontalna traka predstavlja uvećanje male
površinski element. Na mikroskopskom nivou, područje ima mnogo mikro-normala koje imaju za cilj
u različitim pravcima zbog hrapavosti na mikroskopskom nivou. Glatko je
površina, više će se pojaviti mikro-normali sa makro-normalnim; the
češće je površina, to će se mikro normali razlikovati od makro-normala.
(b) Ova hrapavost prouzrokuje širenje sjajnog reflektujućeg svetla. Oblik
zrnaste refleksije naziva se zglobni režanj. U principu, oblik je
zrnasti rež se može razlikovati u zavisnosti od vrste modela koji se modelira.
Za modeliranje hrapavosti matematički, mi koristimo model mikrofaceta, gde smo
modelujte mikroskopsku površinu kao kolekciju malih ravnih elemenata zvanih mikrofaseta; the
mikro normali su normali mikrofaketa. Za datu sliku v i svetlosni vektor L,
želimo da znamo deo mikrofaketa koji odražavaju L u v; Drugim rečima,
frakcija mikrofaceta sa normalnom h = normiranje (L + v); vidi sliku 8.18. Ovo će reći
nama koliko se svetlost reflektuje u oko od ogledalnog razmišljanja - što više mikrofaketa
koji odražavaju L u v svetliju zrcalno svjetlo koje vidi oko.
Vektor h se naziva poluvremeni vektor dok leži na pola puta između L i v.
Štaviše, uvodimo i ugao thh između poluvodnog vektora h i makronormalnog
n.
Definišemo normalizovanu funkciju distribucije r (thh) ∈ [0, 1] da označimo frakciju od
mikrofacete sa normalima h koji čine ugao thh sa makro-normalno n. Intuitivno, mi
očekivati da r (thh) postigne svoj maksimum kada je thh = 0 °. To jest, očekujemo mikrofilm
normali su pristrasni prema makro-normalu, a kao thh se povećava (kako se h razlikuje od
mikro-normalna n) očekujemo da će se delić mikrofaceta s normalnim hom smanjiti. A
popularna kontrolisana funkcija za model r (thh) koja ima o ~ ekivanja o kojima se upravo raspravljalo je:
r (thh) = cosm (thh)
= cosm (n · h)
Imajte na umu da cos (thh) = (n · h) daje i vektore i dužinu jedinice. Slika 8.19 prikazuje
r (thh) = cosh (thh) za različite m. Ovde m kontroliše hrapavost, koja određuje frakciju
mikrofaceta sa normalima h koji čine ugao thh sa makro normalno n. Kao m
smanjuje se, površina postaje grubija, a normali mikrofaceta se sve više razdvajaju
od makro-normalne. Kako se m povećava, površina postaje glatkija, i mikro-faseta
normali se sve više približavaju makro-normalu.
Slika 8.19. Funkcija modeliranja hrapavosti.
Mi možemo kombinovati r (thh) s faktorima normalizacije da bi dobili novu funkciju
modeluje količinu zrcalne refleksije svetlosti zasnovane na hrapavosti:
Slika 8.20 prikazuje ovu funkciju za različite m. Kao i ranije, m kontroliše hrapavost,
ali smo dodali
faktor normalizacije tako da se svetlosna energija konzervira; to suštinski kontroliše
visina krive na slici 8.20 tako da se ukupna svetlosna energija konzervira kao
zrnasta površina proširuje ili se sužava m. Za manje m, površina je grubija i
zrnast spoljni sloj se širi, dok je svetlosna energija više rasprostranjena; stoga očekujemo zrcalište
naglasite da je dimer s obzirom da je energija rasprostranjena. Sa druge strane, za a
veći m, površina je glatka i zglobni rež je uži; stoga, očekujemo
zrcalno osvetljenje je svetlije, s obzirom da je energija koncentrisana.
Geometrijski, m kontroliše širenje zupčanog režnja. Modelovanje glatkih površina (npr
polirani metal) koristićete veliki m, a za grubije površine koristićete malu m.
Da zaključimo ovaj odeljak, dopustimo kombinaciju Fresnel refleksije i površinske hrapavosti. Mi
pokušavaju da izračunu koliko se svetlosti odražava u pravcu pogleda v (pogledajte Sliku
8.18). Podsjetimo da mikrofaceti sa normalima h reflektuju svetlost u v. Neka ah bude ugao
između vektora svetlosti i pola vektora h, onda RF (ah) govori o količini svetlosti
reflektuje oko h u v zbog Fresnelovog efekta. Množenje količine reflektovanog svetla
RF (ah) usled efekta Fresnel-a sa količinom reflektovanja svetlosti zbog hrapavosti S (thh)
daje količinu zrcalno reflektovanog svetla: Neka (mak (L · n, 0) · BL) predstavlja
količina dolazećeg svetla koja udara na površinu koju osvetljavamo, a onda frakcija
od (mak (L · n, 0) · BL) zglobno reflektuje u oko usled hrapavosti i Fresnel
efekat je dat:
Obratite pažnju da ako L · n ≤ 0 svetlost udari u leđa površine koju računamo;
stoga frontalna strana ne dobija svetlost.
8.8 RECAP
Sve zajedno, ukupna svetlost reflektirana sa površine je zbir ambijenta
refleksija svetlosti, refleksija difuzne svetlosti i refleksija svetlosti svetlosti:
1. Ambient Light ca: Modifikuje količinu svetlosti reflektovane od površine usled indirektnih
svetlo.
2. Difuzna svetlost cd: Modeli svetlosti koji ulaze u unutrašnjost medija, rasklapaju se oko sebe
ispod površine gde se neko svetlo apsorbuje, a preostala svetlost raste
nazad od površine. Zato što je teško modelirati ovo podzemno rasipanje, mi
Pretpostavimo da se ponovo emitovano svetlo razdvaja jednako u svim pravcima iznad površine
o tački u kojoj je svetlost ušla.
3. Specular Light cs: Modelira svetlost koja se reflektuje sa površine zbog Fresnel-a
efekt i hrapavost površine.
Ovo dovodi do jednačine osvetljenja koje naše shadere implementiraju u ovoj knjizi:
Svi vektori u ovoj jednačini se pretpostavljaju kao dužina jedinice.
1. L: Vektor svetlosti ima za cilj izvor svetlosti.
2. n: Normalna površina.
3. h: Poluvodni vektor leži na pola puta između svetlosnog vektora i vektora pregleda (vektor
od površinske tačke do osvetljenja).
4. AL: predstavlja količinu dolaznog ambijentalnog svetla.
5. BL: predstavlja količinu dolaznog direktnog svetla.
6. md: Određuje količinu svetla dolazećeg svetla zbog koje se površina odražava
difuzna refleksija.
7. L · n: Zakon Lambertovog kosinusa.
8. ah: Ugao između pola vektora h i svetlosnog vektora L.
9. RF (ah): Određuje količinu svetlosti koja se reflektuje oko h u oko zbog Fresnel-a
efekat.
10. m: Kontrolira hrapavost površine.
11. (n · h) h: Određuje deo mikrofona sa normalima h koji prave ugao
thhvith macro-normal n.
12.(m+8)/8
: Faktor normalizacije za model očuvanja energije u ogledalu.
8.9 IMPLEMENTING MATERIALS
Naša struktura materijala izgleda ovako i definisana je u d3dUtil.h:
// Simple struct to represent a material for our
demos.
struct Material
{
// Unique material name for lookup.
std::string Name;
// Index into constant buffer corresponding to this
material.
int MatCBIndex = -1;
// Index into SRV heap for diffuse texture. Used in
the texturing
// chapter.
int DiffuseSrvHeapIndex = -1;
// Dirty flag indicating the material has changed
and we need to
// update the constant buffer. Because we have a
material constant
// buffer for each FrameResource, we have to apply
the update to each
// FrameResource. Thus, when we modify a material we
should set
// NumFramesDirty = gNumFrameResources so that each
frame resource
// gets the update.
int NumFramesDirty = gNumFrameResources;
// Material constant buffer data used for shading.
DirectX::XMFLOAT4 DiffuseAlbedo = { 1.0f, 1.0f,
1.0f, 1.0f };
DirectX::XMFLOAT3 FresnelR0 = { 0.01f, 0.01f, 0.01f
};
float Roughness = 0.25f;
DirectX::XMFLOAT4X4 MatTransform =
MathHelper::Identity4x4();
};
Za modeliranje materijala iz stvarnog sveta potrebna je kombinacija postavljanja realnih vrednosti za
DiffuseAlbedo i FresnelR0, i neke umetničke tveaking. Na primer, metala
provodnici apsorbuju refrakciono svetlo [Moller08] koje ulazi u unutrašnjost metala, što je
znači da metali neće imati difuzno odraz (tj. DiffuseAlbedo bi bio nula).
Međutim, da bi kompenzirali da ne radimo 100% fizičke simulacije osvetljenja, to je
mogu dati bolje umetničke rezultate da daju nisku vrijednost DiffuseAlbedo umjesto nula.
Poenta je: pokušaćemo da koristimo fizički realne materijalne vrednosti, ali se slobodno menja
vrednosti koje želimo, ako se krajnji rezultat bolje gleda sa umetničkog gledišta.
U našoj materijalnoj strukturi, hrapavost je specificirana u normalizovanoj vrednosti sa plutajućom
tačkom
u opsegu [0, 1]. Hrapavost od 0 označava savršeno glatku površinu, a
hrapavost od 1 bi ukazala na najgrublju površinu fizički moguće. Normalno
opseg olakšava hrapavost autora i upoređuje hrapavost između različitih
materijali. Na primer, materijal sa hrapavost od 0,6 je dvostruko grub kao materijal
sa hrapavosti 0.3. U kodu shadera koristićemo hrapavost da izvedemo eksponenta m
koristi se u jednačini 8.4. Zapazite to sa našom definicijom grubosti, sjajnosti površine
je samo inverzna od hrapavosti: shininess = 1 - hrapavost ∈ [0, 1].
Pitanje je sada na kojoj granularnosti bi trebalo navesti materijalne vrednosti? The
materijalne vrednosti mogu varirati u odnosu na površinu; to jest, mogu imati različite tačke na površini
različite vrednosti materijala. Na primer, razmotrite model automobila kao što je prikazano na slici 8.22,
gde ram, prozori, svetla i gume reflektuju i apsorbuju svetlost drugačije, i tako
materijalne vrednosti bi trebalo da variraju na površini automobila.
Slika 8.22. Automobilska mreža podeljena u pet materijalnih grupa atributa.
Da bi se implementirala ova varijacija, jedno rješenje bi moglo biti određivanje vrijednosti materijala na
per
vertek basis. Ovi materijali verteka bi zatim interpolirali preko trougla tokom
rasterizacija, dajući nam materijalne vrednosti za svaku tačku na površini mreže trougla.
Međutim, kao što smo videli iz demoa "Hills" u Poglavlju 7, i po boji verteka su i dalje previše
grubo da realno modeluje fine detalje. Štaviše, po bojama vertek-a dodajte dodatne podatke
na naše vertikalne strukture, a mi moramo imati alate za bojenje po vertek bojama. Umesto toga,
preovlađujuće rješenje je korištenje mapiranja teksta, koje će morati čekati do sljedećeg poglavlja.
Za ovo poglavlje dozvoljavamo promene materijala na frekvenciji poziva. Da uradimo to, mi
definišite svojstva svakog jedinstvenog materijala i stavite ih u tablicu:
std::unordered_map<std::string,
std::unique_ptr<Material>> mMaterials;
void LitWavesApp::BuildMaterials()
{
auto grass = std::make_unique<Material>();
grass->Name = “grass”;
grass->MatCBIndex = 0;
grass->DiffuseAlbedo = XMFLOAT4(0.2f, 0.6f, 0.6f,
1.0f);
grass->FresnelR0 = XMFLOAT3(0.01f, 0.01f, 0.01f);
grass->Roughness = 0.125f;
// This is not a good water material definition, but
we do not have
// all the rendering tools we need (transparency,
environment
// reflection), so we fake it for now.
auto water = std::make_unique<Material>();
water->Name = “water”;
water->MatCBIndex = 1;
water->DiffuseAlbedo = XMFLOAT4(0.0f, 0.2f, 0.6f,
1.0f);
water->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
water->Roughness = 0.0f;
mMaterials[“grass”] = std::move(grass);
mMaterials[“water”] = std::move(water);
}
Gore navedena tablica čuva materijalne podatke u sistemskoj memoriji. Da bi GPU mogao
pristupiti materijalnim podacima u shaderu, moramo da odrazimo relevantne podatke u konstanti
pufer. Kao što smo uradili sa konstantnim puferima po objektu, svima dodamo konstantni pufer
FrameResource koji će čuvati konstante za svaki materijal:
struct MaterialConstants
{
DirectX::XMFLOAT4 DiffuseAlbedo = { 1.0f, 1.0f,
1.0f, 1.0f };
DirectX::XMFLOAT3 FresnelR0 = { 0.01f, 0.01f, 0.01f
};
float Roughness = 0.25f;
// Used in the chapter on texture mapping.
DirectX::XMFLOAT4X4 MatTransform =
MathHelper::Identity4x4();
};
struct FrameResource
{ public:

std::unique_ptr<UploadBuffer<MaterialConstants>>
MaterialCB = nullptr;

};
Imajte na umu da Struktura MaterialConstants sadrži podskup Materijala
podaci; konkretno, sadrži samo podatke koje su shaderi potrebni za rendering.
U funkciji ažuriranja, materijalni podaci se onda kopiraju u podregion konstante
buffer kad god se promeni ("prljava"), tako da podaci konstantnog bafera grafičkog materijala budu
ažurirani sa podacima o materijalnoj memoriji sistema:
void LitWavesApp::UpdateMaterialCBs(const GameTimer&
gt)
{
auto currMaterialCB = mCurrFrameResource-
>MaterialCB.get();
for(auto& e : mMaterials)
{
// Only update the cbuffer data if the constants
have changed. If
// the cbuffer data changes, it needs to be
updated for each
// FrameResource.
Material* mat = e.second.get();
if(mat->NumFramesDirty > 0)
{
XMMATRIX matTransform = XMLoadFloat4x4(&mat-
>MatTransform);
MaterialConstants matConstants;
matConstants.DiffuseAlbedo = mat->DiffuseAlbedo;
matConstants.FresnelR0 = mat->FresnelR0;
matConstants.Roughness = mat->Roughness;
currMaterialCB->CopyData(mat->MatCBIndex,
matConstants);
// Next FrameResource need to be updated too.
mat->NumFramesDirty—;
}
}
}
Sada svaka stavka za stavke sadrži pokazivač na materijal. Imajte na umu da je višestruko prikazivanje
stavke mogu da se odnose na isti Materijalni objekat; na primer, mogu se pojaviti višestruke stavke
koristite isti "cigli" materijal. Zauzvrat, svaki Materijalni objekat ima indeks koji određuje
da li su njegovi konstantni podaci u materijalnom konstantnom puferu. Iz ovoga možemo se nadoknaditi
virtuelnu adresu konstantnih podataka potrebnih za stavku koja se crta, i podesite je na
root deskriptor koji očekuje konstantne podatke o materijalu. (Alternativno, mogli bismo se nadoknaditi
na CBV deskriptor u kupu i postavio deskriptorsku tabelu, ali smo definisali naš root potpis
u ovom demou da uzme deskriptor korena za materijalni konstantni bafer umesto tabele.)
Sledeći kod pokazuje kako crtamo renderove predmete sa različitim materijalima:
void LitWavesApp::DrawRenderItems(
ID3D12GraphicsCommandList* cmdList,
const std::vector<RenderItem*>& ritems)
{
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize
(sizeof(ObjectConstants));
UINT matCBByteSize =
d3dUtil::CalcConstantBufferByteSize
(sizeof(MaterialConstants));
auto objectCB = mCurrFrameResource->ObjectCB-
>Resource();
auto matCB = mCurrFrameResource->MaterialCB-
>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);
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress =
objectCB->GetGPUVirtualAddress() +
ri->ObjCBIndex*objCBByteSize;
D3D12_GPU_VIRTUAL_ADDRESS matCBAddress =
matCB->GetGPUVirtualAddress() +
ri->Mat->MatCBIndex*matCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(0,
objCBAddress);
cmdList->SetGraphicsRootConstantBufferView(1,
matCBAddress);
cmdList->DrawIndexedInstanced(ri->IndexCount, 1,
ri->StartIndexLocation, ri->BaseVertexLocation,
0);
}
}
Podsećamo čitaocu da su nam potrebni normalni vektori u svakoj tački na površini a
mrežu trougla, tako da možemo odrediti ugao u kojem svetlost udari tačku na mrežu
površinu (za Lambertov zakon kosinusa). Da bi se dobilo normalno približavanje vektora na
svaka tačka na površini mreže trougla, određujemo normale na nivou verteka.
Ovi normali verteka će biti interpolirani preko trougla tokom rasterizacije.
Do sada smo razgovarali o komponentama svetlosti, ali nismo razgovarali o specifičnim
vrste izvora svetlosti. Sledeća tri odjeljka opisuju kako implementirati, paralelno,
i spot svjetla.
8.10 PARALLEL LIGHTS
Paralelno svetlo (ili usmereno svetlo) približava izvor svetlosti koji je veoma udaljen.
Shodno tome, možemo približiti sve dolazeće svetlosne zrake paralelno jedni prema drugima (Sl
8.23). Štaviše, pošto je izvor svetlosti veoma udaljen, možemo ignorisati efekte
rastojanje i samo odrediti intenzitet svetlosti gde svetlost udara na scenu.
Slika 8.23. Paralelni svetlosni zraci udari površinu.
Paralelni izvor svetlosti je definisan vektorom, koji specificira pravac svetlosti
zraci putuju. Pošto su svetlosni zraci paralelni, oni svi koriste isti vektor pravca. The
svetlosni vektor, ima za cilj suprotno pravljenje svetlosni zraci. Česti primer a
izvor svetlosti koji se tačno može modelirati kao usmeravajuće svetlo je Sunce (Slika 8.24).
Slika 8.24. Figura se ne sklanja na skali, ali ako izaberete malu površinu
područje na Zemlji, svetlosni zraci koji udara u to područje su približno paralelni.
8.11 POČETNA SVETLA
Dobar fizički primer tačke svetlosti je sijalica; uopšte zrači sferično
uputstva (slika 8.25). Konkretno, za proizvoljnu tačku P, postoji svetlosni zrak
poreklom iz tačke svetlosne pozicije K koja se kreće ka tački. Kao i obično, definišemo
svetlosni vektor koji ide u suprotan pravac; to jest pravac od tačke P do
točkovni izvor svetlosti P:
U suštini, jedina razlika između tačaka i paralelnih svetala je kako svetlost
vektor je izračunat - varira od tačke do tačke za tačkasta svetla, ali ostaje konstantan
paralelna svetla.
Slika 8.25. Tačkasta svetlost zrači u svakom pravcu; naročito, za proizvoljno
tačka P postoji svetlosni zrak koji potiče od tačke izvora K prema P.
8.11.1 Slabljenje
Fizički, intenzitet svetlosti slabi kao funkcija distanca baziranog na inverznom
kvadratni zakon. To znači da je intenzitet svetlosti u tački rastojanja d od svetlosti
izvor daje:
gde je I0 svetlosni intenzitet na rastojanju d = 1 od izvora svetlosti. Ovo dobro funkcioniše
ako postavite fizički zasnovane svetlosne vrednosti i koristite HDR (visok dinamički raspon) osvetljenje i
tonemapping. Međutim, lakša je formula za početak, a ona koju ćemo koristiti
naši demo, je linearna funkcija padanja:
Grafikon ove funkcije prikazan je na slici 8.26. Spojnice zasićene funkcije
argument za opseg [0, 1]:
Slika 8.26. Faktor slabljenja koji skali svetlosnu vrijednost ostaje u potpunosti
snage (1.0) sve dok rastojanje d ne dostigne pad na početak, onda se linearno raspada na 0.0 as
rastojanje dostiže pad.
Formula za procenu tačne svetlosti je ista kao u jednačini 8.4, ali moramo
skali vrednost svetlosnog izvora BL od faktora atenuacije att (d). Imajte na umu da slabljenje radi
ne utiče na ambijentalni termin, jer se ambijentni izraz koristi za model indirektnog svetla koji ima
bounced around.
Koristeći našu dropoff funkciju, tačka čije je udaljenje od izvora svetlosti veća
nego ili jednako padu ne prima svetlo. Ovo pruža korisnu optimizaciju osvetljenja:
u našim programima shadera, ako je tačka van dometa, onda možemo vratiti ranije i preskočiti
rasvete rasvete sa dinamičkom grana.
8.12 SPOTLIGHTS
Dobar fizički primjer reflektora je svjetiljka. U suštini, centar pažnje ima
pozicija K, usmerena je u pravcu d i zrači svetlost kroz konus (vidi sliku 8.27).
Slika 8.27. Reflektor ima poziciju K, usmeren je u smeru d i zrači
svetlost kroz konus pod uglom phmak.
Da bi sproveli reflektor, počinjemo kao i sa tačkom svetlosti: svetlosni vektor je
dao:
gde je P pozicija osvetljene tačke i K je položaj reflektora.
Na slici 8.27, pogledajte da se P nalazi unutar konusa reflektora (i stoga prima
svetlost) ako i samo ako je ugao ph između -L i d manji od ugla konusa phmak.
Štaviše, sva svetlost u konusu reflektora ne bi trebalo da bude jednakog intenziteta; svetlost
centar konusa bi trebao biti najintenzivniji i intenzitet svetlosti bi trebao nestati na nulu
jer se ph povećava od 0 do phmak.
Dakle, kako kontrolišemo pad intenziteta kao funkcija ph, a takođe i kako
kontrolisati veličinu konusa reflektora? Možemo koristiti funkciju sa istim grafom kao u
Slika 8.19, ali zamenite thh sa ph i m sa s:
To nam daje ono što želimo: intenzitet glatko nestaje kada se ph povećava; dodatno,
izmenom eksponenta s, indirektno možemo kontrolisati phmak (ugao na koji se intenzitet pada
0); to jest, možemo smanjiti ili proširiti konus reflektora promenljivim s. Na primer, ako
postavili smo s = 8, konus ima približno polu ugao od 45 °.
Jednačina u centru pažnje je baš kao u jednačini 8.4, osim što pomnožimo svetlost
izvorna vrednost BL i faktor atenuacije att (d) i faktor refleksije kspot na skali
intenzitet svetlosti zasnovan na tome gde je tačka u odnosu na konus reflektora.
Vidimo da je reflektor skuplji od tačke svetlosti jer moramo
izračunati dodatni kspot faktor i množi ga. Slično tome, vidimo da je tačka svetlosti
skuplje od usmerenog svetla jer je potrebno izračunati rastojanje d (ovo je
je u stvari prilično skupo jer rastojanje uključuje operaciju kvadratnog korena), i mi
potrebno je izračunati i umnožiti faktorom slabljenja. Da sumiramo, usmerena svetla
su najjeftiniji izvor svetlosti, praćeni tačkastim svetlima, praćeni reflektorom
najskuplji izvor svetlosti.
8.13 IMPLEMENTACIJA OSVETLJENJA
Ovaj odeljak razmatra detalje za primenu smera, tačke i spotova.
8.13.1 Laka struktura
U d3dUtil.h, definišemo sledeću strukturu koja podržava svetla. Ova struktura može
predstavljaju smernice, tačke ili spot svjetla. Međutim, u zavisnosti od tipa svetla, neki
vrednosti se neće koristiti; na primer, tačka svetla ne koristi podatke o smeri član
struct Light
{
DirectX::XMFLOAT3 Strength; // Light color
float FalloffStart; // point/spot light only
DirectX::XMFLOAT3 Direction;// directional/spot light
only
float FalloffEnd; // point/spot light only
DirectX::XMFLOAT3 Position; // point/spot light only
float SpotPower; // spot light only
};
The LightingUtils.hlsl file defines structures that mirror these:
struct Light
{
float3 Strength;
float FalloffStart; // point/spot light only
float3 Direction; // directional/spot light only
float FalloffEnd; // point/spot light only
float3 Position; // point light only
float SpotPower; // spot light only
};
Redosled članova podataka koji su navedeni u strukturi Light (i takođe
Struktura MaterialConstantsa) nije proizvoljno. Oni su svesni HLSL-a
pravila pakovanja strukture. Videti dodatak B ("Strukturno pakovanje") za detalje, ali glavno
ideja je da se u HLSL-u oblaganje strukture dešava tako da se elementi upakuju u 4D vektore,
sa ograničenjem da se jedan element ne može podeliti na dva 4D vektora. Ovo znači
gornja struktura se lepo spakuje u tri 4D vektora ovako:

vector 1: (Strength.x, Strength.y, Strength.z,


FalloffStart)
vector 2: (Direction.x, Direction.y, Direction.z,
FalloffEnd)
vector 3: (Position.x, Position.y, Position.z,
SpotPower)
On the other hand, if we wrote our Light structure like this
struct Light
{
DirectX::XMFLOAT3 Strength; // Light color
DirectX::XMFLOAT3 Direction;// directional/spot
light only
DirectX::XMFLOAT3 Position; // point/spot light only
float FalloffStart; // point/spot light only
float FalloffEnd; // point/spot light only
float SpotPower; // spot light only
};
struct Light
{
float3 Strength;
float3 Direction; // directional/spot light only
float3 Position; // point light only
float FalloffStart; // point/spot light only
float FalloffEnd; // point/spot light only
float SpotPower; // spot light only
};
then it would get packed into four 4D vectors like this:
vector 1: (Strength.x, Strength.y, Strength.z, empty)
vector 2: (Direction.x, Direction.y, Direction.z,
empty)
vector 3: (Position.x, Position.y, Position.z, empty)
vector 4: (FalloffStart, FalloffEnd, SpotPower, empty)
Drugi pristup zauzima više podataka, ali to nije glavni problem. Više
ozbiljan problem je u tome što imamo bočnu strukturu aplikacije C ++ koja odražava HLSL
struktura, ali struktura C ++ ne prati iste HLSL pakovanje pravila; Prema tome
Izgledi strukture C ++ i HLSL-a verovatno neće biti usklađeni, osim ako ste pažljivi
pravila HLSL pakovanja i napišite ih tako da rade. Ako je C ++ i HLSL struktura
rasporeda se ne poklapaju, onda ćemo dobiti renderiranje bugova kada stavljamo podatke iz CPU-a
do GPU konstantnih bafera koristeći memcpi.
8.13.2 Zajedničke pomoćne funkcije
Sledeće tri funkcije, koje su definirane u LightingUtils.hlsl, sadrže kod koji je uobičajen
na više vrsta svetlosti, i stoga definišemo pomoćne funkcije.
1. CalcAttenuation: Implements a linear factor of attenuation, vhich applies to point
svetla i spotova.
2. SchlickFresnel: Schlickova aproksimacija Fresnelovim jednačinama; to
aproksimira procenat svetlosti reflektovane sa površine sa normalnom n baziranom na
ugao između svetlosnog vektora L i površinske normale n zbog efekta Fresnel-a.
3. BlinnPhong: izračunava količinu svetlosti koja se reflektuje u oko; to je zbir
difuzna refleksija i refleksija zrna.
float CalcAttenuation(float d, float falloffStart,
float falloffEnd)
{
// Linear falloff.
return saturate((falloffEnd-d) / (falloffEnd -
falloffStart));
}
// Schlick gives an approximation to Fresnel
reflectance
// (see pg. 233 “Real-Time Rendering 3rd Ed.”).
// R0 = ( (n-1)/(n+1) )^2, where n is the index of
refraction.
float3 SchlickFresnel(float3 R0, float3 normal, float3
lightVec)
{
float cosIncidentAngle = saturate(dot(normal,
lightVec));
float f0 = 1.0f - cosIncidentAngle;
float3 reflectPercent = R0 + (1.0f - R0)*
(f0*f0*f0*f0*f0);
return reflectPercent;
}
struct Material
{
float4 DiffuseAlbedo;
float3 FresnelR0;
// Shininess is inverse of roughness: Shininess = 1-
roughness.
float Shininess;
};
float3 BlinnPhong(float3 lightStrength, float3
lightVec,
float3 normal, float3 toEye, Material mat)
{
// Derive m from the shininess, which is derived
from the roughness.
const float m = mat.Shininess * 256.0f;
float3 halfVec = normalize(toEye + lightVec);
float roughnessFactor = (m +
8.0f)*pow(max(dot(halfVec, normal), 0.0f), m) / 8.0f;
float3 fresnelFactor = SchlickFresnel(mat.FresnelR0,
halfVec, lightVec);
// Our spec formula goes outside [0,1] range, but we
are doing
// LDR rendering. So scale it down a bit.
specAlbedo = specAlbedo / (specAlbedo + 1.0f);
return (mat.DiffuseAlbedo.rgb + specAlbedo) *
lightStrength;
}
Koriste se sledeće inherentne HLSL funkcije: tačka, pov i mak, koji su,
respektivno funkcija proizvoda vektora, funkcija snage i maksimalna funkcija.
Opisi većine HLSL unutrašnjih funkcija mogu se naći u Prilogu B zajedno
sa brzim prajmerom na drugoj HLSL sintaksi. Međutim, jedina stvar je da kada se dvoje
vektori se množe sa operatorom *, množenje se vrši komponentno.
Naša formula za računanje specularnog albeda dozvoljava specijalne vrednosti
biti veći od 1, što ukazuje na vrlo jake naglaske. Međutim, naša usluga
cilj očekuje da vrijednosti boje budu u niskom dinamičkom opsegu (LDR) od [0, 1].
Vrednosti izvan ovog opsega jednostavno će biti spuštene na 1.0 od našeg cilja
zahtijeva da vrijednosti boje budu u opsegu [0, 1]. Zbog toga, da biste dobili mekši spektar
naglašavajući bez oštre stezaljke, moramo morati da smanjimo uzorak
albedo:
specAlbedo = specAlbedo / (specAlbedo + 1.0f);
Osvetljenje velikim dinamičkim opsegom (HDR) koristi ciljeve sa plutajućim tačkama
omogućava svetlosnim vrednostima da izađu van opsega [0, 1], a zatim korak koraka tonemapinga
se vrši kako bi se opseg visokog dinamičkog opsega vratio na [0, 1] za prikaz, dok je
čuvajući detalje koji su važni. HDR rendering i tonemapping je a
sam po sebi - pogledajte udžbenik [Reinhard10]. Međutim, [Pettineo12]
pruža dobar uvod i demo za eksperimentisanje.
Na računaru, HLSL funkcije su uvijek inlined; Stoga, nema
performanse iznad glave za funkcije ili prolaz parametara.
8.13.3 Sprovođenje usmjernih svjetala
S obzirom na poziciju oka E i dati tačku p na površini koja je vidljiva za oko
normalne površine n, i svojstva materijala, sledeća HLSL funkcija emituje
količina svetlosti, od usmerenog izvora svetlosti, koja se odražava u smeru oko u v =
normalizovati (E - p). U našim uzorcima, ova funkcija će biti pozvana u piksel shader na
određuje boju piksela na osnovu osvetljenja.
float3 ComputeDirectionalLight(Light L, Material mat,
float3 normal, float3 toEye)
{
// The light vector aims opposite the direction the
light rays travel.
float3 lightVec = -L.Direction;
// Scale light down by Lambert’s cosine law.
float ndotl = max(dot(lightVec, normal), 0.0f);
float3 lightStrength = L.Strength * ndotl;
return BlinnPhong(lightStrength, lightVec, normal,
toEye, mat);
}
8.13.4 Svetla za implementaciju
S obzirom na poziciju oka E i dati tačku p na površini koja je vidljiva za oko
normalne površine n, i svojstva materijala, sledeća HLSL funkcija emituje
količina svetlosti, od tačke svetlosti, koja se odražava u smeru oko u v =
normalizovati (E - p). U našim uzorcima, ova funkcija će biti pozvana u piksel shader na
određuje boju piksela na osnovu osvetljenja.
float3 ComputePointLight(Light L, Material mat, float3
pos, float3 normal, float3 toEye)
{
// The vector from the surface to the light.
float3 lightVec = L.Position - pos;
// The distance from surface to light.
float d = length(lightVec);
// Range test.
if(d > L.FalloffEnd)
return 0.0f;
// Normalize the light vector.
lightVec /= d;
// Scale light down by Lambert’s cosine law.
float ndotl = max(dot(lightVec, normal), 0.0f);
float3 lightStrength = L.Strength * ndotl;
// Attenuate light by distance.
float att = CalcAttenuation(d, L.FalloffStart,
L.FalloffEnd);
lightStrength *= att;
return BlinnPhong(lightStrength, lightVec, normal,
toEye, mat);
}
8.13.5 Sprovođenje reflektora
S obzirom na poziciju oka E i dati tačku p na površini koja je vidljiva za oko
normalne površine n, i svojstva materijala, sledeća HLSL funkcija emituje
količina svetlosti, iz izvornog svetlosnog izvora, koja se odražava u smeru oko u v =
normalizovati (E - p). U našim uzorcima, ova funkcija će biti pozvana u piksel shader na
određuje boju piksela na osnovu osvetljenja.
float3 ComputeSpotLight(Light L, Material mat, float3
pos, float3 normal, float3 toEye)
{
// The vector from the surface to the light.
float3 lightVec = L.Position - pos;
// The distance from surface to light.
float d = length(lightVec);
// Range test.
if(d > L.FalloffEnd)
return 0.0f;
// Normalize the light vector.
lightVec /= d;
// Scale light down by Lambert’s cosine law.
float ndotl = max(dot(lightVec, normal), 0.0f);
float3 lightStrength = L.Strength * ndotl;
// Attenuate light by distance.
float att = CalcAttenuation(d, L.FalloffStart,
L.FalloffEnd);
lightStrength *= att;
// Scale by spotlight
float spotFactor = pow(max(dot(-lightVec,
L.Direction), 0.0f), L.SpotPower);
lightStrength *= spotFactor;
return BlinnPhong(lightStrength, lightVec, normal,
toEye, mat);
}
8.13.6 Akumuliranje višestrukih svetla
Osvetljenje je aditivno, tako da podržavanje više svetala u sceni jednostavno znači da je potrebno
iteracija nad svakim izvorom svetlosti i suma svoj doprinos tački / pikselu koji ocenjujemo
osvetljenje. Naš uzorak podržava do šestina ukupno svjetla. Mi možemo da koristimo sve
kombinacija smera, tačke ili spotova, ali ukupna vrednost ne sme biti veća od šesnaest.
Štaviše, naš kod upotrebljava konvenciju da se svetlosna svetla moraju prvo pojaviti na svetlosti
niz, tačkasta svetla dolaze na drugu, a spot svetla dolaze poslednji. Sledeći kod ocjenjuje
rasvjetna jednačina za tačku
#define MaxLights 16
// Constant data that varies per material.
cbuffer cbPass : register(b2)
{

// 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];
};
float4 ComputeLighting(Light gLights[MaxLights],
Material mat,
float3 pos, float3 normal, float3 toEye,
float3 shadowFactor)
{
float3 result = 0.0f;
int i = 0;
#if (NUM_DIR_LIGHTS > 0)
for(i = 0; i < NUM_DIR_LIGHTS; ++i)
{
result += shadowFactor[i] *
ComputeDirectionalLight(gLights[i], mat, normal, toEye);
}
#endif
#if (NUM_POINT_LIGHTS > 0)
for(i = NUM_DIR_LIGHTS; i <
NUM_DIR_LIGHTS+NUM_POINT_LIGHTS; ++i)
{
result += ComputePointLight(gLights[i], mat, pos,
normal, toEye);
}
#endif
#if (NUM_SPOT_LIGHTS > 0)
for(i = NUM_DIR_LIGHTS + NUM_POINT_LIGHTS;
i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS +
NUM_SPOT_LIGHTS;
++i)
{
result += ComputeSpotLight(gLights[i], mat, pos,
normal, toEye);
}
#endif
return float4(result, 0.0f);
}
Obratite pažnju da se broj svetla za svaki tip kontroliše pomoću #defines. The
Ideja je da shader izvrši jednačinu osvetljenja samo za broj svetla koji su
stvarno je potrebno. Dakle, ako aplikaciji treba samo tri svetla, mi samo vršimo proračune
za tri svetla. Ako vaša aplikacija treba da podrži različite broj svetala
različita vremena, onda samo generišete različite shadere koristeći različite #defines.
Parametar shadovFactor neće se koristiti do poglavlja
senčenje. Dakle, za sada smo to samo postavili vektoru (1, 1, 1), što čini
faktor senke nema efekta u jednačini.
8.13.7 Glavni HLSL fajl
Spodnji kod sadrži vertikale i pikselne shadere koji se koriste za demo ove
poglavlje, i koristi HLSL kod u LightingUtil.hlsl o kojem smo raspravljali
sada.
//*********************************************************************
// Default.hlsl by Frank Luna (C) 2015 All Rights
Reserved.
//
// Default shader, currently supports lighting.
//*********************************************************************
// Defaults for number of lights.
#ifndef NUM_DIR_LIGHTS
#define NUM_DIR_LIGHTS 1
#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”
// Constant data that varies per frame.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
};
cbuffer cbMaterial : register(b1)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
// Constant data that varies per material.
cbuffer cbPass : register(b2)
{
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;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut)0.0f;
// 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);
// Transform to homogeneous clip space.
vout.PosH = mul(posW, gViewProj);
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
// 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);
// Indirect lighting.
float4 ambient = gAmbientLight*gDiffuseAlbedo;
// Direct lighting.
const float shininess = 1.0f - gRoughness;
Material mat = { gDiffuseAlbedo, gFresnelR0,
shininess };
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat,
pin.PosW, pin.NormalW, toEyeW, shadowFactor);
float4 litColor = ambient + directLight;
// Common convention to take alpha from diffuse
material.
litColor.a = gDiffuseAlbedo.a;
return litColor;
}
Obratite pažnju da se broj svetla za svaki tip kontroliše pomoću #defines. The Ideja je da shader izvrši
jednačinu osvetljenja samo za broj svetla koji su stvarno je potrebno. Dakle, ako aplikaciji treba samo tri
svetla, mi samo vršimo proračune za tri svetla. Ako vaša aplikacija treba da podrži različite broj svetala
različita vremena, onda samo generišete različite shadere koristeći različite #defines.
Parametar shadovFactor neće se koristiti do poglavlja
senčenje. Dakle, za sada smo to samo postavili vektoru (1, 1, 1), što čini
faktor senke nema efekta u jednačini.
8.13.7 Glavni HLSL fajl Spodnji kod sadrži vertikale i pikselne shadere koji se koriste za demo ove
poglavlje, i koristi HLSL kod u LightingUtil.hlsl o kojem smo raspravljali
sada.

8.14 OSVETLJENJE DEMO


Demonstracija osvetljenja se svodi na demo "Vaves" iz prethodnog poglavlja. On koristi jedan
usmereno svetlo koje predstavlja sunce. Korisnik može rotirati položaj sunca pomoću levog,
desne, gore i dole tastere sa strelicom. Dok smo razgovarali o tome kako su materijal i svetla
implementirani, sledeći članovi podnose informacije o implementaciji o kojima se još nije raspravljalo.
Slika 8.28 prikazuje snimak ekrana osvetljenja.
Slika 8.28. Snimak ekrana osvetljenja.
8.14.1 Vertek Format
Proračuni osvetljenja zahtevaju normalnu površinu. Definišemo normale na nivou verteka;
ovi normali se onda interpoliraju preko piksela trougla tako da možemo to uraditi
izračunavanje osvetljenja po pikselu. Štaviše, mi više ne postavljamo boju verteka. Umesto toga,
boje piksela se generišu primjenom jednačine osvjetljenja za svaki piksel. Podržati
vertek normali mi modifikujemo naše vertikalne strukture kao što su:
// C++ Vertex structure
struct Vertex
{
DirectX::XMFLOAT3 Pos;
DirectX::XMFLOAT3 Normal;
};
// Corresponding HLSL vertex structure
struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
};
When we add a new vertex format, we need to describe it with a new input layout
description:
mInputLayout =
{
{ “POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “NORMAL”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
8.14.2 Normalna izračunavanja
Funkcije oblika u GeometriGenerator već stvaraju podatke sa verteksom
normale, tako da smo svi postavljeni tamo. Međutim, jer mi modifikujemo visine mreže
ovaj demo da bi izgledao kao teren, moramo generirati normalne vektore za
tereni sami.
Pošto je naša terenska površina data od funkcije i = f (k, z), možemo izračunati
normalni vektori direktno koristeći račun, umesto normalne tehnike usrednjavanja
opisano u §8.2.1. Da bi to učinili, za svaku tačku na površini formiramo dva tangentna vektora
u + k- i + z- pravcima uzimanjem parcijalnih derivata:
Ova dva vektora leže u tangentnoj ravni površinske tačke. Uzimanje krsta
proizvod zatim daje normalni vektor:
Funkcija koju smo koristili za generisanje kopnene mreže je:
Parcialni derivati su:
Normalna površina na površinskoj tački (k, f (k, z), z) je dato:
Napominjemo da ova površina normalne nije dužine jedinice, pa je potrebno normalizovati
pre izračunavanja osvetljenja.
Konkretno, uradimo gore navedeni normalni proračun na svakoj tački verteksa da bi dobili
vertek normali:
XMFLOAT3 LitWavesApp::GetHillsNormal(float x, float
z)const
{
// n = (-df/dx, 1, -df/dz)
XMFLOAT3 n(
-0.03f*z*cosf(0.1f*x) - 0.3f*cosf(0.1f*z),
1.0f,
-0.3f*sinf(0.1f*x) + 0.03f*x*sinf(0.1f*z));
XMVECTOR unitNormal =
XMVector3Normalize(XMLoadFloat3(&n));
XMStoreFloat3(&n, unitNormal);
return n;
}
8.14.3 Ažuriranje smera svetlosti
Kao što je prikazano u §8.13.7, naš niz svetla stavlja se u konstantni bafer za per-pass. The
demo koristi jedno smerno svetlo da predstavlja Sunce, i omogućava korisniku da rotira sunce
položaj pomoću tastera sa strelicama levo, desno, gore i dole. Dakle, svaki ram, moramo
izračunajte novi smer svetlosti od Sunca i podesite je na konstantni bafer za per-pass.
Pratimo poziciju Sunca u sfernim koordinatama (r, th, ph), ali poluprečnik r ne materija, jer
pretpostavljamo da je sunce daleko daleko. Konkretno, samo koristimo r = 1 da leži na jedinici sfere i
tumači (1, th, ph) kao smjer prema Suncu. The pravac svetlosti je samo negativan smjer prema Suncu.
Ispod je relevantan kod za ažuriranje sunca.
float mSunTheta = 1.25f*XM_PI;
float mSunPhi = XM_PIDIV4;
void LitWavesApp::OnKeyboardInput(const GameTimer& gt)
{
const float dt = gt.DeltaTime();
if(GetAsyncKeyState(VK_LEFT) & 0x8000)
mSunTheta -= 1.0f*dt;
if(GetAsyncKeyState(VK_RIGHT) & 0x8000)
mSunTheta += 1.0f*dt;
if(GetAsyncKeyState(VK_UP) & 0x8000)
mSunPhi -= 1.0f*dt;
if(GetAsyncKeyState(VK_DOWN) & 0x8000)
mSunPhi += 1.0f*dt;
mSunPhi = MathHelper::Clamp(mSunPhi, 0.1f,
XM_PIDIV2);
}
void LitWavesApp::UpdateMainPassCB(const GameTimer&
gt)
{

XMVECTOR lightDir = -
MathHelper::SphericalToCartesian(1.0f, mSunTheta,
mSunPhi);
XMStoreFloat3(&mMainPassCB.Lights[0].Direction,
lightDir);
mMainPassCB.Lights[0].Strength = { 0.8f, 0.8f, 0.7f
};
auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(0, mMainPassCB);
}
8.14.4 Ažuriranje root-potpisa
Osvetljenje uvodi novi materijalni konstantni pufer za naše programe senatora. Podržati
ovo, moramo ažurirati naš root potpis kako bismo podržali dodatni konstantni bafer. Kao i sa
konstantni baferi po objektu, koristimo korenski deskriptor za materijalni konstantni bafer
podržava vezivanje konstantnog bafera direktno umjesto da prolazi kroz skripte deskriptora.
8.15 SAŽETAK
1. Sa osvetljenjem više ne određujemo boje per-verteka, već umjesto toga definiramo svetlosne signale
i per-vertek materijala. Materijali se mogu smatrati osobinama koja određuju
kako svetlost komunicira sa površinom objekta. Per vertek materijali su
interpolirali preko lice trougla da bi se dobila vrednost materijala na svakoj površini
tačka mreže trougla. Jednačine osvetljenja tada izračunavaju površinsku boju
oko vidi na osnovu interakcije između svetlosti i površinskih materijala; drugi
Parametri su takođe uključeni, kao što su normalna površina i položaj očiju.
2. Normalna površina je jedinični vektor koji je ortogonalan prema tangentnoj ravni tačke
površina. Normalne površine određuju smer u kome je tačka na površini "okrenuta".
Za izračunavanje rasvete potrebna nam je površina u svakoj tački na površini a
mrežu trougla, tako da možemo odrediti ugao u kojem svetlost udara na tačku
površina mreže. Da bi dobili normalne površine, odredili smo samo površinske normale
tačke verteksa (tzv. vertek normali). Zatim, kako bi se dobila normalna površina
aproksimacija u svakoj tački na površini mreže trougla, ovi normali verteka
će biti interpolisani preko trougla tokom rasterizacije. Za proizvoljan trougao
mreže, normale verteka se obično aproksimiraju putem tehnike nazvane normalno
prosek. Ako se matrica A koristi za transformaciju tačaka i vektora (ne-normalno
vektori), onda se (A-1) T koristi za transformaciju površinskih normala.
3. Paralelno (usmereno) svetlo približava izvor svetlosti koji je veoma udaljen.
Shodno tome, možemo približiti sve dolazeće svetlosne zrake paralelno jedni prema drugima. A
fizički primer usmerenog svetla je sunce u odnosu na zemlju. Tačka svetlosti
emituje svetlost u svakom smeru. Fizički primer tačke svetlosti je sijalica. A
Reflektor emituje svetlost kroz konus. Fizički primjer reflektora je svjetiljka.
4. Zbog efekta Fresnel-a, kada svetlost dostiže interfejs između dva medija sa
različiti indeksi refrakcije se odražavaju na neki od svetlosti i preostalo svetlo
prekriven u medijum. Koliko se reflektuje svetlost zavisi od medija
(neki materijali će biti reflektivniji od drugih), kao i na uglu thi između
normalni vektor n i svetlosni vektor L. Zbog njihove složenosti, punog Fresnel-a
jednačine se obično ne koriste u renderingu u realnom vremenu; umesto toga, Schlick
aproksimacija se koristi.
5. Reflektivni objekti u stvarnom svetu nisu tendencija da budu savršena ogledala. Čak i ako je objekat
površina se pojavljuje ravnomerno, na mikroskopskom nivou može se smatrati da ima grubost.
Možemo da zamislimo savršeno ogledalo kao da nemamo nikakvu hrapavost i sve njegove mikro
normale
cilj u istom pravcu kao i makro-normalna. Kako se hrapavost povećava,
pravac mikro-normala se razdvaja od makro-normalne, što uzrokuje odsjaj
svetlost koja se širi u zglobni režanj.
6. Indirektno svetlo modele indirektno svetlo koje se raspršilo i skupljalo oko scene
toliko puta da udara objekat jednako u svakom pravcu, time ravnomerno
osvetljava ga. Svetlost modela difuzne svetlosti koja ulazi u unutrašnjost medija i
raspršuje se ispod površine gde se neko svetlo apsorbuje i
preostala svetlost raste unazad od površine. Zato što je teško modelirati ovo
podzemno rasipanje, pretpostavljamo da se ponovo emitovano svetlo jednako razdvaja
uputstva iznad površine oko tačke u koji je ulazio svetlo. Specular light modele
svetlost koja se reflektuje sa površine zbog efekta Fresnel-a i površine
hrapavost.
Poglavlje 9 Teksture
Naše demo scene postaju malo interesantnije, ali objekti u stvarnom svetu obično imaju
više detalja nego per-objektni materijali mogu snimiti. Mapiranje teksta je tehnika koja
omogućava nam da mapira podatke o slici na trougao, čime nam omogućavamo da povećamo detalje i
realizam naše scene značajno. Na primer, možemo napraviti kocku i pretvoriti u sanduk
mapirajući teksturu kutije sa svake strane (slika 9.1).
Ciljevi:
1. Da naučite kako da odredite deo teksture koji se mapira u trougao.
2. Da biste saznali kako kreirati i omogućiti teksture.
3. Da biste saznali kako se teksture mogu filtrirati kako bi se napravila glatka slika.
4. Da biste otkrili kako se teksturira nekoliko puta sa adresnim režimima.
5. Da biste saznali kako se višestruke teksture mogu kombinovati da bi se stvorile nove teksture i
posebne
efekte.
6. Da naučite kako da kreirate neke osnovne efekte pomoću teksturne animacije.
Slika 9.1. Demonstrakcija Create stvara kocku sa teksturom kutija.
9.1 TEKSTURA I REZULTAT RESURSA
Podsetimo se da smo već koristili teksture od Poglavlja 4; naročito,
dubinski bafer i bafer za pozadinu su objekti 2D teksture koji predstavljaju
ID3D12Resource interfejs sa D3D12_RESOURCE_DESC :: Dimenzija
D3D12_RESOURCE_DIMENSION_TEKSTURE2D. Radi lakšeg upućivanja, u ovom prvom delu
mi pregledamo veliki deo materijala o teksturama koje smo već pokrivali u Poglavlju 4.
2D tekstura je matrica elementa podataka. Jedna upotreba za 2D teksture je čuvanje 2D
slike, gde svaki element u teksturi čuva boju piksela. Međutim, ovo je
ne jedina upotreba; na primer, u naprednoj tehnickoj tehniku koja se zove normalno mapiranje
element u teksturi skladišti 3D vektor umesto boje. Stoga, iako jeste
uobičajeno da razmišljaju o teksturama kao što čuvaju podatke o slici, one su stvarno opšte svrhe
nego to. 1D tekstura (D3D12_RESOURCE_DIMENSION_TEKSTURE1D) je kao 1D
niz elemenata podataka i 3D teksture
(D3D12_RESOURCE_DIMENSION_TEKSTURE3D) je kao 3D niz elemenata podataka.
Svi 1D, 2D i 3D teksturni interfejsi su predstavljeni generičkim
ID3D12Resource.
Teksture su različite od puferskih resursa, koji samo čuvaju niz podataka; teksture
može imati mipmap nivoe, a GPU može da uradi posebne operacije na njima, kao što je primeniti
filteri i multisampling. Zbog ovih posebnih operacija koje su podržane
teksturni resursi, ograničeni su na određene vrste formata podataka, dok je pufer
resursi mogu da skladište proizvoljne podatke. Podržani formati podataka za teksture opisuju
DKSGI_FORMAT popisani tip. Neki primeri formata su:
1. DKSGI_FORMAT_R32G32B32_FLOAT: Svaki element ima tri 32-bitna plutajuća tačka
komponente.
2. DKSGI_FORMAT_R16G16B16A16_UNORM: Svaki element ima četiri 16-bitne
komponente su mapirane u opsegu [0, 1].
3. DKSGI_FORMAT_R32G32_UINT: Svaki element ima dva 32-bitna nepotpisana ceo broj
komponente.
4. DKSGI_FORMAT_R8G8B8A8_UNORM: Svaki element ima četiri 8-bitna nepotpisana
komponente su mapirane u opsegu [0, 1].
5. DKSGI_FORMAT_R8G8B8A8_SNORM: Svaki element ima četiri 8-bitna potpisana
komponente su mapirane na opseg [-1, 1].
6. DKSGI_FORMAT_R8G8B8A8_SINT: Svaki element ima četiri 8-bitnog celog potpisa
komponente su mapirane na opseg [-128, 127].
7. DKSGI_FORMAT_R8G8B8A8_UINT: Svaki element ima četiri 8-bitnog nepotpisnog cjelina
komponente su mapirane na opseg [0, 255].
Imajte na umu da se slova R, G, B, A koriste za postojanje crvene, zelene, plave i alfa,
redom. Međutim, kao što smo ranije rekli, teksture ne trebaju čuvati informacije o boji; za
primer, format
DKSGI_FORMAT_R32G32B32_FLOAT
ima tri komponente sa plutajućim delom i stoga može sačuvati 3D vektor sa plutajućim tačkama
Koordinate (ne obavezno vektor boja). Tu su takođe i bezoblični formati, gde
upravo smo rezervisali memoriju, a zatim odredili kako ponoviti tumačenje podataka kasnije (sorta
kao cast) kada je tekstura vezana za rendering pipeline; na primer, sledeće
bezpomeni format rezerviše elemente sa četiri 8-bitne komponente, ali ne specificira
tip podataka (npr. integer, floating point, unsigned integer):
DKSGI_FORMAT_R8G8B8A8_TIPELESS
Tekstura može biti vezana za različite faze renderinga; zajednički
primer je da se tekstura koristi kao meta za render (tj. Direct3D se vraća u teksturu) i kao
izvor sidera (tj. tekstura će se uzorkovati u shaderu). Tekstura se takođe može koristiti
kao oba meta i kao izvor senara, ali ne i istovremeno. Rendering to a
teksture, a zatim ga upotrebljava kao izvor sidera, metod koji se zove render-to-tekture, omogućava
neke zanimljive specijalne efekte koje ćemo kasnije koristiti u ovoj knjizi. Da bi tekstura bila
koji se koristi kao meta za renderiranje i izvor sidra, morali bismo napraviti dva deskriptori za taj izvor
teksture: 1) onaj koji živi u kupovini cilja (tj. D3D12_DESCRIPTOR_HEAP_TIPE_RTV) i 2) onaj koji živi u
siderovom izvoru kupina (tj., D3D12_DESCRIPTOR_HEAP_TIPE_CBV_SRV_UAV). (Imajte na umu da
shader
gomilu resursa takođe može sačuvati konstantne prikaze buffer prikaza i neuređeni prikaz pristupa
deskriptori.) Tada se resurs može vezati kao ciljanje ili vezati kao ulaz shadera
na root parametar u korijenskom potpisu (ali nikada u isto vreme):
// Bind as render target.
CD3DX12_CPU_DESCRIPTOR_HANDLE rtv = …;
CD3DX12_CPU_DESCRIPTOR_HANDLE dsv = …;
cmdList->OMSetRenderTargets(1, &rtv, true, &dsv);
// Bind as shader input to root parameter.
CD3DX12_GPU_DESCRIPTOR_HANDLE tex = …;
cmdList- >SetGraphicsRootDescriptorTable(rootParamIndex, tex);
Deskriptori resursa u suštini rade dve stvari: oni kažu Direct3D kako je resurs
biće korišćeni (tj. u kojoj fazi veze će se povezati), i ako format resursa
bio je određen kao bez tipova u vreme kreiranja, onda moramo sada da navedemo tip kada kreirate
pogled. Stoga, bez tipova formata, moguće je posmatrati elemente teksture
kao vrijednosti sa plutajućim vrijednostima u jednoj fazi cjevovoda i kao cijeli broj u drugom; ovo
suštinski
iznosi reinterpretiranje podataka.
U ovom poglavlju mi ćemo biti zainteresovani samo za vezivanje tekstura kao shader resursa
da naši piksel shaderi mogu uzorkovati teksture i koristiti ih za boje piksela.
9.2 TEKSTURNI KOORDINATI
Direct3D koristi sistem koordinata teksture koji se sastoji od u-ose koja se pokreće
horizontalno do slike i v-osa koja se pokreće vertikalno na sliku. Koordinate,
(u, v) tako da 0 ≤ u, v ≤ 1, identifikuje element na teksturi koji se zove teksel. Primetićete da
v-os je pozitivna u pravcu "dole" (vidi sliku 9.2). Takođe, primjetite normalizovanu
koordinatni interval [0, 1], koji se koristi zato što daje Direct3D dimenziju
nezavisni domet za rad; na primer, (0,5, 0,5) uvek specificira srednji teksel
bez obzira da li su stvarne dimenzije tekstura 256 × 256, 512 × 1024 ili 2048 × 2048 in
pikseli. Isto tako, (0,25, 0,75) identifikuje teksel četvrtinu ukupne širine u
horizontalni pravac i tri četvrtine ukupne visine u vertikalnom pravcu. Za
sada su teksturne koordinate uvek u opsegu [0, 1], ali kasnije objašnjavamo šta možemo
se dešava kada izađete izvan ovog opsega.
Slika 9.2. Sistem koordinata teksture, ponekad zvani teksturni prostor.
Slika 9.3. S leve strane je trougao u 3D prostoru, a desno definiramo 2D
trougao na teksturi koji će biti mapiran na 3D trougao.
Za svaki 3D trougao želimo da definišemo odgovarajući trougao na teksturi koji je
da budu mapirani na 3D trougao (videti sliku 9.3). Neka su p0, p1 i p2 vertices a
3D trougao sa odgovarajućim teksturnim koordinatama k0, k1 i k2. Za proizvoljnu tačku (k, i,
z) na 3D trouglu, njegove koordinate teksture (u, v) se nalaze linearno interpolirajući
tekstura verteka koordiniše preko 3D trougla istim s, t parametrima; to jest, ako
Na taj način, svaka tačka na trouglu ima odgovarajuću koordinaturu teksture.
Da bismo ovo implementirali, ponovo modifikujemo strukturu verteka i dodamo par
koordinate teksture koje identifikuju tačku na teksturi. Sada svaki 3D vertek ima a
odgovarajući 2D tekstura vertek. Tako je svaki 3D trougao definisan i sa tri vertikala
definira 2D trougao u teksturnom prostoru (tj. povezali smo 2D teksturni trougao za
svaki 3D trougao).
struct Vertex
{
DirectX::XMFLOAT3 Pos;
DirectX::XMFLOAT3 Normal;
DirectX::XMFLOAT2 TexC;
};>>>>>>>>>>>>>>>>>>
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout =
{
{ “POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “NORMAL”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “TEXCOORD”, 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
Obratite pažnju na to da na slici 9.3 mapujemo celu teksturu na svako lice kocke.
To uopšte nije potrebno. Na geometriju možemo mapirati samo podskup teksture. In
činjenica je da možemo postaviti nekoliko nepovezanih slika na jednu veliku teksturu (to se zove
tekstura
atlasa) i koristite ga za nekoliko različitih objekata (slika 9.4). Koordinate teksture su
šta će utvrditi koji deo teksture se mapira na trouglu.
Slika 9.4. Atlas teksture čuva četiri pod-teksture na jednoj velikoj teksturi. The
Koordinate teksture za svaku vertiksu postavljene su tako da dobije željeni deo teksture
mapirana na geometriju.
9.3 IZVORI TEKSTURNIH PODATAKA
Najprevalentniji način stvaranja tekstura za igre je da ih umetnik napravi
Photoshop ili neki drugi editor slika, a zatim ih sačuvajte kao sliku kao BMP,
DDS, TGA ili PNG. Zatim će aplikacija za igre učitati podatke o slici u vreme učitavanja
ID3D12Resource objekat. Za grafičke aplikacije u realnom vremenu, DDS (DirectDrav
Surface format) je preporučljiv format slike, jer podržava različite formate slika
koje grafički proces definiše; posebno, podržava komprimovanu sliku
formate kojeg GPU može primarno dekompresirati.
Umetnici ne bi trebalo da koriste format DDS kao format radne slike. Umesto toga
oni bi trebali koristiti svoj željeni format za uštedu posla. Onda kada je tekstura
kompletni, oni izvoze u DDS za aplikaciju za igru.
9.3.1 Pregled DDS-a
DDS format je idealan za 3D grafiku jer podržava posebne formate i
teksture koje se posebno koriste za 3D grafiku. To je u suštini format slike
izgrađen za GPU. Na primer, teksture DDS-a podržavaju sledeće funkcije (još ne
diskutovano) korišćeno u 3D grafičkom razvoju:
1. mipmaps
2. kompresovani formati koje GPU može prirodno dekompresirati
3. teksture nizova
4. kocke karte
5. teksture zapremine
DDS format može podržavati različite formate piksela. Format piksela je opisan od strane
član zapisanog tipa DKSGI_FORMAT; međutim, nisu svi formati primenjeni na DDS
teksture. Tipično, za nekomprimirane podatke o slici koristite formate:
1. DKSGI_FORMAT_B8G8R8A8_UNORM ili DKSGI_FORMAT_B8G8R8Ks8_UNORM:
Za slike sa niskim dinamičkim dometom.
2. DKSGI_FORMAT_R16G16B16A16_FLOAT: Za slike visokog dinamičkog dometa.
Zahtevi za memoriju grafičkog procesora za teksture se brzo dodaju kao virtuelni svetovi
raste sa stotinama tekstura (zapamtite da moramo sve ove teksture zadržati u GPU-u
memorije da ih brzo primenite). Da bi pomogli u ublažavanju ovih zahteva za memorijom, Direct3D
podržava komprimirane teksture: BC1, BC2, BC3, BC4, BC5, BC6 i BC7:
1. BC1 (DKSGI_FORMAT_BC1_UNORM): Koristite ovaj format ako želite da komprimujete
format koji podržava tri boje kanala i samo 1-bitnu (on / off) alfa komponentu.
2. BC2 (DKSGI_FORMAT_BC2_UNORM): Koristite ovaj format ako želite da komprimujete
format koji podržava tri boje kanala i samo 4-bitnu alfa komponentu.
3. BC3 (DKSGI_FORMAT_BC3_UNORM): Koristite ovaj format ako vam treba kompresovati
format koji podržava tri boje kanala i 8-bitnu alfa komponentu.
4. BC4 (DKSGI_FORMAT_BC4_UNORM): Koristite ovaj format ako želite da komprimujete
format koji sadrži jedan kanal u boji (npr., siva slika).
5. BC5 (DKSGI_FORMAT_BC5_UNORM): Koristite ovaj format ako želite da komprimujete
format koji podržava dva kanala u boji.
6. BC6 (DKSGI_FORMAT_BC6_UF16): Koristite ovaj format za kompresovani HDR (visoki
dinamički opseg) podataka o slici.
7. BC7 (DKSGI_FORMAT_BC7_UNORM): Koristite ovaj format za visokokvalitetne RGBA
kompresija. Konkretno, ovaj format značajno smanjuje greške koje prouzrokuje
komprimovanje normalnih mapa.
Kompresirana tekstura može se koristiti samo kao ulaz u fazu shadera
renderiranje plinovoda, a ne kao cilja renderiranja.
Pošto algoritmi blokiranja kompresije rade sa blokovima od 4 × 4 piksela,
dimenzije teksture moraju biti višestruke od 4.
Opet, prednost ovih formata je što se oni mogu spremiti komprimovani u GPU-u
memorije, a zatim je dekompresovan na GPU-u kada je to potrebno. Dodatna
Prednost skladištenja vaših tekstura komprimovanih u DDS datotekama je u tome što i oni uzimaju
manje
prostor na čvrstom disku.
9.3.2 Kreiranje DDS datoteka
Ako ste novi u grafičkom programiranju, verovatno ste upoznati sa DDS-om i
verovatno se više koriste za korištenje formata kao što su BMP, TGA ili PNG. Evo dva načina
pretvoriti tradicionalne formate slika u DDS format:
1. NVIDIA isporučuje dodatak za Adobe Photoshop koji može da izvozi slike u DDS formatu. Plugin je
dostupan na https://developer.nvidia.com/nvidia-tekture-toolsadobe- photoshop. Među ostalim
opcijama, to vam omogućava da navedete DKSGI_FORMAT DDS datoteke i generišite mipmaps.
2. Microsoft obezbeđuje alatku za naredbenu liniju zvanu tekconv koja se može koristiti za konverziju
tradicionalne formate slika za DDS. Pored toga, program tekconv se može koristiti
više kao što je promena veličine slike, promena formata piksela, stvaranje mipmapa i čak
više. Dokumentaciju i dovnload link možete naći na sledećoj veb lokaciji
https://directktek.codeplek.com/vikipage?
itle = Tekconv & referringTitle = Dokumentacija. Sledeći primjer unosi BMP datoteku bricks.bmp i izvlači
DDS datoteku bricks.ddsvith formatu BC3_UNORM i generiše lanac mipmaps sa 10 mipmapova.
texconv -m 10 -f BC3_UNORM treeArray.dds
9.4 Stvaranje tekstura
9.4.1 Učitavanje DDS datoteka
Microsoft obezbeđuje lagani izvorni kod za učitavanje DDS datoteka na:
https://github.com/Microsoft/DirectKSTK/viki/DDSTektureLoader
Međutim, u vrijeme ovog pisanja, kod podržava samo DirectKs 11. Imamo izmenio DDSTektureLoader.h
/ .cpp datoteke i obezbedio dodatnu metodu za DirectKs 12 (ovi modifikovani fajlovi se mogu naći u
zajedničkoj fascikli na DVD-u ili izvor za preuzimanje):
HRESULT DirectX::CreateDDSTextureFromFile12(
_In_ ID3D12Device* device,
_In_ ID3D12GraphicsCommandList* cmdList,
_In_z_ const wchar_t* szFileName,
_Out_ Microsoft::WRL::ComPtr<ID3D12Resource>&
texture,
_Out_ Microsoft::WRL::ComPtr<ID3D12Resource>&
textureUploadHeap);
1,uređaj: pokazivač na D3D uređaj za kreiranje teksturnih resursa.
2. cmdList: Lista komandi za slanje GPU komandi (npr. Kopiranje tekstura podataka iz
upload heap na default heap).
3. szFileName: Filename slike za učitavanje.
4. tekstura: vraća resurs teksture sa učitanim podacima o slici.
5. tektureUploadHeap: Vraća resurs teksture koji je korišćen kao upload
kupiti da kopira podatke slike u podrazumevani izvor resursa teksture. Ovaj resurs
ne može se uništiti sve dok GPU ne završi komandu kopiranja.
Da biste kreirali teksturu sa slike pod nazivom VoodCreate01.dds, mi ćemo pisati
sledeći:
struct Texture
{
// Unique material name for lookup.
std::string Name;
std::wstring Filename;
Microsoft::WRL::ComPtr<ID3D12Resource> Resource =
nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap =
nullptr;
};
auto woodCrateTex = std::make_unique<Texture>();
woodCrateTex->Name = “woodCrateTex”;
woodCrateTex->Filename = L”Textures/WoodCrate01.dds”;
ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(
md3dDevice.Get(), mCommandList.Get(),
woodCrateTex->Filename.c_str(),
woodCrateTex->Resource, woodCrateTex-
>UploadHeap));
9.4.2 SRV Heap
Kada se kreira teksturni resurs, potrebno je napraviti SRV deskriptor koji smo mi
može da se podesi na slot za parametre korijenskog potpisa za korišćenje programa shadera. Da bi to
učinili to, prvo moramo da napravimo skript descriptora ID3D12Device :: CreateDescriptorHeap za
čuvanje SRV deskriptora. sledeći kod gradi kuku sa tri deskriptora koji mogu da skladište CBV, SRV ili
UAV deskriptori i vidljivi su za shadere:
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 3;
srvHeapDesc.Type =
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags =
D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&srvHeapDesc, IID_PPV_ARGS(&mSrvDescriptorHeap)));
9.4.3 Creating SRV Descriptors
Once we have an SRV heap, we need to create the actual descriptors. An SRV
descriptor is described by filling out a D3D12_SHADER_RESOURCE_VIEW_DESC
object, which describes how the resource is used and other information—its format,
dimension, mipmaps count, etc.
typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC
{
DXGI_FORMAT Format;
D3D12_SRV_DIMENSION ViewDimension;
UINT Shader4ComponentMapping;
union
{
D3D12_BUFFER_SRV Buffer;
D3D12_TEX1D_SRV Texture1D;
D3D12_TEX1D_ARRAY_SRV Texture1DArray;
D3D12_TEX2D_SRV Texture2D;
D3D12_TEX2D_ARRAY_SRV Texture2DArray;
D3D12_TEX2DMS_SRV Texture2DMS;
D3D12_TEX2DMS_ARRAY_SRV Texture2DMSArray;
D3D12_TEX3D_SRV Texture3D;
D3D12_TEXCUBE_SRV TextureCube;
D3D12_TEXCUBE_ARRAY_SRV TextureCubeArray;
};
} D3D12_SHADER_RESOURCE_VIEW_DESC;
typedef struct D3D12_TEX2D_SRV
{
UINT MostDetailedMip;
UINT MipLevels;
UINT PlaneSlice;
FLOAT ResourceMinLODClamp;
} D3D12_TEX2D_SRV;
Za 2D teksture nas zanimaju samo D3D12_TEKS2D_SRV deo sindikata.
1. Format: Format resursa. Postavite ovo na DKSGI_FORMAT resursa
kreirate pogled da li je format bio bez tipova. Ako ste odredili bez tipova
DKSGI_FORMAT za resursa tokom kreiranja, onda morate navesti nenamjerno
format za prikaz ovde, tako da GPU zna kako da interpretira podatke.
oblik bez tipova
2. VievDimension: dimenzija resursa; Za sada, koristimo 2D teksture
mi odredimo D3D12_SRV_DIMENSION_TEKSTURE2D. Druga obična tekstura
dimenzije bi bile:
1. D3D12_SRV_DIMENSION_TEKSTURE1D: Resurs je 1D tekstura.
2. D3D12_SRV_DIMENSION_TEKSTURE3D: Resurs je 3D tekstura.
3. D3D12_SRV_DIMENSION_TEKSTURECUBE: Resurs je tekstura kocke.
3. Shader4ComponentMapping: Kada se tekstura uzorci u shaderu, to će biti
vratiti vektor podataka teksture u specificiranim teksturnim koordinatama. Ovo polje
nudi način za preuređivanje vektorskih komponenti koje se vraćaju prilikom uzorkovanja teksture.
Na primer, možete koristiti ovo polje da biste zamenili komponente crvene i zelene boje.
Ovo bi se koristilo u posebnim scenarijima, koji nama ne trebaju u ovoj knjizi. Pa mi
samo navedite D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING što će
ne preuredite komponente i samo vratite podatke u redosledu u kome je smešten
resurs teksture.
4. MostDetailedMip: Određuje indeks najnaprednijeg nivoa mipmap-a
pogled. Ovo će biti broj između 0 i MipCount-1.
5. MipLevels: Broj mipmap nivoa za pregled, počevši od
MostDetailedMip. Ovo polje, zajedno sa MostDetailedMip nam omogućava
odredite podređeni nivoa mipmap-a za prikaz. Možete odrediti -1 za prikazivanje za prikaz
svi mipmap nivoi od MostDetailedMip-a do poslednjeg nivoa mipmap-a.
6. PlaneSlice: Indeks plana.
7. ResourceMinLODClamp: Određuje minimalni nivo mipmap-a koji može biti
pristup. 0.0 znači pristup svim nivoima mipmap-a. Određivanje 3.0 znači
Mipmap nivoima 3.0 do MipCount-1 može se pristupiti.
Slijedeće naslikaje koje smo stvorili u prethodnom odeljku stvarno
deskriptori za tri izvora:
// Suppose the following texture resources are already
created.
// ID3D12Resource* bricksTex;
// ID3D12Resource* stoneTex;
// ID3D12Resource* tileTex;
// Get pointer to the start of the heap.
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(
mSrvDescriptorHeap-
>GetCPUDescriptorHandleForHeapStart());
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping =
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = bricksTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = bricksTex-
>GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
md3dDevice->CreateShaderResourceView(bricksTex.Get(),
&srvDesc, hDescriptor);
// offset to next descriptor in heap
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
srvDesc.Format = stoneTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = stoneTex-
>GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(stoneTex.Get(),
&srvDesc, hDescriptor);
// offset to next descriptor in heap
hDescriptor.Offset(1, mCbvSrvDescriptorSize);
srvDesc.Format = tileTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = tileTex-
>GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(tileTex.Get(),
&srvDesc, hDescriptor);
9.4.4 Vezivanje tekstura na cevovod
Trenutno upućujemo materijale za poziv pozivajući promenom materijala konstantnog pufera.
To znači da će sva geometrija u pozivu za prikupljanje imati iste vrednosti materijala. Ovo je
prilično ograničena, jer ne možemo precizirati varijacije materijala po pikselima, tako da našim scenama
nedostaju detalji.
Ideja o mapiranju tekstura je da dobije podatke o materijalu iz teksturnih karata umesto na
materijalni konstantni pufer. Ovo dozvoljava varijacije piksela koje povećavaju detalje i
realizam naše scene, kao što je prikazano na slici 9.1.
U ovom poglavlju, dodamo difuznu mapu teksture Albedo da odredimo difuzni albedo
komponenta našeg materijala. Vrednosti materijala FresnelR0 i Hrapavosti će i dalje biti
biti specificirani na frekvenciji poziva na osnovu izvora materijala pomoću materijalnog konstantnog
pufera; međutim, u
u poglavlju "Normalno mapiranje" opisaćemo kako da koristite teksturisanje
hrapavost na nivou piksela. Imajte na umu da ćemo teksturisanjem i dalje zadržati
DiffuseAlbedo komponenta u puferu za konstantu materijala. Zapravo, mi ćemo ga kombinirati
sa teksturom difuzno albedo vrednost na sledeći način u pikel-shader-u:
// Get diffuse albedo at this pixel from texture.
float4 texDiffuseAlbedo = gDiffuseMap.Sample(
gsamAnisotropicWrap, pin.TexC);
// Multiple texture sample with constant buffer
albedo.
float4 diffuseAlbedo = texDiffuseAlbedo *
gDiffuseAlbedo;
Obično ćemo postaviti DiffuseAlbedo = (1,1,1,1) tako da se ne menja
tekDiffuseAlbedo. Međutim, ponekad je korisno malo podesiti difuzno
albedo bez potrebe da autor nove teksture. Na primer, pretpostavimo da smo imali ciglu
tekstura i umetnik žele da ga blago zatamne. Ovo bi moglo da se postigne
smanjivanje crvenih i zelenih komponenti postavljanjem DiffuseAlbedo = (0,9,0,9,1,1). Dodali smo indeks
našoj definiciji materijala, koji se odnosi na SRV u hematu descriptora koji određuje teksturu vezanu za
materijal:
struct Material
{

// Index into SRV heap for diffuse texture.
int DiffuseSrvHeapIndex = -1;

};
Zatim, pod pretpostavkom da je root potpis definisan tako da očekuje tabelu shadera prikaza resursa da
budu vezani za parametar 0-tog slota, možemo da nacrtamo naše stavke sa teksturom koristeći sledeći
kod:
void CrateApp::DrawRenderItems(
ID3D12GraphicsCommandList* cmdList,
const std::vector<RenderItem*>& ritems)
{
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
UINT matCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));
auto objectCB = mCurrFrameResource->ObjectCB-
>Resource();
auto matCB = mCurrFrameResource->MaterialCB-
>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);
CD3DX12_GPU_DESCRIPTOR_HANDLE tex(
mSrvDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->Mat->DiffuseSrvHeapIndex,
mCbvSrvDescriptorSize);
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress =
objectCB->GetGPUVirtualAddress() +
ri->ObjCBIndex*objCBByteSize;
D3D12_GPU_VIRTUAL_ADDRESS matCBAddress =
matCB->GetGPUVirtualAddress() +
ri->Mat->MatCBIndex*matCBByteSize;
cmdList->SetGraphicsRootDescriptorTable(0, tex);
cmdList->SetGraphicsRootConstantBufferView(1,
objCBAddress);
cmdList->SetGraphicsRootConstantBufferView(3,
matCBAddress);
cmdList->DrawIndexedInstanced(ri->IndexCount,
1, ri->StartIndexLocation,
ri->BaseVertexLocation, 0);
}
}
9.5.1 Povećanje
Elementi mape teksture treba smatrati diskretnim uzorcima boje od a
kontinuirana slika; ne treba ih smatrati pravougaonicima sa područjima. Dakle, pitanje
je: Šta se dešava ako imamo teksturne koordinate (u, v) koje se ne poklapaju sa jednim od ovih
tekel poene? Ovo se može desiti u sledećoj situaciji. Pretpostavimo da se igrač zumira na a
zid na sceni tako da zid pokriva ceo ekran. Zbog primera,
Pretpostavimo da je rezolucija monitora 1024 × 1024, a rezolucija teksture je 256 ×
256. Ovo ilustruje povećanje teksture - pokušavamo da pokrijemo mnoge piksele sa nekoliko
tekels. U našem primeru, između svake tekel tačke leži četiri piksela. Svaki piksel će biti dat
Par jedinstvenih tekstura koordinira kada su koordinate teksture koordinata interpolirane
preko trougla. Tako će biti piksela sa teksturnim koordinatama koje se ne poklapaju
sa jednim od teksela. S obzirom na boje na tekselima možemo približiti boje
između teksela koristeći interpolaciju. Postoje dve metode grafike interpolacije
hardverska podrška: konstantna interpolacija i linearna interpolacija. U praksi, linearni
interpolacija se skoro uvek koristi.
Slika 9.5 ilustruje ove metode u 1D: Pretpostavimo da imamo 1D teksturu sa 256
uzorci i interpolirana teksturna koordinata u = 0.126484375. Ova normalizovana tekstura
koordinata se odnosi na 0.126484375 × 256 = 32.38 tekel. Naravno, ova vrijednost leži
između dva naša uzorka tekela, tako da moramo koristiti interpolaciju da ga približimo.
Slika 9.5. (a) S obzirom na tekselske tačke, mi konstruišemo konstantnu kičmu
funkciju približavanja vrednosti između teksela; ovo se ponekad naziva
uzorkovanje najbližih susednih tačaka, pošto se koristi vrijednost najbližeg teksela. (b)
S obzirom na tekselske tačke, mi konstruišemo kosu linearnu funkciju kako bismo približili
vrednosti između tekselnih tačaka.
2D linearna interpolacija se zove bilinearna interpolacija i ilustrovana je na slici 9.6.
S obzirom na par tekstura koordinata između četiri teksela, radimo dva 1D linearna
interpolacije u smeru u, praćena jednom 1D interpolacijom u smeru v.
Slika 9.6. Ovde imamo četiri tekel tačke: ci, ci, j + 1, ci + 1, j i ci + 1, j + 1. Mi
žele približiti boju c, koja leži između ovih četiri tekel tačke, koristeći
interpolacija; u ovom primeru, c leži 0,75 jedinica desno od ci i 0,38 jedinica ispod
cij. Prvo radimo 1D linearnu interpolaciju između dvije najviše boja da bi dobili cT.
Isto tako, radimo 1D linearnu interpolaciju između donjih dve boje da bi dobili cB.
Na kraju, radimo 1D linearnu interpolaciju između cT i cB kako bi dobili c.
Na slici 9.7 prikazana je razlika između konstantne i linearne interpolacije. Kao što možete
vidi, konstantna interpolacija ima karakteristiku stvaranja slike sa blokom slike.
Linearna interpolacija je glatkija, ali ipak neće izgledati tako dobra kao da imamo stvarne podatke (npr.
tekstura veće rezolucije) umesto izvedenih podataka preko interpolacije.
Slika 9.7. Zumirate na kocku sa strukturom sanduke tako da uvećava
javlja. S leve strane koristimo konstantnu interpolaciju, što dovodi do blokade
izgled; ovo ima smisla jer interpolirajuća funkcija ima diskontinuitet
(Slika 9.5a), što čini promene naglo, a ne glatke. Na desnoj strani mi
koristite linearno filtriranje, što rezultira u boljoj slici zbog kontinuiteta
interpolirajuća funkcija.
Jedna stvar koja treba pomenuti u ovoj diskusiji je da ne postoji pravi način da se približi
uvećanje u interaktivnom 3D programu gde se virtualno oko može slobodno kretati
i istražiti. Sa nekih udaljenosti, teksture će izgledati sjajno, ali će početi da se razbijaju
dok se oko približava njima. Neke igre ograničavaju koliko blizu virtualno oko
dođite do površine da biste izbegli prekomerno uvećanje. Korišćenje tekstura viših rezolucija može
pomoć.
9.5.2 Minifikacija
Minifikacija je suprotna od uvećanja. U minifizaciji su previše teksela
premješten je na premalo piksela. Na primer, razmotrite sledeću situaciju u kojoj smo
imaju zid sa teksturom od 256 × 256. Oko, gledajući zid, drži
pomerajući se unazad tako da zid postaje manji i manji dok ne pokriva samo 64 × 64 piksela
na ekranu. Dakle, sada imamo 256 × 256 teksela koji se mapiraju na 64 × 64 piksela ekrana. In
ova situacija, koordinate tekstura za piksele će se i dalje generalno ne poklapati sa bilo kojim od
tekels mape teksture, pa se i dalje primenjuju konstantni i linearni filteri interpolacije
slučaj minifikacije. Međutim, još se može uraditi sa minifikacijom. Intuitivno, a
potrebno je uvesti prosečnu uštedu uzoraka od 256k255 teksela kako bi se smanjio na 64k
64. Tehnika mipmappinga nudi efikasnu aproksimaciju za ovo na teret
neke dodatne memorije. U inicijalizacijskom vremenu (ili vremenu kreiranja sredstva), manje verzije
tekstura se pravi pomoću smanjenja slike kako bi se napravio mipmap lanac (pogledajte Sliku
9.8). Prema tome, prosečno delo se pretpostavlja za veličine mipmap-a. U toku rada,
grafički hardver će uraditi dvije različite stvari bazirane na postavkama mipmap-a koje određuje
programator:
1. Izaberite i koristite nivo mipmap-a koji najbolje odgovara projektovanoj geometriji ekrana
rezolucija za teksturisanje, primena konstantne ili linearne interpolacije po potrebi. Ovo je
nazvanu filtriranje tačke za mipmaps jer je to kao stalna interpolacija - upravo ste
izaberite najbliži mipmap nivo i koristite ga za teksturu.
2. Izaberite dva najbližih nivoa mipmap-a koji najbolje odgovaraju geometriji projektovanog ekrana
rezolucija za teksturu (jedan će biti veći i jedan će biti manji od ekrana
rezolucija geometrije). Zatim primenite konstantno ili linearno filtriranje na oba ova mipmap-ova
nivoa za proizvodnju boje teksture za svaku od njih. Na kraju, interpolirajte između ove dve
rezultati teksture u boji. Ovo se naziva linearno filtriranje za mipmaps jer je to
linearna interpolacija - linijski interpolirate između dva najbližeg nivoa mipmap-a.
Izborom najboljih tekstura nivoa detalja iz mipmap lanca, količina
minifikacija je značajno smanjena.
Slika 9.8. Lanac mipmapa; svaki sledeći mipmap je pola veličine, u
svake dimenzije, prethodnog mipmap nivoa detalja do 1 × 1.
Kao što je pomenuto u §9.3.2, mipmaps se mogu kreirati pomoću Photoshop DDS
izvoznik plugin, ili pomoću tekconv programa. Ovi programi koriste a
algoritam smanjenja snimanja za generisanje nižih nivoa mipmap-a iz baze
slike podataka. Ponekad ovi algoritmi ne čuvaju detalje koje želimo i
umetnik mora ručno kreirati / uređivati niže nivoe mipmap-a kako bi ga zadržao
važni detalji.
9.5.3 Anizotropno filtriranje
Druga vrsta filtera koja se može koristiti se naziva anizotropno filtriranje. Ovaj filter pomaže
ublažiti distorziju koja se javlja kada je ugao između normalnog vektorja poligona i
Vektor vektorskog izgleda kamere je širok (npr. kada je poligon ortogonalan za prozor prikaza).
Ovaj filter je najskuplji, ali može biti vrijedan troškova za ispravljanje izobličenja
artefakti. Na slici 9.9 prikazan je ekran koji upoređuje anizotropno filtriranje sa linearnim
filtriranje.
Slika 9.9. Gornja strana sanduka je skoro ortogonična prema prozoru za prikaz.
(Levo) Koristeći linearno filtriranje gornji deo sanduka je loše zamagljen. (Desno) anizotropno
Filtriranje čini bolji posao pri postavljanju gornjeg lica kante iz ovog ugla.
9.6 ADRESNI MODI
Tekstura, kombinovana sa konstantnom ili linearnom interpolacijom, definiše vektorski vrednost
funkcija T (u, v) = (r, g, b, a). To jest, s obzirom na koordinate teksture (u, v) ∈ [0, 1] 2
tekstura T vraća boju (r, g, b, a). Direct3D nam dozvoljava da proširimo domen
ovu funkciju na četiri različita načina (nazvani adresni režimi): omot, boja granice, obujmica,
i ogledalo.
1. vrap proširuje funkciju teksture ponavljanjem slike na svakom integralnom spoju
(vidi sliku 9.10).
Slika 9.10. Vrap address mod.
2. Boja boje proširuje funkciju teksture mapiranjem svakog (u, v) ne u [0, 1] 2 do
neku boju koju je programer odredio (pogledajte sliku 9.11).
Slika 9.11. Režim adresa adrese boja.
3. Stezaljka proširuje funkciju teksture tako što mapira svaku (u, v) ne u [0, 1] 2 u boju
T (u0, v0), gde je (u0, v0) najbliža tačka za (u, v) koja je sadržana u [0, 1] 2 (vidi Sliku
9.12).
Slika 9.12. Režim adresiranja spona.
4. ogledalo proširuje funkciju teksture ogledalom slike na svakom integralnom spoju
(vidi sliku 9.13).
Slika 9.13. Režim adresiranja ogledala.
Režim adresiranja je uvek naveden (Vrap mod je podrazumevano), pa zato tekstura
koordinate izvan opsega [0, 1] uvek su definisane.
Verovatno je najčešće korišten način adresiranja zavoja; to nam dozvoljava da pločkimo
teksturu više puta na nekoj površini. Ovo nam efikasno omogućava da povećamo teksturu
rezolucije bez dostavljanja dodatnih podataka (iako je dodatna rezolucija ponavljana).
Sa pločicama, obično je važno da je tekstura besprekorna. Na primer, kratereks nije besprekoran, jer
jasno možete videti ponavljanje. Međutim, na slici 9.14 prikazana je besprekorna tekstura od cigle koja
se ponavlja 2 × 3 puta.
Režimi adresa su opisani u Direct3D preko D3D12_TEKSTURE_ADDRESS_MODE nabrojanog tipa:
typedef enum D3D12_TEXTURE_ADDRESS_MODE
{
D3D12_TEXTURE_ADDRESS_MODE_WRAP = 1,
D3D12_TEXTURE_ADDRESS_MODE_MIRROR = 2,
D3D12_TEXTURE_ADDRESS_MODE_CLAMP = 3,
D3D12_TEXTURE_ADDRESS_MODE_BORDER = 4,
D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE = 5
} D3D12_TEXTURE_ADDRESS_MODE;
9.7 SAMPLER OBJEKTI
Iz prethodnih dve sekcije vidimo da pored podataka o tekstu postoje i dve
drugi ključni koncepti uključeni u upotrebu tekstura: filtriranje tekstura i adresni režimi. Šta
filter i adresni režim koji se koristi prilikom uzorkovanja resursa teksture definiše se uzorku
objekat. U aplikaciji je obično potrebno nekoliko objekata za uzorkovanje uzoraka tekstura
različiti načini.
9.7.1 Kreiranje uzoraka
Kao što ćemo videti u sledećem odeljku, uzorci se koriste u shaderima. Da bi se vezao
za uzorkovanje u shadere za upotrebu, moramo vezati deskriptore za uzorkovanje objekata. Sledeći
kod prikazuje primjer korijenskog potpisa tako da drugi slot uzme tablicu jednog
deskriptor uzorkovanja vezan za slot registratora uzorka 0.
CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1,
0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
1, 0);
descRange[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1,
0);
CD3DX12_ROOT_PARAMETER rootParameters[3];
rootParameters[0].InitAsDescriptorTable(1,
&descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1,
&descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[2].InitAsDescriptorTable(1,
&descRange[2], D3D12_SHADER_VISIBILITY_ALL);
CD3DX12_ROOT_SIGNATURE_DESC descRootSignature;
descRootSignature.Init(3, rootParameters, 0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYO
Ako nameravamo da postavimo deskriptore uzorka, treba nam kupac za uzorkovanje. Sampler
kopija se kreira popunjavanjem instance D3D12_DESCRIPTOR_HEAP_DESC i
navodeći tip slova D3D12_DESCRIPTOR_HEAP_TIPE_SAMPLER:
D3D12_DESCRIPTOR_HEAP_DESC descHeapSampler = {};
descHeapSampler.NumDescriptors = 1;
descHeapSampler.Type =
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
descHeapSampler.Flags =
D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ComPtr<ID3D12DescriptorHeap> mSamplerDescriptorHeap;
ThrowIfFailed(mDevice-
>CreateDescriptorHeap(&descHeapSampler,
__uuidof(ID3D12DescriptorHeap),
(void**)&mSamplerDescriptorHeap));
Once we have a sampler heap, we can create sampler descriptors. It is here that we
specify the address mode and filter type, as well as other parameters by filling out a
D3D12_SAMPLER_DESC object:
typedef struct D3D12_SAMPLER_DESC
{
D3D12_FILTER Filter;
D3D12_TEXTURE_ADDRESS_MODE AddressU;
D3D12_TEXTURE_ADDRESS_MODE AddressV;
D3D12_TEXTURE_ADDRESS_MODE AddressW;
FLOAT MipLODBias;
UINT MaxAnisotropy;
D3D12_COMPARISON_FUNC ComparisonFunc;
FLOAT BorderColor[ 4 ];
FLOAT MinLOD;
FLOAT MaxLOD;
} D3D12_SAMPLER_DESC;
1,Filter: Član D3D12_FILTER navedenog tipa da odredi vrstu
filtriranje za korišćenje.
2. AddressU: režim adresiranja u horizontalnom smeru u pravcu teksture.
3. AddressV: Režim adresa u vertikalnom smeru v-ose teksture.
4. AddressV: režim adresiranja u pravcu dubine v osovine teksture
(primenjuje se samo na 3D teksture).
5. MipLODBias: Vrednost za pristranost izabranog mipmap nivoa. Navedite 0.0 bez pristrasnosti.
6. MakAnisotropija: Maksimalna vrednost anizotropije koja mora biti između 1-16
inclusiveli. Ovo se primjenjuje samo za D3D12_FILTER_ANISOTROPIC ili
D3D12_FILTER_COMPARISON_ANISOTROPIC. Veće vrednosti su više
skupo, ali može dati bolje rezultate.
7. ComparisonFunc: Napredne opcije koje se koriste za neke specijalizovane aplikacije kao što su
mapiranje senki. Za sada, samo postavite na D3D12_COMPARISON_FUNC_ALVAIS do
poglavlje mapiranja senki.
8. BorderColor: Koristi se za određivanje boje granice za režim adresa
D3D12_TEKSTURE_ADDRESS_MODE_BORDER.
9. MinLOD: Minimalni nivo mipmap-a koji se može izabrati.
10. MakLOD: Maksimalan nivo mipmap-a koji se može izabrati.
U nastavku su navedeni primeri najčešće korišćenih tipova D3D12_FILTER:
1. D3D12_FILTER_MIN_MAG_MIP_POINT: Točno filtriranje preko mape teksture, i
filtriranje tačke preko nivoa mipmap-a (tj., koristi se najbliži nivo mipmap-a).
2. D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT: Bilinearno filtriranje preko
mapu teksture i filtriranje tačaka preko nivoa mipmap-a (tj. najbližeg nivoa mipmap-a
se koristi).
3. D3D12_FILTER_MIN_MAG_MIP_LINEAR: Bilinearno filtriranje preko mape teksture,
i linearno filtriranje između dva najbližeg nivoa donjeg i gornjeg mipmap-a. Ovo je
često se naziva trilinearno filtriranje.
4. D3D12_FILTER_ANISOTROPIC: Anizotropno filtriranje za minifikaciju,
uvećanje i mipmapping.
Možete saznati i druge moguće permutacije iz ovih primjera, ili možete
potražite tip naveden u D3D12_FILTER u SDK dokumentaciji.
Sledeći primer pokazuje kako da napravite deskriptor za uzorkovač u tom kupu
koristi linearno filtriranje, režim adresiranja i tipične podrazumevane vrednosti za drugu
parametri:
D3D12_SAMPLER_DESC samplerDesc = {};
samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU =
D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressV =
D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressW =
D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc =
D3D12_COMPARISON_FUNC_ALWAYS;
md3dDevice->CreateSampler(&samplerDesc,
mSamplerDescriptorHeap-
>GetCPUDescriptorHandleForHeapStart());
The following code shows how to bind a sampler descriptor to a root signature
parameter slot for use by the shader programs:
commandList->SetGraphicsRootDescriptorTable(1,
samplerDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart());
1. Filter: Član D3D12_FILTER navedenog tipa da odredi vrstu
filtriranje za korišćenje.
2. AddressU: režim adresiranja u horizontalnom smeru u pravcu teksture.
3. AddressV: Režim adresa u vertikalnom smeru v-ose teksture.
4. AddressV: režim adresiranja u pravcu dubine v osovine teksture
(primenjuje se samo na 3D teksture).
5. MipLODBias: Vrednost za pristranost izabranog mipmap nivoa. Navedite 0.0 bez pristrasnosti.
6. MakAnisotropija: Maksimalna vrednost anizotropije koja mora biti između 1-16
inclusiveli. Ovo se primjenjuje samo za D3D12_FILTER_ANISOTROPIC ili
D3D12_FILTER_COMPARISON_ANISOTROPIC. Veće vrednosti su više
skupo, ali može dati bolje rezultate.
7. ComparisonFunc: Napredne opcije koje se koriste za neke specijalizovane aplikacije kao što su
mapiranje senki. Za sada, samo postavite na D3D12_COMPARISON_FUNC_ALVAIS do
poglavlje mapiranja senki.
8. BorderColor: Koristi se za određivanje boje granice za režim adresa
D3D12_TEKSTURE_ADDRESS_MODE_BORDER.
9. MinLOD: Minimalni nivo mipmap-a koji se može izabrati.
10. MakLOD: Maksimalan nivo mipmap-a koji se može izabrati.
U nastavku su navedeni primeri najčešće korišćenih tipova D3D12_FILTER:
1. D3D12_FILTER_MIN_MAG_MIP_POINT: Točno filtriranje preko mape teksture, i
filtriranje tačke preko nivoa mipmap-a (tj., koristi se najbliži nivo mipmap-a).
2. D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT: Bilinearno filtriranje preko
mapu teksture i filtriranje tačaka preko nivoa mipmap-a (tj. najbližeg nivoa mipmap-a
se koristi).
3. D3D12_FILTER_MIN_MAG_MIP_LINEAR: Bilinearno filtriranje preko mape teksture,
i linearno filtriranje između dva najbližeg nivoa donjeg i gornjeg mipmap-a. Ovo je
često se naziva trilinearno filtriranje.
4. D3D12_FILTER_ANISOTROPIC: Anizotropno filtriranje za minifikaciju,
uvećanje i mipmapping.
Možete saznati i druge moguće permutacije iz ovih primjera, ili možete
potražite tip naveden u D3D12_FILTER u SDK dokumentaciji.
Sledeći primer pokazuje kako da napravite deskriptor za uzorkovač u tom kupu
koristi linearno filtriranje, režim adresiranja i tipične podrazumevane vrednosti za drugu
parametri:
9.7.2 Statički uzorci
Ispostavlja se da grafička aplikacija obično koristi samo šaku samplera.
Zbog toga, Direct3D pruža posebnu prečicu za definisanje niza semplova i podešavanja
bez ići kroz proces stvaranja kupca uzorkovača. Init funkcija
klasa CD3DKS12_ROOT_SIGNATURE_DESC ima dva parametra koji vam omogućavaju
definišite niz takozvanih statičkih semplera koje vaša aplikacija može koristiti. Statički uzorci su
opisana strukturom D3D12_STATIC_SAMPLER_DESC. Ova struktura je veoma
slično D3D12_SAMPLER_DESC, sa sledećim izuzecima:
1. Postoje određena ograničenja u vezi sa onim što može biti granična boja. Konkretno, granica
boja statičkog semplera mora biti član:
enum D3D12_STATIC_BORDER_COLOR
{
D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK =
0,
D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK = (
D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK + 1
),
D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE = (
D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK + 1 )
}D3D12_STATIC_BORDER_COLOR;
2. Sadrži dodatna polja za određivanje registra shadera, registarskog prostora i shadera
vidljivost, koja bi se normalno odredila kao deo kupovine uzorka.
Pored toga, možete definisati samo 2032 statički semplere, što je više od
dovoljno za većinu aplikacija. Međutim, ako vam je potrebno više, možete jednostavno koristiti ne-
statične uzorke i prođite kroz kupovinu uzorkovača.
U našim demo-temama koristimo statičke uzorkovače. Sledeći kod pokazuje kako definišemo naše
statički uzorci. Imajte na umu da nam ne trebaju svi ovi statički uzorci u našim demo-prikazima, ali mi
definišu ih u svakom slučaju, tako da su tamo ako ih trebamo. U svakom slučaju je samo šaka,
i ne boli da definiše nekoliko dodatnih uzorkovača koji se mogu ili ne mogu koristiti.
std::array<const CD3DX12_STATIC_SAMPLER_DESC,
6> TexColumnsApp::GetStaticSamplers()
{
// Applications usually only need a handful of
samplers. So just define them
// all up front and keep them available as part of
the root signature.
const CD3DX12_STATIC_SAMPLER_DESC pointWrap(
0, // shaderRegister
D3D12_FILTER_MIN_MAG_MIP_POINT, // filter
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressU
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressV
D3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressW
const CD3DX12_STATIC_SAMPLER_DESC pointClamp(
1, // shaderRegister
D3D12_FILTER_MIN_MAG_MIP_POINT, // filter
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // addressU
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // addressV
D3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressW
const CD3DX12_STATIC_SAMPLER_DESC linearWrap(
2, // shaderRegister
D3D12_FILTER_MIN_MAG_MIP_LINEAR, // filter
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressU
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressV
D3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressW
const CD3DX12_STATIC_SAMPLER_DESC linearClamp(
3, // shaderRegister
D3D12_FILTER_MIN_MAG_MIP_LINEAR, // filter
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // addressU
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // addressV
D3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressW
const CD3DX12_STATIC_SAMPLER_DESC anisotropicWrap(
4, // shaderRegister
D3D12_FILTER_ANISOTROPIC, // filter
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressU
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressV
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // addressW
0.0f, // mipLODBias
8); // maxAnisotropy
const CD3DX12_STATIC_SAMPLER_DESC anisotropicClamp(
5, // shaderRegister
D3D12_FILTER_ANISOTROPIC, // filter
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // addressU
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // addressV
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // addressW
0.0f, // mipLODBias
8); // maxAnisotropy
return {
pointWrap, pointClamp,
linearWrap, linearClamp,
anisotropicWrap, anisotropicClamp };
}
void TexColumnsApp::BuildRootSignature()
{
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1,
0);
// Root parameter can be a table, root descriptor or
root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[4];
slotRootParameter[0].InitAsDescriptorTable(1,
&texTable, D3D12_SHADER_VISIBILITY_PIXEL);
slotRootParameter[1].InitAsConstantBufferView(0);
slotRootParameter[2].InitAsConstantBufferView(1);
slotRootParameter[3].InitAsConstantBufferView(2);
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);
// 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(mRootSignature.GetAddressOf())));
}
9.8 TEKSTURA ZA UZORKOVANJE U SHADERU
Tekst strukture je definisan u HLSL i dodeljen je registru tekstura sa
sledeća sintaksa:
Tekture2D gDiffuseMap: registru (t0);
Imajte na umu da se tekstura registruje upotrebom navedenom tn gdje je n cijeli broj koji identifikuje
slot za teksturni registar. Definicija korenog potpisa određuje mapiranje iz slota
parametar u shader registar; ovo je kako kod aplikacije može povezati SRV na a
konkretan teksture2D objekat u shaderu.
Slično tome, objekti za uzorkovanje su definirani HLSL i dodeljeni su registru uzoraka
sledeća sintaksa:
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
Ovi uzorci odgovaraju statinskom nizu uzoraka koji smo postavili u prethodnom odeljku.
Imajte na umu da se teksture registruju koristeći odrenene od strane sn gdje je n integer koji identifikuje
uzorkovač
registarski slot.
Sada, s obzirom na par koordinata teksture (u, v) za piksel u pikel shaderu, mi
zapravo probajte teksturu pomoću metode Tekture2D :: Sample:
Texture2D gDiffuseMap : register(t0);
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float2 TexC : TEXCOORD;
};
float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo =
gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) *
gDiffuseAlbedo;

Prošli smo objekat SamplerState za prvi parametar koji ukazuje na to kako tekstura
podaci će biti uzorkovani i mi prosledimo koordinate teksture (u, v) piksela za drugu
parametar. Ova metoda vraća interpoliranu boju sa mape teksture na navedenom
(u, v) koriste metode filtriranja koje je odredio objekt SamplerState.
9.9 CRATE DEMO
Sada pregledamo ključne tačke dodavanja teksture sandučića u kocku (kao što je prikazano na slici
9.1).
9.9.1 Specifikacija koordinata teksta
GeometriGenerator :: CreateBok generiše koordinate teksture za
kutiju tako da se cela tekstura slika mapira na svako lice kutije. Za kratkotrajnost,
prikazujemo samo definicije verteka za prednje, zadnje i gornje lice. Imajte na umu da smo mi
izostaviti koordinate za normalne i tangentne vektore u konstruktoru Vertek-a (npr
koordinate teksture su boldirane).
GeometryGenerator::MeshData
GeometryGenerator::CreateBox(
float width, float height, float depth,
uint32 numSubdivisions)
{
MeshData meshData;
Vertex v[24];
float w2 = 0.5f*width;
float h2 = 0.5f*height;
float d2 = 0.5f*depth;
// Fill in the front face vertex data.
v[0] = Vertex(-w2, -h2, -d2, …, 0.0f, 1.0f);
v[1] = Vertex(-w2, +h2, -d2, …, 0.0f, 0.0f);
v[2] = Vertex(+w2, +h2, -d2, …, 1.0f, 0.0f);
v[3] = Vertex(+w2, -h2, -d2, …, 1.0f, 1.0f);
// Fill in the back face vertex data.
v[4] = Vertex(-w2, -h2, +d2, …, 1.0f, 1.0f);
v[5] = Vertex(+w2, -h2, +d2, …, 0.0f, 1.0f);
v[6] = Vertex(+w2, +h2, +d2, …, 0.0f, 0.0f);
v[7] = Vertex(-w2, +h2, +d2, …, 1.0f, 0.0f);
// Fill in the top face vertex data.
v[8] = Vertex(-w2, +h2, -d2, …, 0.0f, 1.0f);
v[9] = Vertex(-w2, +h2, +d2, …, 0.0f, 0.0f);
v[10] = Vertex(+w2, +h2, +d2, …, 1.0f, 0.0f);
v[11] = Vertex(+w2, +h2, -d2, …, 1.0f, 1.0f);
Refer back to Figure 9.3 if you need help seeing why the texture coordinates are
specified this way.
9.9.2 Creating the Texture
We create the texture from file at initialization time as follows:
// Helper structure to group data related to the
texture.
struct Texture
{
// Unique material name for lookup.
std::string Name;
std::wstring Filename;
Microsoft::WRL::ComPtr<ID3D12Resource> Resource =
nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap =
nullptr;
};
std::unordered_map<std::string,
std::unique_ptr<Texture>> mTextures;
void CrateApp::LoadTextures()
{
auto woodCrateTex = std::make_unique<Texture>();
woodCrateTex->Name = “woodCrateTex”;
woodCrateTex->Filename =
L”Textures/WoodCrate01.dds”;
ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.mCommandList.Get(), woodCrateTex-
>Filename.c_str(),
woodCrateTex->Resource, woodCrateTex-
>UploadHeap));
mTextures[woodCrateTex->Name] =
std::move(woodCrateTex);
}
Sve naše jedinstvene teksture čuvamo na neurednoj mapi tako da ih možemo pogledati
poimence. U proizvodnom kodu, pre nego što učitate teksturu, želeli biste da proverite da li je
podaci o teksturi su već učitani (tj. da li je već sadržan na neuređenoj mapi), tako da
da se ne učitava više puta.
9.9.3 Podešavanje teksture
Kada je stvorena tekstura i SRV je napravljen za njega u opisu
kupe, vezivanje teksture na plinovod, tako da se može koristiti u šemer programima je jednostavno
pitanje postavljanja na parametar root signature koji očekuje teksturu:
// Get SRV to texture we want to bind.
CD3DX12_GPU_DESCRIPTOR_HANDLE tex(
mSrvDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart());
tex.Offset(ri->Mat->DiffuseSrvHeapIndex,
mCbvSrvDescriptorSize);

// Bind to root parameter 0. The root parameter
description specifies which
// shader register slot this corresponds to.
cmdList->SetGraphicsRootDescriptorTable(0, tex);
9.9.4 Updated HLSL
Below is the revised Default.hlsl file that now supports texturing (texturing code has
been bolded):
// 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”
Texture2D gDiffuseMap : register(t0);
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;
};
// 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];
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
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;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut)0.0f;
// 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);
// 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, gMatTransform).xy;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo =
gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) *
gDiffuseAlbedo;
// 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;
const float shininess = 1.0f - gRoughness;
Material mat = { diffuseAlbedo, gFresnelR0,
shininess };
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat,
pin.PosW,
pin.NormalW, toEyeW, shadowFactor);
float4 litColor = ambient + directLight;
// Common convention to take alpha from diffuse
albedo.
litColor.a = diffuseAlbedo.a;
return litColor;
}
9.10 TRANSFORMACIONI TEKSTURI
Dve konstantne varijable bafera o kojima nismo razgovarali su gTekTransform I gMatTransform. Ove
varijable se koriste u vertek shader-u da transformišu ulaz teksture koordinate:
// Output vertex attributes for interpolation across
triangle.
float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f),
gTexTransform);
vout.TexC = mul(texC, gMatTransform).xy;
Teksturne koordinate predstavljaju 2D tačke na teksturiranoj ravni. Dakle, možemo prevesti,
okrenite ih i skalirajte ih kao što biste mogli u bilo kojoj drugoj tački. Evo nekog primera za koji se koristi
transformirajuće teksture:
1. Tekstilna opeka se proteže duž zida. Verzije zida trenutno imaju teksturu
Koordinate u opsegu [0, 1]. Mi skaliramo koordinate teksture za 4 da ih umanjimo
opseg [0, 4], tako da se tekstura ponavlja četiri do četiri puta preko
zid.
2. Imamo teksture oblaka koji se protežu preko čistog plavog neba. Prevođenjem teksture
koordinira kao funkciju vremena, oblaci se animiraju preko neba.
3. Rotiranje teksture ponekad je korisno za efekte čestica, gde rotiramo a
na primer, teksture vatrenog oružja.
U demonstraciji "Crate", koristimo transformaciju identitetne matrice tako da ulazna tekstura
koordinate ostaju neizmenjene, ali u sledećem poglavlju objašnjavamo demo koji koristi
tekstura transformiše.
Imajte na umu da za transformaciju koordinata 2D teksture pomoću matrice 4 × 4, povećavamo ga
4D vektor:
vin.TekC -> float4 (vin.Tek, 0.0f, 1.0f)
Nakon što se multiplikacija završi, dobijeni 4D vektor se vraća nazad u 2D vektor
odbacujući z- i v-komponente. To je,
vout.TekC = mul (float4 (vin.TekC, 0.0f, 1.0f),
gTekTransform).
Koristimo dve odvojene matrice transformacije tekstura gTekTransform i
gMatTransform jer ponekad ima više smisla da se materijal transformiše
teksture (za animirane materijale kao što je voda), ali ponekad ima više smisla za to
tekstura se transformiše kao svojstvo objekta.
Zbog toga što radimo sa 2D koordinatama tekstura, jedino nam je stalo
transformacije su izvršene na prve dve koordinate. Na primer, ako je tekstura matrica
prevedena z-koordinata, to ne bi imalo nikakvog uticaja na rezultujuće koordinate teksture.
9.11 TEKSTIRANI HILJI I VALOVI DEMO
U ovom demonstracijom dodamo teksturu na našu zemlju i vodenu scenu. Prvo ključno pitanje je to
pločićemo teksturu trava preko zemlje. Jer kopnena mreža je velika površina, ako jednostavno
proširila teksturu nad njom, onda su premalo teksela pokrivale svaki trougao. Drugim rečima,
za površinu nema dovoljno rezolucije tekstura; tako ćemo dobiti uvećanje
artefakti. Dakle, ponavljamo teksturu trava preko kopnene mreže da bi dobili više rezolucije.
Drugo ključno pitanje je da pomerimo teksturu vode preko geometrije vode kao a
funkcija vremena. Ovaj dodatni pokret čini vodu malo ubedljivijim. Slika 9.15
prikazuje snimak ekrana.
Slika 9.15. Snimak ekrana Land Tek demo.
9.11.1 Generisanje koordinata teksture mreže
Slika 9.16 prikazuje m × n mrežu u kz-ravnini i odgovarajuću mrežu u
normalizovan domen teksturnog prostora [0, 1] 2. Sa slike, jasno je da tekstura
koordinate ijth grid verteka u kz-ravnini su koordinate ijth grid vertek
u teksturnom prostoru. Koordinate teksturnog prostora ijth verteka su:

Stoga, koristimo sljedeći kod da generišemo koordinate teksta za mrežu u


GeometryGenerator :: CreateGrid metod:
GeometryGenerator::MeshData
GeometryGenerator::CreateGrid(float width, float
depth, uint32 m, uint32 n)
{
MeshData meshData;
uint32 vertexCount = m*n;
uint32 faceCount = (m-1)*(n-1)*2;
float halfWidth = 0.5f*width;
float halfDepth = 0.5f*depth;
float dx = width / (n-1);
float dz = depth / (m-1);
float du = 1.0f / (n-1);
float dv = 1.0f / (m-1);
meshData.Vertices.resize(vertexCount);
for(uint32 i = 0; i < m; ++i)
{
float z = halfDepth - i*dz;
for(uint32 j = 0; j < n; ++j)
{
float x = -halfWidth + j*dx;
meshData.Vertices[i*n+j].Position = XMFLOAT3(x,
0.0f, z);
meshData.Vertices[i*n+j].Normal =
XMFLOAT3(0.0f, 1.0f, 0.0f);
meshData.Vertices[i*n+j].TangentU =
XMFLOAT3(1.0f, 0.0f, 0.0f);
// Stretch texture over grid.
meshData.Vertices[i*n+j].TexC.x = j*du;
meshData.Vertices[i*n+j].TexC.y = i*dv;
}
}
9.11.2 Obloga tekstura
Rekli smo da želimo da pločkimo teksturu trava preko kopnene mreže. Ali do sada tekstura
koordinate koje smo izračunali leži u jediničnom domenu [0, 1] 2; tako da neće doći do pločica. Za
pločice teksture, specificiramo režim adresiranja i skaliramo teksturu koordinate pomoću 5
matrica transformacije teksture. Tako su koordinate teksture mapirane na domen [0,
5] 2, tako da je tekstura popločana 5 × 5 puta preko površine kopčene mreže:
void TexWavesApp::BuildRenderItems()
{
auto gridRitem = std::make_unique<RenderItem>();
gridRitem->World = MathHelper::Identity4x4();
XMStoreFloat4x4(&gridRitem->TexTransform,
XMMatrixScaling(5.0f, 5.0f, 1.0f));

}
9.11.3 Animacija teksture
Za pomicanje teksture vode preko geometrije vode prevođujemo koordinate teksture u teksturnoj ravni
kao funkcija vremena u metodi AnimateMaterials, koja dobivaće se svaki ciklus ažuriranja. Pod uslovom
da je pomeranje malo za svaki okvir, ovo daje iluziju glatke animacije. Koristimo režim adresiranja
zajedno sa a bešavne teksture, tako da možemo bez problema prevesti koordinate teksture oko
ravnoteža teksture. Sledeći kod pokazuje kako izračunavamo vektor offset za teksture vode i kako se
izrađuje i postavlja matrica teksture vode:
void TexWavesApp::AnimateMaterials(const GameTimer&
gt)
{
// Scroll the water material texture coordinates.
auto waterMat = mMaterials[“water”].get();
float& tu = waterMat->MatTransform(3, 0);
float& tv = waterMat->MatTransform(3, 1);
tu += 0.1f * gt.DeltaTime();
tv += 0.02f * gt.DeltaTime();
if(tu >= 1.0f)
tu -= 1.0f;
if(tv >= 1.0f)
tv -= 1.0f;
waterMat->MatTransform(3, 0) = tu;
waterMat->MatTransform(3, 1) = tv;
// Material has changed, so need to update cbuffer.
waterMat->NumFramesDirty = gNumFrameResources;
}
9.12 REZIME
1. Teksturne koordinate se koriste za definisanje trougla na teksturima na koju se mapira
3D trougao.
2. Najprevalentniji način stvaranja tekstura za igre je da ih umetnik napravi
Photoshop ili neki drugi editor slika, a zatim ih sačuvajte kao sliku kao BMP,
DDS, TGA ili PNG. Zatim će aplikacija igre učitati podatke o slici u vreme učitavanja
u ID3D12Resource objekat. Za grafičke aplikacije u realnom vremenu, DDS
(Format DirectDrav Surface format) je preferiran, jer podržava različite
formati slika koji su izvorno shvaćeni od GPU-a; posebno, podržava
formati komprimovanih slika koji se GPC-om može primarno dekompresirati.
3. Postoje dva popularna načina za pretvaranje tradicionalnih formata slike u format DDS:
koristite uredjaj za slike koji se izvozi u DDS ili koristite alatku za naredbe Microsoft
tekconv.
4. Mi možemo kreirati teksture iz datoteka slika sačuvane na disku koristeći
CreateDDSTektureFromFile12 funkcija, koja se nalazi na DVD-u
Zajednički / DDSTektureLoader.h / .cpp.
5. Uvećavanje dođe kada zumom na površini i pokušavamo da pokrijemo previše
ekran piksela sa nekoliko teksela. Minifikacija se javlja kada smanjimo površinu
i previše teksela pokušava da pokrije premalo piksela ekrana. Mipmaps i tekstura
filteri su tehnike za rukovanje uvećanjem i minifizacijom. GPU podržavaju tri
vrste filtriranja teksture prirodno (prema najnižem kvalitetu i najjeftinijoj)
najkvalitetniji i najskuplji): tačkasti, linearni i anizotropni filteri.
6. Režimi adresa definišu šta bi Direct3D trebalo da uradi sa teksturnim koordinatama
van opsega [0, 1]. Na primer, ako bi tekstura bila popločana, ogledala, stegnuta,
itd.?
7. Teksturne koordinate mogu biti skalirane, rotirane i prevedene baš kao i druge tačke. Od strane
postepeno transformišemo teksturne koordinate za malu količinu svakog kadra, mi
animirati teksturu.
Poglavlje 10 BLENDING
Razmotrimo sliku 10.1. Počeli smo da pravimo okvire tako što prvo crtamo teren
nakon čega sledi drveni sanduk, tako da su tereni i mjere piksela na baferu.
Zatim premotavamo površinu vode u zadnji pufer koristeći mešanje, tako da se vode piksele
dodati terenu i piksirati piksele na baferu na takav način da teren
i zabeleške pokazuju kroz vodu. U ovom poglavlju ispitujemo tehnike mešanja koje
dozvolite nam da se upoređuju (kombinuju) piksele koje trenutno radimo (tzv. izvor
pikseli) sa pikselima koji su prethodno rastersani u zadnji bafer (tzv
odredišni pikseli). Ova tehnika nam omogućava, između ostalog, da budemo polutransparentna
predmeti kao što su voda i staklo.
Slika 10.1. Polutransparentna površina vode.
Radi diskusije, specifično pominjemo bafer za pozadinu kao
postavi cilj. Međutim, kasnije ćemo pokazati da možemo da napravimo "off screen"
takođe daje ciljeve. Blending se primenjuje na ove ciljeve za upoređivanje upravo iste,
a odredišni pikseli su vrednosti piksela koje su ranije bile rasterene
ove off ekrane daju ciljeve.
Ciljevi:
1. Da biste razumeli kako mešanje funkcioniše i kako ga koristiti sa Direct3D-om
2. Da biste saznali više o različitim režimima mešanja koje Direct3D podržava
3. Da biste saznali kako se alfa komponenta može koristiti za kontrolu transparentnosti a
primitivna
4. Da saznamo kako možemo sprečiti da piksel bude vučen do zadnjeg pufera u potpunosti
koristeći HLSL klip funkciju
10.1 EKUATION OF BLENDING
Neka Csrc bude izlaz boja iz pikel shadera za ijth piksel koji smo trenutno
rasterizirajući (izvorni piksel), a neka Cdst bude boja ijth piksela trenutno na zadnjoj strani
bafer (odredišni piksel). Bez mešanja, Csrc bi prepisao Cdst (pod pretpostavkom da prođe
test za dubinu / šablon) i postaje nova boja piksela bafera ipd. Ali sa
mešanje, Csrc i Cdst se meša zajedno da bi dobili novu boju C koja će prepisati Cdst
(to jest, mešana boja C će biti upisana u ijth piksel zadnjeg pufera). Direct3D koristi
sledeća jednačina blendiranja za mešanje boja boja izvornog i odredišnog piksela:
Boje Fsrc (izvor mešavnog faktora) i Fdst (faktor mješavine odredišta) mogu biti bilo koji od
vrednosti opisane u §10.3, i oni nam dozvoljavaju da modifikujemo izvorni izvor i odredište
piksela na različite načine, omogućavajući različite efekte. Operator ⊗
znači komponentno pametno množenje za vektore u boji kao što je definirano u §5.3.1; operator
može biti bilo koji od binarnih operatora definisanih u §10.2.
Gornja jednačina blenda drži samo za RGB komponente boja. The
alfa komponenta se zapravo rukuje posebnom sličnom jednačinom:
A = Asrc Fsrc AdstFdst
Jednačina je u suštini isto, ali moguće je da su mešavinski faktori i binarni
operacije su različite. Motivacija za odvajanje RGB-a od alfa je jednostavno tako
možemo ih procesirati samostalno, a time i drugačije, što omogućava veće
razne mogućnosti.
Mešanje alfa komponente je potrebno mnogo češće nego mešanje
RGB komponente. Ovo je uglavnom zato što nas nije briga za leđa
vrednosti bufera alfa. Vrednosti alfa za buffer alfa su važne samo ako imate
neki algoritam koji zahtijeva vrijednosti alfa vrijednosti.
10.2 BLEND OPERACIJE
Binarni operator koji se koristi u jednačini za mešanje može biti jedan od sledećih:
Faktori mešanja se ignorišu u min / mak operaciji.
Ovi isti operateri takođe rade za alfa blendiranje jednačine. Takođe, možete odrediti
drugačiji operator za RGB i alfa. Na primer, moguće je dodati dva RGB-a
ali oduzima dva alfa termina:
Nedavno dodata funkcija Direct3D je mogućnost mešanja boje izvora i
odredišnu boju pomoću logičkog operatora umjesto tradicionalnih jednačina za blende iznad.
Dostupni logički operateri su navedeni u nastavku:
typedef
enum D3D12_LOGIC_OP
{
D3D12_LOGIC_OP_CLEAR = 0,
D3D12_LOGIC_OP_SET = ( D3D12_LOGIC_OP_CLEAR + 1 )
,
D3D12_LOGIC_OP_COPY = ( D3D12_LOGIC_OP_SET + 1 ) ,
D3D12_LOGIC_OP_COPY_INVERTED = (
D3D12_LOGIC_OP_COPY + 1 ) ,
D3D12_LOGIC_OP_NOOP = (
D3D12_LOGIC_OP_COPY_INVERTED + 1 ) ,
D3D12_LOGIC_OP_INVERT = ( D3D12_LOGIC_OP_NOOP + 1
),
D3D12_LOGIC_OP_AND = ( D3D12_LOGIC_OP_INVERT + 1 )
,
D3D12_LOGIC_OP_NAND = ( D3D12_LOGIC_OP_AND + 1 ) ,
D3D12_LOGIC_OP_OR = ( D3D12_LOGIC_OP_NAND + 1 ) ,
D3D12_LOGIC_OP_NOR = ( D3D12_LOGIC_OP_OR + 1 ) ,
D3D12_LOGIC_OP_XOR = ( D3D12_LOGIC_OP_NOR + 1 ) ,
D3D12_LOGIC_OP_EQUIV = ( D3D12_LOGIC_OP_XOR + 1 )
,
D3D12_LOGIC_OP_AND_REVERSE = (
D3D12_LOGIC_OP_EQUIV + 1 ) ,
D3D12_LOGIC_OP_AND_INVERTED = (
D3D12_LOGIC_OP_AND_REVERSE + 1 ) ,
D3D12_LOGIC_OP_OR_REVERSE = (
D3D12_LOGIC_OP_AND_INVERTED + 1 ) ,
D3D12_LOGIC_OP_OR_INVERTED = (
D3D12_LOGIC_OP_OR_REVERSE + 1 )
} D3D12_LOGIC_OP;
Imajte na umu da istovremeno ne možete koristiti tradicionalno mešanje i logički operater; izaberete
jednu ili drugu. Imajte na umu da za korištenje logičkog operatera mora biti podržano miješanje ciljnog
formata render-to bi trebalo da bude format sorte UINT, inače ćete dobiti sljedeće greške:
D3D12 ERROR:
ID3D12Device::CreateGraphicsPipelineState: The render
target format at slot 0 is format (R8G8B8A8_UNORM). This
format does not support logic ops. The Pixel Shader
output signature indicates this output could be written,
and the Blend State indicates logic op is enabled for
this slot. [ STATE_CREATION ERROR #678:
CREATEGRAPHICSPIPELINESTATE_OM_RENDER_TARGET_DOES_NOT_SUPPORT_D3D12 WARNING:
ID3D12Device::CreateGraphicsPipelineState:
Pixel Shader output ‘SV_Target0’ has type that is NOT
unsigned int, while the corresponding Output Merger
RenderTarget slot [0] has logic op enabled. This happens
to be well defined: the raw bits output from the shader
will simply be interpreted as UINT bits in the blender
without any data conversion. This warning is to check
that the application developer really intended to rely on
this behavior. [ STATE_CREATION WARNING #677:
CREATEGRAPHICSPIPELINESTATE_PS_OUTPUT_TYPE_MISMATCH]
10.3 BLEND FAKTORI
Postavljanjem različitih kombinacija za izvorne i odredišne faktore blende
sa različitim operatorima mešavine, može se postići desetine različitih efekata mešanja. Mi
ilustruje neke kombinacije u §10.5, ali ćete morati da eksperimentišete sa drugima
osetite šta rade. Sledeći spisak opisuje osnovne mešavne faktore, koje
primeniti i na Fsrc i Fdst. Pogledajte tip D3D12_BLEND naveden u SDK-u
dokumentaciju za neke dodatne napredne mešavne faktore. Davanje Csrc = (rs, gs, bs), Asrc
= as (vrednosti RGBA iz pikselnog shadera), Cdst = (rd, gd, bd), Adst = ad (
RGBA vrednosti već uskladištene u cilju renderiranja), a F je ili Fsrc ili Fdst i F biti
bilo Fsrc ili Fdst, imamo:
D3D12_BLEND_ZERO: F = (0, 0, 0) and F = 0
D3D12_BLEND_ONE: F = (1, 1, 1) and F = 1
D3D12_BLEND_SRC_COLOR: F = (rs, gs, bs)
D3D12_BLEND_INV_SRC_COLOR: Fsrc = (1 − rs, 1 − gs, 1 − bs)
D3D12_BLEND_SRC_ALPHA: F = (as, as, as) and F = as
D3D12_BLEND_INV_SRC_ALPHA: F = (1 − as, 1 − as, 1 − as) and F = (1 − as)
D3D12_BLEND_DEST_ALPHA: F = (ad, ad, ad) and F = ad
D3D12_BLEND_INV_DEST_ALPHA: F = (1 − ad, 1 − ad, 1 − ad) and F = (1 − ad)
D3D12_BLEND_DEST_COLOR: F = (rd, gd, bd)
D3D12_BLEND_INV_DEST_COLOR: F = (1 − rd, 1 − gd, 1 − bd)
D3D12_BLEND_SRC_ALPHA_SAT: F = (a′s, a′s, a′s) and F = a′s
where a′s = clamp(as, 0, 1)
D3D12_BLEND_BLEND_FACTOR: F = (r, g, b) i F = a, where is color (r, g, b, a) isporučuje se drugom
parametru
ID3D12GraphicsCommandList :: OMSetBlendFactor metoda. To vam dozvoljava
da odredite boju blend faktora za direktno korištenje; Međutim, ona je konstantna dok se ne promenite
stanje mešanja.
D3D12_BLEND_INV_BLEND_FACTOR: F = (1 - r, 1 - g, 1 - b) i F = 1 - a,
gde je boja (r, g, b, a) isporučena od strane drugog parametra
ID3D12GraphicsCommandList :: OMSetBlendFactor metoda. To vam dozvoljava
da odredite boju blend faktora za direktno korištenje; Međutim, ona je konstantna dok se ne promenite
stanje mešanja. Svi navedeni faktori mešanja važe za RGB blending jednačinu. Za alfa jednačina za
mešanje, faktori mešanja koji se završavaju sa _COLOR nisu dozvoljeni. Funkcija stezaljke je definisana
kao: Boja boja mešača možemo podesiti sledećom funkcijom:
void ID3D12GraphicsCommandList :: OMSetBlendFactor (
const FLOAT BlendFactor [4]);
Usvajanje nullptr-a vraća podrazumevani faktor blende (1, 1, 1, 1).
10.4 BLEND STATE
Razgovarali smo o operaterima za mešanje i mešačkim faktorima, ali gde postavljamo
ove vrednosti sa Direct3D? Kao i kod drugih Direct3D država, stanje mešavine je deo
PSO. Do sada smo koristili podrazumevano stanje mešanja, što onemogućava mešanje:
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;
ZeroMemory(&opaquePsoDesc,
sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
… opaquePsoDesc.BlendState =
CD3DX12_BLEND_DESC(D3D12_DEFAULT);
Da konfigurišemo stanje mešanja bez podrazumevane vrednosti, moramo popuniti D3D12_BLEND_DESC
struktura. Struktura D3D12_BLEND_DESC je definisana tako:
typedef struct D3D12_BLEND_DESC {
BOOL AlphaToCoverageEnable; // Default: False
BOOL IndependentBlendEnable; // Default: False
D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;
1. AlphaToCoverageEnable: Specifi true da biste omogućili alpha-to-coverage, što je a
multisampling tehnika korisna prilikom renderinga struktura lišća ili vrata. Navedite netačno
da onemogućite alpha-to-coverage. Alfa-to-coverage zahteva multisampling
omogućeno (tj., bafer za dubinu i dubinu je napravljen sa multisampling).
2. IndependentBlendEnable: Direct3D podržava prikazivanje do osam renderera
ciljevi istovremeno. Kada je ova zastava postavljena na tačnost, to znači da je mešanje moguće
izvodi se za svaki ciljni cilj drugačije (različiti faktori mešanja, različita mešavina
operacije, mešanje onemogućeno / omogućeno, itd.). Ako je ova zastava postavljena na lažno, to znači
sve
renderne ciljeve će se mešati na isti način kao što je opisao prvi element u
D3D12_BLEND_DESC :: RenderTarget niz. Korišćeni su višestruki ciljni ciljevi
za napredne algoritme; za sada, pretpostavimo da smo samo učinili da se jedna meta desi na a
vreme.
3. RenderTarget: niz od 8 D3D12_RENDER_TARGET_BLEND_DESC
elemente, gde i-ti element opisuje kako se vrši mešanje za i-i
istovremeni cilj. Ako je IndependentBlendEnable podešen na lažno, onda sve
ciljevi rendera koriste RenderTarget [0] za mešanje.
The D3D12_RENDER_TARGET_BLEND_DESC structure is defined like so:
typedef struct D3D12_RENDER_TARGET_BLEND_DESC
{
BOOL BlendEnable; // Default: False
BOOL LogicOpEnable; // Default: False
D3D12_BLEND SrcBlend; // Default: D3D12_BLEND_ONE
D3D12_BLEND DestBlend; // Default: D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOp; // Default:
D3D12_BLEND_OP_ADD
D3D12_BLEND SrcBlendAlpha; // Default:
D3D12_BLEND_ONE
D3D12_BLEND DestBlendAlpha; // Default:
D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOpAlpha; // Default:
D3D12_BLEND_OP_ADD
D3D12_LOGIC_OP LogicOp; // Default:
D3D12_LOGIC_OP_NOOP
UINT8 RenderTargetWriteMask; // Default:
D3D12_COLOR_WRITE_ENABLE_ALL
} D3D12_RENDER_TARGET_BLEND_DESC;
1.BlendEnable: Podesite true da biste omogućili mešanje i false da biste je onemogućili. Napomenuti da
BlendEnable i LogicOpEnable ne mogu biti oboje istiniti; ili ga koristite
redovno mešanje ili mešanje logičkog operatera.
2. LogicOpEnable: Podesite true da biste omogućili operaciju mešanja logike. Napomenuti da
BlendEnable i LogicOpEnable ne mogu biti oboje istiniti; ili ga koristite
redovno mešanje ili mešanje logičkog operatera.
3. SrcBlend: Član je D3D12_BLEND navedenog tipa koji određuje
izvor mešavinskog faktora Fsrc za RGB mešanje.
4. DestBlend: Član D3D12_BLEND navedenog tipa koji specificira
ciljni mešavač faktor Fdst za RGB mešanje.
5. BlendOp: Član D3D12_BLEND_OP navedenog tipa koji specificira
RGB mešač operatera.
6. SrcBlendAlpha: Član D3D12_BLEND navedenog tipa koji određuje
ciljni mešavač Fsrc za alfa mešanje.
7. DestBlendAlpha: Član je D3D12_BLEND navedenog tipa
određuje faktor mješavine odredišta Fdst za alfa mešanje.
8. BlendOpAlpha: Član je tipa D3D12_BLEND_OP navedenog tipa
određuje alfa blending operatora.
9. LogicOp: Član D3D12_LOGIC_OP navedenog tipa koji specificira
logički operater koji se koristi za mešanje izvornih i odredišnih boja.
10. RenderTargetVriteMask: Kombinacija jednog ili više od sledećih
zastave:
typedef enum D3D12_COLOR_WRITE_ENABLE {
D3D12_COLOR_WRITE_ENABLE_RED = 1,
D3D12_COLOR_WRITE_ENABLE_GREEN = 2,
D3D12_COLOR_WRITE_ENABLE_BLUE = 4,
D3D12_COLOR_WRITE_ENABLE_ALPHA = 8,
D3D12_COLOR_WRITE_ENABLE_ALL =
( D3D12_COLOR_WRITE_ENABLE_RED |
D3D12_COLOR_WRITE_ENABLE_GREEN |
D3D12_COLOR_WRITE_ENABLE_BLUE |
D3D12_COLOR_WRITE_ENABLE_ALPHA )
} D3D12_COLOR_WRITE_ENABLE;
Ove zastave kontrolišu koji su kolorni kanali u zadnjem puferu upisani u kasnije
mešanje. Na primer, možete onemogućiti pisanje na RGB kanale i samo napisati na
alfa kanala, precizirajući D3D12_COLOR_VRITE_ENABLE_ALPHA. Ovo
fleksibilnost može biti korisna za napredne tehnike. Kada je mešanje onemogućeno, boja
vraćen iz pikel shadera se koristi bez maske za pisanje.
Blending nije besplatan i zahteva samo dodatni rad na jednom pikselu
omogućite je ako vam zatreba i isključite ga kad završite.
Sledeći kod pokazuje primer koda kreiranja i podešavanja stanja mešavine:
// Start from non-blended PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc
= opaquePsoDesc;
D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendDesc.BlendEnable = true;
transparencyBlendDesc.LogicOpEnable = false;
transparencyBlendDesc.SrcBlend =
D3D12_BLEND_SRC_ALPHA;
transparencyBlendDesc.DestBlend =
D3D12_BLEND_INV_SRC_ALPHA;
transparencyBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE;
transparencyBlendDesc.DestBlendAlpha =
D3D12_BLEND_ZERO;
transparencyBlendDesc.BlendOpAlpha =
D3D12_BLEND_OP_ADD;
transparencyBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
transparencyBlendDesc.RenderTargetWriteMask =
D3D12_COLOR_WRITE_ENABLE_ALL;
transparentPsoDesc.BlendState.RenderTarget[0] =
transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&transparentPsoDesc,
IID_PPV_ARGS(&mPSOs[“transparent”])));
10.5 PRIMERI
U sledećim podsekcijama, pogledamo neke kombinacije mešavih faktora koji se koriste
specifični efekti. U ovim primerima, mi samo gledamo na RGB mešanje. Alfa mešanje je
analogno.
10.5.1 Bez pisanja boja
Pretpostavimo da želimo zadržati originalni odredišni piksel tačno onakva kakva je i ne
prepisati ga ili ga spojiti sa izvornim pikselom koji je trenutno rasterisan. Ovo može biti korisno,
na primer, ako samo želite da pišete u buffer za dubinu / stencil, a ne zadnje pufer.
Da biste to uradili, podesite faktor blende izvornog piksela na D3D12_BLEND_ZERO, odredište
faktor blende na D3D12_BLEND_ONE, i operator mešavine na
D3D12_BLEND_OP_ADD. Sa ovim podešavanjem, jednačina za mešanje se smanjuje na:
Ovo je složen primer; drugi način za primjenu iste stvari bi bio postaviti
D3D12_RENDER_TARGET_BLEND_DESC :: RenderTargetVriteMask
člana na 0, tako da nijedan od kanala u boji nije upisan.
Slika 10.2. Dodavanje boje izvora i destinacije. Dodavanje stvara svetlije
slika pošto se dodaju boje.
10.5.2 Dodavanje / oduzimanje
Pretpostavimo da želimo da dodamo izvorne piksele sa odredišnim pikselima (pogledajte Sliku
10.2). Da biste to uradili, podesite faktor blende za izvor na D3D12_BLEND_ONE, odredište
faktor blende na D3D12_BLEND_ONE, i operator mešavine na
D3D12_BLEND_OP_ADD. Sa ovim podešavanjem, jednačina za mešanje se smanjuje na:
Možemo odštampati izvorne piksele iz odredišnih piksela koristeći gornje faktore mešanja
i zameni rad mešavine sa D3D12_BLEND_OP_SUBTRACT (Slika 10.3).
Slika 10.3. Odbitanje boje izvora iz boje odredišta. Oduzimanje stvara
tamnija slika pošto se boja uklanja.
10.5.3 Multiplikovanje
Pretpostavimo da želimo da pomnožimo izvorni piksel sa odgovarajućim odredištem
piksela (pogledajte sliku 10.4). Da bismo to uradili, postavili smo faktor mešanja izvora
D3D12_BLEND_ZERO, faktor mješavine odredišta za D3D12_BLEND_SRC_COLOR,
i operator mešavine na D3D12_BLEND_OP_ADD. Sa ovim podešavanjem, mešanje
Jednačina se smanjuje na:
Slika 10.4. Množenje boje izvora i boje odredišta.
10.5.4 Transparentnost
Neka izvorna alfa komponenta bude smatrana kao procenat koji kontroliše neprovidnost
izvornog piksela (npr. 0 alfa znači 0% neprozirno, 0,4 znači 40% neprozirno i 1,0
znači 100% neprozirno). Odnos između neprozirnosti i transparentnosti je jednostavno T = 1
- A, gde je A prozirnost i T je transparentnost. Na primer, ako je nešto 0,4 neprozirno,
onda je 1 - 0.4 = 0,6 transparentan. Sada pretpostavimo da želimo da spojimo izvor i
odredišni pikseli zasnovani na neprozirnosti izvornog piksela. Da biste to uradili, podesite mešavinu
izvora
faktor na D3D12_BLEND_SRC_ALPHA i faktor mješavine odredišta za
D3D12_BLEND_INV_SRC_ALPHA, i operator mešavine na D3D12_BLEND_OP_ADD.
Na primer, pretpostavimo kao = 0.25, što znači da je izvorni piksel samo 25% neproziren.
Zatim, kada su izvorni i odredišni pikseli spojeni zajedno, očekujemo konačan rezultat
boje će biti kombinacija 25% izvornog piksela i 75% odredišnog piksela
(piksel "iza" izvornog piksela), pošto je izvorni piksel 75% transparentan. The
Jednačina iznad nam daje upravo ovo:
Koristeći ovaj metod mešanja, možemo nacrtati prozirne objekte kao što je on na slici
10.1. Trebalo bi napomenuti da se ovim metodom miješanja naruči da crtate objekte
pitanja. Koristimo sledeće pravilo:
Crtajte objekte koji prvo ne koriste blende. Zatim sortirajte predmete koji koriste mešanje
njihova udaljenost od kamere. Napokon, nacrtajte predmete koji koriste blende na zadnjoj strani
naruči.
Razlog redosleda izvlačenja je tako da se predmeti pomiješu sa
objekti koji se prostiru iza njih. Jer ako je objekat transparentan, možemo videti kroz to da vidimo
scena iza toga. Zato je neophodno da svi pikseli iza prozirnog objekta imaju
već su upisani u zadnje pufer, tako da možemo da mešamo transparentne piksele izvora
sa odredišnim pikselima scene iza nje.
Za metod mešanja u §10.5.1, redosled izvlačenja nije bitan, jer jednostavno
sprečava pisanje piksela izvornog bafera. Za metode mešanja o kojima je reč
§10.5.2 i 10.5.3, i dalje crtamo nepreporučene predmete i poslednje objekte i blende; ovo je
jer želimo prvo postaviti sve nepremešane geometrije na zadnje pufer prije nas
počnite mešati. Međutim, ne moramo sortirati predmete koji koriste mešanje. Ovo je
jer su operacije komutativne. To jest, ako započnete sa bubnom pikselom bubnjeva
B, a zatim učinite n aditivne / subtraktivne / multiplikativne mješavine za taj piksel, naredba ne
materija:
10.5.5 Blendiranje i dubinski odbojnik
Kada se meša sa aditivnim / subtraktivnim / multiplikativnim mešanjem, dolazi do problema
test dubine. Zbog primera, objasniti ćemo samo sa dodavanjem mešanja, ali
ista ideja važi za subtraktivno / multiplikativno mešanje. Ako napravimo skup od S
objekti sa dodatkom mešanja, ideja je da objekti u S ne zamagljuju jedni druge;
Umesto toga, njihove boje su jednostavno nakupljene (vidi sliku 10.5). Stoga, mi to radimo
ne žele da izvrše ispitivanje dubine između objekata u S; jer ako to učinimo, bez povratka
naručivanje narudžbe, jedan od objekata u S bi zamaglio drugi objekat u S, na taj način
što dovodi do odbacivanja fragmenta piksela usled ispitivanja dubine, što znači da je objekat
boje piksela ne bi se nakupljale u zbirnu sumu. Mi možemo onemogućiti test dubine
između objekata u S onemogućavanje piše do dubinskog bafera dok renderira objekte u S.
Zbog toga što se dubina zapisa onemogućava, dubine objekta u S izvučene sa dodatkom
mešanje neće biti upisano u bafer dubine; stoga, ovaj objekat neće zamagliti nikakve
kasnije vučeni objekat u S iza njega usled ispitivanja dubine. Imajte na umu da isključujemo samo dubinu
piše prilikom crtanja objekata u S (skup objekata izvučenih sa dodavanjem mješavine).
Dubina se čita i ispitivanje dubine je još uvek omogućeno. Ovo je tako da geometrija nije mešana
(koji se nacrta pre mešane geometrije) i dalje će prikloniti mešanu geometriju iza nje.
Na primer, ako imate iza zida aditivno mešanih predmeta, nećete
pogledajte mješovite predmete jer ih čvrsti zid zatamnjuje. Kako onemogućiti pisanje dubine
i, generalno, konfigurišite postavke testa dubine će biti pokrivene u sledećem poglavlju.
Slika 10.5. Sa dodatkom mešanja intenzitet je veći u blizini izvorne tačke
gde se više čestica preklapa i dodaju se zajedno. Kao čestice
rasprostranjen, intenzitet slabi jer se manje dijelova preklapa i
biti dodati zajedno.
10.6 ALPHA KANALI
Primjer iz §10.5.4 pokazao je da se izvorne alpha komponente mogu koristiti u RGB
mešanje kako bi se kontrolisala transparentnost. Izvorna boja koja se koristi u jednačini mešanja dolazi
iz piksel shadera. Kao što smo videli u poslednjem poglavlju, vraćamo alfu difuznog materijala
vrednost kao alfa izlaz piksel shadera. Tako je alfa kanal difuzne mape
koristi se za kontrolu transparentnosti.
float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo = gDiffuseMap.Sample(
gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;

// Common convention to take alpha from diffuse
albedo.
litColor.a = diffuseAlbedo.a;
return litColor;
}
Obično možete dodati alfa kanal u bilo kom popularnom softveru za uređivanje slika, kao što je
Adobe Photoshop, a zatim sačuvajte sliku u format slike koji podržava alfa kanal kao DDS.
10.7 CLIPPING PIKSELS
Ponekad želimo potpuno odbaciti izvorni piksel od daljeg obrađivanja.
Ovo se može uraditi sa unutrašnjom HLSL klipom (k) funkcijom. Ova funkcija može biti samo
pozvani u shader piksela i on odbacuje trenutni piksel od dalje obrade ako je k <0.
Ova funkcija je korisna za postavljanje teksturnih ograda, na primjer, kao što je prikazano u
Slika 10.6. To jest, korisno je za rendering piksela piksel je u potpunosti
neprozirno ili potpuno transparentno. Slika 10.6. Tekstura žičane ograde sa alfa kanalom. Pikseli sa
crnom vrednosti alfa će odbaciti funkcija klipa i neće biti izvučene; stoga, samo on
žičana ograda ostaje. U suštini, alfa kanal se koristi da maskira bez ograde
pikseli iz teksture. U piksel shaderu, grabimo alfa komponentu teksture. Ako je to mala vrednost blizu 0,
što ukazuje na to da je piksel potpuno providan, a onda je isecao piksel od dalje obrade.
float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo = gDiffuseMap.Sample(
gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
#ifdef ALPHA_TEST
// Discard pixel if texture alpha < 0.1. We do this
test as soon
// as possible in the shader so that we can
potentially exit the
// shader early, thereby skipping the rest of the
shader code.
clip(diffuseAlbedo.a - 0.1f);
#endif

// Common convention to take alpha from diffuse
albedo.
litColor.a = diffuseAlbedo.a;
return litColor;
}
Obratite pažnju da smo isključili samo ako je ALPHA_TEST definisan; ovo je zato što ne možemo
želimo da pozovemo klip za neke stavke, tako da je potrebno da ga uključimo / isključimo
imaju specijalizovane shadere. Štaviše, postoji trošak za korišćenje alfa testiranja, tako da bi trebali
samo ga koristite ako nam zatreba.
Imajte na umu da se isti rezultat može dobiti korišćenjem mešanja, ali to je efikasnije.
Za jednu stvar, ne treba izvršiti izračunavanje mešanja (mešanje se može onemogućiti). Takođe,
redosled izvlačenja nije bitan. Štaviše, odbacivanjem piksela ranije od
pikel shader, preostala uputstva za piksel shader mogu biti preskočena (nema smisla u tome
kalkulacije za odbačeni piksel).
Zahvaljujući filtriranju, alfa kanal može malo zamagliti, pa bi trebalo da odete
neka baferna prostorija pri sjeckanju piksela. Na primer, snimite piksele sa alfa
vrednosti blizu 0, ali ne nužno tačno nula.
Slika 10.7 prikazuje snimak ekrana "Blend". Očisti poluprovidnu vodu
koristeći blende transparentnosti i čini žičanu ogradenu kutiju pomoću testa klipa. Jedan
druga promena vredna pominjanja je to, jer sada možemo vidjeti kroz kutiju sa
struktura ograde, želimo da isključimo odbacivanje lica za alfa testirane objekte:
// PSO for alpha tested objects
D3D12_GRAPHICS_PIPELINE_STATE_DESC alphaTestedPsoDesc
= opaquePsoDesc;
alphaTestedPsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders[“alphaTestedPS”]-
>GetBufferPointer()),
mShaders[“alphaTestedPS”]->GetBufferSize()
};
alphaTestedPsoDesc.RasterizerState.CullMode =
D3D12_CULL_MODE_NONE;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&alphaTestedPsoDesc,
IID_PPV_ARGS(&mPSOs[“alphaTested”])));
10.8 FOG
Da bi simulirali određene vremenske uslove u našim igrama, moramo biti u mogućnosti
sprovesti efekat magle; videti sliku 10.8. Osim očiglednih razloga magle, magle
pruža neke dodatne prednosti. Na primer, on može maskirati distantne rendering artefakte i
sprečiti popping. Popping se odnosi na kada je objekat koji je ranije bio iza daleke
avion iznenada dolazi ispred frustracije, zbog kretanja kamere, i tako
postaje vidljiv; pa izgleda da se "pop" u scenu iznenada iznenada. Imajući sloj magle
rastojanje je skriveno. Imajte na umu da ako se vaša scena odvija na jasan dan, vi
možda će još uvijek uključiti suptilnu količinu magle na dalekim razdaljinama, jer, čak i na čistom
dana, udaljeni objekti kao što su planine pojavljuju se opasniji i gubitak kontrasta kao funkcija
dubina, i možemo koristiti maglu za simuliranje ove atmosferske perspektive.
Slika 10.8. Snimak ekrana "Blend" sa omogućenom maglom.
Slika 10.9. Rastojanje tačke od oka i fogStart i fogRange
parametri.
Naša strategija za izvođenje maglovnih radova je sledeća: Specifikujemo boju magle, maglu
pokrenite udaljenost od kamere i opseg magline (tj. opseg od početne udaljenosti za maglu
dok magla u potpunosti ne sakrije objekte). Tada je boja tačke na trouglu a
ponderisan prosek uobičajene boje i boje magle:
Parametar s kreće se od 0 do 1 i predstavlja funkciju razdaljine između
položaj kamere i površinska tačka. Kao rastojanje između površine i oka
povećava, tačka postaje sve više zamagljena od strane magle. Parametar s je
definisani kako slijedi:
gde je dist (p, E) rastojanje između površine tačke p i položaja kamere
E. Funkcija zasićenja spaja argument do opsega [0, 1]:

Drugim riječima, magla ne mijenja boju tačaka čije je udaljenje od


kamera je manja od fogStart. Ovo ima smisla na osnovu imena "fogStart"; magla
ne započinite da utičete na boju dok rastojanje od fotoaparata nije barem onog od fogStart.
Neka fogEnd = fogStart + fogRange. Kada dist (p, E) ≥ fogEnd, s = 1 i fogged
boja daje:
foggedColor = fogColor
Drugim rečima, magla potpuno sakriva površinsku tačku na rastojanjima većoj od ili
jednak je magli, pa sve što vidite je boja magle.
Kada fogStart <dist (p, E) <fogEnd, vidimo da je s linearno rampe od 0 do 1 kao
dist (p, E) se povećava od fogStart do fogEnd. To znači da kada se rastojanje povećava,
boja magle postaje sve veća težina dok originalna boja dobija manje i manje težine.
Ovo ima smisla, naravno, jer kada se rastojanje povećava, magla zaklanja više i
više površinske tačke.
// 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”
Texture2D gDiffuseMap : register(t0);
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;
};
// 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 cbPerPassPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
// Allow application to change fog parameters once
per frame.
// For example, we may only use fog for certain
times of day.
float4 gFogColor;
float gFogStart;
float gFogRange;
float2 cbPerPassPad2;
// 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];
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
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;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut)0.0f;
// 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);
// 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, gMatTransform).xy;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
float4 diffuseAlbedo = gDiffuseMap.Sample(
gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
#ifdef ALPHA_TEST
// Discard pixel if texture alpha < 0.1. We do this
test as soon
// as possible in the shader so that we can
potentially exit the
// shader early, thereby skipping the rest of the
shader code.
clip(diffuseAlbedo.a - 0.1f);
#endif
// Interpolating normal can unnormalize it, so
renormalize it.
pin.NormalW = normalize(pin.NormalW);
// Vector from point being lit to eye.
float3 toEyeW = gEyePosW - pin.PosW;
float distToEye = length(toEyeW);
toEyeW /= distToEye; // normalize
// Light terms.
float4 ambient = gAmbientLight*diffuseAlbedo;
const float shininess = 1.0f - gRoughness;
Material mat = { diffuseAlbedo, gFresnelR0,
shininess };
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat,
pin.PosW,
pin.NormalW, toEyeW, shadowFactor);
float4 litColor = ambient + directLight;
#ifdef FOG
float fogAmount = saturate((distToEye - gFogStart) /
gFogRange);
litColor = lerp(litColor, gFogColor, fogAmount);
#endif
// Common convention to take alpha from diffuse
albedo.
litColor.a = diffuseAlbedo.a;
return litColor;
}
Sledeći šifarski kod pokazuje kako se magla implementira. Izračunamo rastojanje
i interpolaciju na nivou piksela, nakon što izračunamo osvijetljenu boju.
Neke scene možda neće želeti da koriste maglu; Stoga, mi napravimo maglu po želji
FOG treba definisati prilikom sastavljanja shadera. Na taj način, ako magla nije poželjna, onda ne
plaćamo troškove proračuna za maglu. U našoj demo, omogućili smo maglu tako što smo dali sledeću
D3D_SHADER_MACRO u funkciju CompileShader:
const D3D_SHADER_MACRO defines[] =
{
“FOG”, “1”,
NULL, NULL
};
mShaders[“opaquePS”] = d3dUtil::CompileShader(
L”Shaders\Default.hlsl”, defines, “PS”, “ps_5_0”);
float3 toEye = normalize(gEyePosW - pin.PosW);
float distToEye = distance(gEyePosW, pin.PosW);
Poglavlje 11 STENCIL
Bušilica matrice je neograničeno pufer koji možemo koristiti da bi postigli neke posebne efekte.
Bufer za matricu ima istu rezoluciju kao bafer za pozadinu i bafer dubine, takav da
ijth piksel u baferu slepila odgovara ijth pikselu u zadnjem puferu i
dubinski bafer. Podsjetimo iz §4.1.5 da kada je specifikovano puferno polje naznačeno, ona se nalazi u
prilogu
do dubinskog bafera. Kao što sugeriše, šablon šablona radi kao šablon i dozvoljava
da blokiramo rendering određenih fragmenta piksela u baferu.
Na primjer, kada implementiramo ogledalo, moramo da odražamo objekat preko njega
ravnica ogledala; Međutim, samo želimo da odrazimo refleksiju u ogledalo. Mi Možemo
koristite šablon za šablone da biste blokirali prikazivanje odsjaja, osim ako se ne vuče
ogledalo (vidi sliku 11.1).
Slika 11.1. (Levo) Reflektovana lobanja se pravilno prikazuje u ogledalu. The
refleksija se ne prikazuje kroz zidne opeke jer to ne uspeva u ovom testu dubine
područje. Međutim, gledajući iza zida, mi smo u stanju da vidimo refleksiju
razbijajući iluziju (refleksija bi trebalo da se pojavi samo kroz ogledalo). (Jel tako)
Korišćenjem bafera za stencil možemo blokirati odbačenu lobanju
osim ako se ne vuče u ogledalo.
Stanje striktnog pufera (kao i stanje bafera dubine) je konfigurisano popunjavanjem a
Primer D3D12_DEPTH_STENCIL_DESC i dodeljuje ga
D3D12_GRAPHICS_PIPELINE_STATE_DESC :: DepthStencilState polje
objekt objektnog objekta (PSO). Učenje da se efikasno koristi senzor pufera najbolje se ostvaruje
proučavanje postojeće primjene. Jednom kada shvatite nekoliko aplikacija
šablon za šablone, imat ćete bolju ideju kako se može koristiti za vaše specifične
potrebe.
Ciljevi:
1. Da biste saznali kako da kontrolišete stanje dubine i matrice šablona ispunjavanjem
D3D12_DEPTH_STENCIL_DESC polje u objektu državnog objekta.
2. Da biste naučili kako da primenite ogledala koristeći odbojnik stencila kako biste sprečili refleksije
od vuče do površina bez ogledala.
3. Da biste mogli identifikovati dvostruko mešanje i razumeti kako može biti zaštitni šablon sprečiti to.
4. Objasniti kompleksnost dubine i opisati dva načina dubinske složenosti scene može se meriti.
11.1 FORMATI DEPTH / STENCIL I KLIRIRANJE
Podsjećajući da je bafer dubine / stencila tekstura, mora se kreirati sa određenim podacima
formati. Formati koji se koriste za bafer dubine / šablona su sledeći:
1. DKSGI_FORMAT_D32_FLOAT_S8Ks24_UINT: Određuje 32-bitnu plutajuću tačku dubinskim baferom,
sa 8-bita (nepotpisani integer) rezervisan za mapirani striktni bafer opseg [0, 255] i 24 bita nisu korišćeni
za dopunjavanje.
2. DKSGI_FORMAT_D24_UNORM_S8_UINT: Određuje nepotpisanu 24-bitnu dubinu
bafer mapiran na opseg [0, 1] sa 8-bita (unsigned integer) rezervisan za
matrični bafer mapiran je na opseg [0, 255].
U našem D3DApp okruženju, kada kreiramo bafer dubine, specificiramo:
DKSGI_FORMAT mDepthStencilFormat =
DKSGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.Format = mDepthStencilFormat;
Takođe, striktni bafer treba da se resetuje na neku vrednost na početku svakog okvira.
Ovo se radi s sledećim metodom (koja takođe briše bafer dubine):
void
ID3D12GraphicsCommandList::ClearDepthStencilView(
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView,
D3D12_CLEAR_FLAGS ClearFlags,
FLOAT Depth,
UINT8 Stencil,
UINT NumRects,
const D3D12_RECT *pRects);
1.DepthStencilViev: Deskriptor za prikaz dubine / stencil bafera koji želimo jasno.
2. ClearFlags: Specificirajte D3D12_CLEAR_FLAG_DEPTH da biste obrisali bafer dubine samo; navedite
D3D12_CLEAR_FLAG_STENCIL da biste izbrisali samo šablon senzora; navesti
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL to jasno oboje.
3. Dubina: float-vrednost za podešavanje svakog piksela u baferu dubine; mora biti plutajući
tačka broj k tako da je 0 ≤ k ≤ 1.
4. Matrica: celobrojna vrednost za postavljanje svakog piksela bufera matrice u; to mora biti
integer n takav da je 0 ≤ n ≤ 255.
5. NumRects: Broj pravougaonika u nizu pRects pokazuje.
6. pRects: niz D3D12_RECTs označava pravougaone regione na
Dubina / stencil pufer da se očisti; navedite nullptr da biste izbrisali čitavu dubinu / šablon za šablone.
Ovu metodu smo već nazvali svakim okvirom u našim demo-prikazima. Na primer:
mCommandList-
> ClearDepthStencilViev (DepthStencilViev (),
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
1.0f, 0, 0, nullptr);
11.2 STENCILSKI TEST
Kao što je ranije rečeno, možemo koristiti stencil-pufer za blokiranje renderinga u određenim oblastima
bafera. Odlučeno je da se odluči blokirati određeni piksel od pisanja
pomoću testa matrice, koji se daje sledećim:
if( StencilRef & StencilReadMask Value &
StencilReadMask )
accept pixel
else
reject pixel
Test šablona se vrši pošto se pikseli rasterizuju (tj., Tokom izlaznog spajanja
stepen), pod pretpostavkom da je omogućeno šabloniranje, i uzima dva operanda:
1. Operand na levoj strani (LHS) koji je određen pomoću ANDing određene aplikacije
referentna vrednost šablona (StencilRef) sa određenom vrijednošću maskiranja
(StencilReadMask).
2. Operand sa desne strane (RHS) koji je određen ANDING unosom već u
striktni bafer određenog piksela koji testiramo (Value) sa definiranom aplikacijom
masking value (StencilReadMask).
Imajte na umu da je StencilReadMask isti za LHS i RHS. The
test šablona zatim upoređuje LHS sa RHS kako je naznačeno za aplikaciju
funkcija poređenja, koja vraća istinitu ili lažnu vrednost. Napišemo piksel na leđa
buffer ako test oceni na tačno (pod pretpostavkom da se i test dubine prolazi). Ako je test
procenjuje na lažno, onda blokiraje da piksel bude upisan u zadnje pufer. I od
Naravno, ako je piksel odbijen zbog neuspjeha testa matrice, on nije upisan u dubinu
ili bafer. Operator je bilo koja od funkcija definisanih u D3D12_COMPARISON_FUNC naveden tip:
typedef enum D3D12_COMPARISON_FUNC
{
D3D12_COMPARISON_NEVER = 1,
D3D12_COMPARISON_LESS = 2,
D3D12_COMPARISON_EQUAL = 3,
D3D12_COMPARISON_LESS_EQUAL = 4,
D3D12_COMPARISON_GREATER = 5,
D3D12_COMPARISON_NOT_EQUAL = 6,
D3D12_COMPARISON_GREATER_EQUAL = 7,
D3D12_COMPARISON_ALWAYS = 8,
} D3D12_COMPARISON_FUNC;

1.D3D12_COMPARISON_NEVER: Funkcija uvek vraća false.


2. D3D12_COMPARISON_LESS: Zamenite sa <operatorom.
3. D3D12_COMPARISON_EKUAL: Zamenite sa == operatorom.
4. D3D12_COMPARISON_LESS_EKUAL: Zamenite sa operatorom ≤.
5. D3D12_COMPARISON_GREATER: Zamenite sa> operatorom.
6. D3D12_COMPARISON_NOT_EKUAL: Zamenite sa! = operator.
7. D3D12_COMPARISON_GREATER_EKUAL: Zamenite sa operatorom ≥.
8. D3D12_COMPARISON_ALVAIS: Funkcija uvek vraća true.
11.3 OPIS STANJA DRŽAVE / STENCILA
Stanje dubine / šablona se opisuje popunjavanjem D3D12_DEPTH_STENCIL_DESC
primer:
typedef struct D3D12_DEPTH_STENCIL_DESC {
BOOL DepthEnable; // Default True
// Default: D3D11_DEPTH_WRITE_MASK_ALL
D3D12_DEPTH_WRITE_MASK DepthWriteMask;
// Default: D3D11_COMPARISON_LESS
D3D12_COMPARISON_FUNC DepthFunc;
BOOL StencilEnable; // Default: False
UINT8 StencilReadMask; // Default: 0xff
UINT8 StencilWriteMask; // Default: 0xff
D3D12_DEPTH_STENCILOP_DESC FrontFace;
D3D12_DEPTH_STENCILOP_DESC BackFace;
} D3D12_DEPTH_STENCIL_DESC;
11.3.1 Postavke dubine
1. DepthEnable: Specifi true da biste omogućili buffering dubine; navesti pogrešno za onemogućavanje
to. Kada je testiranje dubine onemogućeno, redosled izvlačenja je bitan i fragment piksela će biti
biti nacrtani čak i ako je iza oklučenog objekta (pregled §4.1.5). Ako je dubinska baferiranja
onemogućeni, elementi u baferu za dubinu se ne ažuriraju, bez obzira na Postavka DepthVriteMask.
2. DepthVriteMask: Ovo može biti ili D3D12_DEPTH_VRITE_MASK_ZERO ili
D3D12_DEPTH_VRITE_MASK_ALL, ali ne oba. Pod pretpostavkom da je DepthEnable podešen
istina, D3D12_DEPTH_VRITE_MASK_ZERO onemogućava pisanje u bafer dubine, ali
i dalje će se pojaviti testiranje dubine. D3D12_DEPTH_VRITE_MASK_ALL omogućava pisanje
bafer dubine; nove dubine će biti napisane pod uslovom da se dubina i matrica testiraju
pass. Sposobnost kontrole dubine čita i piše postaje neophodna
primenjujući određene specijalne efekte.
3. DepthFunc: Navedite jedan od članova D3D12_COMPARISON_FUNC
naveden tip za definisanje funkcije upoređivanja dubine. Obično je ovo uvek
D3D12_COMPARISON_LESS tako da se obično vrši ispitivanje dubine, kako je opisano
u §4.1.5. To jest, fragment piksela je prihvaćen ako je njegova dubina manja od
dubina prethodnog piksela upisanog u zadnje pufer. Ali, kao što vidite,
Direct3D vam omogućava da prilagodite test dubine ako je potrebno.
11.3.2 Podešavanja matrice
1. StencilEnable: Navedite true da biste omogućili test šablona; navesti pogrešno da je onemogućite.
2. StencilReadMask: StencilReadMask koji se koristi u testu šablona:
if( StencilRef & StencilReadMask Value &
StencilReadMask )
accept pixel
else
reject pixel
The default does not mask any bits:
#define D3D12_DEFAULT_STENCIL_READ_MASK (
0xff )
3. StencilWriteMask: When the stencil buffer is being updated, we can mask off
certain bits from being written to with the write mask. For example, if you wanted to
prevent the top 4 bits from being written to, you could use the write mask of 0x0f.
The default value does not mask any bits:
#define D3D12_DEFAULT_STENCIL_WRITE_MASK (
0xff )
4. FrontFace: A filled out D3D12_DEPTH_STENCILOP_DESC structure indicating
how the stencil buffer works for front facing triangles.
5. BackFace: A filled out D3D12_DEPTH_STENCILOP_DESC structure indicating
how the stencil buffer works for back facing triangles.
typedef struct D3D12_DEPTH_STENCILOP_DESC {
D3D12_STENCIL_OP StencilFailOp; // Default:
D3D12_STENCIL_OP_KEEP
D3D12_STENCIL_OP StencilDepthFailOp; // Default:
D3D12_STENCIL_OP_KEEP
D3D12_STENCIL_OP StencilPassOp; // Default:
D3D12_STENCIL_OP_KEEP
D3D12_COMPARISON_FUNC StencilFunc; // Default:
D3D12_COMPARISON_ALWAYS
} D3D12_DEPTH_STENCILOP_DESC;
1.D3D12_STENCIL_OP_KEEP: Određuje da se ne promeni bafer stencila; to je,
zadržite vrednost koja je trenutno prisutna.
2. D3D12_STENCIL_OP_ZERO: Određuje da postavite stavku striktnog bafera na nulu.
3. D3D12_STENCIL_OP_REPLACE: Određuje da zameni stavku striktnog pufera
sa referentnom vrednošću za šablon (StencilRef) koja se koristi u ispitivanju šablona. Imajte na umu da
StencilRef vrijednost se postavlja kada veznim blokom stanja dubine / šablona dodamo
rendering pipeline (§11.3.3).
4. D3D12_STENCIL_OP_INCR_SAT: Određuje da se inkrementira stavka pufera.
Ako inkrementirana vrednost prelazi maksimalnu vrednost (npr. 255 za 8-bitnu šablonu
bafera), a onda stavljamo ulaz do tog maksimuma.
5. D3D12_STENCIL_OP_DECR_SAT: Određuje da smanji unos buffera stencila.
Ako je deklarisana vrednost manja od nule, onda stavljamo unos na nulu.
6. D3D12_STENCIL_OP_INVERT: Određuje da se invertuju bitovi striktnog bafera
ulaz.
7. D3D12_STENCIL_OP_INCR: Određuje da se povećava unos šablona za stencil. Ako je
povećana vrednost prelazi maksimalnu vrijednost (npr. 255 za 8-bitni šablon šablona),
onda se završimo na 0.
8. D3D12_STENCIL_OP_DECR: Određuje da smanji unos bafera štampe. Ako je
deklarisane vrednosti su manje od nule, a zatim se završavaju do maksimalno dozvoljene vrednosti.
Obratite pažnju na štetno ponašanje prednjeg okretanja i unazad
trouglovi mogu biti različiti. Postavke BackFace su nebitne u slučaju
da se ne vratimo na poligone usled odbacivanja lica. Međutim,
ponekad moramo da vratimo poligone za određenu grafiku
algoritmi ili za transparentnu geometriju (kao što je kutija žičane ograde, gde smo mogli
pogledajte kroz kutiju da vidite zadnje strane). U ovim slučajevima, BackFace
postavke su relevantne.
11.3.3 Kreiranje i povezivanje stanja dubine / stencila
Jednom smo popunili D3D12_DEPTH_STENCIL_DESC instancu
opisujući našu dubinu / šablonsku državu, možemo ga dodijeliti
D3D12_GRAPHICS_PIPELINE_STATE_DESC :: DepthStencilState polje
PSO. Svaka geometrija izvučena ovim PSO će biti prikazana podešavanjima dubine / šablona
PSO.
Jedan detalj koji još nismo spominjali je kako postaviti referentnu vrijednost štampe. The
referentna vrednost šablona je podešena sa
ID3D12GraphicsCommandList :: metoda OMSetStencilRef, koja podrazumeva a
jedan nepotpisani integer parametar; na primer, sledeće postavlja referencu o šablonu
vrednost do 1:
mCommandList-> OMSetStencilRef (1);
11.4 IMPLEMENTING PLANAR OGLEDALA
Mnoge površine u prirodi služe kao ogledala i omogućavaju nam da vidimo refleksije objekata.
Ovaj odeljak opisuje kako možemo simulirati ogledala za naše 3D aplikacije. Zapamtite to za
jednostavnost, smanjimo zadatak primene ogledala samo na ravninske površine. Za
na primer, sjajni automobil može prikazati refleksiju; Međutim, telo automobila je glatko, okruglo i
nije ravno. Umjesto toga, prikazujemo refleksije poput onih koje su prikazane u sjajnom mermeru
pod ili onih koji su prikazani u ogledalu visi na zidu - drugim riječima, ogledala
koji leže na avionu.
Programski usmjeravanje ogledala zahtijeva nam da riješimo dva problema. Prvo smo
moraju naučiti kako da reflektuju objekat oko proizvoljnog plana tako da možemo izvući
refleksija ispravno. Drugo, moramo prikazati samo refleksiju u ogledalu, to jest, mi
mora nekako "označiti" površinu kao ogledalo, a zatim, kao što vidimo, samo nacrtajte
reflektovani objekat ako je u ogledalu. Vratite se na sliku 11.1, koja je prvi put predstavila ovo
koncept.
Prvi problem se lako rešava sa nekim analitičkom geometrijom, o čemu se diskutuje
Dodatak C. Drugi problem se može riješiti pomoću bafera za stencil.
11.4.1 Pregled ogledala
Kada nacrtamo refleksiju, takođe treba da odražamo izvor svetlosti avionom ogledala. U suprotnom,
osvetljenje refleksije ne bi bilo tačno. Slika 11.2 pokazuje da je potrebno da se reflektuje objekat, da ga
reflektujemo avionom ogledala. Međutim, ovo uvodi problem prikazan na slici 11.1. Naime, refleksija
objekta (lobanja u ovom slučaju) je samo još jedan predmet u našoj sceni, i ako ništa ne oklijeva, onda će
ga videti. Međutim, razmišljanje treba da bude samo viđen kroz ogledalo. Ovaj problem možemo riješiti
pomoću bafera za stencil jer je stencil buffer nam omogućava blokiranje renderiranja u određenim
oblastima na zadnjem puferu. Tako možemo koristite šablon za šablone da blokirate rendiranje
reflektovane lobanje ako se ne prikazuje u ogledalo. U nastavku je prikazan korak kako se to može
postići:
1. Podesite pod, zidove i lobanju u zadnje pufer kao normalno (ali ne i ogledalo).
Imajte na umu da ovaj korak ne menja matrični pufer.
2. Obrišite pufer za matricu na 0. Na slici 11.3 prikazan je bafer za pozadinu i šablon za šablon na
ovu tačku (gde zamenjujemo kutiju za lobanje da bi se crtanje učinilo jednostavnijim).
3. Omogućite ogledalo samo na bafer za šablone. Mi možemo onemogućiti pisanje boja u leđa
baferom tako što ćete kreirati stanje mešanja koje postavlja
D3D12_RENDER_TARGET_BLEND_DESC :: RenderTargetVriteMask
= 0;
i možemo onemogućiti pisanje u bafer dubine postavljanjem
D3D12_DEPTH_STENCIL_DESC :: DepthVriteMask =
D3D12_DEPTH_VRITE_MASK_ZERO;
Kada se ogledalo pretvori u šablon šablona, uvek postavljamo test stripe
uspješno (D3D12_COMPARISON_ALVAIS) i navedite taj unos baze podataka
treba zameniti (D3D12_STENCIL_OP_REPLACE) sa 1 (StencilRef) ako
test prođe. Ako test dubine ne uspije, onda ćemo navesti D3D12_STENCIL_OP_KEEP
da se striktni bafer ne menja ako ispitivanje dubine ne uspe (to može da se desi, za
primer, ako lobanja zatvara deo ogledala). S obzirom na to da samo radimo
sledi da će svi pikseli u baferu za stencil biti 0
osim piksela koji odgovaraju vidljivom delu ogledala - oni će imati
a 1. Na slici 11.4 prikazan je ažurirani bafer za šablone. U suštini, označavamo
Vidljivi pikseli ogledala u puferu za matricu.
Slika 11.4. Rendering ogledalo do pufera za stencil, u suštini obeležavanje
piksele u baferu matrice koje odgovaraju vidljivim delovima ogledala.
Čvrsta crna površina na baferu za stencil označava unose šablona postavljene na 1. Napomena
da se područje na puferu matrice okruženo kutijom od tada ne postavi na 1
ne uspe test dubine (kutija je ispred tog dela ogledala).
Važno je izvući ogledalo u bafer za šablone nakon što imamo
skinuo lobanju tako da pikseli ogledala okruženi lobanjom ne uspevaju
dubinski test, i stoga ne menjaju bafer za matricu. Ne želimo da se okrenemo
na delovima pufera za stencil koji su oklučeni; inače će refleksija
pokaži kroz lobanju.
4. Sada odražavamo lobanju na bafer za vraćanje i pufer za matricu. Ali se setite toga
mi ćemo se samo vratiti u bafer ako je prošao test šablona. Ovaj put smo postavili
test šablona da uspe samo ako je vrednost u baferu matrice jednaka 1; ovo je učinjeno
koristeći StencilRef od 1, i operatera šablona
D3D12_COMPARISON_EKUAL. Na taj način, reflektovana lobanja će biti samo
izrađene u oblastima koja imaju 1 u odgovarajućem unosu matrice. Od
oblasti u baferu za matricu koja odgovara vidljivim delovima ogledala su
samo unosi koji imaju 1, sledi da će refleksna lobanja biti prikazana samo
vidljivi delovi ogledala.
5. Na kraju, ogledalo je učinjeno na baferu kao normalno. Međutim, kako bi
refleksija lobanje da se pokaže (koja leži iza ogledala), moramo da učinimo
ogledalo sa mešanjem transparentnosti. Ako nismo napravili ogledalo sa transparentnošću,
ogledalo bi jednostavno okretalo refleksiju, jer je njegova dubina manja od nje
refleksija. Da bismo to primenili, jednostavno treba da definišemo novu instancu za materijal
ogledalo; postavili smo alfa kanal difuzne komponente na 0.3 da napravimo
ogledalo je 30% neprozirno, a učinimo ogledalo u stanju blende transparentnosti
opisano u poslednjem poglavlju (§10.5.4).
auto icemirror = std::make_unique<Material>();
icemirror->Name = “icemirror”;
icemirror->MatCBIndex = 2;
icemirror->DiffuseSrvHeapIndex = 2;
icemirror->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f,
1.0f, 0.3f);
icemirror->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
icemirror->Roughness = 0.5f;
Ova podešavanja daju sledeću jednačinu blendiranja:
C=0,3Csrc+0.7Cdst
Pod pretpostavkom da smo postavili reflektovane piksele lobanje u zadnji pufer, vidimo 30%
boje dolazi iz ogledala (izvor) i 70% boje dolazi iz lobanje
(destinacija).
11.4.2 Definisanje dubine ogledala / stanja šablona
Da bi implementirali prethodno opisani algoritam, trebaju nam dva PSO-a. Prvi se koristi
kada crtanje ogledala označava piksele ogledala na baferu matrice. Druga je
korišćena je za crtanje reflektovane lobanje tako da se ona vuče samo u vidljive delove ogledala.
//
// PSO for marking stencil mirrors.
//
// Turn off render target writes.
CD3DX12_BLEND_DESC mirrorBlendState(D3D12_DEFAULT);
mirrorBlendState.RenderTarget[0].RenderTargetWriteMask
= 0;
D3D12_DEPTH_STENCIL_DESC mirrorDSS;
mirrorDSS.DepthEnable = true;
mirrorDSS.DepthWriteMask =
D3D12_DEPTH_WRITE_MASK_ZERO;
mirrorDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
mirrorDSS.StencilEnable = true;
mirrorDSS.StencilReadMask = 0xff;
mirrorDSS.StencilWriteMask = 0xff;
mirrorDSS.FrontFace.StencilFailOp =
D3D12_STENCIL_OP_KEEP;
mirrorDSS.FrontFace.StencilDepthFailOp =
D3D12_STENCIL_OP_KEEP;
mirrorDSS.FrontFace.StencilPassOp =
D3D12_STENCIL_OP_REPLACE;
mirrorDSS.FrontFace.StencilFunc =
D3D12_COMPARISON_FUNC_ALWAYS;
// We are not rendering backfacing polygons, so these
settings do not
// matter.
mirrorDSS.BackFace.StencilFailOp =
D3D12_STENCIL_OP_KEEP;
mirrorDSS.BackFace.StencilDepthFailOp =
D3D12_STENCIL_OP_KEEP;
mirrorDSS.BackFace.StencilPassOp =
D3D12_STENCIL_OP_REPLACE;
mirrorDSS.BackFace.StencilFunc =
D3D12_COMPARISON_FUNC_ALWAYS;
D3D12_GRAPHICS_PIPELINE_STATE_DESC markMirrorsPsoDesc
= opaquePsoDesc;
markMirrorsPsoDesc.BlendState = mirrorBlendState;
markMirrorsPsoDesc.DepthStencilState = mirrorDSS;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&markMirrorsPsoDesc,
IID_PPV_ARGS(&mPSOs[“markStencilMirrors”])));
//
// PSO for stencil reflections.
//
D3D12_DEPTH_STENCIL_DESC reflectionsDSS;
reflectionsDSS.DepthEnable = true;
reflectionsDSS.DepthWriteMask =
D3D12_DEPTH_WRITE_MASK_ALL;
reflectionsDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
reflectionsDSS.StencilEnable = true;
reflectionsDSS.StencilReadMask = 0xff;
reflectionsDSS.StencilWriteMask = 0xff;
reflectionsDSS.FrontFace.StencilFailOp =
D3D12_STENCIL_OP_KEEP;
reflectionsDSS.FrontFace.StencilDepthFailOp =
D3D12_STENCIL_OP_KEEP;
reflectionsDSS.FrontFace.StencilPassOp =
D3D12_STENCIL_OP_KEEP;
reflectionsDSS.FrontFace.StencilFunc =
D3D12_COMPARISON_FUNC_EQUAL;
// We are not rendering backfacing polygons, so these
settings do not
// matter.
reflectionsDSS.BackFace.StencilFailOp =
D3D12_STENCIL_OP_KEEP;
reflectionsDSS.BackFace.StencilDepthFailOp =
D3D12_STENCIL_OP_KEEP;
reflectionsDSS.BackFace.StencilPassOp =
D3D12_STENCIL_OP_KEEP;
reflectionsDSS.BackFace.StencilFunc =
D3D12_COMPARISON_FUNC_EQUAL;
D3D12_GRAPHICS_PIPELINE_STATE_DESC
drawReflectionsPsoDesc = opaquePsoDesc;
drawReflectionsPsoDesc.DepthStencilState =
reflectionsDSS;
drawReflectionsPsoDesc.RasterizerState.CullMode =
D3D12_CULL_MODE_BACK;
drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise
= true;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&drawReflectionsPsoDesc,
IID_PPV_ARGS(&mPSOs[“drawStencilReflections”])));
11.4.3 Crtanje scene
Sledeći kod opisuje naš metod izvlačenja. Izostavili smo nerelevantne detalje, kao što su postavljanje
konstantnih vrednosti bafera, za kratkotrajnost i jasnoću (pogledajte kod primera za sve detalje).
// Draw opaque items—floors, walls, skull.
auto passCB = mCurrFrameResource->PassCB->Resource();
mCommandList->SetGraphicsRootConstantBufferView(2,
passCB->GetGPUVirtualAddress());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Opaque]);
// Mark the visible mirror pixels in the stencil
buffer with the value 1
mCommandList->OMSetStencilRef(1);
mCommandList-
>SetPipelineState(mPSOs[“markStencilMirrors”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Mirrors]);
// Draw the reflection into the mirror only (only for
pixels where the
// stencil buffer is 1).
// Note that we must supply a different per-pass
constant buffer—one
// with the lights reflected.
mCommandList->SetGraphicsRootConstantBufferView(2,
passCB->GetGPUVirtualAddress() + 1 *
passCBByteSize);
mCommandList-
>SetPipelineState(mPSOs[“drawStencilReflections”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Reflected]);
// Restore main pass constants and stencil ref.
mCommandList->SetGraphicsRootConstantBufferView(2,
passCB->GetGPUVirtualAddress());
mCommandList->OMSetStencilRef(0);
// Draw mirror with transparency so reflection blends
through.
mCommandList-
>SetPipelineState(mPSOs[“transparent”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Transparent]);
Jedna tačka koju treba navesti u gore navedenom kodu je kako promenimo konstantni bafer za per-pass
prilikom crtanja RenderLaier :: Reflected sloja. To je zbog scene Osvetljenje se takođe mora reflektovati
prilikom crtanja refleksije. Svetla se čuvaju u konstantnom baferu za per-pass, tako da stvorimo dodatni
buffer konstantni per-pass koji čuva reflektovano osvetljenje scene. Konstantni bafer za periode koji se
koristi za crtanje refleksija postavljen je sledećim metodom:
PassConstants StencilApp::mMainPassCB;
PassConstants StencilApp::mReflectedPassCB;
void StencilApp::UpdateReflectedPassCB(const
GameTimer& gt)
{
mReflectedPassCB = mMainPassCB;
XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f,
0.0f); // xy plane
XMMATRIX R = XMMatrixReflect(mirrorPlane);
// Reflect the lighting.
for(int i = 0; i < 3; ++i)
{
XMVECTOR lightDir =
XMLoadFloat3(&mMainPassCB.Lights[i].Direction);
XMVECTOR reflectedLightDir =
XMVector3TransformNormal(lightDir, R);
XMStoreFloat3(&mReflectedPassCB.Lights[i].Direction,
reflectedLightDir);
}
// Reflected pass stored in index 1
auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(1, mReflectedPassCB);
}
11.4.4 Naređivanje i refleksije
Kada se trougao odslikava preko ravni, njegov redosled navijanja se ne menja, i
tako da se njegovo lice normalno ne obrne. Prema tome, normalno se suočavaju sa normalnim
normama (vidi sliku 11.5), nakon refleksije. Da bi ispravili ovo, reći ćemo Direct3D da interpretira
trouglove sa potezom za namotavanje u smeru suprotnom od kretanja kazaljki na satu, kako su okrenuti
prema frontu i trouglovi sa smerom navrtanja u smeru kazaljke na satu kao okrenuti nazad (ovo je
suprotno od naše uobičajene konvencije -§5.10.2). Ovo efektivno odražava normalne pravce, tako da se
s obzirom na refleksiju gleda spolja. Mi preusmjeravamo konvenciju porudžbine navoja postavljanjem
sljedećih osobina rasterizera u PSO:
drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise= true;
Mi prethodnu 4 × 4 matricu nazivamo matricom senke u smeru i označimo je sa Sdir-om.
Da vidimo kako je ova matrica jednaka jednačini 11.1, potrebno je samo da izvršimo
množenje. Prvo, međutim, zapazite da ova jednačina modifikuje v-komponentu tako
da je sv = n · L. Stoga, kada se odvija podjela perspektive (§5.6.3.4), svaka koordinata od
s će biti podeljeno sa n · L; Ovako dobijamo podjelu pomoću n · L u jednačini 11.1 koristeći
matrice. Sada rade matrično množenje da dobijemo i-ovu koordinatu s'i za i ∈ {1, 2,
3}, a zatim slede podeljeni perspektivi:
Ovo je tačno ista koordinata s u jednačini 11.1, tako da s = s '.
Da koristimo matricu senki, kombinujemo je sa našom svetskom matricom. Međutim, nakon
svetska transformacija, geometrija još nije projektovana na senku
jer se razlika u perspektivi još nije dogodila. Problem se javlja ako je sv = n · L <0.
jer ovo čini negativan V koordinat. Obično u projekciji perspektive
proces kopiramo z-koordinat u v-koordinatu i negativnu v-koordinatu
to bi značilo da ta tačka nije u vidu volumena i da je tako sklonjena (isecanje se vrši
u homogenom prostoru pre podele). Ovo je problem za planarne senke, jer
mi sada koristimo v-koordinat za implementaciju senki, pored perspektive
podela. Slika 11.8 prikazuje valjanu situaciju u kojoj n · L <0, ali senka neće pokazati
up.
Slika 11.8. Situacija u kojoj je n · L <0.
Da bi ovo popravili, umesto da koristimo pravac L svetlosnog zraka, trebalo bi da koristimo vektor
prema beskonačnom dalekovnom izvoru svjetlosti. Zapazite to i
definišu istu 3D liniju, a tačka preseka između linije i ravni će biti
isto (parametar preseka ts će biti drugačiji za kompenzaciju znaka
razlika između i L). Znači korišćenje nam daje isti odgovor, ali s n · L> 0,
koji izbegava negativnu v-koordinatu.
Slika 11.9. Senka koja se odlazi u odnosu na tačku izvora svetlosti.
11.5.2 Senke senke tačke
Na slici 11.9 prikazana je senka koja se izbacuje u odnosu na izvor svetlosti tačke
čija je pozicija opisana tačkom L. Svetlosni zrak iz tačke svetlosti kroz bilo koji
vertek p je data r (t) = p + t (p - L). Presek zraka r (t) s senkom
ravni (n, d) daje s. Set tačaka preseka pronađenih snimanjem zraka kroz svaku od
vertikali objekta sa ravni definišu projektovanu geometriju sjene. Za
vertek p, njegova projekcija senke daje
Jednačina 11.2 takođe može biti napisana matričnom jednačinom:
Da vidimo kako je ova matrica ekvivalentna jednačini 11.2, samo treba da izvršimo
množenje na isti način kao u prethodnom odeljku. Imajte na umu da je poslednja kolona
nema nula i daje:
Ovo je negativan nazivnik u jednačini 11.2, ali možemo negirati
imenitelje ako negiramo brojac.
11.5.3 Opšta matrica senki
Koristeći homogene koordinate, moguće je napraviti opštu matricu senki koja
radi i za tačkasta i usmeravajuća svetla.
1. Ako je Lv = 0 onda L opisuje pravac prema beskonačno dalekoj izvori svjetlosti
(tj. u suprotnom pravcu paralelni svetlosni zraci putuju).
2. Ako je Lv = 1 onda L opisuje lokaciju tačke svetlosti.
Zatim predstavljamo transformaciju od verteka p do njegove projekcije s sa
sledeći matricu senki:
Lako je vidjeti da je S reduciran na Sdir ako je Lv = 0, a S se smanjuje za Spoint za Lv = 0.
Matematička biblioteka DirectKs pruža sledeću funkciju za izgradnju matrice senke
s obzirom na avion u koji želimo da projektujemo senku i vektor koji opisuje paralelno svetlo
ako je v = 0 ili tačka svetla ako je v = 1:
inline KSMMATRIKS KSM_CALLCONV KSMMatrikShadov (
FKSMVECTOR ShadovPlane,
FKSMVECTOR LightPosition);
Za dalje čitanje, oba [Blinn96] i [Moller02] diskutuju o planarnim senkama.
11.5.4 Korišćenje odbojnika matrice za sprečavanje dvostrukog mešanja
Kada izravnavamo geometriju objekta u ravninu kako bi opisali njegovu senku, to je
moguća (i zapravo verovatno) da će se dva ili više gornjih trouglova preklapati.
Kada senku učinimo transparentnom (koristeći mešanje), ove oblasti koje imaju
trouglovi preklapanja će se mešati više puta i time će izgledati tamnije. Slika 11.10
ovo pokazuje.
Slika 11.10. Primjetite tamne "akne" oblasti sjene na lijevoj slici;
ovo odgovara područjima na kojima se delovi poravnane lobanje preklapaju, čime se uzrokuje a
"Dvostruka mešavina." Slika na desnoj strani pokazuje da je sjena ispravno prikazana bez
dvostruko mešanje.
Ovim problemom možemo rešiti pomoću bafera.
1. Pretpostavimo da su pikseli pufera u stencilima gde će biti prikazana senka
očišćeno na 0. Ovo je istina u našem demo ogledalu jer samo bacamo sjenu
na zemlju, a mi smo samo modifikovali piksele bafera ogledala.
2. Postavite test za stencil kako biste prihvatili samo piksele ako je pufer za stencil ima unos od 0. Ako je
proba ispred šablona, onda povećavamo vrednost striktnog pufera na 1.
Kada prvi put napravimo piksel senke, prozor matrice će proći jer je matrica
Bufer ulaz je 0. Međutim, kada napravimo ovaj piksel, povećavamo i
odgovarajući unos šifre za šablon na 1. Tako, ako pokušamo da prepisujemo na oblast koja je
već je prikazano (označeno u baferu s stencilima vrijednošću 1), matricu
test će propasti. Ovo sprečava crtanje preko istog piksela više puta, i time sprečava
dvostruko mešanje.
11.5.5 Kod senke
Definišemo materijal senke koji se koristi za boju sjene koja je samo 50% transparentna
crni materijal:
auto shadowMat = std::make_unique<Material>();
shadowMat->Name = “shadowMat”;
shadowMat->MatCBIndex = 4;
shadowMat->DiffuseSrvHeapIndex = 3;
shadowMat->DiffuseAlbedo = XMFLOAT4(0.0f, 0.0f, 0.0f,
0.5f);
shadowMat->FresnelR0 = XMFLOAT3(0.001f, 0.001f,
0.001f);
shadowMat->Roughness = 0.0f;
In order to prevent double blending we set up the following PSO with depth/stencil
state:
// We are going to draw shadows with transparency, so
base it off
// the transparency description.
D3D12_DEPTH_STENCIL_DESC shadowDSS;
shadowDSS.DepthEnable = true;
shadowDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
shadowDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
shadowDSS.StencilEnable = true;
shadowDSS.StencilReadMask = 0xff;
shadowDSS.StencilWriteMask = 0xff;
shadowDSS.FrontFace.StencilFailOp =
D3D12_STENCIL_OP_KEEP;
shadowDSS.FrontFace.StencilDepthFailOp =
D3D12_STENCIL_OP_KEEP;
shadowDSS.FrontFace.StencilPassOp =
D3D12_STENCIL_OP_INCR;
shadowDSS.FrontFace.StencilFunc =
D3D12_COMPARISON_FUNC_EQUAL;
// We are not rendering backfacing polygons, so these
settings do not
// matter.
shadowDSS.BackFace.StencilFailOp =
D3D12_STENCIL_OP_KEEP;
shadowDSS.BackFace.StencilDepthFailOp =
D3D12_STENCIL_OP_KEEP;
shadowDSS.BackFace.StencilPassOp =
D3D12_STENCIL_OP_INCR;
shadowDSS.BackFace.StencilFunc =
D3D12_COMPARISON_FUNC_EQUAL;
D3D12_GRAPHICS_PIPELINE_STATE_DESC shadowPsoDesc =
transparentPsoDesc;
shadowPsoDesc.DepthStencilState = shadowDSS;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&shadowPsoDesc,
IID_PPV_ARGS(&mPSOs[“shadow”])));
We then draw the skull shadow with the shadow PSO with a StencilRef value of
0:
// Draw shadows
mCommandList->OMSetStencilRef(0);
mCommandList->SetPipelineState(mPSOs[“shadow”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Shadow]);
where the skull shadow render-item’s world matrix is computed like so:
// Update shadow world matrix.
XMVECTOR shadowPlane = XMVectorSet(0.0f, 1.0f, 0.0f,
0.0f); // xz plane
XMVECTOR toMainLight = -
XMLoadFloat3(&mMainPassCB.Lights[0].Direction);
XMMATRIX S = XMMatrixShadow(shadowPlane, toMainLight);
XMMATRIX shadowOffsetY = XMMatrixTranslation(0.0f,
0.001f, 0.0f);
XMStoreFloat4x4(&mShadowedSkullRitem->World,
skullWorld * S * shadowOffsetY);

Poglavlje 12 GEOMETRIJA SHADER


Pod pretpostavkom da ne koristimo faze tezelacije, faza geometrijskog shadera je
neobavezna faza koja se nalazi između faze vertek-a i piksel-shadera. Dok je vertek shader
ulazi vertices, geometrijski shader unosi celu primitivu. Na primer, ako smo bili
crtanje trokutnih listi, zatim konceptualno bi se izvršio program geometrijskog shadera
za svaki trougao T u listi:
za (UINT i = 0; i <numTriangles; ++ i)
OutputPrimitiveList = GeometriShader (
T [i] .vertekList);
Imajte na umu da su tri vertikala svakog trougla uneta u geometrijski shader, i
geometrijski shader izvodi listu primitiva. Za razliku od vertek shadera koji ne mogu uništiti
ili kreirati vertikale, glavna prednost geometrijskog shadera je to što može da stvori ili
uništiti geometriju; ovo omogućava neke zanimljive efekte koji se implementiraju na GPU-u.
Na primer, ulazni primitiv može se proširiti u jedan ili više drugih primitiva, ili na
geometrijski shader može izabrati da ne izlazi primitivu na osnovu nekog stanja. Napomenuti da
izmitni primitivi ne moraju biti isti tip kao ulazni primitiv; na primer, a
zajednička primena geometrijskog shadera je da proširi tačku u četvoro (dva
trouglovi).
Izlazi primitiva iz geometrijskog shadera definišu se listom verteka. Vertek
položaji koji napuštaju geometrijski shader moraju se transformisati u homogeni prostor za snimanje.
Nakon faze geometrijskog shadera, imamo listu vertisa koji definišu primitive
homogeni prostor za klipove. Ove vertikale su projektovane (homogena podela), a zatim
Rastezanje se odvija kao i obično.
Ciljevi:
1. Da naučite kako programirati geometrijske shadere.
2. Da biste otkrili kako se bilbordi mogu efikasno implementirati pomoću geometrije
shader.
3. Prepoznati automatsko generisane primitivne ID-ove i neke od njihovih aplikacija.
4. Da biste saznali kako kreirati i koristiti teksture i razumeti zašto su korisni.
5. Da biste razumeli kako alfa-do-pokrivenost pomaže problem aluzije aluzije
izreza.
12.1 PROGRAMIRANJE GEOMETRIJSKIH STEKLA
Programiranje geometrijskih shadera je puno poput programskog verteka ili piksel shadera, ali
postoje neke razlike. Sledeći kod pokazuje opšti oblik:
[maxvertexcount(N)]
void ShaderName (
PrimitiveType InputVertexType InputName
[NumElements],
inout StreamOutputObject<OutputVertexType>
OutputName)
{
// Geometry shader body…
}
Prvo moramo navesti maksimalni broj verti- kova koje će geometrijski shader izlaz za jedan poziv
(geometrijski shader je pozvan po primitivu). Ovo je učinjeno postavljanjem maksimalnog broja verteka
pre definicije shadera koristeći sledeći atribut sintaksa:
[makvertekcount (N)]
gde je N maksimalan broj vertisa koje će geometrijski shader izaći za a
jednokratno pozivanje. Broj vertisa koji geometrijski shader može izlaziti po pozivu je
promenljiva, ali ne može preći definisani maksimum. Za potrebe performansi,
makvertekcount treba da bude što manji; [NVIDIA08] navodi taj vrhunac
performanse GS se postižu kada GS izlazi između 1-20 skalara i
performanse padaju na 50% ako GS izlazi između 27-40 skalara. Broj skalara
izlaz po pozivu je proizvod makvertekcount i broj skalara u
Izlazna vertek struktura tipa. Rad sa takvim ograničenjima je teško u praksi, tako da
možemo prihvatiti niže od vrhunskih performansi dovoljno dobro, ili izabrati
alternativna implementacija koja ne koristi geometrijski shader; Međutim, moramo i mi
smatraju da alternativna implementacija može imati i druge nedostatke, što i dalje može
učiniti implementaciju geometrijskog shadera bolji izbor. Osim toga,
preporuke u [NVIDIA08] su iz 2008 (geometrijske shadere prve generacije), tako da
stvari su se trebale poboljšati.
Geometrijski shader ima dva parametra: ulazni parametar i izlaz
parametar. (Zapravo, može se uzeti više, ali to je posebna tema, vidi §12.2.4.) Ulaz
parametar je uvek niz verzija koji definišu primitivno-jedan vertek za tačku,
dva za liniju, tri za trougao, četiri za liniju s susjedstvom, i šest za trougao sa
susedstvo. Tip verteka ulaznih vertices je tip verteka vra} en od strane verteka
shader (npr. VertekOut). Parametar ulaza mora biti prefiksovan primitivnim tipom,
opisujući vrstu primitiva koji ulaze u geometrijski shader. Ovo može biti bilo ko
od sledećeg:
1. tačka: ulazni primitivi su tačke.
2. red: ulazni primitivi su linije (liste ili trake).
3. trougao: ulazni primitivni trouglovi (liste ili trake).
4. lineadj: ulazni primitivi su linije sa susedstvom (liste ili trake).
5. triangleadj: ulazni primitivi su trouglovi sa susedstvom (liste ili trake).
Ulazni ulaz u geometrijski shader je uvek potpuni primitivan
(npr., dve vertice za liniju, i tri vertikala za trougao). Prema tome
geometrijski shader ne mora razlikovati liste i trake. Za
Na primer, ako crtate trokutne trake, geometrijski shader se i dalje izvršava
za svaki trougao u traci, a tri glave svakog trougla se prenose
u geometrijski shader kao ulaz. Ovo podrazumijeva dodatne nadmoć, kao vertikale
koje dele više višestrukih primitiva se obrađuju više puta u
geometrijski shader.
Izlazni parametar uvek ima inout modifikator. Pored toga, izlaz
parametar je uvek tip teksta. Tip potoka čuva listu vertikala koja definira
geometrija geometrijski shader izlazi. Geometrijski shader dodaje vertek u
lista odlaznih struja pomoću intrinzične metode dodavanja:
void
StreamOutputObject<OutputVertexType>::Append(OutputVertexType
v);
Tip teksta je tip šablona, gde se argument argumenta koristi za određivanje
vertek tip odlaznih vertikala (npr., GeoOut). Postoje tri moguća tipa teksta:
1. PointStream <OutputVertekTipe>: Lista vertisa koja definiše tačku.
2. LineStream <OutputVertekTipe>: Lista vertisa koja definiše traku linija.
3. TriangleStream <OutputVertekTipe>: Spisak vertikala koji definišu trougao
traka.
Točke koje izlaze geometrijski shader formiraju primitive; tip izlaznog primitiva
je označen vrstom streama (PointStream, LineStream, TriangleStream).
Za linije i trouglove, izlazni primitiv je uvek traka. Liste linija i trouglova,
međutim, može se simulirati korišćenjem unutrašnje metode RestartStrip:
praznina
StreamOutputObject <OutputVertekTipe> :: RestartStrip ();
Na primer, ako ste želeli da izlistate trougao liste, onda biste pozvali
RestartStrip svaki put nakon što su tri tačke dodate u izlazni tok.
U nastavku su navedeni konkretni primeri potpisa geometrijskih shadera:
// EXAMPLE 1: GS ouputs at most 4 vertices. The input
primitive is a
// line.
// The output is a triangle strip.
//
[maxvertexcount(4)]
void GS(line VertexOut gin[2],
inout TriangleStream<GeoOut> triStream)
{
// Geometry shader body…
}/
/
// EXAMPLE 2: GS outputs at most 32 vertices. The
input primitive is
// a triangle. The output is a triangle strip.
//
[maxvertexcount(32)]
void GS(triangle VertexOut gin[3],
inout TriangleStream<GeoOut> triStream)
{>>>>>>>>>>>>>>>>>>
// Geometry shader body…
}/
/
// EXAMPLE 3: GS outputs at most 4 vertices. The input
primitive
// is a point. The output is a triangle strip.
//
[maxvertexcount(4)]
void GS(point VertexOut gin[1],
inout TriangleStream<GeoOut> triStream)
{
// Geometry shader body…
}
Slika 12.1. Podeli trougao u četiri ravnopravna trougla. Posmatrajte da su tri nova vertikala srednja tačka
duž ivica prvobitnog trougla. Sledeći geometrijski shader ilustruje Append i RestartStrip metode; uvodi
trougao, deli ga (Slika 12.1) i izlazi četiri podeljena trouglovi:
struct VertexOut
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float2 Tex : TEXCOORD;
};
struct GeoOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float2 Tex : TEXCOORD;
float FogLerp : FOG;
};
void Subdivide(VertexOut inVerts[3], out VertexOut
outVerts[6])
{
// 1
// *
// / \
// / \
// m0*–—*m1
// / \ / \
// / \ / \
// *–—*–—*
// 0 m2 2
VertexOut m[3];
// Compute edge midpoints.
m[0].PosL = 0.5f*(inVerts[0].PosL+inVerts[1].PosL);
m[1].PosL = 0.5f*(inVerts[1].PosL+inVerts[2].PosL);
m[2].PosL = 0.5f*(inVerts[2].PosL+inVerts[0].PosL);
// Project onto unit sphere
m[0].PosL = normalize(m[0].PosL);
m[1].PosL = normalize(m[1].PosL);
m[2].PosL = normalize(m[2].PosL);
// Derive normals.
m[0].NormalL = m[0].PosL;
m[1].NormalL = m[1].PosL;
m[2].NormalL = m[2].PosL;
// Interpolate texture coordinates.
m[0].Tex = 0.5f*(inVerts[0].Tex+inVerts[1].Tex);
m[1].Tex = 0.5f*(inVerts[1].Tex+inVerts[2].Tex);
m[2].Tex = 0.5f*(inVerts[2].Tex+inVerts[0].Tex);
outVerts[0] = inVerts[0];
outVerts[1] = m[0];
outVerts[2] = m[2];
outVerts[3] = m[1];
outVerts[4] = inVerts[2];
outVerts[5] = inVerts[1];
};
void OutputSubdivision(VertexOut v[6],
inout TriangleStream<GeoOut> triStream)
{
GeoOut gout[6];
[unroll]
for(int i = 0; i < 6; ++i)
{
// Transform to world space space.
gout[i].PosW = mul(float4(v[i].PosL, 1.0f),
gWorld).xyz;
gout[i].NormalW = mul(v[i].NormalL,
(float3x3)gWorldInvTranspose);
// Transform to homogeneous clip space.
gout[i].PosH = mul(float4(v[i].PosL, 1.0f),
gWorldViewProj);
gout[i].Tex = v[i].Tex;
}
// 1
// *
// / \
// / \
// m0*–—*m1
// / \ / \
// / \ / \
// *–—*–—*
// 0 m2 2
// We can draw the subdivision in two strips:
// Strip 1: bottom three triangles
// Strip 2: top triangle
[unroll]
for(int j = 0; j < 5; ++j)
{
triStream.Append(gout[j]);
}
triStream.RestartStrip();
triStream.Append(gout[1]);
triStream.Append(gout[5]);
triStream.Append(gout[3]);
}
[maxvertexcount(8)]
void GS(triangle VertexOut gin[3], inout
TriangleStream<GeoOut>)
{
VertexOut v[6];
Subdivide(gin, v);
OutputSubdivision(v, triStream);
}
Geometrijski shaderi su sakupljeni vrlo slično s vertekima i pikselnim shaderima. Pretpostavimo da
imamo geometrijski shader koji se zove GS u TreeSprite.hlsl, onda ćemo kompilirati shader na bajtekode
tako:
mShaders[“treeSpriteGS”] = d3dUtil::CompileShader(
L”Shaders\TreeSprite.hlsl”, nullptr, “GS”,
“gs_5_0”);
Kao i vertek i piksel shaderi, određeni geometrijski shader je vezan za rendering
cevovod kao dio objekta objekta za gasovod (PSO):
pipeline as part of a pipeline state object (PSO):
D3D12_GRAPHICS_PIPELINE_STATE_DESC treeSpritePsoDesc =
opaquePsoDesc;
…t
reeSpritePsoDesc.GS =
{
reinterpret_cast<BYTE*>(mShaders[“treeSpriteGS”]-
>GetBufferPointer()),
mShaders[“treeSpriteGS”]->GetBufferSize()
12.2 TREE BILLBOARDS DEMO
12.2.1 Pregled
Kada su drveća daleko, tehnika efikasnosti se koristi. To je,
umesto da bi se prikazala geometrija za potpuno 3D stablo, kuad sa slikama 3D stabla
obojen na njemu (vidi sliku 12.2). Sa distance, ne možete reći da je bilbord
koristi se. Međutim, trik je da se uverite da se bilbord uvek suočava sa kamerom
(inače bi iluzija pala).
Slika 12.2. Tekstura bilborda sa alfa kanalom.
Pod pretpostavkom da je i-osa gore, a kz-ravnina je ravna zemlja, bilbordi za drvo
uglavnom će biti usklađeni sa i-osom i samo se suočiti sa kamerom u kz-ravni. Figura
12.3 prikazuje lokalne koordinatne sisteme nekoliko bilborda iz vidika ptičje perspektive -
primijetite da bilbordi "gledaju" na kameri.
Slika 12.3. Bilbordi okrenuti ka kameri.
Dakle, s obzirom na središnju poziciju C = (Ck, Ci, Cz) bilbord u svetskom prostoru i
položaj kamere E = (Ek, Ei, Ez) u svetskom prostoru, imamo dovoljno informacija
opisati lokalni koordinatni sistem bilborda u odnosu na svetski prostor:
S obzirom na lokalni koordinatni sistem bilborda u odnosu na svetski prostor, i
svetska veličina bilborda, kvadratne vertikale bilborda:
v[0] = float4(gin[0].CenterW + halfWidth*right -
halfHeight*up, 1.0f);
v[1] = float4(gin[0].CenterW + halfWidth*right +
halfHeight*up, 1.0f);
v[2] = float4(gin[0].CenterW - halfWidth*right -
halfHeight*up, 1.0f);
v[3] = float4(gin[0].CenterW - halfWidth*right +
halfHeight*up, 1.0f);
računati za svaki bilbord.
Za ovu demo, napravićemo listu tačaka primitive (D3D12_PRIMITIVE_TOPOLOGI_TIPE_POINT za
PrimitiveTopologiTipe PSO I D3D_PRIMITIVE_TOPOLOGI_POINTLIST kao argument za
ID3D12GraphicsCommandList :: IASetPrimitiveTopologi) koji ležaju blago iznad kopnene mase. Ove tačke
predstavljaju centre bilborda koje želimo izvući. In geometrijski shader, proširićemo ove tačke na
kvadratiće bilborda. Pored toga, mi ćemo izračunati svetsku matricu bilborda u geometrijskom shaderu.
Slika 12.5 prikazuje a screenshot demo.
Kao što se vidi na slici 12.5, ovaj uzorak sagrađuje demo "Blend" iz poglavlja 10. Uobičajena
implementacija bilborda CPU-a bi bila upotreba četiri vertikala po bilbord u dinamičkom baferu buke (tj.,
upload upload heap). Zatim svaki put fotoaparat se pomerio, vertices bi se ažurirali na CPU-u i
memcpied to GPU bafer tako da se bilbordi suočavaju sa kamerom. Ovaj pristup mora pošaljite četiri
vertikala po bilbordu u IA fazu i zahteva ažuriranje dinamički vertikalni baferi, koji imaju iznad glave. Sa
geometrijskim shaderom pristup, možemo koristiti statičke bafere verteka, jer geometrijski shader to
radi ekspanziju bilborda i da se bilbordi suočavaju sa kamerom. Štaviše, Memorijski otisak bilborda je
prilično mali, jer moramo samo podneti vertek po bilbordu do IA faze.
12.2.2 Struktura verteka
Koristimo sledeću strukturu vertikala za naše tačke bilborda:
use the following vertex structure for our billboard points:
struct TreeSpriteVertex
{
XMFLOAT3 Pos;
XMFLOAT2 Size;
};
mTreeSpriteInputLayout =
{
{ “POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ “SIZE”, 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
Vertek čuva tačku koja predstavlja središnju poziciju bilborda u svetu prostor. Takođe uključuje i član
veličine, koji čuva širinu / visinu bilborda (skalirane prema svetskim svemirskim jedinicama); ovo je tako
da geometrijski shader zna koliko je veliki bilbord treba da bude nakon ekspanzije (Slika 12.6). Ako se
veličina razlikuje po verteksu, lako možemo dozvoliti bilborde različitih veličina.
Slika 12.6. Proširenje tačke u četvoro.
Osim teksturnih nizova (§12.2.4), drugi C ++ kod na demo "Tree Billboard" trebalo bi da bude rutinski
Direct3D kod do sada (stvaranje bafera verteka, efekata, pozivajući se na izvlačenje metode, itd.). Tako
ćemo sada skrenuti pažnju na datoteku TreeSprite.hlsl.
12.2.3 HLSL fajl
Pošto je ovo naš prvi demo sa geometrijskim shaderom, pokazaćemo celu HLSL datoteku
ovde, tako da možete videti kako se uklapa zajedno sa verteksima i pikselnim shaderima. Ovaj efekat
takođe uvodi neke nove objekte za koje još nismo razgovarali (SV_PrimitiveID
i Tekture2DArrai); o ovim stavkama će se razmatrati sledeće. Za sada, uglavnom se fokusirajte
program geometrijskog shadera GS; ovaj shader proširuje tačku u kvadrata u skladu sa
svetska i-osa koja se suočava sa kamerom, kako je opisano u §12.2.1.
//****************************************************************************
// TreeSprite.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”
Texture2DArray gTreeMapArray : register(t0);
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;
};
// 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 cbPerPassPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
float4 gFogColor;
float gFogStart;
float gFogRange;
float2 cbPerPassPad2;
// 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];
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
struct VertexIn
{
float3 PosW : POSITION;
float2 SizeW : SIZE;
};
struct VertexOut
{
float3 CenterW : POSITION;
float2 SizeW : SIZE;
};
struct GeoOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
float2 TexC : TEXCOORD;
uint PrimID : SV_PrimitiveID;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Just pass data over to geometry shader.
vout.CenterW = vin.PosW;
vout.SizeW = vin.SizeW;
return vout;
}
// We expand each point into a quad (4 vertices), so
the maximum number of vertices
// we output per geometry shader invocation is 4.
[maxvertexcount(4)]
void GS(point VertexOut gin[1],
uint primID : SV_PrimitiveID,
inout TriangleStream<GeoOut> triStream)
{
//
// Compute the local coordinate system of the sprite
relative to the world
// space such that the billboard is aligned with the
y-axis and faces the eye.
//
float3 up = float3(0.0f, 1.0f, 0.0f);
float3 look = gEyePosW - gin[0].CenterW;
look.y = 0.0f; // y-axis aligned, so project to xzplane
look = normalize(look);
float3 right = cross(up, look);
//
// Compute triangle strip vertices (quad) in world
space.
//
float halfWidth = 0.5f*gin[0].SizeW.x;
float halfHeight = 0.5f*gin[0].SizeW.y;
float4 v[4];
v[0] = float4(gin[0].CenterW + halfWidth*right -
halfHeight*up, 1.0f);
v[1] = float4(gin[0].CenterW + halfWidth*right +
halfHeight*up, 1.0f);
v[2] = float4(gin[0].CenterW - halfWidth*right -
halfHeight*up, 1.0f);
v[3] = float4(gin[0].CenterW - halfWidth*right +
halfHeight*up, 1.0f);
//
// Transform quad vertices to world space and
output
// them as a triangle strip.
//
float2 texC[4] =
{
float2(0.0f, 1.0f),
float2(0.0f, 0.0f),
float2(1.0f, 1.0f),
float2(1.0f, 0.0f)
};
GeoOut gout;
[unroll]
for(int i = 0; i < 4; ++i)
{
gout.PosH = mul(v[i], gViewProj);
gout.PosW = v[i].xyz;
gout.NormalW = look;
gout.TexC = texC[i];
gout.PrimID = primID;
triStream.Append(gout);
}
}
float4 PS(GeoOut pin) : SV_Target
{
float3 uvw = float3(pin.TexC, pin.PrimID%3);
float4 diffuseAlbedo = gTreeMapArray.Sample(
gsamAnisotropicWrap, uvw) * gDiffuseAlbedo;
#ifdef ALPHA_TEST
// Discard pixel if texture alpha < 0.1. We do this
test as soon
// as possible in the shader so that we can
potentially exit the
// shader early, thereby skipping the rest of the
shader code.
clip(diffuseAlbedo.a - 0.1f);
#endif
// Interpolating normal can unnormalize it, so
renormalize it.
pin.NormalW = normalize(pin.NormalW);
// Vector from point being lit to eye.
float3 toEyeW = gEyePosW - pin.PosW;
float distToEye = length(toEyeW);
toEyeW /= distToEye; // normalize
// Light terms.
float4 ambient = gAmbientLight*diffuseAlbedo;
const float shininess = 1.0f - gRoughness;
Material mat = { diffuseAlbedo, gFresnelR0,
shininess };
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat,
pin.PosW,
pin.NormalW, toEyeW, shadowFactor);
float4 litColor = ambient + directLight;
#ifdef FOG
float fogAmount = saturate((distToEye - gFogStart) /
gFogRange);
litColor = lerp(litColor, gFogColor, fogAmount);
#endif
// Common convention to take alpha from diffuse
albedo.
litColor.a = diffuseAlbedo.a;
return litColor;
}

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];

}

U ovom odeljku objašnjavamo kako da implementiramo algoritam zamućenja na računaru.


Počinjemo opisujući matematičku teoriju zamućenja. Onda ćemo razgovarati o tome
tehnika tehnike rendering-do-teksture, koju naša demo koristi za generisanje izvora
slika za zamućenje. Na kraju, pregledavamo kod za implementaciju izračunavanja shadera i
raspravljajte kako da se nosite sa određenim detaljima koji čine primenu malo komplikovano.
Slika 13.5. Da zamućimo piksel Pij izračunamo ponderisani prosjek m × n
matrica piksela centrirana oko piksela. U ovom primeru, matrica je kvadrat 3 × 3
matrica, sa radijusom zamućenja a = b = 1. Obratite pažnju da se središnja težina v00 poravna sa
pikel Pij.
13.7.1 Teorija zamagljivanja
Algoritam zamućenja koji koristimo opisan je kako slijedi: Za svaki pikel Pij. u izvoru
image, izračunajte ponderisani prosek m × n matrice piksela usredsređenih na
pikel Pij (vidi sliku 13.5); ovaj ponderisani prosek postaje ijth piksel u zamućenom
slika. Matematički,

gde je m = 2a + 1 i n = 2b + 1. Usmeravajući m i n biti čudni, mi obezbedimo da m ×


n matrica uvek ima prirodan "centar". Zovemo vertikalni poluprečnik zamućenja i b
horizontalan radijus zamućenja. Ako je a = b, onda se samo odnosi na radijus zamućenja bez potrebe
navedite dimenziju. M × n matrica težina naziva se zamućenjem kernela. Posmatrajte takođe
da tegovi moraju da se svode na 1. Ako je zbir težina manji od jednog, zamućen
slika će se pojaviti tamnije jer je boja uklonjena. Ako je zbir težina veći
od jednog, zamućena slika će se pojaviti svetlije s obzirom da je dodata boja.
Postoje različiti načini izračunavanja težine sve dok se slažu 1. Poznati
Operator zamućenja pronađen u mnogim programima za uređivanje slika je Gaussian blur, što je
dobija svoje težine iz funkcije Gaussa
. Grafikon ove funkcije prikazan je na slici 13.6 za različite s.
Za Gausovu zamućenost je poznato da se može odvojiti, što znači da se može razdvojiti na dva
1D zamućuje kako slijedi.
1. Zamućite ulaznu sliku koristeći 1D horizontalno zamućenje: IH = BlurH (I).
2. Zamućite izlaz iz prethodnog koraka koristeći 1D vertikalno zamućenje: Blur (I) = BlurV (IH
).
Napisano kraće, imamo:
Pretpostavimo da je jezgro zamućenja matrica 9 × 9, tako da nam je potrebno ukupno 81 uzorak
da napravite 2D zamućenje. Ako odvojimo zamućenje na dva 1D bljeska, potrebno je samo 9 + 9 = 18
Uzorci. Tipično, mi ćemo zamagliti teksture; kako je pomenuto u ovom poglavlju, preuzimanje
uzorci teksture su skupi, tako da smanjenje uzoraka tekstura odvajanjem zamućenja je a
dobrodošlo poboljšanje. Čak i ako zamućenje nije odvojeno (neki zamućivači nisu), mi
često mogu učiniti pojednostavljenje i pretpostaviti da je to učinjeno, dokle god
konačni snimak izgleda dovoljno precizan.
13.7.2 Render-to-Tekture
Do sada u našim programima, mi smo bili na baferu. Ali šta je to
Back buffer? Ako preispitamo naš D3DApp kod, vidimo da je bafer za leđa samo tekstura
u svap lancu:
Microsoft::WRL::ComPtr<ID3D12Resource>
mSwapChainBuffer[SwapChainBufferCount];
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap-
>GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
ThrowIfFailed(mSwapChain->GetBuffer(i,
IID_PPV_ARGS(&mSwapChainBuffer[i])));
md3dDevice->CreateRenderTargetView(
mSwapChainBuffer[i].Get(), nullptr,
rtvHeapHandle);
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
We instruct Direct3D to render to the back buffer by binding a render target view of
the back buffer to the OM stage of the rendering pipeline:
// Specify the buffers we are going to render to.
mCommandList->OMSetRenderTargets(1,
&CurrentBackBufferView(),
true, &DepthStencilView());
Ako razmislimo o ovom kodu, ne postoji ništa što nas sprečava da stvaramo drugu teksturu,
kreira prikaz ciljne tačke za prikazivanje i povezuje ga sa fazom OM ploče za rendering.
Stoga cemo crtati na ovu drugu teksturu "van ekrana" (moguce je sa drugom
kamera) umesto bafera za leđa. Ova tehnika je poznata kao tekstura na ekranu
ili jednostavno napraviti teksturu. Jedina razlika je u tome što ova tekstura nije leđa
bafera, tokom prezentacije se ne prikazuje na ekranu.
Slika 13.7. Kamera je smeštena iznad igrača od ptičije perspektive i
snima scenu u teksturu van ekrana. Kada nacrtamo scenu iz
plaier-ovi na zadnje pufer, mapirajemo teksturu na četvoro u donjem desnom uglu
ugao ekrana za prikaz radarske mape.
Shodno tome, render-to-tekture može izgledati bezvredno na početku jer nije dostupan
predstavljen na ekranu. Ali, nakon što smo izrađeni u teksturu, možemo se povezati sa leđima
bafera nazad u fazu OM, i nastaviti geometriju crtanja u zadnje pufer. Mi Možemo
tekstura geometrija sa teksturom koju smo generirali tokom perioda render-do-teksture.
Ova strategija se koristi za implementaciju različitih specijalnih efekata. Na primer, možete
prikaže scenu iz ptičje perspektive na teksturu. Zatim, kada crtate na
Back buffer, možete da nacrtate četverost u donjem desnom uglu ekrana sa ptičijim očima
pogledati teksturu da simulira radarski sistem (vidi sliku 13.7). Drugi rendering-u-teksturu
tehnike uključuju:
1. Mapiranje senki
2. Okruženje ambientalnog ekrana na ekranu
3. Dinamička refleksija sa mapama kocke
Koristeći render-to-tekture, primena algoritma za zamućenje na GPU-u bi funkcionisala
na sledeći način: učinite našu normalnu demo scenu teksturu van ekrana. Ova tekstura
će biti ulaz u naš algoritam zamućenja koji se izvršava na računaru shadera. Posle
tekstura je zamućena, crtaćemo četvrti ekran u zadnje pufer sa zamućenim
tekstura se primenjuje tako da možemo videti zamućeni rezultat kako bismo testirali našu zamućenost.
The
koraci su opisani na sledeći način:
1. Crtajte scenu kao i obično na tekstu van ekrana.
2. Zamućite teksturu na ekranu pomoću programa za izračunavanje shadera.
3. Vratite bafer sa zadnje strane kao meta za prikazivanje i nacrtajte kvadratić sa ekranom u potpunosti
primenjena zamućena tekstura.
Korišćenjem render-to-tekture za primenu zamućenja radi u redu, i da li je potreban pristup ako
želimo da scenu podesite drugačijoj teksturiranoj strukturi od zadnjeg pufera. Međutim, ako
pretpostavljamo da naše teksture na ekranu odgovaraju formatu i veličini našeg leđa
bafer, umesto da preusmeravamo prikazivanje na našu teksturu koja nije na ekranu, možemo se vratiti
nazad
buffer kao i obično, a onda uradite CopiResource da biste kopirali sadržaj sadržaja nazad u našu
tekstura van ekrana. Zatim možemo da uradimo računarski rad na našim tekstovima na ekranu, a zatim
nacrtati kuad na ekranu sa punim ekranom na zadnjem puferu sa zamućenom teksturom kako bi
proizveo finale
ekran.
// Kopirajte ulaz (nazad-bafer u ovom primeru) u
BlurMap0.
cmdList-> CopiResource (mBlurMap0.Get (), ulaz);
Ovo je tehnika koju ćemo koristiti za implementaciju naše zamućene demo, ali vježba 6 pita
da implementirate drugi filter koristeći rendering-do-teksturu.
Gore navedeni proces zahteva od nas da crtamo sa uobičajenim linijama za rendering,
pređite na kompjuterski shader i izračunajte rad, i konačno vratite se na
uobičajeni rendering pipeline. U principu, pokušajte da izbegnete prelazak i odlazak
između renderovanja i izrade računskog rada, jer postoji opterećenje zbog a
kontekstni prekidač [NVIDIA10]. Za svaki okvir pokušajte da izvršite sve računarske radove, i
onda uradite sve što radite. Ponekad je nemoguće; na primer, u
proces koji je gore opisan, potrebno je učiniti scenu teksturiranom, zamućiti je
izračunati shader, a zatim prikazati zamućene rezultate. Međutim, pokušajte
minimizirajte broj prekidača.
13.7.3 Pregled primene zamućenja
Pretpostavljamo da je zamućenje odvojivo, tako da razbijemo zamućenje na računanje dva
1D zamagljuje - horizontalni i vertikalni. Za implementaciju ovo zahtijeva dvije teksture
bafera gde možemo da čitamo i pišemo oboje; stoga nam treba oba SRV i UAV
teksture. Nazovimo jednu od tekstura A i drugu teksturu B. Algoritam zamućenja
nastavi na sledeći način:
1. Vezati SRV na A kao ulaz u izračunavajući shader (ovo je ulazna slika koja će
biti horizontalno zamućen).
2. Vezati UAV na B kao izlaz u izračunavajući shader (ovo je izlazna slika koja
čuće zamućeni rezultat).
3. Otpustite grupe navoja da izvršite horizontalno zamućenje. Posle ovoga,
tekstura B čuva horizontalni zamućeni rezultat BlurH (I), gdje je slika za zamućenje.
4. Vezati SRV na B kao ulaz u izračunavajući shader (ovo je horizontalno zamagljeno
slika koja će zatim biti vertikalno zamagljena).
5. Vezati UAV na A kao izlaz u izračunavajući shader (ovo je izlazna slika koju
čuvaće poslednji zamućeni rezultat).
6. Otpustite grupe navoja da izvršite operaciju vertikalnog zamućenja. Posle ovoga, tekstura
A čuva poslednji zamućeni rezultat Blur (I), gdje je slika za zamućenje.
Ova logika implementira formulu Blur (I) = BlurV (BlurH (I)). Posmatrajte
da obe teksture A i tekstura B služe kao ulaz i izlaz u izračunavajući shader na
neka tačka, ali ne istovremeno. (To je Direct3D greška za povezivanje resursa kao inputa i
izlaz istovremeno.) Kombinovani horizontalni i vertikalni prelazi zamućenja čine jedan
potpuna zamućenja blata. Dobivena slika može biti zamagljena dalje obavljanjem drugog zamućenja
Prolazite. Možemo više puta zamutiti sliku dok se slika ne zamuti do željenog nivoa.
Tekstura koju prikazujemo na scenu ima istu rezoluciju kao oblast prozora klijenta.
Zbog toga, moramo ponovo izgraditi teksturu van ekrana, kao i drugi buffer za teksturu B
koristi se u algoritmu zamućenja. Mi to radimo na metodu OnResize:

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).

14.2.2 Hull Shader kontrolne tačke


Shader kontrolne tačke ulazi u više kontrolnih tačaka i izlazi broj
kontrolnih tačaka. Shader trupa kontrolne tačke se poziva jednom po izlazu kontrolne tačke.
Jedna primena šatora za trup je promena površinskih reprezentacija, recimo iz
obični trougao (dostavljen na gasovod sa tri kontrolne tačke) do kubnog Bezier-a
triangle patch (patch sa deset kontrolnih tačaka). Na primer, pretpostavimo da je vaša mreža
modelirani po uobičajenom trouglu (tri kontrolne tačke); možete da koristite shader za trup
povećati trougao u višim redosledom kubnog Bezier trouglica sa 10 kontrolnih tačaka,
onda se detalje mogu dodati dodatnim kontrolnim tačkama i trikotom
osetljiv na željenu količinu. Ova strategija je takozvana šema N-patches ili PN
shema trouglova [Vlachos01]; pogodno je zato što koristi tezelaciju za poboljšanje
postojeće mrežaste triangle bez modifikacije art plinovoda. Za našu prvu demo, hoće
biti jednostavni prolazni šater, gde jednostavno prolazimo kontrolnom tačkom kroz neizmenjene.
struct HullOut
{
float3 PosL : POSITION;
};
[domain(“quad”)]
[partitioning(“integer”)]
[outputtopology(“triangle_cw”)]
[outputcontrolpoints(4)]
[patchconstantfunc(“ConstantHS”)]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p,
uint i : SV_OutputControlPointID,
uint patchId : SV_PrimitiveID)
{
HullOut hout;
hout.PosL = p[i].PosL;
return hout;
}
Shader trupa ulazi sve kontrolne tačke patch preko InputPatch-a
parametar. Vrednost sistema SV_OutputControlPointID daje identifikaciju indeksa
kontrolna tačka izlaza na čijoj liniji radi. Imajte na umu da ulazna kontrola
broj tačaka ne treba da odgovara broju kontrolnih tačaka izlaza; na primer, ulaz
patch može imati 4 kontrolne tačke i izlazni patch može imati šesnaest kontrolnih tačaka;
dodatne kontrolne tačke mogu biti izvedene iz četiri kontrolne tačke ulazne jedinice.
Šader kontrolnog punkta ukazuje na niz atributa:
1. domen: Tip patchesa. Valjani argumenti su tri, kuad ili isoline.
2. particija: Specificira režim subdivisiona tessellation-a.
1. cijeli broj: Nove vertikale se dodaju / uklanjaju samo u celom faktoru tezelacije
vrednosti. Frakcijski deo faktora tezelacije je zanemaren. Ovo stvara a
primetno "popping" kada je mrežna promena nivo tessellation.
2. Djelomična tezelacija (frakcijski_event / fractional_odd): Novo
vertikali se dodaju / uklanjaju u celokupnim vrednostima faktora tezelacije, ali "slajd" u
postepeno baziran na frakcionom delu faktora tezelacije. Ovo je korisno
kada želite da glatko prelazite sa grube verzije mreže na a
finiju verziju kroz tezelaciju, a ne iznenada u celom koraku. The
razlika između celog i frakcionog tezeliranja najbolje razumije
animacije, tako da će vam vežbe na kraju ovog poglavlja eksperimentisati
vidi razliku od prve ruke.
3. outputtopologija: redosled navijanja trouglova nastalih podjelom.
1. triangle_cv: redosled poravnanja u smeru kazaljke na satu.
2. triangle_ccv: redosled navođenja u suprotnom smeru.
3. linija: za linijsku tezelaciju.
4. outputcontrolpoints: broj puta koji izvršava šater za trup,
izlazak jedne kontrolne tačke svaki put. Sistemska vrednost
SV_OutputControlPointID daje indeks koji identifikuje kontrolnu tačku izlaza
radnik na trupu radi.
5. patchconstantfunc: string koji određuje ime konstantnog shadera trupa.
6. maktessfactor: nagoveštaj vozaču koji određuje maksimalni faktor tezelacije
tvoj shader koristi. Ovo može potencijalno omogućiti optimizaciju pomoću hardvera ako je to
poznaje ovu gornju granicu, jer će znati koliko su resursa potrebna za
tessellation. Maksimalni tessellation faktor koji podržava hardver Direct3D 11 je
64.
14.3 FAZA TESSELATION
Kao programeri, mi nemamo kontrolu faze tezelacije. Ova faza je sve
koji se vrši pomoću hardvera, i tessellates patches na osnovu izlaza faktora tessellation
iz konstantnog programskog programa shadera. Sledeće slike ilustruju različite
podjele na osnovu faktora tezelacije.
14.3.1 Kuad Patch Tessellation Primjeri
Slika 14.2. Kvadne podjele bazirane na faktorima ivice i unutrašnjosti.
14.3.2 Primeri tezelacije triangle patcha
Slika 14.3. Triangle podređaji bazirani na ivici i unutrašnjoj tezelaciji
Faktori.
14.4 DOMENSKI SHADER
Faza tessellation izlazi sve naše novoosnovane vertikale i trouglove. The
domen shader se poziva za svaku vertek stvorenu tezelacijom. Sa
tessellation enabled, a vertek shader deluje kao vertek shader za svaku kontrolu
tačka, shader trupa je u suštini verteksni shader za tessellated patch. Naročito,
ovde se projektuje vertikala tezeljenog zakrpa u homogeni prostor za klipove.
Za kuad patch, shader domena ulazi na faktore tezelacije (i bilo koje druge per
informacije o dopunama koje izlazite iz konstantnog shadera trupa), parametar (u, v)
koordinate tessellated pozicija verteksa, i sve izlazeće točke kontrole
kontrolni tački šaltera. Imajte na umu da shader domena ne daje stvarne podatke
pozicije teksiranog verteksa; Umjesto toga daje vam parametarske (u, v) koordinate (slika
14.4) ovih tačaka u prostoru patch domena. Na vama je da koristite ove parametre
koordinate i kontrolne tačke za izvođenje stvarnih pozicije 3D verteka; u kodu
dole, to radimo putem bilinearne interpolacije (koja funkcioniše kao teksturno linearno filtriranje).
struct DomainOut
{
float4 PosH : SV_POSITION;
};
// The domain shader is called for every vertex
created by the tessellator.
// It is like the vertex shader after tessellation.
[domain(“quad”)]
DomainOut DS(PatchTess patchTess,
float2 uv : SV_DomainLocation,
const OutputPatch<HullOut, 4> quad)
{
DomainOut dout;
// Bilinear interpolation.
float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x);
float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x);
float3 p = lerp(v1, v2, uv.y);
float4 posW = mul(float4(p, 1.0f), gWorld);
dout.PosH = mul(posW, gViewProj);
return dout;
}
Shader domena za triangle patch je sličan, osim da umesto parametričkog (u, v) vrijednosti koje se
unose, float3 barecentričke (u, v, v) koordinate vrha su ulaz (vidi §C.3) za objašnjenje barecentričkih
koordinata. Razlog za izlazak barecentričke koordinate za triugle zakrpe je vjerovatno posledica činjenice
da Bezier trougao zakrpe se definišu u smislu barecentričkih koordinata.
14.5 TESELJIVANJE KVADA
Za našu demo u ovom poglavlju, podnosimo kuad patch za rendering cevovoda, tezelirajte ga na osnovu
udaljenosti od kamere i zamenite generisanu vertikala matematičkom funkcijom koja je slična onoj koju
smo koristili za ovo "Brda" u našim ranijim demosima. Naš vertikalni bafer koji čuva četiri kontrolne
tačke kreiran je tako:
void BasicTessellationApp::BuildQuadPatchGeometry()
{
std::array<XMFLOAT3,4> vertices =
{
XMFLOAT3(-10.0f, 0.0f, +10.0f),
XMFLOAT3(+10.0f, 0.0f, +10.0f),
XMFLOAT3(-10.0f, 0.0f, -10.0f),
XMFLOAT3(+10.0f, 0.0f, -10.0f)
};
std::array<std::int16_t, 4> indices = { 0, 1, 2, 3
};
const UINT vbByteSize = (UINT)vertices.size() *
sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() *
sizeof(std::uint16_t);
auto geo = std::make_unique<MeshGeometry>();
geo->Name = “quadpatchGeo”;
ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo-
>VertexBufferCPU));
CopyMemory(geo->VertexBufferCPU->GetBufferPointer(),
vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo-
>IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(),
indices.data(), ibByteSize);
geo->VertexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(),
vbByteSize, geo->VertexBufferUploader);
geo->IndexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(),
ibByteSize, geo->IndexBufferUploader);
geo->VertexByteStride = sizeof(XMFLOAT3);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;
SubmeshGeometry quadSubmesh;
quadSubmesh.IndexCount = 4;
quadSubmesh.StartIndexLocation = 0;
quadSubmesh.BaseVertexLocation = 0;
geo->DrawArgs[“quadpatch”] = quadSubmesh;
mGeometries[geo->Name] = std::move(geo);
}
Our render-item for the quad patch is created as follows:
void BasicTessellationApp::BuildRenderItems()
{
auto quadPatchRitem = std::make_unique<RenderItem>
();
quadPatchRitem->World = MathHelper::Identity4x4();
quadPatchRitem->TexTransform =
MathHelper::Identity4x4();
quadPatchRitem->ObjCBIndex = 0;
quadPatchRitem->Mat = mMaterials[“whiteMat”].get();
quadPatchRitem->Geo =
mGeometries[“quadpatchGeo”].get();
quadPatchRitem->PrimitiveType =
D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST;
quadPatchRitem->IndexCount = quadPatchRitem->Geo-
>DrawArgs[“quadpatch”].IndexCount;
quadPatchRitem->StartIndexLocation =
quadPatchRitem->Geo-
>DrawArgs[“quadpatch”].StartIndexLocation;
quadPatchRitem->BaseVertexLocation =
quadPatchRitem->Geo-
>DrawArgs[“quadpatch”].BaseVertexLocation;
mRitemLayer[(int)RenderLayer::Opaque].push_back(quadPatchRitem.mAllRitems.push_back(std::move(
quadPatchRitem));
}
Sada ćemo skrenuti pažnju na šatre trupa. Shader trupa je sličan onome što mi pokazali su u § 14.2.1 i
§14.2.2, osim što sada određujemo faktore za vezivanje na osnovu udaljenosti od oka. Ideja koja stoji iza
toga je da se na daljinu koristi nisko-poli mreža i poveća tezelacija (a time i broj trougla), dok se mreža
približava očima (vidi Sliku 14.5). Shader trupa je jednostavno prolazni šater.
struct VertexIn
{
float3 PosL : POSITION;
};
struct VertexOut
{
float3 PosL : POSITION;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
vout.PosL = vin.PosL;
return vout;
}
struct PatchTess
{
float EdgeTess[4] : SV_TessFactor;
float InsideTess[2] : SV_InsideTessFactor;
};
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch,
uint patchID : SV_PrimitiveID)
{
PatchTess pt;
float3 centerL = 0.25f*(patch[0].PosL +
patch[1].PosL +
patch[2].PosL +
patch[3].PosL);
float3 centerW = mul(float4(centerL, 1.0f),
gWorld).xyz;
float d = distance(centerW, gEyePosW);
// Tessellate the patch based on distance from the
eye such that
// the tessellation is 0 if d >= d1 and 64 if d <=
d0. The interval
// [d0, d1] defines the range we tessellate in.
const float d0 = 20.0f;
const float d1 = 100.0f;
float tess = 64.0f*saturate( (d1-d)/(d1-d0) );
// Uniformly tessellate the patch.
pt.EdgeTess[0] = tess;
pt.EdgeTess[1] = tess;
pt.EdgeTess[2] = tess;
pt.EdgeTess[3] = tess;
pt.InsideTess[0] = tess;
pt.InsideTess[1] = tess;
return pt;
}
struct HullOut
{
float3 PosL : POSITION;
};
[domain(“quad”)]
[partitioning(“integer”)]
[outputtopology(“triangle_cw”)]
[outputcontrolpoints(4)]
[patchconstantfunc(“ConstantHS”)]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p,
uint i : SV_OutputControlPointID,
uint patchId : SV_PrimitiveID)
{
HullOut hout;
hout.PosL = p[i].PosL;
return hout;
}
Jednostavno vezivanje nije dovoljno za dodavanje detalja, jer novi trouglovi leže na
patch koji je bio podijeljen. Moramo na neki način bolje nadoknaditi te dodatne vertikale
približiti oblik objekta koji modelujemo. Ovo se radi u shaderu domena. U ovom demo, mi smo
koordinisali i-koordinate pomoću funkcije "brda" koju smo uveli u §7.7.3.
struct DomainOut
{
float4 PosH : SV_POSITION;
};
// The domain shader is called for every vertex
created by the tessellator.
// It is like the vertex shader after tessellation.
[domain(“quad”)]
DomainOut DS(PatchTess patchTess,
float2 uv : SV_DomainLocation,
const OutputPatch<HullOut, 4> quad)
{
DomainOut dout;
// Bilinear interpolation.
float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x);
float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x);
float3 p = lerp(v1, v2, uv.y);
// Displacement mapping
p.y = 0.3f*( p.z*sin(p.x) + p.x*cos(p.z) );
float4 posW = mul(float4(p, 1.0f), gWorld);
dout.PosH = mul(posW, gViewProj);
return dout;
}
float4 PS(DomainOut pin) : SV_Target
{
return float4(1.0f, 1.0f, 1.0f, 1.0f); }
14.6 KUPOVI ZA KUBIČKE BESERIJE
U ovom odeljku opisujemo kubične Bezier kvadrante kako bi pokazali kako su površine
izgrađen preko većeg broja kontrolnih tačaka. Međutim, pre nego što dođemo do površina
pomaže da se prvo započne sa krivinama Bezier.
14.6.1 Bezier Curves
Razmotrimo tri ne-linearne tačke p0, p1 i p2 koje ćemo nazvati kontrolnim tačkama.
Ove tri kontrolne tačke definišu krivu Bezier na sledeći način. Tačka p (t) na
kriva se najpre pronađe linearnim interpoliranjem između p0 i p1 na t, a p1 i p2 prema t
dobijete međupodne tačke:
Tada p (t) se nađe linearnim interpolacijom između i t:
Drugim rečima, ova konstrukcija ponovljenom interpolacijom dovodi do parametra
formula za kvadratni (stepen 2) Bezierova krivulja:
Na sličan način, četiri kontrolne tačke p0, p1, p2 i p3 definišu kubni (stepen 3)
Bezierova krivulja, a tačka p (t) na krivini se ponovo otkriva ponovljenom interpolacijom. Figura
14.6. Pokazuje situaciju. Prvo linearno interpoliraju duž svakog segmenta linije koje su date
kontrolne tačke definišu da dobiju tri intermedijarne tačke prve generacije:
Slika 14.6. Ponovljene linearne interpolacije definisane tačke na kubnom Bezieru
kriva. Na slici se koristi t = 0,5. (a) četiri kontrolne tačke i krivu koju definišu.
(b) Linearno interpolirati između kontrolnih tačaka za izračunavanje prve generacije
srednjih tačaka. (c) Linearno interpolirati između srednje generacije prve generacije
ukazuje na srednju tačku druge generacije. (d) Linearno interpolirati
između druge generacije polaznih tačaka da bi dobili tačku na krivini.
Zatim, linearno interpolirajte duž svakog linijskog segmenta ove srednje generacije prve generacije
tačke definišu da dobiju dve srednje generičke tačke druge generacije:
Na kraju, p (t) se može naći linearno interpoliranjem između ove poslednje generacije
srednji pintovi:
što pojednostavljuje na parametarsku formulu za kubičnu (stupanj 3) krivu Bezier:
Generalno, ljudi se zaustavljaju na kubnim krivinama, jer daju dovoljno glatkoću i stepenima
slobodu za kontrolu krivine, ali možete nastaviti sa krivulama višeg reda sa
isti rekurzivni obrazac ponovljene interpolacije.
Ispostavlja se da formula za Bezierove krivulje stepena n može biti napisana u smislu
Bernsteinove osnovne funkcije, koje su definisane:
Za krive stepena 3, osnovne funkcije Bernstein su:
Uporedite ove vrednosti sa faktorima u jednačini 14.1. Zbog toga možemo da napišemo kubiku
Bezier kriva kao:
Derivati kubnih Bernsteinovih osnovnih funkcija mogu se naći primenom
pravila moći i proizvoda:
A derivat kubne krivine Bezier je:
Derivati su korisni za izračunavanje tangentnog vektora duž krive.
Na internetu se nalaze aparati Bezier curve koji vam omogućavaju postavljanje i manipulaciju
kontrolne tačke vide kako se krive oblikuju interaktivno.
14.6.2 Kubične površine Bezier
Pogledajte sliku 14.7 tokom ovog odeljka. Razmislite o patchu 4 × 4 kontrolnih tačaka.
Zbog toga svaki red sadrži 4 kontrolne tačke koje se mogu koristiti za definisanje kubnog Bezier-a
kriva; Bezirjeva krivina s-ista reda daje:
Ako procijenimo svaku od ovih krivih Bezier u reci u0, onda dobijamo "kolonu" od 4
poena, po jedan uz svaku krivu. Mi možemo koristiti ove 4 tačke da definišemo još jednu krivu Bezier-a
leži na površini Bezier u u0:
Slika 14.7. Izgradnja površine Bezier. Neke su pojednostavljene
čine ga lakšim za razumevanje - kontrolne tačke ne leže u ravni, sve
ki (u) ne mora biti isti kao što predlaže figura (one bi bile jednake ako
kontrolne tačke bile su iste za svaki red da daju iste krivulje), i p (v)
obično ne bi bila ravna linija, već kubična krivina Bezier.
Sada, ako dozvolimo da se i razlikujemo, izvlačimo porodicu kubnih krivina Bezier-a koje se formiraju
kubična površina Bezier:
Parcialni derivati površine Bezier su korisni za izračunavanje tangente i normale
vektori:
14.6.3 Cubic Bezier Code for Evaluation Surface
U ovom odeljku daju se kod za procenu kubne površine Bezier. Da pomognem da razumem
kod koji sledi, proširujemo sabirnu notaciju:
Kod ispod dole se direktno navodi na formule upravo date:
14.6.4 Definisanje patching geometrije
Our vertex buffer storing the sixteen control points is created like so:
void BezierPatchApp::BuildQuadPatchGeometry()
{
std::array<XMFLOAT3,16> vertices =
{
// Row 0
XMFLOAT3(-10.0f, -10.0f, +15.0f),
XMFLOAT3(-5.0f, 0.0f, +15.0f),
XMFLOAT3(+5.0f, 0.0f, +15.0f),
XMFLOAT3(+10.0f, 0.0f, +15.0f),
// Row 1
XMFLOAT3(-15.0f, 0.0f, +5.0f),
XMFLOAT3(-5.0f, 0.0f, +5.0f),
XMFLOAT3(+5.0f, 20.0f, +5.0f),
XMFLOAT3(+15.0f, 0.0f, +5.0f),
// Row 2
XMFLOAT3(-15.0f, 0.0f, -5.0f),
XMFLOAT3(-5.0f, 0.0f, -5.0f),
XMFLOAT3(+5.0f, 0.0f, -5.0f),
XMFLOAT3(+15.0f, 0.0f, -5.0f),
// Row 3
XMFLOAT3(-10.0f, 10.0f, -15.0f),
XMFLOAT3(-5.0f, 0.0f, -15.0f),
XMFLOAT3(+5.0f, 0.0f, -15.0f),
XMFLOAT3(+25.0f, 10.0f, -15.0f)
};
std::array<std::int16_t, 16> indices =
{
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15
};
const UINT vbByteSize = (UINT)vertices.size() *
sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() *
sizeof(std::uint16_t);
auto geo = std::make_unique<MeshGeometry>();
geo->Name = “quadpatchGeo”;
ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo-
>VertexBufferCPU));
CopyMemory(geo->VertexBufferCPU->GetBufferPointer(),
vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo-
>IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(),
indices.data(), ibByteSize);
geo->VertexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize,
geo->VertexBufferUploader);
geo->IndexBufferGPU =
d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize,
geo->IndexBufferUploader);
geo->VertexByteStride = sizeof(XMFLOAT3);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;
SubmeshGeometry quadSubmesh;
quadSubmesh.IndexCount = (UINT)indices.size();
quadSubmesh.StartIndexLocation = 0;
quadSubmesh.BaseVertexLocation = 0;
geo->DrawArgs[“quadpatch”] = quadSubmesh;
mGeometries[geo->Name] = std::move(geo);
}
There is no restriction that the control points need to be equidistant to form a
uniform grid.
Our render-item for the quad patch is created as follows:
void BezierPatchApp::BuildRenderItems()
{
auto quadPatchRitem = std::make_unique<RenderItem>
();
quadPatchRitem->World = MathHelper::Identity4x4();
quadPatchRitem->TexTransform =
MathHelper::Identity4x4();
quadPatchRitem->ObjCBIndex = 0;
quadPatchRitem->Mat = mMaterials[“whiteMat”].get();
quadPatchRitem->Geo =
mGeometries[“quadpatchGeo”].get();
quadPatchRitem->PrimitiveType =
D3D11_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST;
quadPatchRitem->IndexCount = quadPatchRitem->Geo-
>DrawArgs[“quadpatch”].IndexCount;
quadPatchRitem->StartIndexLocation =
quadPatchRitem->Geo-
>DrawArgs[“quadpatch”].StartIndexLocation;
quadPatchRitem->BaseVertexLocation =
quadPatchRitem->Geo-
>DrawArgs[“quadpatch”].BaseVertexLocation;
mRitemLayer[(int)RenderLayer::Opaque].push_back(quadPatchRitem.mAllRitems.push_back(std::move(
quadPatchRitem));
Deo 3 TEME
U ovom delu, fokusirali smo se na primenu Direct3D za implementaciju nekoliko 3D aplikacija,
demonstriraju tehnike kao što su rendisanje neba, ambijentalna okluzija, animacija karaktera,
biranje, mapiranje okruženja, normalno mapiranje i mapiranje senki. Kratak
sledi opis poglavlja u ovom delu.
Poglavlje 15, Izgradnja kamere prve osobe i dinamično indeksiranje: U ovom poglavlju,
pokazujemo kako dizajnirati sistem kamere koji se ponaša više kao što biste očekivali u prvom
osobna igra. Pokazujemo kako da kontrolišemo kameru preko tastature i miša. In
Pored toga, predstavljamo novu Direct3D 12 tehniku pod nazivom dinamički indeksiranje, gde smo
može dinamički indeksirati niz teksturnih objekata u shaderu.
Poglavlje 16, Instancing i Frustum Culling: Instanciranje je podržano na hardveru
tehnika koja optimizuje crtanje iste geometrije više puta sa različitim
svojstva (recimo na različitim položajima u sceni i različitim bojama). Frustum
oduzimanje je tehnika optimizacije u kojoj odbacujemo čitav predmet od postojanja
podneti na rendering pipeline ako se nalazi potpuno izvan polja virtuelne kamere
pogled. Takođe pokazujemo kako izračunati graničnu kutiju i sferu mreže.
Poglavlje 17, Dobijanje: Ovo poglavlje pokazuje kako odrediti određeni 3D objekat (ili
3D primitiva) koju je korisnik odabrao pomoću miša. Obaranje je često nužnost u 3D
igara i aplikacija u kojima korisnik interaktuje sa 3D svetom pomoću miša.
Poglavlje 18, Mapiranje kocke: U ovom poglavlju prikazujemo kako da reflektuju okruženja
proizvoljna mreža sa mapiranjem okruženja; Pored toga, koristimo mapu za okolinu
tekstura neba sfera.
Poglavlje 19, Normalno mapiranje: Ovo poglavlje pokazuje kako dobiti detaljnije realno vreme
rezultati osvetljenja koristeći normalne mape, koje su teksture koje čuvaju normalne vektore. Ovo
daje površinske normale na finijoj granularnosti nego per-vertek normala što rezultira više
realno osvetljenje.
Poglavlje 20, Mapiranje senki: Mapiranje senki je tehnika snimanja u realnom vremenu,
koji senku proizvoljnu geometriju (nije ograničen na planarne senke). Pored toga, mi
saznajte kako funkcioniše projektivna tekstura.
Poglavlje 21, Ambient Occlusion: Osvetljenje igra važnu ulogu u stvaranju naših scena
izgledaš realistično. U ovom poglavlju, poboljšavamo ambijentalni termin naše jednačine osvetljenja
procjenjuje kako je okružena tačka na našoj sceni od dolazeće svjetlosti.
Poglavlje 22, Kvaternioni: U ovom poglavlju proučavamo matematičke objekte
kuaternions. Pokazujemo da jedinična kvateriona predstavlja rotaciju i može biti interpolirana u a
jednostavan način, time nam daje način za interpolaciju rotacija. Jednom možemo interpolirati
rotacije, možemo kreirati 3D animacije.
Poglavlje 23, animacija karaktera: Ovo poglavlje pokriva teoriju karaktera
animacije i pokazati kako animirati tipičan ljudski karakter igre sa kompleksom
animacija za hodanje.
Poglavlje 15 IZGRADNJA PRVI LIČNA KAMERA I DINAMIČKI INDEKSING
U ovom poglavlju pokrivamo dve odvojene kratke teme. Prvo, dizajniramo sistem kamera koji se ponaša
više kao što biste očekivali u igri prvog čoveka. Ovaj sistem fotoaparata će zamenite orbiti sistem
fotoaparata koji smo do sada koristili na demo. Drugo, mi predstaviti novu Direct3D fig12 tehniku pod
nazivom dinamički indeksiranje (novi model shadera 5.1), gde možemo dinamički indeksirati niz
teksturnih objekata (Tekture2D gDiffuseMap [n]). Ovo je slično onom kako smo indeksirali posebni
objekt teksture (Tekture2DArrai) u poglavlju 12, ali za razliku od Tekture2DArrai teksture u ovom
niz može biti različitih veličina i formata, čineći ga fleksibilnijem od
Tekture2DArrais.
Ciljevi:
1. Pregledati matematiku transformacije prostora za prikaz.
2. Da bi mogli identifikovati tipičnu funkcionalnost kamere prve osobe.
3. Da biste naučili kako da implementirate kameru prvog čoveka.
4. Da biste razumeli kako se dinamički indeksiraju u nizu tekstura.
15.1 PREGLED TRANSFORMACIJE VIEVA
Prostor za prikaz je koordinatni sistem povezan sa fotoaparatom kao što je prikazano na slici 15.1.
Kamera se nalazi u porijeklu gledajući niz pozitivnu z-osu, k-osa ima desnu stranu
kamere, a i-osa je 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 15.1. Sistem koordinatnih kamera. U odnosu na svoju koordinatu
sistem, aparat se nalazi u porijeklu gledajući niz pozitivnu z-osu.
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:

Kao i kod svih transformacija promena koordinata, nećemo ništa pomerati


scena. Koordinate se menjaju jer koristimo referentni prostor za fotoaparat
umesto svetskog referentnog prostora.
15.2 KAMERA KLASA
Da bismo kodirali naš kod koji se odnosi na fotoaparat, definišemo i implementiramo klasu kamere.
Podaci klase kamere čuvaju dva ključna dela informacija. Pozicija, desno, gore,
i pogledati vektore kamere koje definišu, odnosno, poreklo, k-osu, i-osu i z-ose
koordinatnog sistema pogleda u svetskim koordinatama, i svojstva sistema
frustum. Možete zamišljati objektiv fotoaparata kao definisanje frustuma (njegovog vidnog polja
i blizu i daljih aviona). Većina metoda je trivijalna (npr. Jednostavne metode pristupa).
Pogledajte komentare ispod za pregled metoda i članova podataka. Pregledamo
izabrane metode u narednom odeljku.
class Camera
{p
ublic:
Camera();
˜Camera();
// Get/Set world camera position.
DirectX::XMVECTOR GetPosition()const;
DirectX::XMFLOAT3 GetPosition3f()const;
void SetPosition(float x, float y, float z);
void SetPosition(const DirectX::XMFLOAT3& v);
// Get camera basis vectors.
DirectX::XMVECTOR GetRight()const;
DirectX::XMFLOAT3 GetRight3f()const;
DirectX::XMVECTOR GetUp()const;
DirectX::XMFLOAT3 GetUp3f()const;
DirectX::XMVECTOR GetLook()const;
DirectX::XMFLOAT3 GetLook3f()const;
// Get frustum properties.
float GetNearZ()const;
float GetFarZ()const;
float GetAspect()const;
float GetFovY()const;
float GetFovX()const;
// Get near and far plane dimensions in view space
coordinates.
float GetNearWindowWidth()const;
float GetNearWindowHeight()const;
float GetFarWindowWidth()const;
float GetFarWindowHeight()const;
// Set frustum.
void SetLens(float fovY, float aspect, float zn,
float zf);
// Define camera space via LookAt parameters.
void LookAt(DirectX::FXMVECTOR pos,
DirectX::FXMVECTOR target,
DirectX::FXMVECTOR worldUp);
void LookAt(const DirectX::XMFLOAT3& pos,
const DirectX::XMFLOAT3& target,
const DirectX::XMFLOAT3& up);
// Get View/Proj matrices.
DirectX::XMMATRIX GetView()const;
DirectX::XMMATRIX GetProj()const;
DirectX::XMFLOAT4X4 GetView4x4f()const;
DirectX::XMFLOAT4X4 GetProj4x4f()const;
// Strafe/Walk the camera a distance d.
void Strafe(float d);
void Walk(float d);
// Rotate the camera.
void Pitch(float angle);
void RotateY(float angle);
// After modifying camera position/orientation,
call to rebuild the view matrix.
void UpdateViewMatrix();
private:
// Camera coordinate system with coordinates
relative to world space.
DirectX::XMFLOAT3 mPosition = { 0.0f, 0.0f, 0.0f };
DirectX::XMFLOAT3 mRight = { 1.0f, 0.0f, 0.0f };
DirectX::XMFLOAT3 mUp = { 0.0f, 1.0f, 0.0f };
DirectX::XMFLOAT3 mLook = { 0.0f, 0.0f, 1.0f };
// Cache frustum properties.
float mNearZ = 0.0f;
float mFarZ = 0.0f;
float mAspect = 0.0f;
float mFovY = 0.0f;
float mNearWindowHeight = 0.0f;
float mFarWindowHeight = 0.0f;
bool mViewDirty = true;
// Cache View/Proj matrices.
DirectX::XMFLOAT4X4 mView =
MathHelper::Identity4x4();
DirectX::XMFLOAT4X4 mProj =
MathHelper::Identity4x4();
};
15.3 ODABRANE IMPLEMENTACIJE METODE
Mnoge od metoda klase kamere su trivijalne metode za postavljanje / postavljanje koje ćemo ovde
izostaviti.
Međutim, pregledaćemo nekoliko važnih u ovom odeljku.
15.3.1 KSMVECTOR Povratne varijacije
Prvo, želimo napomenuti da pružamo KSMVECTOR povratne varijante za mnoge od ovih
"Dobiti" metode; ovo je samo zbog pogodnosti, tako da klijentski kod ne treba pretvoriti
ako im treba KSMVECTOR:
XMVECTOR Camera::GetPosition()const
{
return XMLoadFloat3(&mPosition);
}
XMFLOAT3 Camera::GetPosition3f()const
{
return mPosition;
}
15.3.2 SetLens
Možemo smatrati frustumom kao objektivom naše kamere, jer ona kontroliše naš pogled na pogled. Mi
cache frustum osobine i izgradi matrica projekcija je SetLens metoda:
void Camera::SetLens(float fovY, float aspect, float
zn, float zf)
{
// cache properties
mFovY = fovY;
mAspect = aspect;
mNearZ = zn;
mFarZ = zf;
mNearWindowHeight = 2.0f * mNearZ * tanf( 0.5f*mFovY
);
mFarWindowHeight = 2.0f * mFarZ * tanf( 0.5f*mFovY
);
XMMATRIX P = XMMatrixPerspectiveFovLH(mFovY,
mAspect, mNearZ, mFarZ);
XMStoreFloat4x4(&mProj, P);
}
15.3.3 Izvedene informacije o Frustumu
Kao što smo upravo videli, kašnjemo vertikalno polje ugla gledanja, ali dodatno obezbeđujemo a metoda
koja izvodi horizontalni ugao gledanja. Pored toga, nudimo i metode vratiti širinu i visinu frustuma na
bližoj i dalekoj ravni, što je ponekad korisno je znati. Implementacije ovih metoda su samo
trigonometrija, i ako ste imaju problema da prate jednačine, zatim pogledajte §5.6.3:
float Camera::GetFovX()const
{
float halfWidth = 0.5f*GetNearWindowWidth();
return 2.0f*atan(halfWidth / mNearZ);
}
float Camera::GetNearWindowWidth()const
{
return mAspect * mNearWindowHeight;
}
float Camera::GetNearWindowHeight()const
{
return mNearWindowHeight;
}
float Camera::GetFarWindowWidth()const
{
return mAspect * mFarWindowHeight;
}
float Camera::GetFarWindowHeight()const
{
return mFarWindowHeight;
}
15.3.4 Transformacija kamere
Za kameru prve osobe, ignorišući otkrivanje sudara, želimo da:
1. Pomerite kameru duž vektorskog izgleda kako biste se pomerali napred i nazad. Ovo može biti
implementira se prevođenjem pozicije kamere duž njegovog vektorskog izgleda.
2. Pomerite fotoaparat duž desnog vektora da biste se uveli desno i levo. Ovo može biti
implementira se prevođenjem pozicije kamere duž njegovog desnog vektora.
3. Okrenite fotoaparat oko njegovog desnog vektora da biste pogledali gore i dole. Ovo može biti
koji se primenjuju rotiranjem videa i vektora fotoaparata oko njegovog desnog vektora
koristeći KSMMatrikRotationAkis funkciju.
4. Okrenite fotoaparat oko svetske i osi (pod pretpostavkom da i-osa odgovara
svetski "up" smer) vektora za gledanje desno i levo. Ovo se može implementirati
rotirajući sve bazne vektore oko svetske i osi koristeći
XMMatrixRotationY function.
void Camera::Walk(float d)
{
// mPosition += d*mLook
XMVECTOR s = XMVectorReplicate(d);
XMVECTOR l = XMLoadFloat3(&mLook);
XMVECTOR p = XMLoadFloat3(&mPosition);
XMStoreFloat3(&mPosition, XMVectorMultiplyAdd(s, l,
p));}
void Camera::Strafe(float d)
{
// mPosition += d*mRight
XMVECTOR s = XMVectorReplicate(d);
XMVECTOR r = XMLoadFloat3(&mRight);
XMVECTOR p = XMLoadFloat3(&mPosition);
XMStoreFloat3(&mPosition, XMVectorMultiplyAdd(s, r,
p));
}
void Camera::Pitch(float angle)
{
// Rotate up and look vector about the right vector.
XMMATRIX R =
XMMatrixRotationAxis(XMLoadFloat3(&mRight), angle);
XMStoreFloat3(&mUp, XMVector3TransformNormal(XMLoadFloat3(&R));
XMStoreFloat3(&mLook,
XMVector3TransformNormal(XMLoadFloat3(&mLook), R));
}
void Camera::RotateY(float angle)
{
// Rotate the basis vectors about the world y-axis.
XMMATRIX R = XMMatrixRotationY(angle);
XMStoreFloat3(&mRight, XMVector3TransformNormal(XMLoadFloat3(&R));
XMStoreFloat3(&mUp,
XMVector3TransformNormal(XMLoadFloat3(&mUp), R));
XMStoreFloat3(&mLook,
XMVector3TransformNormal(XMLoadFloat3(&mLook), R));
}
15.3.5 Izgradnja Matrik Viev-a
Prvi deo metode UpdateVievMatrik reorthonormalizuje kameru desno, gore i pogledajte vektore. To
znači, znači, oni su međusobno ortogonalni jedni druge i dužine jedinice. Ovo je neophodno, jer nakon
nekoliko rotacija, numerički greške se mogu akumulirati i uzrokovati da ovi vektori postanu ne-
ortonormalni. Kada ovose dešavaju, vektori više ne predstavljaju pravougaoni koordinatni sistem, već
iskrivljenikoordinatni sistem, što nije ono što želimo. Drugi deo ovog metoda se samo priključuje
vektor kamere u jednačinu 15.1 za izračunavanje matrice transformacije pogleda.
void Camera::UpdateViewMatrix()
{
if(mViewDirty)
{
XMVECTOR R = XMLoadFloat3(&mRight);
XMVECTOR U = XMLoadFloat3(&mUp);
XMVECTOR L = XMLoadFloat3(&mLook);
XMVECTOR P = XMLoadFloat3(&mPosition);
// Keep camera’s axes orthogonal to each other and
of unit length.
L = XMVector3Normalize(L);
U = XMVector3Normalize(XMVector3Cross(L, R));
// U, L already ortho-normal, so no need to
normalize cross product.
R = XMVector3Cross(U, L);
// Fill in the view matrix entries.
float x = -XMVectorGetX(XMVector3Dot(P, R));
float y = -XMVectorGetX(XMVector3Dot(P, U));
float z = -XMVectorGetX(XMVector3Dot(P, L));
XMStoreFloat3(&mRight, R);
XMStoreFloat3(&mUp, U);
XMStoreFloat3(&mLook, L);
mView(0, 0) = mRight.x;
mView(1, 0) = mRight.y;
mView(2, 0) = mRight.z;
mView(3, 0) = x;
mView(0, 1) = mUp.x;
mView(1, 1) = mUp.y;
mView(2, 1) = mUp.z;
mView(3, 1) = y;
mView(0, 2) = mLook.x;
mView(1, 2) = mLook.y;
mView(2, 2) = mLook.z;
mView(3, 2) = z;
mView(0, 3) = 0.0f;
mView(1, 3) = 0.0f;
mView(2, 3) = 0.0f;
mView(3, 3) = 1.0f;
mViewDirty = false;
}
}
15.4 KAMERA DEMO KOMENTARI
Sada možemo ukloniti sve stare varijable iz naše klase aplikacija koje su povezane
na sistem orbitalne kamere kao što su mPhi, mTheta, mRadius, mViev, i
mProj. Dodaćemo člansku varijablu:
Camera mCam;
Kada se prozor promeni veličine, mi znamo da duže ponovo izgradimo matricu projekcije eksplicitno,
i umesto toga delegirati delo u kategoriju Camera sa SetLens:
void CameraApp::OnResize()
{
D3DApp::OnResize();
mCamera.SetLens(0.25f*MathHelper::Pi, AspectRatio(),
1.0f, 1000.0f);
}
In the UpdateScene method, we handle keyboard input to move the camera:
void CameraApp::UpdateScene(float dt)
{
if( GetAsyncKeyState(‘W’) & 0x8000 )
mCamera.Walk(10.0f*dt);
if( GetAsyncKeyState(‘S’) & 0x8000 )
mCamera.Walk(-10.0f*dt);
if( GetAsyncKeyState(‘A’) & 0x8000 )
mCamera.Strafe(-10.0f*dt);
if( GetAsyncKeyState(‘D’) & 0x8000 )
mCamera.Strafe(10.0f*dt);
In the OnMouseMove method, we rotate the camera’s look direction:
void CameraAndDynamicIndexingApp::OnMouseMove(WPARAM
btnState, int x, int y)
{
if( (btnState & MK_LBUTTON) != 0 )
{
// Make each pixel correspond to a quarter of a
degree.
float dx = XMConvertToRadians(
0.25f*static_cast<float>(x - mLastMousePos.x));
float dy = XMConvertToRadians(
0.25f*static_cast<float>(y - mLastMousePos.y));
mCamera.Pitch(dy);
mCamera.RotateY(dx);
}
mLastMousePos.x = x;
mLastMousePos.y = y;
}
Na kraju, za rendering, pogledu i matrici projekcije se mogu pristupiti iz instanca kamere:
mCamera.UpdateViewMatrix();
XMMATRIX view = mCamera.View();
XMMATRIX proj = mCamera.Proj();
15.5 DINAMIČKA INDEKSIJA
Ideja dinamičkog indeksiranja je relativno jednostavna. Mi dinamički indeksiramo
niz resursa u programu shader-a; u ovom demo, resursi će biti niz
teksture. Indeks se može navesti na različite načine:
1. Indeks može biti element u konstantnom puferu.
2. Indeks može biti sistemski ID kao SV_PrimitiveID, SV_VertekID,
SV_DispatchThreadID ili SV_InstanceID.
3. Indeks može biti rezultat izračunavanja.
4. Indeks može doći iz teksture.
5. Indeks može doći iz komponente vertikalne strukture.
Sledeća sintaksa shadera deklariše niz tekstura od 4 elementa i pokazuje kako mi
može indeksirati u niz teksture gde indeks dolazi iz konstantnog bafera:
cbuffer cbPerDrawIndex : register(b0)
{
int gDiffuseTexIndex;
};
Texture2D gDiffuseMap[4] : register(t0);
float4 texValue =
gDiffuseMap[gDiffuseTexIndex].Sample(
gsamLinearWrap, pin.TexC);
Za ovu demo, naš cilj je sledeći: želimo da minimiziramo broj
deskriptori koje smo postavili na osnovu pojedinih stavki. Sada postavljamo objekat konstantnom
baferu,
materijalnog konstantnog pufera i mape difuzne teksture SRV na osnovu po stavci po renderu.
Minimiziranje broja deskriptora koje moramo postaviti učinit će naš korijen potpis manji,
što znači manje režijske troškove; Štaviše, ova tehnika dinamičkog indeksiranja
će se pokazati naročito korisnim za instanciranje (tema sledećeg poglavlja). Naša strategija je
kao što sledi:
1. Kreirajte strukturirani bafer koji čuva sve podatke o materijalu. To je, umjesto
čuvanje naših materijalnih podataka u konstantnim baferima, mi ćemo ga uskladištiti u strukturiranom
puferu. A
strukturirani bafer može biti indeksiran u programu shadera. Ovaj strukturirani bafer će biti
vezana za cevovod za rendering jednom po okvirima, čime su svi materijali vidljivi
shader programe.
2. Dodajte polje MaterialIndek u naš objekat konstantni bafer da navedete indeks
materijal koji se koristi za ovaj poziv za poziv. U našim programima shadera, ovo koristimo za
indeksiranje
u materijalno strukturirani bafer.
3. Vezati sve teksturne SRV deskriptore koji se koriste u sceni jednom po kadru, umjesto
vezivanje jedne teksture SRV po render-item.
4. Dodajte polje DiffuseMapIndek materijalnim podacima koji određuju mapu teksture
povezane sa materijalom. Koristimo ovo da indeksiramo niz tekstura koje smo vezali
na gasovod u prethodnom koraku.
Sa ovim podešavanjem, potrebno je samo postaviti konstantni bafer za svaki objekat za svaku stavku
renderera.
Jednom kada to imamo, koristimo MaterialIndek za preuzimanje materijala koji se koristi za izvlačenje
poziv, i od toga koristimo DiffuseMapIndek da preuzmemo teksturu koju treba koristiti za
nacrtati poziv.
Setimo se da je strukturirani bafer samo niz podataka nekog tipa koji mogu
žive u GPU memoriji i pristupaju programima shader-a. Zato što nam još uvek treba
Da bi mogli da ažuriramo materijale na letu, koristimo bafer za otpremu umesto a
podrazumevani bafer. Materijalno strukturirani bafer zamenjuje našu konstantu materijala
bafer u okviru kadrovskih klasa i kreiran je tako:
struct MaterialData
{
DirectX::XMFLOAT4 DiffuseAlbedo = { 1.0f, 1.0f,
1.0f, 1.0f };
DirectX::XMFLOAT3 FresnelR0 = { 0.01f, 0.01f, 0.01f
};
float Roughness = 64.0f;
// Used in texture mapping.
DirectX::XMFLOAT4X4 MatTransform =
MathHelper::Identity4x4();
UINT DiffuseMapIndex = 0;
UINT MaterialPad0;
UINT MaterialPad1;
UINT MaterialPad2;
};
MaterialBuffer =
std::make_unique<UploadBuffer<MaterialData>>(
device, materialCount, false);
Other than that, the code for the material structured buffer is not much different
from the code with the material constant buffer.
We update the root signature based on the new data the shader expects as input:
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 4, 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].InitAsConstantBufferView(0);
slotRootParameter[1].InitAsConstantBufferView(1);
slotRootParameter[2].InitAsShaderResourceView(0, 1);
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);
Now, before we draw any render-items, we can bind all of our materials and texture
SRVs once per frame rather than per-render-item, and then each render-item just sets the
object constant buffer:
void CameraAndDynamicIndexingApp::Draw(const GameTimer&
gt)
{

auto passCB = mCurrFrameResource->PassCB-
>Resource();
mCommandList->SetGraphicsRootConstantBufferView(1,
passCB->GetGPUVirtualAddress());
// 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(2,
matBuffer->GetGPUVirtualAddress());
// Bind all the textures used in this scene. Observe
// that we only have to specify the first descriptor
in the table.
// The root signature knows how many descriptors are
expected in the table.
mCommandList->SetGraphicsRootDescriptorTable(3,
mSrvDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart());
DrawRenderItems(mCommandList.Get(),
mOpaqueRitems);

}
void CameraAndDynamicIndexingApp::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->SetGraphicsRootConstantBufferView(0,
objCBAddress);
cmdList->DrawIndexedInstanced(ri->IndexCount, 1,
ri->StartIndexLocation, ri->BaseVertexLocation,
0);
}
}
Napominjemo da je struktura ObjectConstants ažurirana da ima a MaterialIndek. Vrednost koju ste
podesili za ovo je isti indeks koji biste koristili ispuštanje u materijalni konstantni pufer:
// UpdateObjectCBs…
ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.World,
XMMatrixTranspose(world));
XMStoreFloat4x4(&objConstants.TexTransform,
XMMatrixTranspose(texTransform));
objConstants.MaterialIndex = e->Mat->MatCBIndex;
The modified shader code demonstrating dynamic indexing is included below with
relevant sections bolded:
// 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;
};
// 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(t0);
// 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];
};
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;
};
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);
// 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 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;
}
The above shader code demonstrated writing an explicit register space:
StructuredBuffer<MaterialData> gMaterialData :
register(t0, space1);
Ako ne eksplicitno navedete prostor, podrazumevano je prostor0. Prostor samo daje
dodajte još jednu dimenziju da biste odredili registar shadera i koristite ga za sprečavanje
preklapanje resursa. Na primer, možemo da stavimo više resursa u registar t0 ako
oni su u različitim prostorima:
Texture2D gDiffuseMap : register(t0, space0);
Texture2D gNormalMap : register(t0, space1);
Texture2D gShadowMap : register(t0, space2);
Često se koriste kada postoje nizovi resursa. Na primjer,
sledeći 4 polja elemenata teksture zauzima registre t0, t1, t2 i t3:
Texture2D gDiffuseMap [4]: register (t0);
Mogli smo da računamo i shvatimo da je sledeći besplatni registar t4, ili mi ne bi mogla brinuti o tome i
uvesti novi prostor:
// 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);
Da zaključimo ovaj odeljak, date su tri dodatne upotrebe dinamičkog indeksiranja:
1. Spojite u blizini mreže sa različitim teksturama u jednu pojedinačnu stavku, tako da oni može se
nacrtati jednim pozivom. Ove mreže mogu da skladište teksturu / materijal za upotrebu kao atribut u
strukturi verteka.
2. Multitekturing u jednom rendering-prolazu gde teksture imaju različite veličine I formati.
3. Instaliranje maltera sa različitim teksturama i materijalima koji koriste SV_InstanceID vrednost kao
indeks. U sledećem ćemo videti primer ovoga poglavlje.
Poglavlje 16 INSTANCING I FRUSTUM CULLING
U ovom poglavlju proučavamo instanciranje i frustrirano lišavanje. Instanciranje se odnosi na crtanje
isti objekat više puta u sceni. Instanciranje može pružiti značajno
optimizaciju, i tako postoji podrška Direct3D za instanciranje. Frustum izlečenje
odnosi se na odbacivanje čitavih grupa trouglova iz dalje obrade koja je izvan
gledanje frustuma jednostavnim testom.
Ciljevi:
1. Da naučite kako da implementirate hardversko instanciranje.
2. Da biste se upoznali sa graničnim količinama, zašto su korisni, kako ih kreirati,
i kako ih koristiti.
3. Da biste otkrili kako da primenite frustumno izlečenje.
16.1 INSTANCIJA HARDVARE
Instanciranje se odnosi na crtanje istog objekta više puta u sceni, ali sa
različite pozicije, orijentacije, skale, materijale i teksture. Evo nekoliko primera:
1. Nekoliko različitih modela drveća su nacrtani više puta kako bi se izgradila šuma.
2. Nekoliko različitih modela asteroida nacrtano je više puta da bi se napravilo polje asteroida.
3. Nekoliko različitih modela karaktera su nacrtani više puta da bi se izgradila gomila
ljudi.
Bilo bi rasipno da se dupliraju podaci o vertici i indeksima za svaku instancu. Umesto toga,
čuvamo samo jednu kopiju geometrije (tj., verteks i liste indeksa) u odnosu na objekt
lokalni prostor. Zatim crtamo objekat nekoliko puta, ali svaki put sa drugačijim svetom
matrice i drugog materijala ukoliko je poželjna dodatna sorta.
Iako ova strategija spašava memoriju, i dalje zahtijeva iznad glasa API-a po objektu. To je, za svaki
objekat, moramo postaviti svoj jedinstveni materijal, svoju svjetsku matricu i pozvati izvlačenje
komanda. Iako je Direct3D 12 redizajniran da bi smanjio puno API troškova koji je postojao u Direct3D 11
prilikom izvršavanja pozivnog poziva, i dalje postoji neki nadbiskup. The Direct3D instancing API vam
omogućava da instaliraju objekat više puta sa jednim nacrtati poziv; i više, sa dinamičkim indeksiranjem
(pokrivenim u prethodnom poglavlju), instancing je fleksibilniji nego u Direct3D 11. Zašto briga o API-u
iznad glave? To je bilo uobičajeno za Direct3D 11 aplikacije koje su CPU vezane zbog API overhead (ovo
znači CPU bio je usko grlo, a ne GPU). Razlog za to su oni dizajneri nivoa kao što su
crtati mnoge predmete sa jedinstvenim materijalima i teksturama, a to zahtijeva stanje
promeni i izvlači poziv za svaki objekat. Kada postoji visok nivo CPU-a
iznad glave za svaki API poziv, scene bi bile ograničene na nekoliko hiljada izvlačenja
poziva da bi i dalje održavali brzine renderinga u realnom vremenu. Grafički motori
onda bi primenili tehnike doziranja (vidi [Vloka03]) kako bi se smanjio broj
poziva poziva. Hardversko instanciranje je jedan aspekt u kojem API pomaže u izvođenju
šarže.
16.1.1 Crtanje instanciranih podataka
Možda je iznenađujuće, već smo crtali instanced podatke u svim prethodnim
demo poglavlja. Međutim, broj primeraka je uvek bio 1 (drugi parametar):

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:

auto srvCpuStart = mSrvDescriptorHeap-


>GetCPUDescriptorHandleForHeapStart();
auto srvGpuStart = mSrvDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart();
auto rtvCpuStart = mRtvHeap-
>GetCPUDescriptorHandleForHeapStart();
// Cubemap RTV goes after the swap chain descriptors.
int rtvOffset = SwapChainBufferCount;
CD3DX12_CPU_DESCRIPTOR_HANDLE cubeRtvHandles[6];
for(int i = 0; i < 6; ++i)
cubeRtvHandles[i] = CD3DX12_CPU_DESCRIPTOR_HANDLE(
rtvCpuStart, rtvOffset + i, mRtvDescriptorSize);
mDynamicCubeMap->BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE(
srvCpuStart, mDynamicTexHeapIndex,
mCbvSrvDescriptorSize),
CD3DX12_GPU_DESCRIPTOR_HANDLE(
srvGpuStart, mDynamicTexHeapIndex,
mCbvSrvDescriptorSize),
cubeRtvHandles);
void
CubeRenderTarget::BuildDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE
hCpuSrv,
CD3DX12_GPU_DESCRIPTOR_HANDLE
hGpuSrv,
CD3DX12_CPU_DESCRIPTOR_HANDLE
hCpuRtv[6])
{
// Save references to the descriptors.
mhCpuSrv = hCpuSrv;
mhGpuSrv = hGpuSrv;
for(int i = 0; i < 6; ++i)
mhCpuRtv[i] = hCpuRtv[i];
// Create the descriptors
BuildDescriptors();
}
18.5.4 Izgradnja deskriptora
U prethodnom odeljku dodelili smo prostor za naše deskriptore i cachedreference na deskriptore, ali
nismo stvarno napravili nikakve deskriptore za resurse. Sada moramo da kreiramo SRV za resurse karte
kocke, tako da možemo da ga uzorku uđemo u piksel shader nakon što je izgrađen, a takođe moramo
napraviti i prikaz ciljnog prikaza za svaki od njih element u nizu teksture polja kubusa, tako da možemo
da stavimo na svaku mapu kocke lice onebi- jedan. Sledeći metod kreira neophodne prikaze:
void CubeRenderTarget::BuildDescriptors()
{
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping =
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = mFormat;
srvDesc.ViewDimension =
D3D12_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube.MostDetailedMip = 0;
srvDesc.TextureCube.MipLevels = 1;
srvDesc.TextureCube.ResourceMinLODClamp = 0.0f;
// Create SRV to the entire cubemap resource.
md3dDevice->CreateShaderResourceView(mCubeMap.Get(),
&srvDesc, mhCpuSrv);
// Create RTV to each cube face.
for(int i = 0; i < 6; ++i)
{
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
rtvDesc.ViewDimension =
D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Format = mFormat;
rtvDesc.Texture2DArray.MipSlice = 0;
rtvDesc.Texture2DArray.PlaneSlice = 0;
// Render target to ith element.
rtvDesc.Texture2DArray.FirstArraySlice = i;
// Only view one element of the array.
rtvDesc.Texture2DArray.ArraySize = 1;
// Create RTV to ith cubemap face.
md3dDevice->CreateRenderTargetView(mCubeMap.Get(),
&rtvDesc, mhCpuRtv[i]);
}
}
18.5.5 Izgradnja dubinskog pufera
Uopšteno gledano, lica kocke na karti će imati drugačiju rezoluciju od glavnog leđa pufer. Zbog toga, za
rendering na kocke mape, potreban je dubinski bafer sa dimenzije koje odgovara rezoluciji kocke mape
lica. Međutim, pošto se kocka obradi jedan po jedan, potreban je samo jedan dubinski bafer za
rendering kocke. Mi gradimo dodatni bafer za dubinu i DSV sa sledećim kodom:
void DynamicCubeMapApp::BuildCubeDepthStencil()
{
// Create the depth/stencil buffer and view.
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension =
D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = CubeMapSize;
depthStencilDesc.Height = CubeMapSize;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.Format = mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
depthStencilDesc.Layout =
D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags =
D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mCubeDepthStencilBuffer.GetAddressOf())));
// Create descriptor to mip level 0 of entire
resource using
// the format of the resource.
md3dDevice->CreateDepthStencilView(
mCubeDepthStencilBuffer.Get(), nullptr, mCubeDSV);
// Transition the resource from its initial state to
be used as a depth buffer.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mCubeDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_DEPTH_WRITE));
}
18.5.6 Cube Map Vievport i Scissor Rectangle
Budući da će se kocke face mape imati drugačije rezolucije od glavnog bafera za zaglavljivanje, moramo definisati
novi pravougaonik pogleda i škare koji pokriva lice kocke: CubeRenderTarget::CubeRenderTarget(ID3D12Device*
device,
UINT width, UINT height,
DXGI_FORMAT format)
{
md3dDevice = device;
mWidth = width;
mHeight = height;
mFormat = format;
mViewport = { 0.0f, 0.0f, (float)width,
(float)height, 0.0f, 1.0f };
mScissorRect = { 0, 0, width, height };
BuildResource();
}
D3D12_VIEWPORT CubeRenderTarget::Viewport()const
{
return mViewport;
}
D3D12_RECT CubeRenderTarget::ScissorRect()const
{
return mScissorRect
}
18.5.7 Podešavanje Camera Cube Map
Podsjetimo da je za kreiranje ideje za mapu kocke postaviti kameru u središte nekih objekat O u sceni sa
ugao vidnog polja od 90 ° (vertikalno i horizontalno). Zatim će kamera gledati niz pozitivnu k-osu,
negativnu k-osu, pozitivnu i-osu, negativnu i osu, pozitivnu z-osu i negativnu z-osu, kao i da snimaju
scenu (isključujući objekat O) iz svake od ovih šest tačaka. Da bismo olakšali ovo, mi generišemo
šest kamera, po jedna za svako lice, centrirano na datoj poziciji (x, y z):
Camera mCubeMapCamera[6];
void DynamicCubeMapApp::BuildCubeFaceCamera(float x,
float y, float z)
{
// Generate the cube map about the given position.
XMFLOAT3 center(x, y, z);
XMFLOAT3 worldUp(0.0f, 1.0f, 0.0f);
// Look along each coordinate axis.
XMFLOAT3 targets[6] =
{
XMFLOAT3(x + 1.0f, y, z), // +X
XMFLOAT3(x - 1.0f, y, z), // -X
XMFLOAT3(x, y + 1.0f, z), // +Y
XMFLOAT3(x, y - 1.0f, z), // -Y
XMFLOAT3(x, y, z + 1.0f), // +Z
XMFLOAT3(x, y, z - 1.0f) // -Z
};
// Use world up vector (0,1,0) for all directions
except +Y/-Y. In these cases, we
// are looking down +Y or -Y, so we need a different
“up” vector.
XMFLOAT3 ups[6] =
{
XMFLOAT3(0.0f, 1.0f, 0.0f), // +X
XMFLOAT3(0.0f, 1.0f, 0.0f), // -X
XMFLOAT3(0.0f, 0.0f, -1.0f), // +Y
XMFLOAT3(0.0f, 0.0f, +1.0f), // -Y
XMFLOAT3(0.0f, 1.0f, 0.0f), // +Z
XMFLOAT3(0.0f, 1.0f, 0.0f) // -Z
};
for(int i = 0; i < 6; ++i)
{
mCubeMapCamera[i].LookAt(center, targets[i],
ups[i]);
mCubeMapCamera[i].SetLens(0.5f*XM_PI, 1.0f, 0.1f,
1000.0f);
mCubeMapCamera[i].UpdateViewMatrix();
}
}
S obzirom da rendering na svakoj slici kocke kocke koristi drugu kameru, svaki kockasti lice
potreban je sopstveni skup PassConstants-a. To je dovoljno lako, pošto samo povećamo naše
PassConstants broji šest puta kada kreiramo resurse za okvir.
void DynamicCubeMapApp::BuildFrameResources()
{
for(int i = 0; i < gNumFrameResources; ++i)
{
mFrameResources.push_back(std::make_unique<FrameResource>
(md3dDevice.Get(),
7, (UINT)mAllRitems.size(),
(UINT)mMaterials.size()));
}
}
Element 0 će odgovarati našoj glavnoj prolaznoj renderingu, a elementi 1-6 će biti
odgovara našoj kocki obraz lica.Sproveli smo sledeći metod da postavimo konstantne podatke za svaku
mapu kocke kocke:
void DynamicCubeMapApp::UpdateCubeMapFacePassCBs()
{
for(int i = 0; i < 6; ++i)
{
PassConstants cubeFacePassCB = mMainPassCB;
XMMATRIX view = mCubeMapCamera[i].GetView();
XMMATRIX proj = mCubeMapCamera[i].GetProj();
XMMATRIX viewProj = XMMatrixMultiply(view, proj);
XMMATRIX invView =
XMMatrixInverse(&XMMatrixDeterminant(view), view);
XMMATRIX invProj =
XMMatrixInverse(&XMMatrixDeterminant(proj), proj);
XMMATRIX invViewProj =
XMMatrixInverse(&XMMatrixDeterminant(viewProj),
viewProj);
XMStoreFloat4x4(&cubeFacePassCB.View,
XMMatrixTranspose(view));
XMStoreFloat4x4(&cubeFacePassCB.InvView,
XMMatrixTranspose(invView));
XMStoreFloat4x4(&cubeFacePassCB.Proj,
XMMatrixTranspose(proj));
XMStoreFloat4x4(&cubeFacePassCB.InvProj,
XMMatrixTranspose(invProj));
XMStoreFloat4x4(&cubeFacePassCB.ViewProj,
XMMatrixTranspose(viewProj));
XMStoreFloat4x4(&cubeFacePassCB.InvViewProj,
XMMatrixTranspose(invViewProj));
cubeFacePassCB.EyePosW =
mCubeMapCamera[i].GetPosition3f();
cubeFacePassCB.RenderTargetSize =
XMFLOAT2((float)CubeMapSize,
(float)CubeMapSize);
cubeFacePassCB.InvRenderTargetSize =
XMFLOAT2(1.0f / CubeMapSize, 1.0f /
CubeMapSize);
auto currPassCB = mCurrFrameResource-
>PassCB.get();
// Cube map pass cbuffers are stored in elements
1-6.
currPassCB->CopyData(1 + i, cubeFacePassCB);
}
}
18.5.8 Vraćanje u mapu kocke
Za ovu demo imamo tri renderera:
enum class RenderLayer : int
{
Opaque = 0,
OpaqueDynamicReflectors,
Sky,
Count
};
Sloj OpakueDinamicReflectors sadrži središnju sferu na slici () koji će koristiti mapu dinamičke kocke da
odražava lokalne dinamičke objekte. Naš prvi korak jeste nacrtajte scenu na svako lice mape kocke, ali
ne uključujući središnju sferu; ovo znači samo treba da učinimo neprozirne i nebesne slojeve na mapu
kocke:
void DynamicCubeMapApp::DrawSceneToCubeMap()
{
mCommandList->RSSetViewports(1, &mDynamicCubeMap-
>Viewport());
mCommandList->RSSetScissorRects(1, &mDynamicCubeMap-
>ScissorRect());
// Change to RENDER_TARGET.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mDynamicCubeMap->Resource(),
D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_RESOURCE_STATE_RENDER_TARGET));
UINT passCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants));
// For each cube map face.
for(int i = 0; i < 6; ++i)
{
// Clear the back buffer and depth buffer.
mCommandList->ClearRenderTargetView(
mDynamicCubeMap->Rtv(i), Colors::LightSteelBlue,
0, nullptr);
mCommandList->ClearDepthStencilView(mCubeDSV,
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,
&mDynamicCubeMap->Rtv(i),
true, &mCubeDSV);
// Bind the pass constant buffer for this cube map
face so we use
// the right view/proj matrix for this cube face.
auto passCB = mCurrFrameResource->PassCB-
>Resource();
D3D12_GPU_VIRTUAL_ADDRESS passCBAddress =
passCB->GetGPUVirtualAddress() +
(1+i)*passCBByteSize;
mCommandList->SetGraphicsRootConstantBufferView(1,
passCBAddress);
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Opaque]);
mCommandList-
>SetPipelineState(mPSOs[“sky”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Sky]);
mCommandList-
>SetPipelineState(mPSOs[“opaque”].Get());
}
// Change back to GENERIC_READ so we can read the
texture in a shader.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mDynamicCubeMap->Resource(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_GENERIC_READ));
}
Konačno, nakon što smo prikazali scenu na mapu kocke, postavili smo glavne ciljne ciljeve i nacrtati
scenu kao normalno, ali sa mapom dinamičke kocke koja se primjenjuje na centar sfera:
…D
rawSceneToCubeMap();
// Set main render target settings.
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());
auto passCB = mCurrFrameResource->PassCB->Resource();
mCommandList->SetGraphicsRootConstantBufferView(1,
passCB->GetGPUVirtualAddress());
// Use the dynamic cube map for the dynamic reflectors
layer.
CD3DX12_GPU_DESCRIPTOR_HANDLE dynamicTexDescriptor(
mSrvDescriptorHeap-
>GetGPUDescriptorHandleForHeapStart());
dynamicTexDescriptor.Offset(mSkyTexHeapIndex + 1,
mCbvSrvDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(3,
dynamicTexDescriptor);
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::OpaqueDynamicReflectors]);
// Use the static “background” cube map for the other
objects (including the sky)
mCommandList->SetGraphicsRootDescriptorTable(3,
skyTexDescriptor);
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Opaque]);
mCommandList->SetPipelineState(mPSOs[“sky”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Sky]);
// 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));

18.6 DIMAMIČKI KUPOVI ZA KUPU SA GEOMETRIJSKOM ŠEŠEROM
U prethodnom odeljku, šest puta smo preusmerili scenu kako bismo generisali mapu kocke – jednom za
svaku mapu kocke. Nacrt poziva nije besplatan, i moramo raditi na tome da ih minimiziramo. Postoji
Direct3D 10 uzorak pod nazivom "CubeMapGS", koji koristi geometrijski shader u snimiti kocke mapu
tako što crta scenu samo jednom. U ovom odeljku ističemo glavnu ideje o tome kako ovaj uzorak radi.
Imajte na umu da iako smo prikazali Direct3D 10 kod, ista strategija može se koristiti u Direct3D 12 i
prenosi kod je jednostavan. Prvo, ona stvara prikaz ciljne tačke za cjelokupni teksturni niz (ne svako
pojedinačno
tekstura lica):
// Create the 6-face render target view
D3D10_RENDER_TARGET_VIEW_DESC DescRT;
DescRT.Format = dstex.Format;
DescRT.ViewDimension =
D3D10_RTV_DIMENSION_TEXTURE2DARRAY;
DescRT.Texture2DArray.FirstArraySlice = 0;
DescRT.Texture2DArray.ArraySize = 6;
DescRT.Texture2DArray.MipSlice = 0;
V_RETURN( pd3dDevice->CreateRenderTargetView(
g_pEnvMap, &DescRT, &g_pEnvMapRTV ) );
Štaviše, ova tehnika zahteva kocke mape dubinskih bafera (po jedan za svako lice). Pregled dubinskog
šablona za ceo teksturni niz dubinskih bafera stvara se na sledeći način:
// Create the depth stencil view for the entire cube
D3D10_DEPTH_STENCIL_VIEW_DESC DescDS;
DescDS.Format = DXGI_FORMAT_D32_FLOAT;
DescDS.ViewDimension =
D3D10_DSV_DIMENSION_TEXTURE2DARRAY;
DescDS.Texture2DArray.FirstArraySlice = 0;
DescDS.Texture2DArray.ArraySize = 6;
DescDS.Texture2DArray.MipSlice = 0;
V_RETURN( pd3dDevice->CreateDepthStencilView(
g_pEnvMapDepth, &DescDS, &g_pEnvMapDSV ) );
It then binds this render target and depth stencil view to the OM stage of the pipeline:
ID3D10RenderTargetView* aRTViews[ 1 ] = { g_pEnvMapRTV
};
pd3dDevice-
>OMSetRenderTargets(sizeof(aRTViews)/sizeof(aRTViews[0]),
aRTViews, g_pEnvMapDSV );
dubinske šablone za šablone za OM fazu, a mi ćemo ići na svaku rezoluciju
istovremeno.
Sada, scena je prikazana jednom i niz od šest matrica prikaza (jedan za gledanje u
odgovarajući smjer svake mape kocke) dostupan je u konstantnim baferima. The
Geometrijski shader šest puta uvećava ulazni trougao i dodeljuje trougao jednom
Šest reda reznih ciljnih reda. Napravljen je trougao na segmentu cilja ciljne grupe
postavljanjem vrijednosti sistema SV_RenderTargetArraiIndek. Ova vrednost sistema je
vrednost celog indeksa koji se može podesiti samo kao izlaz iz geometrijskog shadera za određivanje
indeks rendera ciljne nize rendera na koji primitiv treba da bude prikazan. Ovo
sistemska vrednost se može koristiti samo ako je ciljni prikaz rendera zapravo prikaz na niz
resurs.
struct PS_CUBEMAP_IN
{
float4 Pos : SV_POSITION; // Projection coord
float2 Tex : TEXCOORD0; // Texture coord
uint RTIndex : SV_RenderTargetArrayIndex;
};
[maxvertexcount(18)]
void GS_CubeMap( triangle GS_CUBEMAP_IN input[3],
inout TriangleStream<PS_CUBEMAP_IN> CubeMapStream )
{
// For each triangle
for( int f = 0; f < 6; ++f )
{
// Compute screen coordinates
PS_CUBEMAP_IN output;
// Assign the ith triangle to the ith render
target.
output.RTIndex = f;
// For each vertex in the triangle
for( int v = 0; v < 3; v++ )
{
// Transform to the view space of the ith cube
face.
output.Pos = mul( input[v].Pos, g_mViewCM[f] );
// Transform to homogeneous clip space.
output.Pos = mul( output.Pos, mProj );
output.Tex = input[v].Tex;
CubeMapStream.Append( output );
}
CubeMapStream.RestartStrip();
}
}
Tako vidimo da smo prikazali scenu na svaku mapu kocke mape tako što je prikazao
scene samo jednom umjesto šest puta.
Rezimirali smo glavnu ideju ovog uzorka, ali se pozovite na
"CubeMapGS" Direct3D 10 uzorak za pun izvorni kod za popunjavanje bilo kog detalja.
Ova strategija je interesantna i pokazuje istovremene ciljeve umanjenja i
Vrednost sistema SV_RenderTargetArraiIndek; Međutim, to nije definitivna pobeda.
Postoje dva pitanja koja ovu metodu čine neprivlačnim:
1. Koristi geometrijski shader da izlazi veliki skup podataka. Podsetimo iz poglavlja 12 to
pomenuli smo da geometrijski shader deluje neefikasno kada izlazi veliki skup
podaci. Zbog toga, korišćenje geometrijskog shadera za ovu svrhu može da naruši performanse.
2. U tipičnoj sceni, trougao se neće preklapati više od jedne kocke mape lica (pogledajte ponovo
Slika 18.9). Stoga, čin replikacije trougla i svrstavanja na svaki
kockasto lice, kada ga pet od šest od lica ugine, je rasipno.
Doduše, naša demo za ovo poglavlje takođe čini cijelu scenu svaku kartu kocke
lice za jednostavnost. Međutim, u stvarnim aplikacijama (ne-demo), koristićemo frustum
izbacivanje (Poglavlje 16), i samo prikazivanje objekata vidljivim za određenu mapu kocke
lice. Frustumno skidanje na nivou objekta ne može se izvršiti geometrijskim shaderom
implementacija.
S druge strane, situacija u kojoj ova strategija funkcioniše dobro bi bila a
mreža koja okružuje scenu. Na primer, pretpostavimo da ste imali dinamičan sistem neba
gde se oblaci pomeraju i boja neba se menja na osnovu vremena dana. Zbog
nebo se menja, ne možemo koristiti teksturu mape kocke da odražava nebo, tako da imamo
da koristite dinamičku mapu kocke. Pošto mreža neba okružuje čitavu scenu, ona je vidljiva
svih šest kockica se suočava sa mapom. Prema tome, druga metoda gore navedene ne odnosi se, i
geometrijski shader metod bi mogao biti pobeda smanjujući pozive sa šest na jedan, pretpostavljajući
upotreba geometrijskog shadera ne šteti previše performansi.
Nedavne optimizacije dostupne u NVIDIA-ovoj Maksvelovoj arhitekturi omogućavaju geometrijske
ciljeve bez penala korišćenja geometrijskog shadera (videti
http://docs.nvidia.com/gamevorks/content/gamevorkslibrari/graphicssamples/opengl_vhich koristi
funkcije Vievport Multicast i Fast Geometri Shader). U vreme kada je to izložio Direct3D 12, ali će
verovatno biti u budućem ažuriranju.

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).

20.4.2 Podsticanje i preimućstvo


Mape sjene čuvaju dubinu najbližih vidljivih piksela u odnosu na svoje
povezan izvor svetlosti. Međutim, mapa sjaja ima samo određenu konačnu rezoluciju. Tako
svaka teksela mapa seksa odgovara određenoj oblasti scene. Dakle, karta sjene je samo a
diskretno uzorkovanje dubine scene iz perspektive svetlosti. Ovo uzrokuje probleme sa zamahom
poznate kao akne senke (vidi sliku 20.7).
Slika 20.7. Obratite pažnju na aluziju na ravnom podu sa "steppingom"
promena između svetlosti i senke. Ova greška za uznemiravanje se često naziva akni senke.
Slika 20.8 prikazuje jednostavan dijagram da objasni zašto se javljaju akne u senci. Jednostavan
rešenje je da primenite konstantnu pristrasnost da biste nadmašili dubinu karte senke. Slika 20.9
prikazuje
kako ovo ispravlja problem.
Slika 20.8. Mape senke uzorkuju dubinu scene. Obratite pažnju na to
da bi se ograničila rezolucija mape sjene, svaka karta seksa tekel odgovara određenoj oblasti
scene. Oko E vidi dve tačke na sceni p1 i p2 koje odgovaraju
različiti ekranski pikseli. Međutim, sa stanovišta svetlosti, obe tačke su
pokriven istom mapom tekela senke (tj. s (p1) = s (p2) = s). Kada to uradimo
test mape sjene, imamo d (p1)> s i d (p2) ≤ s. Dakle, p1 će biti obojen kao da je
bili su u senci, a p2 biće obojen kao da nije u senci. Ovo uzrokuje
senka akne.
Slika 20.9. Podstičući vrednosti dubine na mapi senke, nema lažnog senčenja
javlja. Imamo taj d (p1) ≤ s i d (p2) ≤ s. Pronalaženje prave pristrasnosti dubine je obično
izvršeno eksperimentisanjem.
Previše podmukla rezultira u artefaktu koji se zove peter-panning, gde je senka
čini se da se odvaja od objekta (vidi Sliku 20.10).
Slika 20.10. Peter-panning-senka se odvaja od kolone
zbog velike pristrasnosti.
Nažalost, fiksna pristrasnost ne funkcioniše za svu geometriju. Konkretno, Slika 20.11
pokazuje da su trouglovi sa velikim kosinama (u odnosu na izvor svetlosti) potrebna veća pristrasnost.
Odlično je izabrati dovoljno dubinsko odstupanje za sve padine. Međutim, kao Figura
20.10 pokazao, ovo vodi ka peter-panning-u.
Slika 20.11. Potrebno je poligone sa velikim kosinama, u odnosu na izvor svetlosti
više pristrasnosti od poligona sa malim kosinama u odnosu na izvor svetlosti.
Ono što želimo je način merenja nagiba poligona u odnosu na izvor svetlosti,
i primeniti više pristrasnosti za veće kosim poligonima. Na sreću, grafički hardver ima
unutrašnja podrška za ovo putem takozvanih karakteristika stanja rasterećenja nagiba:
typedef struct D3D12_RASTERIZER_DESC {
[…]
INT DepthBias;
FLOAT DepthBiasClamp;
FLOAT SlopeScaledDepthBias;
[…]
} D3D12_RASTERIZER_DESC;
1.DepthBias: Fiksna pristrasnost za primenu; pogledajte komentare ispod kako vrijedi ovaj cijeli broj
koristi se za UNORM format dubine bafera.
2. DepthBiasClamp: dozvoljena maksimalna pristrasnost dubine. Ovo nam omogućava da postavimo
vezu na dubini pristrasnosti, jer možemo da zamislimo da za vrlo strmim padinama, nagib nagiba
bilo bi previše i izazvalo peter-panning artefakte.
3. SlopeScaledDepthBias: Faktor skale za kontrolu koliko pristrasnost zasniva na
nagib poligona; pogledajte komentare ispod za formulu. Imajte na umu da primenimo nagib skliznu-
pristrasnost kada preuzimamo scenu mapa senke. To je zato što želimo pristrasnost zasnovanu na
nagibu poligona u odnosu na izvor svetlosti. Shodno tome, mi mijenjamo vrijednosti mape sjene. U našoj
demo koristimo vrednosti:
// [From MSDN]
// If the depth buffer currently bound to the outputmerger
stage
// has a UNORM format or no depth buffer is bound the
bias value
// is calculated like this:
//
// Bias = (float)DepthBias * r + SlopeScaledDepthBias
* MaxDepthSlope;
//
// where r is the minimum representable value > 0 in
the
// depth-buffer format converted to float32.
// [/End MSDN]
//
// For a 24-bit depth buffer, r = 1 / 2^24.
//
// Example: DepthBias = 100000 ==> Actual DepthBias =
100000/2^24 = .006
// These values are highly scene dependent, and you
will need
// to experiment with these values for your scene to
find the
// best values.
D3D12_GRAPHICS_PIPELINE_STATE_DESC smapPsoDesc =
opaquePsoDesc;
smapPsoDesc.RasterizerState.DepthBias = 100000;
smapPsoDesc.RasterizerState.DepthBiasClamp = 0.0f;
smapPsoDesc.RasterizerState.SlopeScaledDepthBias =
1.0f;
20.4.3 Filtriranje PCF-a
Koordinate projektivne teksture (u, v) koje se koriste za uzorkovanje mape sjena uglavnom će biti ne
poklapa se sa tekselom na mapi senke. Obično će biti između četiri teksela. Sa teksturiranjem boje, ovo
se rešava pomoću bilinearne interpolacije (§9.5.1). Međutim, [Kilgard01] ukazuje na to da ne treba da
prosečnim vrednostima dubine, jer može dovesti do netačnih rezultata piksel je označen u senci. (Iz istog
razloga, takođe ne možemo generisati mipmapove za mapu senke.) Umesto interpolacije dubinskih
vrednosti, interpoliramo rezultate - ovo se zove procentualno filtriranje (PCF). To znači da koristimo
filtriranje tačke (MIN_MAG_MIP_POINT) i uzorku teksture sa koordinatama (u, v), (u + Dk, v), (u,
v + Dk), (u + Dk, v + Dk), gde je Dk = 1 / SHADOV_MAP_SIZE. Pošto koristimo tačku uzimajući uzorak, ove
četiri tačke će pogoditi najbližih četiri teksela s0, s1, s2 i s3, respektivno, okruženje (u, v), kao što je
prikazano na slici 20.12. Zatim vršimo test mape senke za svaku od ove uzorkovane dubine i bilinearno
interpoliraju rezultate mape sjene:
static const float SMAP_SIZE = 2048.0f;
static const float SMAP_DX = 1.0f / SMAP_SIZE;

// Sample shadow map to get nearest depth to light.
float s0 = gShadowMap.Sample(gShadowSam,
projTexC.xy).r;
float s1 = gShadowMap.Sample(gShadowSam,
projTexC.xy + float2(SMAP_DX, 0)).r;
float s2 = gShadowMap.Sample(gShadowSam,
projTexC.xy + float2(0, SMAP_DX)).r;
float s3 = gShadowMap.Sample(gShadowSam,
projTexC.xy + float2(SMAP_DX, SMAP_DX)).r;
// Is the pixel depth <= shadow map value?
float result0 = depth <= s0;
float result1 = depth <= s1;
float result2 = depth <= s2;
float result3 = depth <= s3;
// Transform to texel space.
float2 texelPos = SMAP_SIZE*projTexC.xy;
// Determine the interpolation amounts.
float2 t = frac( texelPos );
// Interpolate results.
return lerp( lerp(result0, result1, t.x),
lerp(result2, result3, t.x), t.y);
Na taj način, to nije situacija u cjelini ili ništa; piksel može biti delimično u senci. Za
primer, ako su dva uzorka u senci, a dva nisu u senci, onda je piksel
50% u senci. Ovo stvara gladak prelaz od seniranih piksela do ne-senki
piksela (pogledajte Sliku 20.13).
HLSL frac funkcija vraća frakcioni deo plivajuće tačke
broj (tj. mantisa). Na primjer, ako SMAP_SIZE = 1024 i
projTek.ki = (0,23, 0,68), onda tekelPos = (235,52,
696.32) i frac (tekelPos) = (0.52, 0.32). Ove frakcije govore
nas koliko da interpoliramo između uzoraka. HLSL lerp (k, i, s)
funkcija je linearna interpolacija funkcija i vraća k + s (i - k) = (1 - s) k +
si.
Slika 20.13. Na vrhu slike, posmatrajte artefakte "stepping-stepping" na
granica senke. Na donjoj slici, ovi artifakti za izravnanje se izjednačavaju a
bitno sa filtriranjem.
Čak i sa našim filtriranjem, senke su i dalje veoma teške i preklapanje
artefakti i dalje mogu biti nezadovoljavajući. Mogu biti agresivniji metodi
koristi; vidi [Uralski05], na primer. Napominjemo i da koristimo višu rezoluciju
mapa senke pomaže, ali može se koštati previše.
Glavni nedostatak PCF filtriranja kao što je gore opisano je da to zahteva četiri
uzorci teksture. Teksture uzorkovanja su jedna od skupljih operacija na modernoj
GPU zato što se propusni opseg memorije i latencija memorije nisu poboljšali koliko god
sirova računarska snaga GPU-a [Moller08]. Srećom, Direct3D 11+ grafika
hardver je ugradio podršku za PCF pomoću metode SampleCmpLevelZero:

Texture2D gShadowMap : register(t1);


SamplerComparisonState gsamShadow : register(s6);
// Complete projection by doing division by w.
shadowPosH.xyz /= shadowPosH.w;
// Depth in NDC space.
float depth = shadowPosH.z;
// Automatically does a 4-tap PCF.
gShadowMap.SampleCmpLevelZero(gsamShadow,
shadowPosH.xy, depth).r;
LevelZero deo imena metode znači da on gleda samo na vrh mipmap-a
nivo, što je dobro jer to je ono što želimo za mapiranje senki (mi ne generiramo
mipmap lanac za mapu senke). Ovaj metod ne koristi tipičan objekat uzorkovača,
ali umesto toga koristi takozvani primerak za uzorkovanje. To je tako da hardver može učiniti
test upoređivanja mape sjena, što treba uraditi pre filtriranja rezultata. Za PCF,
morate koristiti filter
D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT i podesite
funkcija upoređivanja sa LESS_EKUAL-om (MANJI takođe funkcioniše, jer mi izbacujemo dubinu). The
prvi i drugi parametri su koordinator za upoređivanje i teksturu,
redom. Treći parametar je vrijednost koja se poredi s uzorcima mape sjene.
Zato podesite vrednost upoređivanja prema dubini i funkciju poređenja na LESS_EKUAL
mi vršimo upoređivanja:
float result0 = depth <= s0;
float result1 = depth <= s1;
float result2 = depth <= s2;
float result3 = depth <= s3;
const CD3DX12_STATIC_SAMPLER_DESC shadow(
6, // shaderRegister
D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT, //
filter
D3D12_TEXTURE_ADDRESS_MODE_BORDER, // addressU
D3D12_TEXTURE_ADDRESS_MODE_BORDER, // addressV
D3D12_TEXTURE_ADDRESS_MODE_BORDER, // addressW
0.0f, // mipLODBias
16, // maxAnisotropy
D3D12_COMPARISON_FUNC_LESS_EQUAL,
D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK);
Tada hardver binarno interpolira rezultate kako bi završio PCF. Sledeći kod pokazuje kako opisujemo
primerak za uzorkovanje za sjenu mapiranje:
const CD3DX12_STATIC_SAMPLER_DESC shadow(
6, // shaderRegister
D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT, //
filter
D3D12_TEXTURE_ADDRESS_MODE_BORDER, // addressU
D3D12_TEXTURE_ADDRESS_MODE_BORDER, // addressV
D3D12_TEXTURE_ADDRESS_MODE_BORDER, // addressW
0.0f, // mipLODBias
16, // maxAnisotropy
D3D12_COMPARISON_FUNC_LESS_EQUAL,
D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK);
Do sada u ovom odeljku koristili smo PCF kernel s 4 klešta. Veće kernele se mogu koristiti
ivice senki veće, pa čak i glatke, na skupu ekstra
SampleCmpLevelZero poziva. U našem demo imenujemo SampleCmpLevelZero u 3 ×
3 kutija filtera filtera. S obzirom da svaki SampleCmpLevelZero poziv vrši PCF sa 4 cevi, mi
koriste 4 × 4 jedinstvene uzorke tačke sa mape sjena (na osnovu našeg obrazca postoji
neko preklapanje tačaka uzoraka). Korišćenje velikih jezgara za filtriranje može prouzrokovati akne senke
problem povratka; objašnjavamo zašto i opisujemo rešenje u §20.5.
Zapažanje je da PCF zaista treba samo izvesti na ivicama senke.
U senci nema mešanja, a van sjene nema mešanja.
Na osnovu ove opservacije, metode su osmišljene tako da rade samo PCF u senci
ivice. [Isidoro06b] opisuje jedan način da to uradi. Takva tehnika zahteva dinamiku
ogranak u kodu shadera: "Ako smo na ivici senke, uradite skupi PCF, u protivnom samo
uzmite uzorak mape senki. "
Imajte na umu da je ekstra skupa takvog postupka samo vredno ako je
vaš PCF jezgro je veliki (recimo 5 × 5 ili više); međutim, ovo je samo opšti savet i
moraćete da profilirate kako biste potvrdili trošak / korist.
Jedna završna napomena je da vaš PCF jezgro ne mora biti mreža filtera za kutije. Mnogi članak
napisani su o nasumičnim podacima koji se nalaze u PCF jezgru.
20.4.4 Izgradnja Mape senke
Prvi korak u mapiranju senki je izgradnja mape sjene. Da bismo to uradili, mi kreiramo
Primjer ShadovMap:
mShadovMap = std :: make_unikue <ShadovMap> (
md3dDevice.Get (), 2048, 2048);
Zatim definišemo matricu prikaza svetlosti i projekcionu matricu (što predstavlja okvir svetlosti
i pogledajte volumen). Matrica prikaza svetlosti izvedena je iz primarnog izvora svjetlosti, i
volumen prikaza svetlosti izračunava se tako da odgovara graničnoj sferi čitave scene.
DirectX::BoundingSphere mSceneBounds;
ShadowMapApp::ShadowMapApp(HINSTANCE hInstance)
: D3DApp(hInstance)
{
// Estimate the scene bounding sphere manually since
we know how the
// scene was constructed.
// The grid is the “widest object” with a width of
20 and depth of
// 30.0f, and centered at
// the world space origin. In general, you need to
loop over every
// world space vertex
// position and compute the bounding sphere.
mSceneBounds.Center = XMFLOAT3(0.0f, 0.0f, 0.0f);
mSceneBounds.Radius = sqrtf(10.0f*10.0f +
15.0f*15.0f);
}
void ShadowMapApp::Update(const GameTimer& gt)
{
[…]
//
// Animate the lights (and hence shadows).
//
mLightRotationAngle += 0.1f*gt.DeltaTime();
XMMATRIX R = XMMatrixRotationY(mLightRotationAngle);
for(int i = 0; i < 3; ++i)
{
XMVECTOR lightDir =
XMLoadFloat3(&mBaseLightDirections[i]);
lightDir = XMVector3TransformNormal(lightDir, R);
XMStoreFloat3(&mRotatedLightDirections[i],
lightDir);
}
AnimateMaterials(gt);
UpdateObjectCBs(gt);
UpdateMaterialBuffer(gt);
UpdateShadowTransform(gt);
UpdateMainPassCB(gt);
UpdateShadowPassCB(gt);
}
void ShadowMapApp::UpdateShadowTransform(const
GameTimer& gt)
{
// Only the first “main” light casts a shadow.
XMVECTOR lightDir =
XMLoadFloat3(&mRotatedLightDirections[0]);
XMVECTOR lightPos =
-2.0f*mSceneBounds.Radius*lightDir;
XMVECTOR targetPos =
XMLoadFloat3(&mSceneBounds.Center);
XMVECTOR lightUp = XMVectorSet(0.0f, 1.0f, 0.0f,
0.0f);
XMMATRIX lightView = XMMatrixLookAtLH(lightPos,
targetPos, lightUp);
XMStoreFloat3(&mLightPosW, lightPos);
// Transform bounding sphere to light space.
XMFLOAT3 sphereCenterLS;
XMStoreFloat3(&sphereCenterLS,
XMVector3TransformCoord(targetPos, lightView));
// Ortho frustum in light space encloses scene.
float l = sphereCenterLS.x - mSceneBounds.Radius;
float b = sphereCenterLS.y - mSceneBounds.Radius;
float n = sphereCenterLS.z - mSceneBounds.Radius;
float r = sphereCenterLS.x + mSceneBounds.Radius;
float t = sphereCenterLS.y + mSceneBounds.Radius;
float f = sphereCenterLS.z + mSceneBounds.Radius;
mLightNearZ = n;
mLightFarZ = f;
XMMATRIX lightProj =
XMMatrixOrthographicOffCenterLH(l, r, b, t, n, f);
// Transform NDC space [-1,+1]^2 to texture space
[0,1]^2
XMMATRIX T(
0.5f, 0.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f);
XMMATRIX S = lightView*lightProj*T;
XMStoreFloat4x4(&mLightView, lightView);
XMStoreFloat4x4(&mLightProj, lightProj);
XMStoreFloat4x4(&mShadowTransform, S);
}
Rendering the scene into the shadow map is done like so:
void ShadowMapApp::DrawSceneToShadowMap()
{
mCommandList->RSSetViewports(1, &mShadowMap-
>Viewport());
mCommandList->RSSetScissorRects(1, &mShadowMap-
>ScissorRect());
// Change to DEPTH_WRITE.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mShadowMap->Resource(),
D3D12_RESOURCE_STATE_GENERIC_READ,
D3D12_RESOURCE_STATE_DEPTH_WRITE));
UINT passCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof
(PassConstants));
// Clear the back buffer and depth buffer.
mCommandList->ClearDepthStencilView(mShadowMap-
>Dsv(),
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
1.0f, 0, 0, nullptr);
// Set null render target because we are only going
to draw to
// depth buffer. Setting a null render target will
disable color writes.
// Note the active PSO also must specify a render
target count of 0.
mCommandList->OMSetRenderTargets(0, nullptr, false,
&mShadowMap->Dsv());
// Bind the pass constant buffer for the shadow map
pass.
auto passCB = mCurrFrameResource->PassCB-
>Resource();
D3D12_GPU_VIRTUAL_ADDRESS passCBAddress = passCB-
>GetGPUVirtualAddress()
+ 1*passCBByteSize;
mCommandList->SetGraphicsRootConstantBufferView(1,
passCBAddress);
mCommandList-
>SetPipelineState(mPSOs[“shadow_opaque”].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Opaque]);
// Change back to GENERIC_READ so we can read the
texture in a shader.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(
mShadowMap->Resource(),
D3D12_RESOURCE_STATE_DEPTH_WRITE,
D3D12_RESOURCE_STATE_GENERIC_READ));
}
Obratite pažnju da smo postavili nultu ciljnu renderu, koja u suštini onemogućava pisanje boja. Ovo je
jer kada stavimo scenu na mapu sjena, sve što nas zanima je dubina vrednosti scene u odnosu na izvor
svetlosti. Grafičke kartice su samo optimizovane dubina crtanja; dubina samo za rendisanje je znatno
brža od crtanja boje I dubina. Status objekta aktivnog konektora mora takođe odrediti broj ciljanih
renderera od 0:
D3D12_GRAPHICS_PIPELINE_STATE_DESC smapPsoDesc =
opaquePsoDesc;
smapPsoDesc.RasterizerState.DepthBias = 100000;
smapPsoDesc.RasterizerState.DepthBiasClamp = 0.0f;
smapPsoDesc.RasterizerState.SlopeScaledDepthBias =
1.0f;
smapPsoDesc.pRootSignature = mRootSignature.Get();
smapPsoDesc.VS =
{
reinterpret_cast<BYTE*>(mShaders[“shadowVS”]-
>GetBufferPointer()),
mShaders[“shadowVS”]->GetBufferSize()
};
smapPsoDesc.PS =
{
reinterpret_cast<BYTE*>
(mShaders[“shadowOpaquePS”]->GetBufferPointer()),
mShaders[“shadowOpaquePS”]->GetBufferSize()
};
// Shadow map pass does not have a render target.
smapPsoDesc.RTVFormats[0] = DXGI_FORMAT_UNKNOWN;
smapPsoDesc.NumRenderTargets = 0;
ThrowIfFailed(md3dDevice-
>CreateGraphicsPipelineState(
&smapPsoDesc,
IID_PPV_ARGS(&mPSOs[“shadow_opaque”])));
The shader programs we use for rendering the scene from the perspective of the light
is quite simple because we are only building the shadow map, so we do not need to do any
complicated pixel shader work.
//*********************************************************************
// Shadows.hlsl by Frank Luna (C) 2015 All Rights
Reserved.
//*********************************************************************
// Include common HLSL code.
#include “Common.hlsl”
struct VertexIn
{
float3 PosL : POSITION;
float2 TexC : TEXCOORD;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float2 TexC : TEXCOORD;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut)0.0f;
MaterialData matData =
gMaterialData[gMaterialIndex];
// Transform to world space.
float4 posW = mul(float4(vin.PosL, 1.0f), 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;
}
// This is only used for alpha cut out geometry, so
that shadows
// show up correctly. Geometry that does not need to
sample a
// texture can use a NULL pixel shader for depth pass.
void PS(VertexOut pin)
{
// Fetch the material data.
MaterialData matData =
gMaterialData[gMaterialIndex];
float4 diffuseAlbedo = matData.DiffuseAlbedo;
uint diffuseMapIndex = matData.DiffuseMapIndex;
// Dynamically look up the texture in the array.
diffuseAlbedo *=
gTextureMaps[diffuseMapIndex].Sample(gsamAnisotropicWrap,
pin.TexC);
#ifdef ALPHA_TEST
// Discard pixel if texture alpha < 0.1. We do this
test as soon
// as possible in the shader so that we can
potentially exit the
// shader early, thereby skipping the rest of the
shader code.
clip(diffuseAlbedo.a - 0.1f);
#endif
}
Imajte na umu da piksel shader ne vraća vrijednost jer je potrebno samo izlaziti
dubinske vrednosti. Piksel shader se koristi isključivo za fiksiranje fragmenata piksela sa nultom ili
niskom alfom
vrednosti koje pretpostavljamo ukazuju na potpunu transparentnost. Na primer, razmotrite drvo
tekstura lista na slici 20.14; ovde, želimo samo crteći piksele sa bijelim alfa vrijednostima
na mapu senke. Da bismo ovo olakšali, nudimo dve tehnike: onaj koji radi alfa
klip operacije, a onaj koji ne radi. Ako alfa klip ne mora da se radi, onda mi
može vezati null pikel shader, što bi bilo još brže od vezivanja piksel shadera koji
samo uzorci teksture i vrši klip operaciju.
Slika 20.14. Leaf tekstura.
Iako nisu prikazani za kratkotrajnost, shaderi za prikaz dubine
tezaljana geometrija su nešto više uključena. Prilikom crtanja tessellated
geometriju u mapu senke, potrebno je da na geometriji uradimo isti način
mi ga tessellate kada se vuče u zadnji pufer (tj., na osnovu
udaljenost od očiju igrača). Ovo je za doslednost; geometrija koju vidi oko
treba da bude isto što i svetlost vidi. To je rekao, ako je tessellated
geometrija nije premeštena previše, pomjeranje možda i nije
primetan u senkama; stoga moguća optimizacija možda nije
smanjiti geometriju prilikom prikazivanja mape senke. Ova optimizacija
tačnost trgovine za brzinu.
20.4.5 Faktor senke
Faktor senke je novi faktor koji dodamo u jednačinu osvetljenja. Faktor senke
je skalar u opsegu od 0 do 1. Vrednost od 0 označava da je tačka u senci i vrednost od 1
ukazuje da tačka nije u senci. Sa PCF (§20.4.3), tačka takođe može biti delimično u
senka, u kom slučaju će faktor sjene biti između 0 i 1. The
Implementacija CalcShadovFactor je u Common.hlsl.
float CalcShadowFactor(float4 shadowPosH)
{
// Complete projection by doing division by w.
shadowPosH.xyz /= shadowPosH.w;
// Depth in NDC space.
float depth = shadowPosH.z;
uint width, height, numMips;
gShadowMap.GetDimensions(0, width, height, numMips);
// Texel size.
float dx = 1.0f / (float)width;
float percentLit = 0.0f;
const float2 offsets[9] =
{
float2(-dx, -dx), float2(0.0f, -dx), float2(dx, -
dx),
float2(-dx, 0.0f), float2(0.0f, 0.0f), float2(dx,
0.0f),
float2(-dx, +dx), float2(0.0f, +dx), float2(dx,
+dx)
};
[unroll]
for(int i = 0; i < 9; ++i)
{
percentLit +=
gShadowMap.SampleCmpLevelZero(gsamShadow,
shadowPosH.xy + offsets[i], depth).r;
}
return percentLit / 9.0f;
}
In our model, the shadow factor will be multiplied against the direct lighting (diffuse
and specular) terms:
// Only the first light casts a shadow.
float3 shadowFactor = float3(1.0f, 1.0f, 1.0f);
shadowFactor[0] = CalcShadowFactor(pin.ShadowPosH);
const float shininess = (1.0f - roughness) *
normalMapSample.a;
Material mat = { diffuseAlbedo, fresnelR0, shininess
};
float4 directLight = ComputeLighting(gLights, mat,
pin.PosW,
bumpedNormalW, toEyeW, shadowFactor);
float4 ComputeLighting(Light gLights[MaxLights],
Material mat,
float3 pos, float3 normal, float3 toEye,
float3 shadowFactor)
{
float3 result = 0.0f;
int i = 0;
#if (NUM_DIR_LIGHTS > 0)
for(i = 0; i < NUM_DIR_LIGHTS; ++i)
{
result += shadowFactor[i] *
ComputeDirectionalLight(gLights[i], mat, normal, toEye);
}
#endif
#if (NUM_POINT_LIGHTS > 0)
for(i = NUM_DIR_LIGHTS; i <
NUM_DIR_LIGHTS+NUM_POINT_LIGHTS; ++i)
{
result += ComputePointLight(gLights[i], mat, pos,
normal, toEye);
}
#endif
#if (NUM_SPOT_LIGHTS > 0)
for(i = NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; i <
NUM_DIR_LIGHTS +
NUM_POINT_LIGHTS + NUM_SPOT_LIGHTS; ++i)
{
result += ComputeSpotLight(gLights[i], mat, pos,
normal, toEye);
}
#endif
return float4(result, 0.0f);
}
Faktor senke ne utiče na ambijentalno svjetlo, jer je to indirektno svjetlo, a isto tako
ne utiče na reflektivno svetlo koje dolazi sa mape životne sredine.
20.4.6 Test sjene mape Nakon što smo napravili mapu senki prikazivši scenu iz perspektive
svetlost, možemo da uzorku mapu senke u našoj glavnoj prospektu za rendering da utvrdimo da li je
piksel u senci ili ne. Ključno pitanje je izračunavanje d (p) i s (p) za svaki piksel p. Vrednost
d (p) se pronalazi transformacijom tačke u prostor NDC svetlosti; zatim zoordinat
daje normalizovanu vrednost dubine tačke iz izvora svetlosti. Vrednost
s (p) se nalazi projekcijom mape sjena na scenu kroz volumen prikaza svetla
koristeći projektivno teksturiranje. Imajte na umu da se s ovim podešavanjem i d (p) i s (p) izmeru u
NDC prostor svetla, tako da se mogu upoređivati. Matrica transformacije
gShadovTransform pretvara se iz svetskog prostora u teksturu prostora senke
(§20.3).
// Generate projective tex-coords to project shadow
map onto scene
// in vertex shader.
vout.ShadowPosH = mul(posW, gShadowTransform);
// Do the shadow map test in pixel shader.
float3 shadowFactor = float3(1.0f, 1.0f, 1.0f);
shadowFactor[0] = CalcShadowFactor(pin.ShadowPosH);
Matrica gShadovTransforma se čuva kao konstanta per-passa.
20.4.7 Rendering the Shadov Map
Za ovu demo, takođe, prikazujemo mapu senki na četvrtinu koja zauzima niži nivo
ugao ekrana. Ovo nam omogućava da vidimo koju kartu senki izgleda za svaku
Ram. Podsjetimo da je mapa sjenila samo tekstura dubine bafera i možemo napraviti SRV
tako da se može uzorkovati u programu shadera. Mape sjene prikazane su kao a
siva slika jer ona čuva jednodimenzionalnu vrednost za svaki piksel (vrednost dubine).
Slika 20.15 prikazuje snimak ekrana "Shadov Map" demo.
Slika 20.15. Snimak ekrana mape sjene mape.
20.5 LARGE PCF ZVUČNICI
U ovom odeljku, raspravljamo o problemu kada se koristi veliki PCF jezgro. Naši demo
nemojte koristiti veliki PCF jezgro, tako da je ovaj odeljak u nekom smislu opcionalan, ali on uvodi
neke zanimljive ideje.
Pogledajte Sliku 20.16, gde izračunavamo test sjene za piksel p vidljiv
oko. Bez PCF-a, izračunamo rastojanje d = d (p) i uporedimo ga sa
odgovarajuća vrijednost mape sjene s0 = s (p). Sa PCF-om, poredimo i susjedne
vrednosti mape senke s-1 i s1 u odnosu na d. Međutim, nije valjano uporediti d sa s-1 i
s1. Tekseli s-1 i s1 opisuju dubine različitih oblasti scene koje mogu ili mogu
ne biti na istom poligonu kao str.
Scenario na slici 20.16 zapravo dovodi do greške u PCF-u. Konkretno,
kada uradimo test mape sjene izračunamo:
Kada su rezultati interpolirani, dobijamo da je p 1/3 u senci, što je
netačan jer ništa ne oklanja str.
Na slici 20.16 pogledajte da će više pogađanja dubine popraviti grešku. Međutim, u
ovaj primer, mi samo uzimamo uzorke sledećih vrata u susjednoj mapi. Ako
proširujemo jezgro PCF-a, a onda je potrebno još više pristrasnosti. Tako za male PCF jezgre,
jednostavno izrađivanje pristrasnosti dubine kako je objašnjeno u §20.4.2, dovoljno je da se suprotstavi
ovom problemu i
nema razloga za zabrinutost. Ali za velike PCF jezgre kao što su 5 × 5 ili 9 × 9, koje su
koji se koristi za stvaranje mekih senki, ovo može postati pravi problem.
Slika 20.16. Poređenje dubine d (p) sa s0 je tačno, pošto je teksel s0
pokriva područje scene p sadrži. Međutim, nije tačno da se uporede d (p)
sa s-1 i s1, pošto ti tekseli pokrivaju područja scene koji nisu povezani sa str.
20.5.1 Funkcije DDKS i DDI
Pre nego što pogledamo aproksimativno rešenje ovog problema, prvo moramo da razgovaramo
funkcije ddk i ddi HLSL. Ove funkcije procjenjuju ∂p / ∂k i ∂p / ∂i,
gde je k prostor ekrana k-osa, a i je ekranski prostor i-osa. Sa ovim
funkcije koje možete odrediti kako se količina p piksela p razlikuje od piksela do piksela. Primeri
o tome šta bi funkcije derivata mogle da se koriste za:
1. Procijenite kako boje mijenjaju piksel po pikselima.
2. Procijenite kako dubine mijenjaju piksel po pikselima.
3. Procijenite kako normali mijenjaju piksel po pikselima.
Kako hardver procenjuje ove parcijalne derivate nije komplikovano. The
hardver procesira piksele u 2 × 2 kvadrata u isto vrijeme paralelno. Zatim parcijalni derivat
u k-smjeru može se procijeniti pomoću jednačine naprednih razlika kk + 1, i - kk, i
(procenjuje kako se količina k menja od piksela (k, i) do piksela (k + 1, i)), i slično
za parcijalni derivat u pravcu i.
20.5.2 Rešenje problema velikog PCF jezgra
Rešenje koje opisujemo je iz [Tuft10]. Strategija je da pretpostavimo
susedni pikseli p leže na istoj ravni kao str. Ova pretpostavka nije uvek tačna, ali
to je najbolje što moramo raditi.
Neka su p = (u, v, z) koordinate u prostoru svetlosti. Koriste se koordinate (u, v)
indeksira mapu senke, a vrednost z je rastojanje od izvora svetlosti koji se koristi u
test mape senke. Mi možemo izračunati vektore
i
sa ddk i ddi, koji leže u tangentnoj ravni poligona. Ovo nam govori kako smo
krećite se u svetlosnom prostoru kada se krećemo na ekranu. Posebno, ako se pomerimo (Dk, Di)
jedinice na ekranu, pomeramo se
jedinice u svetlosnom prostoru u pravcu tangentnih vektora. Ignorišući pojam dubine za
trenutak, ako pomeramo (Dk, Di) jedinice u prostoru ekrana, mi se krećemo
jedinice u svetlosnom prostoru na uv-ravni; ovo se može izraziti matričnom jednačinom:
Dakle,
Podsjetimo iz 2. poglavlja to
Ova nova jednačina nam govori da ako pomerimo (Du, Dv) jedinice u prostoru svetlosti na uvlačenju,
onda pomeramo (Dk, Di) jedinice u prostoru ekrana. Pa zašto je važna Jednačina 20.1
nas? Pa, kada izgradimo naš PCF kernel, mi smo kompenzirali naše teksturne koordinate da uzorkujemo
susedne vrednosti u mapi senke:
// Texel size.
const float dx = SMAP_DX;
float percentLit = 0.0f;
const float2 offsets[9] =
{
float2(-dx, -dx), float2(0.0f, -dx), float2(dx,
-dx),
float2(-dx, 0.0f), float2(0.0f, 0.0f),
float2(dx, 0.0f),
float2(-dx, +dx), float2(0.0f, +dx), float2(dx,
+dx)
};
// 3x3 box filter pattern. Each sample does a 4-tap
PCF.
[unroll]
for(int i = 0; i < 9; ++i)
{
percentLit +=
shadowMap.SampleCmpLevelZero(samShadow,
shadowPosH.xy + offsets[i], depth).r;
}
znati (Du, Dv). Jednačina 20.1 nam govori da kada se pomeramo (Du, Dv) jedinice u prostoru svetlosti
mi se krećemo (Dk, Di) u prostoru ekrana.
Sada, vratimo se dubinskom terminu koji smo ignorisali. Ako se pomerimo (Dk, Di)
jedinice u prostoru ekrana, onda se dubina svetlosnog prostora pomera
. Stoga, kada mi nadoknađujemo koordinate teksture da uradimo PCF, možemo promeniti dubinu
vrednost koja se koristi u testu dubine: z '= z + Dz (vidi Sliku 20.17).
Dajte da rezimiramo:
1. U našoj implementaciji PCF-a, nadoknađujemo naše koordinate teksture da uzorkujemo susedne
vrednosti na mapi senke. Dakle, za svaki uzorak znamo (Du, Dv).
2. Možemo koristiti Jednačinu 20.1 da nađemo pomeranje prostora na ekranu (Dk, Di) kada se
pomerimo
(Du, Dv) jedinice u prostoru svetlosti.
3. Sa (Dk, Di) rešenim, primenite
da saznamo promenu dubine prostora.
Demonstracija "CascadedShadovMaps11" u DirectKs 11 SDK-u implementira ovaj metod
u CalculateRightAndUpTekelDepthDeltas i
Izračunajte PCFPercentLit funkcije.
20.5.3 Alternativno rešenje za veliki PCF jezgro problem
Ovo rešenje predstavljeno u [Isidoro06] je u istom duhu kao i prethodni odeljak, ali
uzima malo drugačiji pristup.
Slika 20.17. Ilustriramo u 2D za jednostavnost. Ako se od p = (u, z) pomaknemo sa
Du u smeru u za dobijanje (u + Du, z), onda je potrebno da se kompenzuje pomoću Dz da bi se
ostanu na poligonu da bi dobili p '= (u + Du, z + Dz).
Neka su p = (u, v, z) koordinate u prostoru svetlosti. Koriste se koordinate (u, v)
indeksira mapu senke, a vrednost z je rastojanje od izvora svetlosti koji se koristi u
test mape senke. Možemo računati sa ddk i ddi.
Posebno, činjenica da možemo uzeti ove derivate znači u = u (k, i), v = v (k, i)
i z = z (k, i) su sve funkcije k i i. Međutim, možemo zamisliti i funkciju z
u i v-to jest, z = z (u, v); dok se pomeramo u svetlosnom prostoru u u- i v-pravcima, dubini
z se menja duž poligonske ravni. Prema pravilu lanca, imamo:
Dakle, ovim pristupom ne moramo da se transformišemo na ekranski prostor, ali možemo ostati unutra
svetlosni prostor - razlog je to što smo direktno shvatili koliko se dubina menja kada su u i
v promene, dok smo u prethodnom odeljku samo znali kako se dubina promenila kada su k i i
izmenjeno u ekranu.
Poglavlje 21 AMBIENT OCCLUSION
Zbog ograničenja u performansama, uobičajeno je da modeli za osvetljenje u realnom vremenu ne
uzimaju
indirektno svetlo (t.e., svetlost koja je odbila druge objekte na sceni) u obzir.
Međutim, mnogo svetlosti koju vidimo u stvarnom svetu je indirektno. U poglavlju 8 smo predstavili
ambijentalni izraz za jednačinu osvetljenja:
AL boja određuje ukupnu količinu indirektnog (ambijentalnog) svetla na površini prima
iz izvora svetlosti, a difuzni albedo md određuje količinu dolazećeg svetla
površina se odražava zbog difuzne refleksije. Sav ambijentalni termin je jednako
malenʹkij razmesatʹsa predmet, čtobi ne polučitʹ černo v senci - net
stvarna kalkulacija fizike uopšte. Ideja je da se indirektno svetlo raspršilo i odbacilo
oko scene tako mnogo puta da udara objekat jednako u svakom pravcu. Figura
21.1 pokazuje da ako nacrtamo model koristeći samo ambijentalni izraz, on se prikazuje kao a
konstantna boja.
Slika 21.1. Mreža izvedena samo uz ambijentalni izraz pojavljuje se kao čvrsta
boja.
Slika 21.1 jasno stavlja do znanja da naš ambijentni termin može da koristi neka poboljšanja. U ovo
poglavlje, raspravljamo o popularnoj tehnikama ambijentalne okluzije kako bismo poboljšali naš
ambijent
termina.
Ciljevi:
1. Da bi se razumela osnovna ideja koja se tiče ambijentalne okluzije i kako se primjenjuje
ambijentalna okluzija putem livenja zraka.
2. Da biste naučili kako da primenite realno vreme apsorpcije ambijentalne okluzije na ekranu
prostor nazvan okruženje ambijentalnog prostora.
21.1 AMBIJENT OCCLUSION VIA RAI CASTING
Ideja ambijentalne okluzije je da količina indirektnog svetla tačka p na površini
prima se proporcionalno onome kako je okružen do dolazeće svetlosti oko hemisfere
p-vidi Sliku 21.2.
Slika 21.2. (a) Točka p je potpuno neobuzdana i svako dolazno svetlo prelazi
hemisfera oko p dosega p. (b) Geometrija delimično prekriva p i blokove
dolazeći svetlosni zraci preko hemisfere oko str.
Jedan od načina procene okluzije tačke p je putem lijevanja zraka. Naskočeno bacamo
zraci preko hemisfere oko p, i provjerite za raskrsnice prema mrežu (Fig
21.3). Ako bacimo N zrake, a h njih se presecaju u mrežu, tačka ima okluziju
vrednost:
Samo zraci sa tačkom ukrštanja k čija je rastojanje od p manje od neke
granična vrednost d treba da doprinese oceni okluzije; to je zato što
tačka preseka k daleko od p je suviše daleko da bi se okarakterisala.
Slika 21.3. Procena ambijentalne okluzije putem livenja zraka.
Faktor okluzije mjeri kako je okružena tačka (tj. Koliko svjetlosti radi
nije primljeno). U svrhu proračuna, volimo da radimo sa inverznim ovakvim.
To jest, želimo da znamo koliko svetlost tačka primi - to se naziva
pristupačnost (ili ga zovemo ambijentalnim pristupom) i izvedena je iz okluzije kao:
Sledeći kod izvršava žarku zraka po trouglu, a zatim prosjeku okluziju
rezultati su sa vertikalama koje dele trougao. Poreklo zraka je središnji trougao,
i generišemo nasumičan pravac zračenja preko hemisfere trougla.
void AmbientOcclusionApp::BuildVertexAmbientOcclusion(
std::vector<Vertex::AmbientOcclusion>& vertices,
const std::vector<UINT>& indices)
{
UINT vcount = vertices.size();
UINT tcount = indices.size()/3;
std::vector<XMFLOAT3> positions(vcount);
for(UINT i = 0; i < vcount; ++i)
positions[i] = vertices[i].Pos;
Octree octree;
octree.Build(positions, indices);
// For each vertex, count how many triangles contain
the vertex.
std::vector<int> vertexSharedCount(vcount);
// Cast rays for each triangle, and average triangle
occlusion
// with the vertices that share this triangle.
for(UINT i = 0; i < tcount; ++i)
{
UINT i0 = indices[i*3+0];
UINT i1 = indices[i*3+1];
UINT i2 = indices[i*3+2];
XMVECTOR v0 = XMLoadFloat3(&vertices[i0].Pos);
XMVECTOR v1 = XMLoadFloat3(&vertices[i1].Pos);
XMVECTOR v2 = XMLoadFloat3(&vertices[i2].Pos);
XMVECTOR edge0 = v1 - v0;
XMVECTOR edge1 = v2 - v0;
XMVECTOR normal = XMVector3Normalize(
XMVector3Cross(edge0, edge1));
XMVECTOR centroid = (v0 + v1 + v2)/3.0f;
// Offset to avoid self intersection.
centroid += 0.001f*normal;
const int NumSampleRays = 32;
float numUnoccluded = 0;
for(int j = 0; j < NumSampleRays; ++j)
{
XMVECTOR randomDir =
MathHelper::RandHemisphereUnitVec3(normal);
// Test if the random ray intersects the scene
mesh.
//
// TODO: Technically we should not count
intersections
// that are far away as occluding the triangle,
but
// this is OK for demo.
if( !octree.RayOctreeIntersect(centroid,
randomDir) )
{
numUnoccluded++;
}
}
float ambientAccess = numUnoccluded /
NumSampleRays;
// Average with vertices that share this face.
vertices[i0].AmbientAccess += ambientAccess;
vertices[i1].AmbientAccess += ambientAccess;
vertices[i2].AmbientAccess += ambientAccess;
vertexSharedCount[i0]++;
vertexSharedCount[i1]++;
vertexSharedCount[i2]++;
}
// Finish average by dividing by the number of
samples we added,
// and store in the vertex attribute.
for(UINT i = 0; i < vcount; ++i)
{
vertices[i].AmbientAccess /= vertexSharedCount[i];
}
}
Na slici 21.4 prikazan je snimak ekrana prikazan samo sa ambijentalnom okluzijom
generisani prethodnim algoritmom (nema izvora svetlosti u sceni). Ambijent
Okluzija se generiše kao korak pretkomputacije tokom inicijalizacije i čuva se kao vertek
atributi. Kao što vidimo, to je ogromno poboljšanje u odnosu na Sliku 21.1 - model zapravo
sada izgleda 3D.
Precizna ambijentalna okluzija radi dobro za statičke modele; postoje čak i alati
(http://vvv.knormal.net) koji generišu mape ambijentalne okluzije - teksture koje čuvaju
podaci ambijentalne okluzije. Međutim, za animirane modele ovi statički pristupi raskida
dole. Ako učitate i pokrenete demo "Ambient Occlusion", primetićete da je potrebno
nekoliko sekundi da pretkompuni ambijentalnu okluziju samo za jedan model. Stoga, lijevanje zraka
u izvodjenju za implementaciju dinamičke ambijentalne okluzije nije izvodljivo. U sledećem odeljku, mi
ispitati popularnu tehniku računanja ambijentalne okluzije u realnom vremenu koristeći ekran
prostorne informacije.
21.2 OCCLUSION AMBIENT
Strategija ambijentalne okluzije na ekranskom prostoru (SSAO) je za svaki okvir prikazivanje
normalne vrednosti prostora za prikaz scene do cilja celog ekrana i dubine scene uobičajene
dubinom / stencil baferom, a zatim procenite ambijentalnu okluziju za svaki piksel koristeći samo
pogledajte ciljni ciljni prostor i dubinu / šablon kao ulaz. Jednom kada imamo
tekstura koja predstavlja ambijentalnu okluziju na svakom pikselu, činimo scenu kao i obično
zadnjeg pufera, ali primenjuju SSAO informacije kako bi skali ambijentalni termin na svakom pikselu.
21.2.1 Norme rendera i dubina prolaska
Prvo ćemo prikazati normalne vektore objekata snimanja na veličinu ekrana
DKSGI_FORMAT_R16G16B16A16_FLOAT tekstura, dok uobičajena dubina / matrica
bafer je dužan da položi dubinu scene. Šefovi verteka / piksela koriste se za ovaj prolaz
su sledeći:
// 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 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];
// 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.
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosH = mul(posW, gViewProj);
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;
uint diffuseMapIndex = matData.DiffuseMapIndex;
uint normalMapIndex = matData.NormalMapIndex;
// Dynamically look up the texture in the array.
diffuseAlbedo *=
gTextureMaps[diffuseMapIndex].Sample(gsamAnisotropicWrap,
pin.TexC);
#ifdef ALPHA_TEST
// Discard pixel if texture alpha < 0.1. We do this
test as soon
// as possible in the shader so that we can
potentially exit the
// shader early, thereby skipping the rest of the
shader code.
clip(diffuseAlbedo.a - 0.1f);
#endif
// Interpolating normal can unnormalize it, so
renormalize it.
pin.NormalW = normalize(pin.NormalW);
// NOTE: We use interpolated vertex normal for SSAO.
// Write normal in view space coordinates
float3 normalV = mul(pin.NormalW, (float3x3)gView);
return float4(normalV, 0.0f);
}
Kao što kod pokazuje, piksel shader izlazi normalni vektor u prikaznom prostoru. Posmatrajte
da pišemo ciljnu pločicu sa plutajućim tačkama, tako da nema problema sa pisanjem
proizvoljni podaci sa plutajućim tačkama.
21.2.2 Prolaz okruženja ambijenta
Nakon što smo postavili normale pogleda i dubinu scene, onemogućavamo dubinu
bafera (ne trebamo ga za generisanje teksture okluzije ambijenta) i nacrtati punu
screen kuad da se pozove na SSAO pikel shader na svakom pikselu. Zatim će piksel shader koristiti
normalnu teksturu i dubinu pufera da generišu vrednost ambijentalne dostupnosti na svakom
pikel. Mi pozivamo generisanu teksturu mapu u ovom pasusu SSAO mapu. Iako činimo
mapu normalne / dubinske rezolucije pri rezoluciji punog ekrana (tj., rezolucija našeg bafera), mi
pružiti SSAO mapu na pola širine i visine zadnjeg pufera za performanse
razloge. Rendering na polovini dimenzija ne preterano utiče na kvalitet, kao i ambijent
Okluzija je niskofrekventni efekat. Pogledajte Sliku 21.5 tokom čitavog sledećeg
podsekcije.
Slika 21.5. Tačke uključene u SSAO. Tačka p odgovara
trenutni piksel koji se obrađuje, i ona se rekonstruiše od vrednosti dubine koja se čuva
bafer dubine i vektor v koji prolazi kroz blisku ravninu na ovom pikselu. The
tačka k je nasumična tačka u hemisferi p. Tačka r odgovara
najbliža vidljiva tačka duž zraka od oka do k. Tačka r doprinosi
okluzija p ako | pz - rz | je dovoljno mali i ugao između r - p i n je
manje od 90 °. U demo, uzimamo 14 slučajnih uzoraka i prosečno
okluzije iz svakog da proceni ambijentalnu okluziju u prostoru ekrana.
21.2.2.1 Rekonstruisati poziciju prostora za prikaz
Kada nacrtamo četvorostruki ekran da bi pokrenuli SSAO piksel shader na svakom pikselu
SSAO mapu, možemo koristiti inverse of projekcionu matricu da transformišemo kuad
uglovne tačke u NDC prostoru za tačke na prozoru prozora blizu projekcije:
static const float2 gTexCoords[6] =
{
float2(0.0f, 1.0f),
float2(0.0f, 0.0f),
float2(1.0f, 0.0f),
float2(0.0f, 1.0f),
float2(1.0f, 0.0f),
float2(1.0f, 1.0f)
};
// Draw call with 6 vertices
VertexOut VS(uint vid : SV_VertexID)
{
VertexOut vout;
vout.TexC = gTexCoords[vid];
// Quad covering screen in NDC space.
vout.PosH = float4(2.0f*vout.TexC.x - 1.0f, 1.0f -
2.0f*vout.TexC.y, 0.0f, 1.0f);
// Transform quad corners to view space near plane.
float4 ph = mul(vout.PosH, gInvProj);
vout.PosV = ph.xyz / ph.w;
return vout;
}
Ovi vektori koji se nalaze blizu ravni su interpolirani preko četvrtog kvadrata i daju nam vektor v
(Slika 21.5) od očiju do blize za svaki piksel. Sada, za svaki piksel, uzorkujemo
bafer dubine, tako da imamo z-koordinat pz najbližu vidljivu tačku do oka
u NDC koordinatama. Cilj je da se rekonstruiše pozicija pozicije p = (pk, pi, pz)
iz uzorka NDC z-koordinata pz i interpoliranog v-blizu-ravninskog vektora v. Ovo
rekonstrukcija se vrši na sledeći način. Pošto zraka v prolazi kroz p, postoji takav t
da je p = tv. Konkretno, pz = tvz tako da je t = pz / vz. Tako. Kod rekonstrukcije u shaderu piksela je
sledeći:
float NdcDepthToViewDepth(float z_ndc)
{
// We can invert the calculation from NDC space to
view space for the
// z-coordinate. We have that
// z_ndc = A + B/viewZ, where gProj[2,2]=A and
gProj[3,2]=B.
// Therefore…
float viewZ = gProj[3][2] / (z_ndc - gProj[2][2]);
return viewZ;
}
float4 PS(VertexOut pin) : SV_Target
{
// Get z-coord of this pixel in NDC space from depth
map.
float pz = gDepthMap.SampleLevel(gsamDepthMap,
pin.TexC, 0.0f).r;
// Transform depth to view space.
pz = NdcDepthToViewDepth(pz);
// Reconstruct the view space position of the point
with depth pz.
float3 p = (pz/pin.PosV.z)*pin.PosV;
[…]
}
21.2.2.2 Generisanje slučajnih uzoraka
Ovaj korak je analogan slučajnom žarku nad hemisferom. Mi nasumično
uzorak N bodova k oko p koji su takođe ispred p i unutar određene okluzije
radijus. Radijus okluzija je umetnički parametar koji kontroliše koliko je daleko od p
žele da uzmu slučajne uzorke. Izbor samo uzoraka tačaka ispred p je
analogno samo kretanju zraka preko hemisfere umjesto čitave sfere kada
rastvaranje okruženja okruženog zracima.
Sledeće pitanje je kako generisati slučajne uzorke. Možemo generirati slučajno
vektore i skladištiti ih na teksturiranoj mapi, a zatim uzorci ovu teksturnu mapu na N različitom
pozicije za dobijanje N slučajnih vektora. Međutim, pošto su oni slučajni, nemamo garanciju
da će vektori koje uzorkuramo biti ravnomerno raspoređeni - svi se mogu zajedno spustiti
približno u istom pravcu, što bi dalo procjenu loše okluzije. Da bi ovo prevazišli,
mi radimo sledeći trik. U našoj primeni, koristimo N = 14 uzoraka, a mi generišemo
četrnaest jednako distribuiranih vektora u C ++ kodu:
void Ssao::BuildOffsetVectors()
{
// Start with 14 uniformly distributed vectors. We
choose the
// 8 corners of the cube and the 6 center points
along each
// cube face. We always alternate the points on
opposite sides
// of the cubes. This way we still get the vectors
spread out
// even if we choose to use less than 14 samples.
// 8 cube corners
mOffsets[0] = XMFLOAT4(+1.0f, +1.0f, +1.0f, 0.0f);
mOffsets[1] = XMFLOAT4(-1.0f, -1.0f, -1.0f, 0.0f);
mOffsets[2] = XMFLOAT4(-1.0f, +1.0f, +1.0f, 0.0f);
mOffsets[3] = XMFLOAT4(+1.0f, -1.0f, -1.0f, 0.0f);
mOffsets[4] = XMFLOAT4(+1.0f, +1.0f, -1.0f, 0.0f);
mOffsets[5] = XMFLOAT4(-1.0f, -1.0f, +1.0f, 0.0f);
mOffsets[6] = XMFLOAT4(-1.0f, +1.0f, -1.0f, 0.0f);
mOffsets[7] = XMFLOAT4(+1.0f, -1.0f, +1.0f, 0.0f);
// 6 centers of cube faces
mOffsets[8] = XMFLOAT4(-1.0f, 0.0f, 0.0f, 0.0f);
mOffsets[9] = XMFLOAT4(+1.0f, 0.0f, 0.0f, 0.0f);
mOffsets[10] = XMFLOAT4(0.0f, -1.0f, 0.0f, 0.0f);
mOffsets[11] = XMFLOAT4(0.0f, +1.0f, 0.0f, 0.0f);
mOffsets[12] = XMFLOAT4(0.0f, 0.0f, -1.0f, 0.0f);
mOffsets[13] = XMFLOAT4(0.0f, 0.0f, +1.0f, 0.0f);
for(int i = 0; i < 14; ++i)
{
// Create random lengths in [0.25, 1.0].
float s = MathHelper::RandF(0.25f, 1.0f);
XMVECTOR v = s *
XMVector4Normalize(XMLoadFloat4(&mOffsets[i]));
XMStoreFloat4(&mOffsets[i], v);
}
}
Mi koristimo 4D homogene vektore samo zato da ne moramo brinuti o bilo kojoj
problemi poravnanja prilikom podešavanja niza ofset vektora na efekat.
Sada, u pikel shader-u samo jednom uzorku slučajne vektorske teksture mape i koristimo
da odražava naše četrnaest jednako raspoređenih vektora. Ovo rezultira sa 14 jednako distribuiranih
slučajni vektori.
21.2.2.3 Generisanje potencijalnih okružnih tačaka
Sada imamo slučajne uzorke tačke k koje okružuju p. Međutim, mi ništa ne znamo
o njima - da li zauzimaju prazan prostor ili čvrst predmet; Stoga, ne možemo da koristimo
da ih testiraju ako okreću str. Da bi pronašli potencijalne okcidne tačke, potrebna nam je dubina
informacije iz dubine bafera. Dakle, ono što radimo je stvaranje projektivne teksture
koordinira za svaki k u odnosu na fotoaparat, i koristite ih za uzorak dubine bafera
da biste dobili dubinu u NDC prostoru, a zatim transformisati da biste videli prostor da biste dobili
dubinu rz od
najbliži vidljivi piksel duž zraka od oka do k. Sa z-koordinatama rz poznatim,
možemo da rekonstruišemo puni 3D prostorni položaj r na isti način kao i mi
§21.2.2.1. Pošto vektor od oka do k prolazi kroz r postoji postoji t takav da je r
= tk. Konkretno, rz = tkz pa t = rz / kz. Dakle,
. Tačke r, jedna koja se generiše za svaku slučajnu tačku uzorka k, su naš potencijalni okluzivni
bodova.
21.2.2.4 Izvršite test okluzije
Sada kada imamo potencijalne okluzivne tačke r, možemo izvršiti naš test okluzija
da proceni da li oni okružuju str. Test se oslanja na dve količine:
1. Razdaljina dubine razmaka pogleda | pz - rz |. Linearno smanjujemo okluziju kao
Povećanje rastojanja, jer tačke daleko daleko manje imaju uticaj okluzije. Ako
rastojanje je izvan određenog maksimalnog rastojanja, onda se ne pojavljuje nikakva okluzija.
Takođe, ako je rastojanje veoma malo, pretpostavimo da su p i k na istoj ravni tako da je k
ne može okružiti str.
2. Ugao između n i r - p meren sa
. Ovo je da se spriječi samouskrcavanje (pogledajte Sliku 21.6).
Slika 21.6. Ako r leži na istoj ravni kao p, može proći prvi uslov da
razdaljina | pz-rz | je dovoljno mali da r okružuje str. Međutim, na slici se vidi
ovo je netačno jer r ne okreće p, jer leže na istoj ravni. Skaliranje
okluzija od strane
sprečava ovu situaciju.
21.2.2.5 Završetak kalkulacije
Nakon što smo sumirali okluziju iz svakog uzorka, računamo prosek
okluzija deljenjem brojačem uzorka. Zatim računamo pristup ambijenta, i
konačno podići pristup ambijenta do snage kako bi povećao kontrast. Možda i želite
povećajte osvetljenost ambijentalne mape tako što ćete dodati neki broj da biste povećali
intenzitet. Možete eksperimentisati sa različitim vrednostima kontrasta / osvetljenosti.
occlusionSum /= gSampleCount;
float access = 1.0f - occlusionSum;
// Sharpen the contrast of the SSAO map to make the
SSAO affect more dramatic.
return saturate(pow(access, 4.0f));
21.2.2.6 Implementacija
U prethodnom odeljku opisani su ključni sastojci za generisanje SSAO mape. U nastavku su HLSL
programi:
//====================================================================
// Ssao.hlsl by Frank Luna (C) 2015 All Rights
Reserved.
//====================================================================
cbuffer cbSsao : register(b0)
{
float4x4 gProj;
float4x4 gInvProj;
float4x4 gProjTex;
float4 gOffsetVectors[14];
// For SsaoBlur.hlsl
float4 gBlurWeights[3];
float2 gInvRenderTargetSize;
// Coordinates given in view space.
float gOcclusionRadius;
float gOcclusionFadeStart;
float gOcclusionFadeEnd;
float gSurfaceEpsilon;
};
cbuffer cbRootConstants : register(b1)
{
bool gHorizontalBlur;
};
// Nonnumeric values cannot be added to a cbuffer.
Texture2D gNormalMap : register(t0);
Texture2D gDepthMap : register(t1);
Texture2D gRandomVecMap : register(t2);
SamplerState gsamPointClamp : register(s0);
SamplerState gsamLinearClamp : register(s1);
SamplerState gsamDepthMap : register(s2);
SamplerState gsamLinearWrap : register(s3);
static const int gSampleCount = 14;
static const float2 gTexCoords[6] =
{
float2(0.0f, 1.0f),
float2(0.0f, 0.0f),
float2(1.0f, 0.0f),
float2(0.0f, 1.0f),
float2(1.0f, 0.0f),
float2(1.0f, 1.0f)
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosV : POSITION;
float2 TexC : TEXCOORD0;
};
VertexOut VS(uint vid : SV_VertexID)
{
VertexOut vout;
vout.TexC = gTexCoords[vid];
// Quad covering screen in NDC space.
vout.PosH = float4(2.0f*vout.TexC.x - 1.0f, 1.0f -
2.0f*vout.TexC.y, 0.0f, 1.0f);
// Transform quad corners to view space near plane.
float4 ph = mul(vout.PosH, gInvProj);
vout.PosV = ph.xyz / ph.w;
return vout;
}
// Determines how much the sample point q occludes the
point p as a function
// of distZ.
float OcclusionFunction(float distZ)
{
//
// If depth(q) is “behind” depth(p), then q cannot
occlude p. Moreover, if
// depth(q) and depth(p) are sufficiently close,
then we also assume q cannot
// occlude p because q needs to be in front of p by
Epsilon to occlude p.
//
// We use the following function to determine the
occlusion.
//
//
// 1.0 ––––-\
// | | \
// | | \
// | | \
// | | \
// | | \
// | | \
// ––|––|–––—|––––-|–––|—> zv
// 0 Eps z0 z1
//
float occlusion = 0.0f;
if(distZ > gSurfaceEpsilon)
{
float fadeLength = gOcclusionFadeEnd -
gOcclusionFadeStart;
// Linearly decrease occlusion from 1 to 0 as
distZ goes
// from gOcclusionFadeStart to
gOcclusionFadeEnd.
occlusion = saturate( (gOcclusionFadeEnddistZ)/
fadeLength );
}
return occlusion;
}
float NdcDepthToViewDepth(float z_ndc)
{
// z_ndc = A + B/viewZ, where gProj[2,2]=A and
gProj[3,2]=B.
float viewZ = gProj[3][2] / (z_ndc - gProj[2][2]);
return viewZ;
}
float4 PS(VertexOut pin) : SV_Target
{
// p — the point we are computing the ambient
occlusion for.
// n — normal vector at p.
// q — a random offset from p.
// r — a potential occluder that might occlude p.
// Get viewspace normal and z-coord of this pixel.
float3 n = gNormalMap.SampleLevel(gsamPointClamp,
pin.TexC, 0.0f).xyz;
float pz = gDepthMap.SampleLevel(gsamDepthMap,
pin.TexC, 0.0f).r;
pz = NdcDepthToViewDepth(pz);
//
// Reconstruct full view space position (x,y,z).
// Find t such that p = t*pin.PosV.
// p.z = t*pin.PosV.z
// t = p.z / pin.PosV.z
//
float3 p = (pz/pin.PosV.z)*pin.PosV;
// Extract random vector and map from [0,1] —> [-1,
+1].
float3 randVec = 2.0f*gRandomVecMap.SampleLevel(
gsamLinearWrap, 4.0f*pin.TexC, 0.0f).rgb - 1.0f;
float occlusionSum = 0.0f;
// Sample neighboring points about p in the
hemisphere oriented by n.
for(int i = 0; i < gSampleCount; ++i)
{
// Are offset vectors are fixed and uniformly
distributed (so that
// our offset vectors do not clump in the same
direction). If we
// reflect them about a random vector then we get
a random uniform
// distribution of offset vectors.
float3 offset = reflect(gOffsetVectors[i].xyz,
randVec);
// Flip offset vector if it is behind the plane
defined by (p, n).
float flip = sign( dot(offset, n) );
// Sample a point near p within the occlusion
radius.
float3 q = p + flip * gOcclusionRadius * offset;
// Project q and generate projective tex-coords.
float4 projQ = mul(float4(q, 1.0f), gProjTex);
projQ /= projQ.w;
// Find the nearest depth value along the ray from
the eye to q
// (this is not the depth of q, as q is just an
arbitrary point
// near p and might occupy empty space). To find
the nearest depth
// we look it up in the depthmap.
float rz = gDepthMap.SampleLevel(gsamDepthMap,
projQ.xy, 0.0f).r;
rz = NdcDepthToViewDepth(rz);
// Reconstruct full view space position r =
(rx,ry,rz). We know r
// lies on the ray of q, so there exists a t such
that r = t*q.
// r.z = t*q.z ==> t = r.z / q.z
float3 r = (rz / q.z) * q;
//
// Test whether r occludes p.
// * The product dot(n, normalize(r - p))
measures how much in
// front of the plane(p,n) the occluder point r
is. The more in
// front it is, the more occlusion weight we give
it. This also
// prevents self shadowing where a point r on an
angled plane (p,n)
// could give a false occlusion since they have
different depth
// values with respect to the eye.
// * The weight of the occlusion is scaled based
on how far the
// occluder is from the point we are computing the
occlusion of. If
// the occluder r is far away from p, then it does
not occlude it.
//
float distZ = p.z - r.z;
float dp = max(dot(n, normalize(r - p)), 0.0f);
float occlusion = dp * OcclusionFunction(distZ);
occlusionSum += occlusion;
}
occlusionSum /= gSampleCount;
float access = 1.0f - occlusionSum;
// Sharpen the contrast of the SSAO map to make the
SSAO affect more
// dramatic.
return saturate(pow(access, 2.0f));
}
21.2.3 Blur Pass
Slika 21.7 prikazuje na koji način trenutno izgleda naša mapa okruženja ambijenta. Buka je
zbog činjenice da smo uzeli samo nekoliko slučajnih uzoraka. Uzimajući dovoljno uzoraka
Sakrijte buku je nepraktično u realnom vremenu. Uobičajeno rešenje je primena ivice
čuvanje zamućenja (tj. bilateralno zamućenje) na kartici SSAO-a kako bi je izvukla. Ako koristimo nečak
čuvajući zamućenje, onda gubimo definiciju na sceni kada postanu ostri diskontinuitet
izravnan. Blur koji čuva ivice je sličan zamućenju koje smo primenili u poglavlju
13, osim što dodamo uslovnu izjavu tako da ne zamagljujemo preko ivica (ivice su
otkrivena sa mape normalne / dubinske):
//====================================================================
// SsaoBlur.hlsl by Frank Luna (C) 2015 All Rights
Reserved.
//
// Performs a bilateral edge preserving blur of the
ambient map. We use
// a pixel shader instead of compute shader to avoid
the switch from
// compute mode to rendering mode. The texture cache
makes up for some
// of the loss of not having shared memory. The
ambient map uses 16-bit
// texture format, which is small, so we should be
able to fit a lot of
// texels in the cache.
//=====================================================================
cbuffer cbSsao : register(b0)
{
float4x4 gProj;
float4x4 gInvProj;
float4x4 gProjTex;
float4 gOffsetVectors[14];
// For SsaoBlur.hlsl
float4 gBlurWeights[3];
float2 gInvRenderTargetSize;
// Coordinates given in view space.
float gOcclusionRadius;
float gOcclusionFadeStart;
float gOcclusionFadeEnd;
float gSurfaceEpsilon;
};
cbuffer cbRootConstants : register(b1)
{
bool gHorizontalBlur;
};
// Nonnumeric values cannot be added to a cbuffer.
Texture2D gNormalMap : register(t0);
Texture2D gDepthMap : register(t1);
Texture2D gInputMap : register(t2);
SamplerState gsamPointClamp : register(s0);
SamplerState gsamLinearClamp : register(s1);
SamplerState gsamDepthMap : register(s2);
SamplerState gsamLinearWrap : register(s3);
static const int gBlurRadius = 5;
static const float2 gTexCoords[6] =
{
float2(0.0f, 1.0f),
float2(0.0f, 0.0f),
float2(1.0f, 0.0f),
float2(0.0f, 1.0f),
float2(1.0f, 0.0f),
float2(1.0f, 1.0f)
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float2 TexC : TEXCOORD;
};
VertexOut VS(uint vid : SV_VertexID)
{
VertexOut vout;
vout.TexC = gTexCoords[vid];
// Quad covering screen in NDC space.
vout.PosH = float4(2.0f*vout.TexC.x - 1.0f, 1.0f -
2.0f*vout.TexC.y, 0.0f, 1.0f);
return vout;
}
float NdcDepthToViewDepth(float z_ndc)
{
// z_ndc = A + B/viewZ, where gProj[2,2]=A and
gProj[3,2]=B.
float viewZ = gProj[3][2] / (z_ndc - gProj[2][2]);
return viewZ;
}
float4 PS(VertexOut pin) : SV_Target
{
// unpack into float array.
float blurWeights[12] =
{
gBlurWeights[0].x, gBlurWeights[0].y,
gBlurWeights[0].z, gBlurWeights[0].w,
gBlurWeights[1].x, gBlurWeights[1].y,
gBlurWeights[1].z, gBlurWeights[1].w,
gBlurWeights[2].x, gBlurWeights[2].y,
gBlurWeights[2].z, gBlurWeights[2].w,
};
float2 texOffset;
if(gHorizontalBlur)
{
texOffset = float2(gInvRenderTargetSize.x,
0.0f);
}
else
{
texOffset = float2(0.0f,
gInvRenderTargetSize.y);
}
// The center value always contributes to the sum.
float4 color = blurWeights[gBlurRadius] *
gInputMap.SampleLevel(
gsamPointClamp, pin.TexC, 0.0);
float totalWeight = blurWeights[gBlurRadius];
float3 centerNormal =
gNormalMap.SampleLevel(gsamPointClamp, pin.TexC,
0.0f).xyz;
float centerDepth = NdcDepthToViewDepth(
gDepthMap.SampleLevel(gsamDepthMap, pin.TexC,
0.0f).r);
for(float i = -gBlurRadius; i <=gBlurRadius; ++i)
{
// We already added in the center weight.
if( i == 0 )
continue;
float2 tex = pin.TexC + i*texOffset;
float3 neighborNormal =
gNormalMap.SampleLevel(gsamPointClamp, tex, 0.0f).xyz;
float neighborDepth = NdcDepthToViewDepth(
gDepthMap.SampleLevel(gsamDepthMap, tex,
0.0f).r);
//
// If the center value and neighbor values differ
too much
// (either in normal or depth), then we assume we
are
// sampling across a discontinuity. We discard
such
// samples from the blur.
//
if( dot(neighborNormal, centerNormal) >= 0.8f &&
abs(neighborDepth - centerDepth) <= 0.2f )
{
float weight = blurWeights[i + gBlurRadius];
// Add neighbor pixel to blur.
color += weight*gInputMap.SampleLevel(
gsamPointClamp, tex, 0.0);
totalWeight += weight;
}
}
// Compensate for discarded samples by making total
weights sum to 1.
return color / totalWeight;
}
21.2.4 Korišćenje Mape okruženja ambijenta
Do sada smo konstruisali mapu ambijentalne okluzije. Poslednji korak je da ga primenite na scenu. Može
se smatrati da koristi alfa mešanje i modulira mapu ambijenta Back buffer. Međutim, ako to učinimo,
onda ambijentalna karta ne menja samo ambijentalni izraz, ali i difuzni i sjajni izraz jednačine osvetljenja,
što je neispravan. Umesto toga, kada izradimo scenu do zadnjeg pufera, vezujemo ambijentalnu kartu
kao input shadera. Zatim generišemo projektivne koordinate teksture (u odnosu na kamera), uzmite
SSAO mapu i primenite je samo na ambijentalni termin osvetljenja jednačina:
// In Vertex shader, generate projective tex-coords
// to project SSAO map onto scene.
vout.SsaoPosH = mul(posW, gViewProjTex);
// In pixel shader, finish texture projection and
sample SSAO map.
pin.SsaoPosH /= pin.SsaoPosH.w;
float ambientAccess = gSsaoMap.Sample(gsamLinearClamp,
pin.SsaoPosH.xy, 0.0f).r;
// Scale ambient term of lighting equation.
float4 ambient =
ambientAccess*gAmbientLight*diffuseAlbedo;
Slika 21.9 prikazuje scenu sa primenjenom SSAO mapom. SSAO može biti suptilna,
a vaša scena mora da odražava dovoljno ambijentalnog svetla, tako da ga skalirate pomoću
ambijentalnog pristupa
čini dovoljno primetne razlike. Prednost SSAO je najočiglednija kada
objekti su u senci. Kada su objekti u senci, difuzni i sjajni izrazi su
ubijen; tako se pojavljuje samo ambijentni izraz. Bez SSAO, objekti u senci će biti
izgledaju potpuno osvetljeni stalnim ambijentalnim izrazom, ali sa SSAO će zadržati svoj 3D
definicija.
Slika 21.9. Screenshot demo. Uticaji su suptilni jer utiču samo na njih
ambijentalni izraz, ali možete videti tamnjenje u osnovi kolona i kutije,
ispod sfere, i oko lobanje.
Kad izradimo scenu normalne vrednosti prostorija, pravimo i bafer za dubinu
scena. Shodno tome, kada drugi put prikažemo scenu sa SSAO mapom, mi
izmenite test upoređivanja dubine na "EKUALS". Ovo sprečava prelazak u
drugi rendering prolaz, pošto će samo najniže vidljive piksele proći ovu dubinsku usporedbu
test. Štaviše, drugi prelaz za rendering ne mora da piše u bafer dubine
jer smo već napisali scenu u dubinu bafera u normalnom prolazu cilja.
opaquePsoDesc.DepthStencilState.DepthFunc =
D3D12_COMPARISON_FUNC_EQUAL;
opaquePsoDesc.DepthStencilState.DepthWriteMask =
D3D12_DEPTH_WRITE_MASK_ZERO;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&opaquePsoDesc, IID_PPV_ARGS(&mPSOs[“opaque”])));
Poglavlje 22
KUATERNIONS
U Poglavlju 1 uvodili smo novu klasu matematičkih objekata pod nazivom vektori. In
Posebno smo saznali da se 3D vektor sastoji od naručenog 3-tuple realnih brojeva, i
definisali smo operatore na vektorima koji su korisni geometrijski. Isto tako, u 2. poglavlju mi
uvedene su matrice, koje su pravougaone tablice realnih brojeva sa definisanim operacijama
na one koji su korisni; na primer, videli smo kako matrice mogu da predstavljaju linearne i afinite
transformacije i kako matrično množenje odgovara transformaciji
kompozicija. U ovom poglavlju saznajemo o drugom tipu matematičkih objekata koji se zovu
kuaternions. Videćemo da se kvateron jedinice može koristiti za predstavljanje 3D rotacije i
ima pogodne interpolacijske osobine. Za čitaoce koji traže sveobuhvatan tretman
od kuaternions (i rotacija), mi volimo knjigu posvećenu ovoj temi [Kuipers99].
Ciljevi:
1. Da biste pregledali složene brojeve i podsjetili na složeno razmnožavanje broja
vrši rotaciju u ravni.
2. Da biste dobili razumevanje kvateriona i operacija definisanih na njima.
3. Da biste otkrili kako skup jediničnih kvaterniona predstavlja 3D rotacije.
4. Da biste saznali kako se pretvarati između različitih prikaza rotacije.
5. Da naučite kako da interpolirate između jedinica kvaterniona i shvatite da je to
geometrijski ekvivalentan interpolaciji između 3D orijentacija.
6. Da biste se upoznali sa kuaternion funkcijama i klasi DirectKs Math biblioteke.
22.1 PREGLED KOMPLEKSNIH BROJA
Kvaternioni se mogu posmatrati kao generalizacija kompleksnih brojeva; ovo
motiviše nas da proučavamo kompleksne brojeve pre kvaternih. Konkretno, naš glavni cilj je
ovaj odeljak pokazuje da množenjem kompleksnog broja p (misli se kao 2D vektor ili
tačka) jediničnim kompleksnim brojem rezultira rotacijom p. Zatim ćemo pokazati u § 22.3 to
poseban proizvod kvateriona koji uključuje jedinstveni kvaternion dovodi do 3D rotacije vektora
ili tačka p.
22.1.1 Definicije
Postoje različiti načini da se uvedu složeni brojevi. Mi ih upoznajemo u takvom
način na koji oni odmah izazivaju razmišljanje složenih brojeva kao 2D tačke ili vektore.
Redovan par realnih brojeva z = (a, b) je kompleksan broj. Prva komponenta
se zove pravi deo, a druga komponenta se zove imaginarni deo. Štaviše,
jednakost, dodavanje, oduzimanje, množenje i podela su definisani na sledeći način:
1. (a, b) = (c, d) ako i samo ako a = c i b = d.
2. (a, b) ± (c, d) = (a ± c, b ± d).
3. (a, b) (c, d) = (ac-bd, ad + bc).
4.
.
Lako je proveriti da li se uobičajena aritmetička svojstva realnih brojeva zadržavaju
kompleksna aritmetika (npr. komutativnost, asocijativnost, distributivni zakoni); pogledajte vežbu 1.
Ako kompleksni broj ima oblik (k, 0), onda je uobičajeno da ga identifikuje
realni broj k i napisati k = (k, 0); pa se svaki stvarni broj može smatrati kompleksnim
broj sa nultom imaginarnom komponentom. Obratite tada taj pravi broj puta a
kompleksni broj je dat sa k (a, b) = (k, 0) (a, b) = (ka, kb) = (a, b) (k, 0) = (a, b) k,
podseća na skalarno-vektorsko množenje.
Definišemo imaginarnu jedinicu i = (0, 1). Koristeći našu definiciju kompleksa
množenje, zapazimo da i2 = (0,1) (0, 1) = (-1, 0) = -1, što podrazumeva
. To nam govori da ja rešavam jednačinu k2 = -1.
Kompleksni konjugat kompleksnog broja z = (a, b) označava se sa
i dati
= (a, -b). Jednostavan način da zapamtite kompleksnu formulu za podelu je da pomnožite
numerator i imenitelj sa konjugatom imenovalca, tako da je imenitelj
postaje pravi broj:
Zatim, pokazujemo da se kompleksni broj (a, b) može napisati u obliku a + ib. Mi
ima a = (a, 0), b = (b, 0) i i = (0, 1), tako da
Koristeći oblik a + ib, možemo preoblikovati formule za sabiranje, oduzimanje,
razmnožavanje i podelu na sledeći način:
Pored toga, u ovom obliku, kompleksni konjugat z = a + ib je dat.
Poglavlje 22 Vidi
Poglavlje 23 ANIMACIJA KARAKTERA
Delovi ovog poglavlja pojavio se u knjizi Frank D. Luna,
Uvod u 3D Game Programiranje sa DirectKs 9.0c: Shader Approach,
2006: Jones i Bartlett Learning, Burlington, MA. vvv.jblearning.com.
Ponovljeno sa dozvolom.
U ovom poglavlju naučimo kako animirati složene likove poput čoveka ili životinje.
Karakteri su složeni zato što imaju mnogo pokretnih delova koji se svi kreću istim
vreme. Razmislite o ljudskom trčanju - svaka kost se kreće na neki način. Stvaranje takvog
Komplikovane animacije nisu praktične ruke, a postoje i specijalne modele i
alatke za animaciju za ovaj zadatak. Pod pretpostavkom da već imamo karakter i njegov odgovarajući
animirani podaci stvoreni, u ovom poglavlju ćemo naučiti kako animirati i iskoristiti ga koristeći
Direct3D.
Ciljevi:
1. Da se upoznamo sa terminologijom animiranih mrežica.
2. Da biste naučili matematiku transformacija mrežne hijerarhije i kako da pređete treebased
mrežne hijerarhije.
3. Da biste razumeli ideju i matematiku mešanja verteka.
4. Da biste saznali kako unositi podatke o animaciji iz datoteke.
5. Da biste otkrili kako da implementirate karakternu animaciju u Direct3D-u.
23.1 FRAME HIERARCHIES
Mnogi predmeti se sastoje od delova, sa odnosom roditelj-dijete, gdje jedan ili
više detskih objekata može samostalno da se kreće (sa mogućim fizičkim kretanjem
ograničenja - npr., ljudski zglobovi imaju određeni spektar kretanja), ali su takođe prisiljeni
kretati kada se roditelj kreće. Na primer, razmotrite ruku podeljenu na delove: gornja
ruku, podlakticu i ruku. Ruka se može okretati u izolaciji oko zglobnog zgloba; međutim, ako
podlaktica se okreće oko spoja lakta, onda se ruka mora okrenuti njime. Slično tome, ako je
nadlaktica se okreće oko ramena zgloba, podlaktica se okreće sa njom, i ako podlaktica
rotira, a onda se ruka okreće sa njom (vidi sliku 23.1). Tako vidimo definitivan objekat
hijerarhija: ruka je dijete podlaktice; podlaktica je dete nadlaktice, i
ako smo proširili našu situaciju, nadlaktica bi bila dijete torza, i tako dalje
dok ne završimo skelet (Slika 23.2 pokazuje složeniju hijerarhiju
primer).
Slika 23.1. Hijerarhijska transformacija; primetimo da je roditeljska transformacija a
kosti utiču na sebe i na sve svoje djece.
Slika 23.2. Složenija hijerarhija drveća za model bipedalnog humanoida
karakter. Strelice nadole predstavljaju odnose "prvo dijete" i strelice desno
predstavljaju odnose između "sestre". Na primjer, "Lijevo stomak", "Desno stomak" i
"Lover Spine" su sve djece kosti "Pelvis".
Cilj ovog odeljka je pokazati kako staviti objekat u scenu zasnovan na svom
poziciju, kao i položaj svojih predaka (tj. njegov roditelj, deda, velika garda,
itd.).
23.1.1 Matematička formulacija
Čitač možda želi da pregleda poglavlje 3 ove knjige, konkretno temu
transformacija promena koordinata.
Da bi stvari bile jednostavne i konkretne, radimo sa nadlakticom (korenom), podlakticom,
i hijerarhiju ruku, koju označavamo kao Bone 0, Bone 1 i Bone 2, respektivno (videti
Slika 23.3).
Slika 23.3. Jednostavna hijerarhija.
Kada se razume osnovni koncept, koristi se direktna generalizacija
rukuju složenijim situacijama. Dakle, dati objekat u hijerarhiji, kako to ispravno
pretvori ga u svetski prostor? Očigledno je da ne možemo samo da je transformišemo direktno u svet
prostor, jer moramo uzeti u obzir i transformacije svojih predaka
jer oni takođe utiču na njegovo plasiranje na scenu.
Svaki objekt u hijerarhiji je modeliran o sopstvenom lokalnom koordinatnom sistemu
njegov okretni zglob u poreklu radi olakšavanja rotacije (vidi sliku 23.4).
Slika 23.4. Geometrija svake kosti je opisana u odnosu na sopstvenu lokalnu
koordinatni sistem. Osim toga, pošto svi koordinatni sistemi postoje u istoj
univerzum, možemo ih povezati jedni sa drugima.
Pošto svi koordinatni sistemi postoje u istom univerzumu, možemo ih povezati; in
naročito, za proizvoljan trenutak u vremenu (mi popravljamo vreme i proučavamo snimak, jer, u
generalno, ove mrežne hijerarhije su animirane i tako se ovi odnosi menjaju kao a
funkcija vremena), opisujemo svaki koordinatni sistem u odnosu na njegovu matičnu koordinatu
sistem. (Matični koordinatni sistem korijenskog okvira F0 je svetska koordinata prostora
sistem V; to jest, koordinatni sistem F0 je opisan u odnosu na svetsku koordinatu
sistem.) Sada kada smo povezali sisteme deteta i matične koordinate, možemo
transformišu se iz dečjeg prostora u prostor roditelja sa transformacionom matricom. (Ovo je
ista ideja kao i transformacija lokalno u svijet. Međutim, umjesto da se transformišu iz
lokalnog prostora u svetski prostor, pretvaramo se iz lokalnog prostora u prostor roditelja.)
Neka A2 bude matrica koja transformiše geometriju iz okvira F2 u F1, a A1 je matrica koja
pretvoriti geometriju iz okvira F1 u F0, a A0 je matrica koja pretvara geometriju
od rama F0 u V. (Ai naziva matricu matricu matricu jer transformiše geometriju iz koordinatnog sistema
Achilda u koordinatni sistem svog roditelja.) Zatim, možemo transformisati svoj predmet u hijerarhiju
ruku u svetski prostor pomoću matrice Mi definisane na sledeći način: konkretno, u našem primer, M2 =
A2A1A0, M1 = A1A0 i M0 = A0 pretvara ruku u svetski prostor, podlakticu u svetski prostor, a nadu u
svetski prostor, respektivno. Obratite pažnju na to da objekat nasledi transformacije svojih pretka; ovo
je ono što će učiniti da se ruka pomeri ako se nadlaktica pomeri, na primer.
Slika 23.5 ilustruje ono što Jednačina 23.1 kaže grafički; u suštini, da se transformišu
objekat u hijerarhiji ruke, jednostavno primenimo transformaciju objekta na sve-roditelj i sve to
svojih predaka (u rastućem redosledu) da percoliraju hijerarhiju koordinatnog sistema sve do
objekat stiže u svetski prostor.
Slika 23.5. Pošto koordinatni sistemi postoje u istom univerzumu, možemo
povezati ih, i stoga, pretvarati iz jedne na drugu. Posebno se odnosi
opisujući koordinatni sistem svake kosti u odnosu na koordinat svog roditelja
sistem. Iz toga možemo konstruisati matricu transformacije do roditelja koja se transformiše
geometriju kosti od njenog lokalnog koordinatnog sistema do koordinate svog roditelja
sistem. Jednom u koordinatnom sistemu roditelja, onda možemo da se transformišu od strane roditelja
matricu matrice da se transformiše u koordinatni sistem dadilja i tako dalje i tako dalje
i tako dalje, dok ne posetimo svaki koordinatni sistem predaka i konačno stignemo
svetski prostor.
Primer sa kojim smo radili je jednostavna linearna hijerarhija. Ali isto
ideja generalizuje na hijerarhiju drveća; da je za bilo koji objekt u hijerarhiji njen svet
prostorna transformacija se može naći primenom primene transformacije objekta na sve roditelje i svih
svojih predaka (u rastućem redosledu) da percoliraju hijerarhiju koordinatnog sistema sve do
objekt stiže u svetski prostor (opet smo odredili da je matični koordinatni sistem
korena je svetski prostor). Jedina stvarna razlika je u tome što imamo više
komplikovana struktura podataka o drvetu za prelazak umesto linearne liste.
Kao primer, razmotrite levu kavezicu na slici 23.2. To je brat i sestra
kost, a samim tim i dijete gornje kičme. Gornja kičma je dijete niže
kičma, a donja kičma je dijete karlice. Zbog toga, svetska transformacija
leva klavikula formirana je sjedinjavanjem transformacije leve kavikle na roditelj, nakon čega sledi
transformacija gornjeg dela kičme do matrice, praćena transformacijom donjeg dela kičme,
praćeno transformacijom karlice "do-roditelj".
23.2 SKINNED MESE
23.2.1 Definicije
Slika 23.6 prikazuje karakternu mrežu. Istaknut lanac kostiju na slici je
nazvao je skelet. Skelet daje prirodnu hijerarhijsku strukturu za vođenje karaktera
animacijski sistem. Skelet je okružen spoljašnjom kožom, koju modelujemo kao 3D
geometrija (vertices i poligoni). U početku su vertikale kože relativne prema veznom prostoru,
što je lokalni koordinatni sistem da je cela koža definisana u odnosu na (obično
root koordinatni sistem). Svaka kost u skeletu utiče na oblik i poziciju
podskup kože na koje utiče (to jest, vertikala na koje utiče), baš kao u stvarnom životu. Tako, kao i mi
animirati skelet, vezana koža se animira prema tome da odražava trenutnu pozu
skeleta.
Slika 23.6. Karakter mreža. Istaknut lanac kostiju predstavlja
kostur karaktera. Poligoni tamne boje predstavljaju kožu lika. The
vertikali kože su relativni za vezni prostor, što je koordinatni sistem mreža
je modeliran.
23.2.2 Reformulisanje transformacije kostiju u korenu
Jedna razlika od §23.1 je da ćemo se ovde transformisati iz korene koordinate
sistem u svetski koordinatni sistem u posebnom koraku. Dakle, umjesto pronalaženja tovara
matrica za svaku kost, mi nalazimo korenu (tj. transformaciju koja se transformiše
od lokalnog koordinatnog sistema kostiju do koordinatnog sistema korena kostiju) matrica za
svaka kost.
Druga razlika je u tome što smo u § 23.1 prošli predaka čvora u donjem delu
modu, gde smo krenuli sa kostima i pomerili svoje prednike. Međutim, zapravo je
efikasnije za pristup odozgo prema dolje (vidi Jednačinu 23.2), gdje počinjemo u korenu
i krenite niz drvo. Označavajući n kosti sa brojem 0, 1, ..., n-1, mi
imaju sledeću formulu za ekspresiju transformacije u kosti:
Ovde, p je oznaka kosti roditelja kosti i. Da li to smisla? Zaista,
toRootp nam daje direktnu kartu koja šalje geometriju iz koordinatnog sistema kosti p
koordinatni sistem korena. Dakle, da dođemo do root koordinatnog sistema, to sledi
samo treba da dobijemo geometriju iz koordinatnog sistema kosti i koordinate
sistem njegove matične kosti p, i toParenti radi taj posao.
Jedino pitanje je da za to radimo, kada idemo da obradimo ista kost, moramo
već su izračunali korensku transformaciju svog roditelja. Međutim, ako pređemo
stablo odozgo nadole, a pre toga roditeljska transformacija u korenu uvek će biti izračunata
transformacija svoje dece u korenu.
Takođe vidimo zašto je ovo efikasnije. Sa pristupom odozgo prema dolje, za bilo koju kost
i, već imamo matricu matrice transformacije svog roditelja; tako smo samo jedan
odvojite se od korena transformacije za kosti i. Sa tehnikom dotoka, mi smo
pređite celom poreklo za svaku kost i mnoga množenja matriksa bi se duplirali kada kosti dijele
zajedničke pretke.
23.2.3 Offset Transform
Postoji mala suptilnost koja dolazi od činjenice da su vertikali pod uticajem a
kosti nisu relativno u odnosu na koordinatni sistem kosti (relativno su vezani
prostor, koji je koordinatni sistem na koji je modelirana mreža). Pa pre nego što se prijavimo
Jednačina 15.2, prvo moramo transformisati vertikale iz veznog prostora u prostor
kost koja utiče na vertikale. Tzv. Offset transformacija to radi; pogledajte sliku
23.7.
Slika 23.7. Prvo transformiramo vertikale pod uticajem kosti iz vezivanja
prostor u prostor kosti koja utječe preko offset transformacije. Onda, jednom u
prostor kosti, primenjujemo transformaciju kostiju u korenu kako bismo transformisali
vrha od prostora kosti do prostora korene kosti. Finale
transformacija je kombinacija offset transformacije, a zatim sledi korena
preobraziti.
Stoga, transformisanjem vertikala pomoću offset matrice neke proizvoljne kosti B, mi
pomerite vrhove iz veznog prostora na prostor kostiju B. Zatim, kada imamo
vrhove u kostnom prostoru B, možemo koristiti B-to-root transformaciju kako bi ga ponovo pozicionirali
karakter u svom trenutnom animiranom položaju.
Sada predstavljamo novu transformaciju, zovemo je konačnu transformaciju koja kombinuje a
transformacija kostne transformacije sa svojom korenom transformacije. Matematički, konačna
transformacija matrica i-ove kosti Fi daje:
23.2.4 Animacija skeleta
U demo u poslednjem poglavlju pokazali smo kako animirati jedan objekat. Definisali smo
da ključni okvir određuje položaj, orijentaciju i skalu objekta u jednoj instanci
vreme, i da je animacija spisak ključnih okvira sortiranih po vremenu, što grubo definišu
izgled celokupne animacije. Zatim smo pokazali kako da interpolirate između ključnih okvira
izračunajte postavljanje predmeta na vreme između ključnih okvira. Sada se produžavamo
animacijski sistem za animiranje skeleta. Ove klase animacije su definisane u
SkinnedData.h / .cpp u demonstraciji "Skinned Mesh" ovog poglavlja.
Animiranje skeleta nije mnogo teže nego animiranje jednog objekta. Dok smo mi
može razmišljati o jednom objektu kao jednoj kosti, a skelet je samo zbirka povezanih
kosti. Pretpostavićemo da svaka kost može da se kreće nezavisno. Zato, da animirate a
skelet, samo ćemo animirati svaku kost lokalno. Zatim je svaka kost uradila svoju lokalnu
animaciju, uzimamo u obzir kretanje svojih predaka i transformišemo ga
root prostor.Mi definišemo snimak animacije kao listu animacija (po jedan za svaku kost u
skelet) koji rade zajedno da formiraju specifičnu animaciju skeleta. Na primer,
"Hodanje", "trčanje", "borba", "ducking" i "skakanje" su primeri animacije
klipovi.
///<summary>
/// Examples of AnimationClips are “Walk”, “Run”,
“Attack”, “Defend”.
/// An AnimationClip requires a BoneAnimation for
every bone to form
/// the animation clip.
///</summary>
struct AnimationClip
{
// Smallest end time over all bones in this clip.
float GetClipStartTime()const;
// Largest end time over all bones in this clip.
float GetClipEndTime()const;
// Loops over each BoneAnimation in the clip and
interpolates
// the animation.
void Interpolate(float t, std::vector<XMFLOAT4X4>&
boneTransforms)const;
// Animation for each bone.
std::vector<BoneAnimation> BoneAnimations;
};
Karakter će obično imati nekoliko snimaka za animaciju za sve animacijekarakter treba da izvodi u
aplikaciji. Svi klipovi za animaciju rade na istom skelet, međutim, tako da koriste isti broj kostiju (mada
neke kosti mogu biti stacionarno za određenu animaciju). Možemo koristiti strukturu podataka
unordered_map sačuvajte sve snimke animacije i uputite se na snimak za animaciju pomoću čitljivog
imena:
std::unordered_map<std::string, AnimationClip>
mAnimations;
AnimationClip& clip = mAnimations[“attack”];
Na kraju, kao što je već rečeno, svaka kost treba transformisati offset da bi se transformisala
vrha od veznog prostora do prostora kosti; a osim toga, potreban nam je i način
predstavljaju skeletnu hijerarhiju (koristimo niz - pogledajte sledeći odeljak za detalje). Ovo
daje nam našu konačnu strukturu podataka za čuvanje podataka o skeletnim animacijama:
class SkinnedData
{ public:
UINT BoneCount()const;
float GetClipStartTime(const std::string&
clipName)const;
float GetClipEndTime(const std::string&
clipName)const;
void Set(
std::vector<int>& boneHierarchy,
std::vector<DirectX::XMFLOAT4X4>& boneOffsets,
std::unordered_map<std::string, AnimationClip>&
animations);
// In a real project, you’d want to cache the result
if there was a
// chance that you were calling this several times
with the same
// clipName at the same timePos.
void GetFinalTransforms(const std::string& clipName,
float timePos,
std::vector<DirectX::XMFLOAT4X4>&
finalTransforms)const;
private:
// Gives parentIndex of ith bone.
std::vector<int> mBoneHierarchy;
std::vector<DirectX::XMFLOAT4X4> mBoneOffsets;
std::unordered_map<std::string, AnimationClip>
mAnimations;
};
23.2.5 Izračunavanje konačne transformacije
Naša hijerarhija okvira za karakter će obično biti drvo, slično onom u kojem se nalazi
Slika 23.2. Mi hijerarhiju modelujemo s nizom integera tako da je unos matrice
daje matični indeks iste kosti. Štaviše, ith entri odgovara i I BoneAnimation u snimku radne animacije i
ista usta odgovara i I ofset transformacija. Root kost je uvek u elementu 0 i nema roditelja. Tako da
primer, animacija i offset transformacija bake i babe od kosti i dobija se:
int parentIndex = mBoneHierarchy[i];
int grandParentIndex = mBoneHierarchy[parentIndex];
XMFLOAT4X4 offset = mBoneOffsets[grandParentIndex];
AnimationClip& clip = mAnimations[“attack”];
BoneAnimation& anim =
clip.BoneAnimations[grandParentIndex];
We can therefore compute the final transform for each bone like so:
void SkinnedData::GetFinalTransforms(const
std::string& clipName,
float timePos, std::vector<XMFLOAT4X4>&
finalTransforms)const
{
UINT numBones = mBoneOffsets.size();
std::vector<XMFLOAT4X4>
toParentTransforms(numBones);
// Interpolate all the bones of this clip at the
given time instance.
auto clip = mAnimations.find(clipName);
clip->second.Interpolate(timePos,
toParentTransforms);
//
// Traverse the hierarchy and transform all the
bones to the
// root space.
//
std::vector<XMFLOAT4X4> toRootTransforms(numBones);
// The root bone has index 0. The root bone has no
parent, so
// its toRootTransform is just its local bone
transform.
toRootTransforms[0] = toParentTransforms[0];
// Now find the toRootTransform of the children.
for(UINT i = 1; i < numBones; ++i)
{
XMMATRIX toParent =
XMLoadFloat4x4(&toParentTransforms[i]);
int parentIndex = mBoneHierarchy[i];
XMMATRIX parentToRoot =
XMLoadFloat4x4(&toRootTransforms[parentIndex]);
XMMATRIX toRoot = XMMatrixMultiply(toParent,
parentToRoot);
XMStoreFloat4x4(&toRootTransforms[i], toRoot);
}
// Premultiply by the bone offset transform to get
the final transform.
for(UINT i = 0; i < numBones; ++i)
{
XMMATRIX offset =
XMLoadFloat4x4(&mBoneOffsets[i]);
XMMATRIX toRoot =
XMLoadFloat4x4(&toRootTransforms[i]);
XMStoreFloat4x4(&finalTransforms[i],
XMMatrixMultiply(offset, toRoot));
}
}
Postoji jedan zahtev koji je potreban da se napravi ovaj posao. Kada prelazimo kosti u
petlju, tražimo transformaciju korena u korenu do korena:
int parentIndex = mBoneHierarchy[i];
XMMATRIX parentToRoot =
XMLoadFloat4x4(&toRootTransforms[parentIndex]);
Ovo funkcioniše samo ako se garantuje da transformacija matične kosti u korenu ima
već je obrađen ranije u petlji. Mi, u stvari, možemo napraviti tu garanciju ako mi
uverite se da se kosti uvek naručuju u nizovima tako da uvek bude roditeljska kost
dolazi pre dječje kosti. Naši uzorci 3D podaci su generirani tako da je ovo
slučaj. Evo nekih podataka uzoraka prvih deset kostiju u hijerarhijskom nizu nekih
karakter model:
ParentIndexOfBone0: -1
ParentIndexOfBone1: 0
ParentIndexOfBone2: 0
ParentIndexOfBone3: 2
ParentIndexOfBone4: 3
ParentIndexOfBone5: 4
ParentIndexOfBone6: 5
ParentIndexOfBone7: 6
ParentIndexOfBone8: 5
ParentIndexOfBone9: 8
Zato uzmi Bone9. Njegov roditelj je Bone8, roditelj Bone8 je Bone5, roditelj Bone5
je Bone4, roditelj Bone4 je Bone3, roditelj Bone3 je Bone2, a roditelj
Bone2 je root čvor Bone0. Obratite pažnju da dječja kost nikad ne dolazi pre svoje matične kosti
u poručenom nizu.
23.3 VERTEKS BLENDING
Pokazali smo kako animirati skelet. U ovom delu ćemo se fokusirati
animiranje kože vertikala koja pokrivaju skelet. Algoritam za to jeste
zove se vertek mešanje.
Strategija mešanja riječi je sljedeća. Imamo osnovnu hijerarhiju kostiju,
ali sama koža je jedna neprekidna mreža (tj. ne prekidamo mrežu u delove
odgovaraju svakoj kosti i animiraju ih pojedinačno). Štaviše, jedna ili više kostiju
može uticati na vertikale kože; neto rezultat se određuje ponderiranim prosekom
konačnih transformacija utjecajnih kostiju (težine određuje umetnik kada je
model se pravi i čuva u fajlu). Sa ovim podešavanjem, može se postići glatka prelazna mešavina
postignutih na zglobovima (koji su obično problematična područja), čime se koža osjeća
elastičan; vidi sliku 23.8.
Slika 23.8. Koža je jedna kontinualna mrežica koja pokriva obe kosti. Posmatrajte
da su vertikale u blizini zgloba pod uticajem i kosti A i kosti B, kako bi se stvorila a
glatka prelazna mešavina za simuliranje fleksibilne kože.
U praksi, [Moller08] napominje da obično ne trebamo više od četiri kosti
Uticaji po verteksu. Dakle, u našem dizajnu ćemo razmotriti najviše četiri
uticajne kosti po verteksu. Da bi se sprovelo mešanje verteka, modelirali smo karakter
mrežastu kožu kao jednu neprekidnu mrežu. Svaka verzija sadrži do četiri indeksa koji indeksiraju
u paletu kostne matrice, što je niz finalnih transformacionih matrica (jedan unos
za svaku kost u skeletu). Pored toga, svaka verteka ima i do četiri težine
opisati odgovarajuću količinu uticaja koji svaka od četiri uticajna kost ima na to
vertek. Stoga imamo sljedeću strukturu verteka za mešanje verteksa (Slika 23.9):
Kontinualna mreža čije su vertikale ovaj format spremna za mešanje verteka, i
mi to zovemo od kože.
Slika 23.9. Matrica paleta čuva konačnu transformaciju za svaku kost.
Obratite pažnju na to kako četiri indeksa kosti indeksiraju u paletu matrice. Indeksi kostiju
identifikovati kosti skeleta koji utiču na tačku. Imajte na umu da vertek nije
nužno pod utjecajem četiri kosti; na primer, samo dva od četiri indeksa mogu
koristi se, ukazujući na to da samo dve kosti utiču na tačku. Možemo podesiti
koštanu težinu na nulu kako bi efikasno uklonili kost od uticaja na tačku.
Vrelo-mešana pozicija v 'bilo kojeg verteka v, u odnosu na korijenski okvir (zapamtite
izvršimo svetsku transformaciju kao poslednji korak kada imamo sve u korenu
koordinatni sistem), može se izračunati sa sledećom ponderiranom prosječnom formulom:
Gde je v0 + v1 + v2 + v3 = 1; to jest, zbir tegova se svodi na jedan.
Obratite pažnju da u ovoj jednačini pretvorimo datu vertek v pojedinačno od strane svih
konačne transformacije kostiju koje utiču na to (tj. matrice F0, F1, F2, F3). Zatim uzmemo
ponderisani prosek ovih pojedinačno transformisanih tačaka za izračunavanje poslednjeg verteka
mešana pozicija v '.
Transformisanje normalnih i tangente se vrši slično:
Ovde pretpostavljamo da matrice transformacije Fi ne sadrže nejednake
skaliranje. U suprotnom, moramo da koristimo inverznu transponaciju kada transformišemo normale
(vidi § 8.2.2).
Sledeći fragment shadera verteka pokazuje ključni kod koji pravi mešanje verteka
sa maksimalno četiri uticaja kost po verteksu:
cbuffer cbSkinned : register(b1)
{
// Max support of 96 bones per character.
float4x4 gBoneTransforms[96];
};
struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float2 TexC : TEXCOORD;
float4 TangentL : TANGENT;
#ifdef SKINNED
float3 BoneWeights : WEIGHTS;
uint4 BoneIndices : BONEINDICES;
#endif
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 ShadowPosH : POSITION0;
float4 SsaoPosH : POSITION1;
float3 PosW : POSITION2;
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];
#ifdef SKINNED
float weights[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
weights[0] = vin.BoneWeights.x;
weights[1] = vin.BoneWeights.y;
weights[2] = vin.BoneWeights.z;
weights[3] = 1.0f - weights[0] - weights[1] -
weights[2];
float3 posL = float3(0.0f, 0.0f, 0.0f);
float3 normalL = float3(0.0f, 0.0f, 0.0f);
float3 tangentL = float3(0.0f, 0.0f, 0.0f);
for(int i = 0; i < 4; ++i)
{
// Assume no nonuniform scaling when transforming
normals, so
// that we do not have to use the inversetranspose.
posL += weights[i] * mul(float4(vin.PosL, 1.0f),
gBoneTransforms[vin.BoneIndices[i]]).xyz;
normalL += weights[i] * mul(vin.NormalL,
(float3x3)gBoneTransforms[vin.BoneIndices[i]]);
tangentL += weights[i] * mul(vin.TangentL.xyz,
(float3x3)gBoneTransforms[vin.BoneIndices[i]]);
}
vin.PosL = posL;
vin.NormalL = normalL;
vin.TangentL.xyz = tangentL;
#endif
// 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.TangentL, (float3x3)gWorld);
// Transform to homogeneous clip space.
vout.PosH = mul(posW, gViewProj);
// Generate projective tex-coords to project SSAO
map onto scene.
vout.SsaoPosH = mul(posW, gViewProjTex);
// 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;
// Generate projective tex-coords to project shadow
map onto scene.
vout.ShadowPosH = mul(posW, gShadowTransform);
return vout;
}
Ako gore navedeni vertek shader vrši mešanje verteka sa maksimalno četiri kosti
uticaj po verteku, zašto onda unosimo samo tri težine po tačku umesto četiri?
Pa, setite se da ukupna težina mora biti suma na jednu; tako, za četiri težine imamo:
.
23.4 PODEŠAVANJE PODATAKA ANIMACIJE IZ DATA
Koristimo tekstualnu datoteku da sačuvate mrežnu mrežu sa animiranim podacima. To zovemo
.m3d fajl za "model 3D". Format je dizajniran za jednostavnost u učitavanju i
čitljivost - ne za performanse - a format se upravo koristi za ovu knjigu.
23.4.1 Zaglavlje
Na početku, .m3d format definiše zaglavlje koje određuje broj
materijale, vertikale, trouglove, kosti i animacije koje čine model:
***************m3d-File-Header***************
#Materials 3
#Vertices 3121
#Triangles 4062
#Bones 44
#AnimationClips 15
1.#Materiali: Broj različitih materijala koji mreža koristi.
2. # Vertices: Broj vertisa mreže.
3. #Triangles: Broj trouglova u mreži.
4. #Bones: broj kostiju u mreži.
5. #AnimationClips: Broj snimaka za animaciju u mreži.
23.4.2 Materijali
Sledeći "komad" u formatu .m3d je lista materijala. Ispod je primer prva dva materijala u vojsci.m3d fajl:
***************Materials*********************
Name: soldier_head
Diffuse: 1 1 1
Fresnel0: 0.05 0.05 0.05
Roughness: 0.5
AlphaClip: 0
MaterialTypeName: Skinned
DiffuseMap: head_diff.dds
NormalMap: head_norm.dds
Name: soldier_jacket
Diffuse: 1 1 1
Fresnel0: 0.05 0.05 0.05
Roughness: 0.8
AlphaClip: 0
MaterialTypeName: Skinned
DiffuseMap: jacket_diff.dds
NormalMap: jacket_norm.dds
Datoteka sadrži materijalne podatke sa kojima smo upoznati (difuzna, neravnina, itd.), Ali
takođe sadrži dodatne informacije kao što su teksture koje se primenjuju, bilo da je alpha klipping
treba da se primeni i ime tipa materijala. Da se koristi ime tipa materijala
naznačite koji su programi sijeda potrebni za dati materijal. U gore navedenom primeru,
tip "Skinned" označava da će materijal morati biti prikazan sa shader-om
programi koji podržavaju skinenje.
23.4.3 Podseti
Mreža se sastoji od jednog ili više podgrupa. Podgrupa je grupa trouglova u mreži koja
svi mogu biti izvedeni koristeći isti materijal. Slika 23.10 ilustruje kako mreža
predstavljanje automobila može se podijeliti na nekoliko podgrupa.
Slika 23.10. Auto razbijen po podskupu. Ovde samo materijali po podskupu
razlikuju se, ali takođe možemo zamisliti da se teksture dodaju i razlikuju. In
Pored toga, stanja renderovanja mogu se razlikovati; na primer, stakleni prozori mogu biti
rendered sa alfa mešanjem za transparentnost.
Postoji podskup koji odgovara svakom materijalu i i-i podskupa odgovara
i materijal. I-i podgrupa definiše susedni blok geometrije koji treba da se izvrši
sa i-tom materijalom.
***************SubsetTable*******************
SubsetID: 0 VertexStart: 0 VertexCount: 3915
FaceStart: 0 FaceCount: 7230
SubsetID: 1 VertexStart: 3915 VertexCount: 2984
FaceStart: 7230 FaceCount: 4449
SubsetID: 2 VertexStart: 6899 VertexCount: 4270
FaceStart: 11679 FaceCount: 6579
SubsetID: 3 VertexStart: 11169 VertexCount: 2305
FaceStart: 18258 FaceCount: 3807
SubsetID: 4 VertexStart: 13474 VertexCount: 274
FaceStart: 22065 FaceCount: 442
U prethodnom primeru, prvi 7230 trouglovi mreže (koje su referentne vertices [0,
3915)) treba da bude izveden sa materijalom 0, a sledeći 4449 trouglovi mreže (koji
referentne vertikale [3915, 6899)) treba da se izvede sa materijalom 1.
23.4.4 Podaci o vertici i trougloviSledeća dva dela podataka su samo spisak verteksa i indeksa (3 indeksa
po trougao):
***************Vertices**********************
Position: -14.34667 90.44742 -12.08929
Tangent: -0.3069077 0.2750875 0.9111171 1
Normal: -0.3731041 -0.9154652 0.150721
Tex-Coords: 0.21795 0.105219
BlendWeights: 0.483457 0.483457 0.0194 0.013686
BlendIndices: 3 2 39 34
Position: -15.87868 94.60355 9.362272
Tangent: -0.3069076 0.2750875 0.9111172 1
Normal: -0.3731041 -0.9154652 0.150721
Tex-Coords: 0.278234 0.091931
BlendWeights: 0.4985979 0.4985979 0.002804151 0
BlendIndices: 39 2 3 0

***************Triangles*********************
012
345
678
9 10 11
12 13 14

23.4.5 Transformacija offset kostiju
Prelazni deo transformacije kostiju, samo čuva spisak 4 × 4 matrica, po jedan za svaku kost.
***************BoneOffsets*******************
BoneOffset0 -0.8669753 0.4982096 0.01187624 0
0.04897417 0.1088907 -0.9928461 0
-0.4959392 -0.8601914 -0.118805 0
-10.94755 -14.61919 90.63506 1
BoneOffset1 1 4.884964E-07 3.025227E-07 0
-3.145564E-07 2.163151E-07 -1 0
4.884964E-07 0.9999997 -9.59325E-08 0
3.284225 7.236738 1.556451 1

23.4.6 Hijerarhija
Hijerarhijski deo čuva niz hijerarhije - niz integersa kao što je I unos polja daje matičnom indeksu iste
kosti.
***************BoneHierarchy*****************
ParentIndexOfBone0: -1
ParentIndexOfBone1: 0
ParentIndexOfBone2: 1
ParentIndexOfBone3: 2
ParentIndexOfBone4: 3
ParentIndexOfBone5: 4
ParentIndexOfBone6: 5
ParentIndexOfBone7: 6
ParentIndexOfBone8: 7
ParentIndexOfBone9: 7
ParentIndexOfBone10: 7
ParentIndexOfBone11: 7
ParentIndexOfBone12: 6
ParentIndexOfBone13: 12

23.4.7 Podaci o animaciji
Poslednji deo koji trebamo pročitati su snimci animacije. Svaka animacija ima čitljivu ime i listu ključnih
okvira za svaku kost u skeletu. Svaki ključni okvir čuva vremenski položaj, vektor prevođenja koji
određuje položaj kosti, vektor skaliranja navodeći skalu kostiju i kvaternion koji specificira orijentaciju
kosti.
***************AnimationClips****************
AnimationClip run_loop
{
Bone0 #Keyframes: 18
{
Time: 0 Pos: 2.538344 101.6727 -0.52932
Scale: 1 1 1
Quat: 0.4042651 0.3919331 -0.5853591 0.5833637
Time: 0.0666666
Pos: 0.81979 109.6893 -1.575387
Scale: 0.9999998 0.9999998 0.9999998
Quat: 0.4460441 0.3467651 -0.5356012 0.6276384

}
Bone1 #Keyframes: 18
{
Time: 0
Pos: 36.48329 1.210869 92.7378
Scale: 1 1 1
Quat: 0.126642 0.1367731 0.69105 0.6983587
Time: 0.0666666
Pos: 36.30672 -2.835898 93.15854
Scale: 1 1 1
Quat: 0.1284061 0.1335271 0.6239273 0.7592083

}

}
AnimationClip walk_loop
{
Bone0 #Keyframes: 33
{
Time: 0
Pos: 1.418595 98.13201 -0.051082
Scale: 0.9999985 0.999999 0.9999991
Quat: 0.3164562 0.6437552 -0.6428624 0.2686314
Time: 0.0333333
Pos: 0.956079 96.42985 -0.047988
Scale: 0.9999999 0.9999999 0.9999999
Quat: 0.3250651 0.6395872 -0.6386833
0.2781091

}
Bone1 #Keyframes: 33
{
Time: 0
Pos: -5.831432 2.521564 93.75848
Scale: 0.9999995 0.9999995 1
Quat: -0.033817 -0.000631005 0.9097761
0.4137191
Time: 0.0333333
Pos: -5.688324 2.551427 93.71078
Scale: 0.9999998 0.9999998 1
Quat: -0.033202 -0.0006390021 0.903874 0.426508

}

}
… The following code shows how we read the animation
clips from file:
void M3DLoader::ReadAnimationClips(
std::ifstream& fin,
UINT numBones,
UINT numAnimationClips,
std::unordered_map<std::string,
AnimationClip>& animations)
{
std::string ignore;
fin >> ignore; // AnimationClips header text
for(UINT clipIndex = 0; clipIndex <
numAnimationClips; ++clipIndex)
{
std::string clipName;
fin >> ignore >> clipName;
fin >> ignore; // {
AnimationClip clip;
clip.BoneAnimations.resize(numBones);
for(UINT boneIndex = 0; boneIndex < numBones;
++boneIndex)
{
ReadBoneKeyframes(fin, numBones,
clip.BoneAnimations[boneIndex]);
}
fin >> ignore; // }
animations[clipName] = clip;
}
}
void M3DLoader::ReadBoneKeyframes(
std::ifstream& fin,
UINT numBones,
BoneAnimation& boneAnimation)
{
std::string ignore;
UINT numKeyframes = 0;
fin >> ignore >> ignore >> numKeyframes;
fin >> ignore; // {
boneAnimation.Keyframes.resize(numKeyframes);
for(UINT i = 0; i < numKeyframes; ++i)
{
float t = 0.0f;
XMFLOAT3 p(0.0f, 0.0f, 0.0f);
XMFLOAT3 s(1.0f, 1.0f, 1.0f);
XMFLOAT4 q(0.0f, 0.0f, 0.0f, 1.0f);
fin >> ignore >> t;
fin >> ignore >> p.x >> p.y >> p.z;
fin >> ignore >> s.x >> s.y >> s.z;
fin >> ignore >> q.x >> q.y >> q.z >> q.w;
boneAnimation.Keyframes[i].TimePos = t;
boneAnimation.Keyframes[i].Translation = p;
boneAnimation.Keyframes[i].Scale = s;
boneAnimation.Keyframes[i].RotationQuat = q;
}
fin >> ignore; // }
}
23.4.8 M3DLoader
The code to load the data from an .m3d file is contained in LoadM3D.h/.cpp, in
particular, the LoadM3d function:
bool M3DLoader::LoadM3d(
const std::string& filename,
std::vector<SkinnedVertex>& vertices,
std::vector<USHORT>& indices,
std::vector<Subset>& subsets,
std::vector<M3dMaterial>& mats,
SkinnedData& skinInfo)
{
std::ifstream fin(filename);
UINT numMaterials = 0;
UINT numVertices = 0;
UINT numTriangles = 0;
UINT numBones = 0;
UINT numAnimationClips = 0;
std::string ignore;
if( fin )
{
fin >> ignore; // file header text
fin >> ignore >> numMaterials;
fin >> ignore >> numVertices;
fin >> ignore >> numTriangles;
fin >> ignore >> numBones;
fin >> ignore >> numAnimationClips;
std::vector<XMFLOAT4X4> boneOffsets;
std::vector<int> boneIndexToParentIndex;
std::unordered_map<std::string, AnimationClip>
animations;
ReadMaterials(fin, numMaterials, mats);
ReadSubsetTable(fin, numMaterials, subsets);
ReadSkinnedVertices(fin, numVertices, vertices);
ReadTriangles(fin, numTriangles, indices);
ReadBoneOffsets(fin, numBones, boneOffsets);
ReadBoneHierarchy(fin, numBones,
boneIndexToParentIndex);
ReadAnimationClips(fin, numBones,
numAnimationClips, animations);
skinInfo.Set(boneIndexToParentIndex, boneOffsets,
animations);
return true;
}
return false;
}
Pomoćni funkcioneri ReadMateriali, i sl., Su jednostavni razdvajanje tekstualnog fajla koristeći std ::
ifstream. Ostavimo je čitanju da ispitamo izvorni kod za detalji o implementaciji.
23.5 ANALIZA KARAKTERA DEMO
Kao što smo videli u šifriranim mrežnim šifriranim kodovima, konačne transformacije kostova se čuvaju u
a konstantnog bafera u kojem se pristupa u vertek shader-u radi animacije transformacije.
cbuffer cbSkinned : register(b1)
{
// Max support of 96 bones per character.
float4x4 gBoneTransforms[96];
};
We therefore need to add these new constant buffers, one for each skinned mesh
object, to our frame resources:
struct SkinnedConstants
{
DirectX::XMFLOAT4X4 BoneTransforms[96];
};
std::unique_ptr<UploadBuffer<SkinnedConstants>>
SkinnedCB = nullptr;
SkinnedCB =
std::make_unique<UploadBuffer<SkinnedConstants>>(
device, skinnedObjectCount, true);
Za svaku instancu animiranog karaktera će nam biti potreban jedan SkinnedConstants.
Instanca animiranog karaktera će obično biti sastavljena od više stavki renderera (po jedan po
materijalu), ali sve stavke renderera za istu instancu karaktera mogu da dele iste
SkinnedConstants, jer svi koriste isti osnovni animirani skelet.
Da predstavimo instancu animiranog karaktera u određenoj instanci, definišemo
sledeća struktura:

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.

You might also like