DirectX 11 Урок по C #2 Начертайте триъгълник, програмиране за всеки
Така че здравейте. Вие сте от онези, които не са се уплашили от първия урок и са готови да продължат напред. Нека да минем без философски въведения този път, особено след като имаме много работа.
Да рисуваме. Защо триъгълник? Просто защото е базов модел за 3D графика - примитив. Примитивите се наричат форми, от които е създадено всичко. Освен с триъгълници, можете да рисувате с линии и точки, но кой има нужда от герой или чудовище, състоящо се от линии и живеещо в свят на точки? Следователно, когато се говори за примитиви, те обикновено имат предвид триъгълници. Представете си, че ако успешно комбинирате два триъгълника, ще получите квадрат. И ако комбинирате 6 квадрата, вече можете да създадете куб. Но ние ще дадем обем на нашите творения малко по-късно.
Разбрахме защо имаме нужда от триъгълници. Сега нека помислим как можете да нарисувате такъв невероятен обект. Тук няма нищо сложно: просто задайте координатите на три точки и изяснете на Direct3D, че искаме да начертаем точно триъгълник, а не точки или линии). Изглежда нещо подобно:
На практика нещата са малко по-сложни. Тези точки се наричат върхове в 3D графиките. Един връх може да съдържа информация не само за позицията си в пространството. В противен случай бихме могли да създадем само сиви едноцветни светове. Обикновено те правят това: те създават структура, която ще описва върховете в играта. Такава структура може да съдържа информация за цвета на върха, координатите на текстурата, нормата (използва се за изчисляване на осветлението). В този урок нашите върхове изглеждат много прости:
Ужасяващият XMFLOAT3 е просто структура от три плаващи стойности, която определя координатите x, y и z на върха. Координатата z не енеобходимо, защото нашият триъгълник все още е плосък, като торта.
След като се справихме с формата на върховете, ще създадем буфер. Както може би се досещате, ще създадем масив от три върха. След това зареждаме тези данни в DirectX буферID3D11Buffer.
Проблемът е, че DirectX няма представа за формата на нашите върхове. Както казах, много различна информация може да се съхранява за всеки връх, така че трябва да кажем на DirectX за съдържанието на структуратаSimpleVertex. Тук ще ни помогне функцията CreateInputLayout(...), разположена в интерфейса на устройството (устройство,ID3D11Device ). Той ще създаде интерфейсен обект Input Layout (шаблон за въвеждане,ID3D11InputLayout), който ще свържем с устройството за рисуване:
Ще разгледаме подробно този процес още при писането на програма.
Строго погледнато, в шейдърите няма нищо сложно. От друга страна, това бяха шейдъри, които не можах да преодолея преди много години, когато изучавах DirectX8. Тогава обаче те бяха само обещаваща иновация, сега шейдърите се превърнаха в неразделна част от триизмерното програмиране. Шейдърът е просто подпрограма (функция), използвана за дефиниране/промяна на параметрите на всеки обект или изображение. Най-лесният начин е да го сравните с малко човече, което стои на конвейера. Да кажем, че нашият връх №1 се движи по конвейерната лента за изобразяване на триъгълници. Някъде към края лентата се забавя, шейдърът прави малка промяна на върха и го изпраща по-нататък, право във функцията, която рисува върха на екрана. Връх 2 и 3 ще направят същия път.В този пример разгледахме работата на върховите шейдъри. Има и геометрични и пикселни. Геометричните обработват цял примитив (триъгълник), а пикселните, както се досещате, всекипиксел. Например, за да покрием триъгълник с текстура, в пикселния шейдър можем да изчислим цвета на текстурата в пикселните координати и да върнем този цвят.
Както казах, шейдърите са само функции. Верхният шейдър връща обект, описващ върха (имамеSimpleVertex ), а пикселният шейдър връща цвета. Но шейдърите не са написани на C++, а на подобен език, наречен HLSL. Шейдърите ще се съхраняват в отделен файл Urok2.fx, който не е включен в програмата. Ще заредим и компилираме този файл динамично, докато зареждаме програмата (не се страхувайте, DirectX ще свърши по-голямата част от работата вместо нас).
Ето пример за пикселен шейдър, който ще оцвети триъгълник в жълто в нашата програма:
Функцията за рисуване за всяка точка от триъгълника ще извика шейдъра PS(...) (съкращение от Pixel Shader) и ще го запълни с получения цвят.
Мисля, че ще бъде по-лесно да научите трудни места още в хода на писане на програма.
Общата схема е следната: Създаване на прозорец a Създаване на DX устройства a Зареждане на шейдъри, създаване на върхов буфер и върхов формат (Input Layout обект) a Готово! В цикъла на съобщенията рисуваме триъгълник от нашия буфер. Нека отново създадем празен обект и добавим файла Urok2.cpp и файла с иконата с ID IDI_ICON1 към него. Незабавно добавете библиотеките към командния ред на линкера. Голяма част от кода няма да се различава от предишния урок, така че можете да използвате стария проект като основа.
Тази функция зарежда и компилира шейдър от файл в движение. Няма да се спираме на това, въпреки че тук всичко е много просто. Всъщност извикването на D3DX11CompileFromFile(...) върши цялата работа, а останалата част от кода обработва възможни грешки. Тъй като трябва да заредим два шейдъра, Microsoft реши да премести тази обработка в отделенфункция.
Вторият урок, функцията за инициализация на устройството, както можете да видите, беше прехвърлен без хирургическа намеса.
Надявам се, че няма нужда да ви напомням, че всички Direct3D обекти се създават с помощта на устройство? Тук всичко е много просто. В случай на грешка, не забравяйте да освободите паметта. Шейдърът е създаден, сега нека опишем формата на нашите върхове.
Както вече споменахме, върховете могат да имат различни параметри - координати в пространството, нормали, цвят, координати на текстура. Шаблонът за върха указва кои параметри съдържат върховете, които ще използваме. Нашите върхове (SimpleVertex) съдържат само информация за координатите в пространството. Следователно структурата, описваща формата на върховете, се състои от един ред. В този ред се интересуваме от следните параметри:
1) „ПОЗИЦИЯ“ е семантично име. Ясно е, че описваме позицията на върха и сега DirectX ще разбере това.
2) DXGI_FORMAT_R32G32B32_FLOAT - формат или размер. Обърнете внимание на частта R32G32B32: имаме три стойности от 32 бита всяка (float) или общо 12 байта. Трудно ми е да разбера начина на мислене на програмистите на Microsoft, които решиха: „Колко готино! Положението в пространството се дава от три координати, а цветът от три компонента. Какво съвпадение! Да спестим от константите, нека позицията също да бъде дадена с буквите R, G, B! И същите тези хора измислят такива константи, чиито имена не се вписват в ширината на екрана! 3) Отместване в байтове (пети параметър, равен на нула). Нашето отместване е нула, защотоSimpleVertex::Pos е в самото начало наSimpleVertex.
Нека ви напомня, че обектътpVSBlob все още съдържа компилирания вертекс шейдър. Отново всичко е примитивно: създаваме обект на шаблон за въвеждане (Input Layout) и го свързваме към чертожното устройство. Сега малко dejavu: Нека повторим последните стъпки (с изключение на създаването на шаблон за въвеждане) за пикселния шейдър.
Бързо се справихме със зареждането на шейдъри и настройката на формата на върха. Остава това, около което беше целият шум - създаване на върхов буфер, състоящ се от три триъгълни върха. Нека разбием това парче по парче:
В този случай редът на точките няма значение. Триъгълникът винаги е триъгълник, независимо от кой връх започвате да рисувате и в какъв ред не го рисувате. Друго нещо е например квадрат или по-сложен обект. Можете да се заблудите и да вземете някаква безкрайно голяма кофа вместо космически кораб. Въпреки това ще говорим подробно за реда на изтегляне на точки от буфера.
Стар приятел е структура, описваща буфер. Все пак буферите са различни, но интерфейсът е един и същ за всички - ID3D11Buffer. За щастие на помощ идва параметърът BindFlags, който задава типа на буфера. Той също така използва спомагателна структура - указател към масив от върхове. С негова помощ създаваме буферен обект. И последното действие с буфера на върховете: задайте го като източник на върхове за рисуване в устройството за рисуване (контекст на устройството).
Следващият ред ще ни забави дълго време, така че се пригответе. Можете да се настаните удобно или, обратно, да се разходите. Дори подготвих снимка за този случай.
Тук задаваме топологията на примитивите като Triangle List - списък от триъгълници. Човешки казано, ние задаваме начина (последователността) на рисуване на примитиви по върхове от буфера. И така, какви са нашите възможности тук? Първо, нека си припомним, че можете да рисувате не само триъгълници, но и точки и линии. Следователно изброяването D3D11_PRIMITIVE_TOPOLOGY съдържа членовете (…)_POINTLIST и (…)_LINELIST. Но не това е важното. Помнете какво казах за квадратитекосмически кораб и безкрайно голяма кофа? Нека си представим, че имаме 7 точки (A-G) в пространството, както е показано по-долу:
Наистина искаме да начертаем 5 триъгълника в тези точки. Списък с триъгълници е метод за рисуване, който указва трите върха на всеки триъгълник поотделно. Тоест ще трябва да създадем масив от SimpleVertex v[15] = < A, B, C, B, C, D, C, D, E, D, E, F, E, F, G >. Много излишна информация, нали? Но този начин на рисуване има предимства, които ще станат ясни по-късно.
Има втори често срещан начин за рисуване - това е лента от триъгълници (D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP). Когато рисува лента, алгоритъмът използва последните три точки всеки път, за да начертае триъгълник. Масивът от върхове ще изглежда така: SimpleVertex v[7] = < A, B, C, D, E, F, G >.
Функция Direct3D, която рисува триъгълници от буфер, би го третирала по следния начин: v[0]-v[1]-v[2] е първият триъгълник, v[1]-v[2]-v[3] е вторият триъгълник и т.н.
В нашия пример има само един триъгълник, така че промяната на метода на рисуване от списъка към лентата няма да промени нищо.
Урокът се оказа много дълъг, но има още много за разказване. Така че да продължим.
Затворете функцията InitGeometry(). Това, което следва, е имплементацията на функция, която премахва DirectX обекти. В кода на приложението остана само функцията за рисуване:
Тук има три нови реда. Първите два свързват шейдъри към чертожния обект, вторият рисува три върха от буфера на върховете (който свързахме с обектаg_pImmediateContext в средата на функцията InitGeometry().
4. И отново шейдъри.
Не забравяйте, че това не е C++, въпреки че изглежда много подобно. Езикът се нарича HLSL (High Level Shading Language).Функцията за шейдър на върха взема координатите на върха и ги предава непроменени. Тукfloat4, както лесно можете да се досетите, е структура от 4 променливи от типfloat (x, y, z и a). Последната променлива изобщо не е необходима. Семантичното име „POSITION“, отделено от името на параметъра с двоеточие, ясно показва, че говорим за координатите на върха (в края на краищата върхът може да съдържа други параметри). Беше ПОЗИЦИЯ, която посочихме при създаването на шаблона за въвеждане. Семантичното име „SV_POSITION“ показва, че функцията за шейдър връща подготвена позиция на върха.
Пикселният шейдър връща цвят във формат RGBA.
Запазете файла, компилирайте проекта и стартирайте.
За да разберете добре как работят шейдърите, нека направим два експеримента.
1. Добавете първия ред към върховия шейдър:
Триъгълникът трябва да се свие, защото координатите X на върховете ще бъдат наполовина.
2. Нека леко модифицираме кода на пикселния шейдър.
Нека цветът на един пиксел зависи от неговите координати!
3. Ами ако вместо триъгълник искаме да начертаем квадрат? Трябва да променим някои редове. Първо, трябва да добавите върха към масива с координатите на върха:
Второ, редът, който задава размера на буфера на върховете, преди да бъде създаден, ще се промени:
Както вероятно се досещате, променихме начина, по който показваме примитивите:
И във функцията за изобразяване сега трябва да нарисувате не три точки, а четири:
Това е всичко. Като упражнение можете да нарисувате кръг с помощта на функциите sinf и cosf (подсказка: кръгът е просто правилен многоъгълник с много ъгли).
Твърд? Мога да се радвам: просто този урок беше много труден. Следващия път ще преместим триъгълника от 2D на3D. Или може би дори заменете плоския триъгълник с нещо по-интересно.