編寫本內容僅僅是爲了完善當前的教程體系,入門級別的內容其實基本上都是千篇一概,僅有一些必要細節上的擴充。要入門HLSL,只是掌握入門語法,即使把HLSL的所有語法也吃透了也並不表明你就能着色器代碼了,還須要結合到渲染管線中,隨着教程的不斷深刻來不斷學習須要用到的新的語法,而後嘗試修改着色器,再根據實際需求本身編寫着色器來實現特定的效果。html
注意:在翻閱HLSL文檔的時候,要避開Effects11相關的內容。由於當前教程與Effects11是不兼容的。git
DirectX11 With Windows SDK完整目錄github
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。函數
經常使用標量類型以下:oop
類型 | 描述 |
---|---|
bool | 32位整數值用於存放邏輯值true和false |
int | 32位有符號整數 |
uint | 32位無符號整數 |
half | 16位浮點數(僅提供用於向後兼容) |
float | 32位浮點數 |
double | 64位浮點數 |
注意:一些平臺可能不支持
int
,half
和double
,若是出現這些狀況將會使用float
來模擬佈局
此外,浮點數還有規格化的形式:性能
snorm float
是IEEE 32位有符號且規格化的浮點數,表示範圍爲-1到1unorm float
是IEEE 32位無符號且規格化的浮點數,表示範圍爲0到1向量類型能夠支持2到4個同類元素學習
一種表示方式是使用相似模板的形式來描述測試
vector<float, 4> vec1; // 向量vec1包含4個float元素 vector<int, 2> vec2; // 向量vec2包含2個int元素
另外一種方式則是直接在基本類型後面加上數字
float4 vec1; // 向量vec1包含4個float元素 int3 vec2; // 向量vec2包含3個int元素
固然,只使用vector
自己則表示爲一種包含4個float元素的類型
vector vec1; // 向量vec1包含4個float元素
向量類型有以下初始化方式:
float2 vec0 = {0.0f, 1.0f}; float3 vec1 = float3(0.0f, 0.1f, 0.2f); float4 vec2 = float4(vec1, 1.0f);
向量的第1到第4個元素既能夠用x, y, z, w來表示,也能夠用r, g, b, a來表示。除此以外,還能夠用索引的方式來訪問。下面展現了向量的取值和訪問方式:
float4 vec0 = {1.0f, 2.0f, 3.0f, 0.0f}; float f0 = vec0.x; // 1.0f float f1 = vec0.g; // 2.0f float f2 = vec0[2]; // 3.0f vec0.a = 4.0f; // 4.0f
咱們還可使用swizzles
的方式來進行賦值,能夠一次性提供多個份量進行賦值操做,這些份量的名稱能夠重複出現:
float4 vec0 = {1.0f, 2.0f, 3.0f, 4.0f}; float3 vec1 = vec0.xyz; // (1.0f, 2.0f, 3.0f) float2 vec2 = vec0.rg; // (1.0f, 2.0f) float4 vec3 = vec0.zzxy; // (4.0f, 4.0f, 1.0f, 2.0f) vec3.wxyz = vec3; // (2.0f, 4.0f, 4.0f, 1.0f) vec3.yw = ve1.zz; // (2.0f, 3.0f, 4.0f, 3.0f)
矩陣有以下類型(以float
爲例):
float1x1 float1x2 float1x3 float1x4 float2x1 float2x2 float2x3 float2x4 float3x1 float3x2 float3x3 float3x4 float4x1 float4x2 float4x3 float4x4
此外,咱們也可使用相似模板的形式來描述:
matrix<float, 2, 2> mat1; // float2x2
而單獨的matrix
類型的變量實際上能夠看作是一個包含了4個vector
向量的類型,即包含16個float
類型的變量。matrix
自己也能夠寫成float4x4
:
matrix mat1; // float4x4
矩陣的初始化方式以下:
float2x2 mat1 = { 1.0f, 2.0f, // 第一行 3.0f, 4.0f // 第二行 }; float3x3 TBN = float3x3(T, B, N); // T, B, N都是float3
矩陣的取值方式以下:
matrix M; // ... float f0 = M._m00; // 第一行第一列元素(索引從0開始) float f1 = M._12; // 第一行第二列元素(索引從1開始) float f2 = M[0][1]; // 第一行第二列元素(索引從0開始) float4 f3 = M._11_12; // Swizzles
矩陣的賦值方式以下:
matrix M; vector v = {1.0f, 2.0f, 3.0f, 4.0f}; // ... M[0] = v; // 矩陣的第一行被賦值爲向量v M._m11 = v[0]; // 等價於M[1][1] = v[0];和M._22 = v[0]; M._12_21 = M._21_12; // 交換M[2][3]和M[3][2]
不管是向量仍是矩陣,乘法運算符都是用於對每一個份量進行相乘,例如:
float4 vec0 = 2.0f * float4(1.0f, 2.0f, 3.0f, 4.0f); //(2.0f, 4.0f, 6.0f, 8.0f) float4 vec1 = vec0 * float4(1.0f, 0.2f, 0.1f, 0.0f); //(2.0f, 0.8f, 0.6f, 0.0f)
若要進行向量與矩陣的乘法,則須要使用mul
函數。
在C++代碼層中,DirectXMath數學庫建立的矩陣都是行矩陣,但當矩陣從C++傳遞給HLSL時,HLSL默認是列矩陣的,看起來就好像傳遞的過程當中進行了一次轉置那樣。若是但願不發生轉置操做的話,能夠添加修飾關鍵字row_major
:
row_major matrix M;
和C++同樣,咱們能夠聲明數組:
float M[4][4]; int p[4]; float3 v[12]; // 12個3D向量
HLSL的結構體和C/C++的十分類似,它能夠存聽任意數目的標量,向量和矩陣類型,除此以外,它還能夠存放數組或者別的結構體類型。結構體的成員訪問也和C/C++類似:
struct A { float4 vec; }; struct B { int scalar; float4 vec; float4x4 mat; float arr[8]; A a; }; // ... B b; b.vec = float4(1.0f, 2.0f, 3.0f, 4.0f);
關鍵字 | 含義 |
---|---|
static | 該着色器變量將不會暴露給C++應用層,須要在HLSL中本身初始化,不然使用默認初始化 |
extern | 與static相反,該着色器變量將會暴露給C++應用層 |
uniform | 該着色器變量容許在C++應用層被改變,但在着色器執行的過程當中,其值始終保持不變(運行前可變,運行時不變)。着色器程序中的全局變量默認爲既uniform又extern |
const | 和C++中的含義相同,它是一個常量,須要被初始化且不能夠被修改 |
HLSL有着極其靈活的類型轉換機制。HLSL中的類型轉換語法和C/C++的相同。下面是一些例子:
float f = 4.0f; float4x4 m = (float4x4)f; // 將浮點數f複製到矩陣m的每個元素當中 float3 n = float3(...); float3 v = 2.0f * n - 1.0f; // 這裏1.0f將會隱式轉換成(1.0f, 1.0f, 1.0f) float4x4 WInvT = float4x4(...); float3x3 mat = (float3x3)WInvT; // 只取4x4矩陣的前3行前3列
和C++同樣,typedef關鍵字用來聲明一個類型的別稱:
typedef float3 point; typedef const float cfloat; point p; // p爲float3 cfloat f = 1.0f; // f爲const float
本教程不列出關鍵字,在學習的時候再逐漸接觸鬚要用到的會好一點。
C/C++中能用的運算符在HLSL中基本上都能用,也包括位運算。這裏只列出運算符的一些特例狀況。
例如:
float3 pos = {1.0f, 2.0f, 3.0f}; float3 p1 = pos * 2.0f; // (2.0f, 4.0f, 6.0f) float3 p2 = pos * pos; // (1.0f, 4.0f, 9.0f) bool3 b = (p1 == p2); // (false, true, false) ++pos; // (2.0f, 3.0f, 4.0f)
所以,若是乘法運算符的兩邊都是矩陣,則表示爲矩陣的份量乘法,而不是矩陣乘法。
最後是二元運算中變量類型的提高規則:
x
會變爲(x, x, x)
。可是不支持像float2
到float3
的提高。HLSL也支持if
, else
, continue
, break
, switch
關鍵字,此外discard
關鍵字用於像素着色階段拋棄該像素。
條件的判斷使用一個布爾值進行,一般由各類邏輯運算符或者比較運算符操做獲得。注意向量之間的比較或者邏輯操做是獲得一個存有布爾值的向量,不可以直接用於條件判斷,也不能用於switch
語句。
基於值的條件分支只有在程序執行的時候被編譯好的着色器彙編成兩種方式:判斷(predication)和動態分支(dynamic branching)。
若是使用的是判斷的形式,編譯器會提早計算兩個不一樣分支下表達式的值。而後使用比較指令來基於比較結果來"選擇"正確的值。
而動態分支使用的是跳轉指令來避免一些非必要的計算和內存訪問。
着色器程序在同時執行的時候應當選擇相同的分支,以防止硬件在分支的兩邊執行。一般狀況下,硬件會同時將一系列連續的頂點數據傳入到頂點着色器並行計算,或者是一系列連續的像素單元傳入到像素着色器同時運算等。
動態分支會因爲執行分支指令所帶來的開銷而致使必定的性能損失,所以要權衡動態分支的開銷和能夠跳過的指令數目。
一般狀況下編譯器會自行選擇使用判斷仍是動態分支,但咱們能夠經過重寫某些屬性來修改編譯器的行爲。咱們能夠在條件語句前能夠選擇添加下面兩個屬性之一:
屬性 | 描述 |
---|---|
[branch] | 根據條件值的結果,只計算其中一邊的內容,會產生跳轉指令。默認不加屬性的條件語句爲branch型。 |
[flatten] | 兩邊的分支內容都會計算,而後根據條件值選擇其中一邊。能夠避免跳轉指令的產生。 |
用法以下:
[flatten] if (x) { x = sqrt(x); }
HLSL也支持for
, while
和do while
循環。和條件語句同樣,它可能也會在基於運行時的條件值判斷而產生動態分支,從而影響程序性能。若是循環次數較小,咱們可使用屬性[unroll]
來展開循環,代價是產生更多的彙編指令。用法以下:
times = 4; sum = times; [unroll] while (times--) { sum += times; }
若沒有添加屬性,默認使用的則爲[loop]
。
函數的語法也和C/C++的十分相似,但它具備如下屬性:
此外,HLSL函數的形參能夠指定輸入/輸出類別:
輸入輸出類別 | 描述 |
---|---|
in | 僅讀入。實參的值將會複製到形參上。若未指定則默認爲in |
out | 僅輸出。對形參修改的最終結果將會複製到實參上 |
inout | 即in和out的組合 |
例如:
bool foo(in bool b, // 輸入的bool類型參數 out int r1, // 輸出的int類型參數 inout float r2) // 具有輸入/輸出的float類型參數 { if (b) { f1 = 5; } else { r1 = 1; } // 注意r1不能出如今等式的右邊 // r2既能夠被讀入,也能夠寫出結果到外面的實參上 r2 = r2 * r2 * r2; return true; }
HLSL提供了一些內置全局函數,它一般直接映射到指定的着色器彙編指令集。這裏只列出一些比較經常使用的函數:
函數名 | 描述 | 最小支持着色器模型 |
---|---|---|
abs | 每一個份量求絕對值 | 1.1 |
acos | 求x份量的反餘弦值 | 1.1 |
all | 測試x份量是否按位全爲1 | 1.1 |
any | 測試x份量是否按位存在1 | 1.1 |
asdouble | 將值按位從新解釋成double類型 | 5.0 |
asfloat | 將值按位從新解釋成float類型 | 4.0 |
asin | 求x份量的反正弦值 | 1.1 |
asint | 將值按位從新解釋成int類型 | 4.0 |
asuint | 將值按位從新解釋成uint類型 | 4.0 |
atan | 求x份量的反正切值值 | 1.1 |
atan2 | 求(x,y)份量的反正切值 | 1.1 |
ceil | 求不小於x份量的最小整數 | 1.1 |
clamp | 將x份量的值限定在[min, max] | 1.1 |
clip | 丟棄當前像素,若是x份量的值小於0 | 1.1 |
cos | 求x份量的餘弦值 | 1.1 |
cosh | 求x份量的雙曲餘弦值 | 1.1 |
countbits | 計算輸入整數的位1個數(對每一個份量) | 5.0 |
cross | 計算兩個3D向量的叉乘 | 1.1 |
ddx | 估算屏幕空間中的偏導數\(\partial \mathbf{p} / \partial x\)。這使咱們能夠肯定在屏幕空間的x軸方向上,相鄰像素間某屬性值\(\mathbf{p}\)的變化量 | 2.1 |
ddy | 估算屏幕空間中的偏導數\(\partial \mathbf{p} / \partial y\)。這使咱們能夠肯定在屏幕空間的y軸方向上,相鄰像素間某屬性值\(\mathbf{p}\)的變化量 | 2.1 |
degrees | 將x份量從弧度轉換爲角度制 | 1.1 |
determinant | 返回方陣的行列式 | 1.1 |
distance | 返回兩個點的距離值 | 1.1 |
dot | 返回兩個向量的點乘 | 1.1 |
dst | 計算距離向量 | 5.0 |
exp | 計算e^x | 1.1 |
exp2 | 計算2^x | 1.1 |
floor | 求不大於x份量的最大整數 | 1.1 |
fmod | 求x/y的餘數 | 1.1 |
frac | 返回x份量的小數部分 | 1.1 |
isfinite | 返回x份量是否爲有限的布爾值 | 1.1 |
isinf | 返回x份量是否爲無窮大的布爾值 | 1.1 |
isnan | 返回x份量是否爲nan的布爾值 | 1.1 |
length | 計算向量的長度 | 1.1 |
lerp | 求x + s(y - x) | 1.1 |
lit | 返回一個光照係數向量(環境光亮度, 漫反射光亮度, 鏡面光亮度, 1.0f) | 1.1 |
log | 返回以e爲底,x份量的對數 | 1.1 |
log10 | 返回以10爲底,x份量的對數 | 1.1 |
log2 | 返回以2爲底,x份量的天然對數 | 1.1 |
mad | 返回mvalue * avalue + bvalue | 1.1 |
max | 返回x份量和y份量的最大值 | 1.1 |
min | 返回x份量和y份量的最小值 | 1.1 |
modf | 將值x分開成整數部分和小數部分 | 1.1 |
mul | 矩陣乘法運算 | 1 |
normalize | 計算規格化的向量 | 1.1 |
pow | 返回x^y | 1.1 |
radians | 將x份量從角度值轉換成弧度值 | 1 |
rcp | 對每一個份量求倒數 | 5 |
reflect | 返回反射向量 | 1 |
refract | 返回折射向量 | 1.1 |
reversebits | 對每一個份量進行位的倒置 | 5 |
round | x份量進行四捨五入 | 1.1 |
rsqrt | 返回1/sqrt(x) | 1.1 |
saturate | 對x份量限制在[0,1]範圍 | 1 |
sign | 計算符號函數的值,x大於0爲1,x小於0爲-1,x等於0則爲0 | 1.1 |
sin | 計算x的正弦 | 1.1 |
sincos | 返回x的正弦和餘弦 | 1.1 |
sinh | 返回x的雙曲正弦 | 1.1 |
smoothstep | 給定範圍[min, max],映射到值[0, 1]。小於min的值取0,大於max的值取1 | 1.1 |
step | 返回(x >= a) ? 1 : 0 | 1.1 |
tan | 返回x的正切值 | 1.1 |
tanh | 返回x的雙曲正切值 | 1.1 |
transpose | 返回矩陣m的轉置 | 1 |
trunc | 去掉x的小數部分並返回 | 1 |
語義一般是附加在着色器輸入/輸出參數上的字符串。它在着色器程序的用途以下:
輸入 | 描述 | 類型 |
---|---|---|
BINORMAL[n] | 副法線(副切線)向量 | float4 |
BLENDINDICES[n] | 混合索引 | uint |
BLENDWEIGHT[n] | 混合權重 | float |
COLOR[n] | 漫反射/鏡面反射顏色 | float4 |
NORMAL[n] | 法向量 | float4 |
POSITION[n] | 物體座標系下的頂點座標 | float4 |
POSITIONT | 變換後的頂點座標 | float4 |
PSIZE[n] | 點的大小 | float |
TANGENT[n] | 切線向量 | float4 |
TEXCOORD[n] | 紋理座標 | float4 |
Output | 僅描述輸出 | Type |
FOG | 頂點霧 | float |
n是一個可選的整數,從0開始。好比POSITION0, TEXCOORD1等等。
輸入 | 描述 | 類型 |
---|---|---|
COLOR[n] | 漫反射/鏡面反射顏色 | float4 |
TEXCOORD[n] | 紋理座標 | float4 |
Output | 僅描述輸出 | Type |
DEPTH[n] | 深度值 | float |
全部的系統值都包含前綴SV_
。這些系統值將用於某些着色器的特定用途(並未所有列出)
系統值 | 描述 | 類型 |
---|---|---|
SV_Depth | 深度緩衝區數據,能夠被任何着色器寫入/讀取 | float |
SV_InstanceID | 每一個實例都會在運行期間自動生成一個ID。在任何着色器階段都能讀取 | uint |
SV_IsFrontFace | 指定該三角形是否爲正面。能夠被幾何着色器寫入,以及能夠被像素着色器讀取 | bool |
SV_Position | 若被聲明用於輸入到着色器,它描述的是像素位置,在全部着色器中均可用,可能會有0.5的偏移值 | float4 |
SV_PrimitiveID | 每一個原始拓撲都會在運行期間自動生成一個ID。可用在幾何/像素着色器中寫入,也能夠在像素/幾何/外殼/域着色器中讀取 | uint |
SV_StencilRef | 表明當前像素着色器的模板引用值。只能夠被像素着色器寫入 | uint |
SV_VertexID | 每一個實例都會在運行期間自動生成一個ID。僅容許做爲頂點着色器的輸入 | uint |
全部的着色器階段使用通用着色器核心來實現相同的基礎功能。此外,頂點着色階段、幾何着色階段和像素着色階段則提供了獨特的功能,例如幾何着色階段能夠生成新的圖元或刪減圖元,像素着色階段能夠決定當前像素是否被拋棄等。下圖展現了數據是怎麼流向一個着色階段,以及通用着色器核心與着色器內存資源之間的關係:
Input Data:頂點着色器從輸入裝配階段獲取數據;幾何着色器則從上一個着色階段的輸出獲取等等。經過給形參引入可使用的系統值能夠提供額外的輸入
Output Data:着色器生成輸出的結果真後傳遞給管線的下一個階段。有些輸出會被通用着色器核心解釋成特定用途(如頂點位置、渲染目標對應位置的值),另一些輸出則由應用程序來解釋。
Shader Code:着色器代碼能夠從內存讀取,而後用於執行代碼中所指望的內容。
Samplers:採樣器決定了如何對紋理進行採樣和濾波。
Textures:紋理可使用採樣器進行採樣,也能夠基於索引的方式按像素讀取。
Buffers:緩衝區可使用讀取相關的內置函數,在內存中按元素直接讀取。
Constant Buffers:常量緩衝區對常量值的讀取有所優化。他們被設計用於CPU對這些數據的頻繁更新,所以他們有額外的大小、佈局和訪問限制。
着色器常量存在內存中的一個或多個緩衝區資源當中。他們能夠被組織成兩種類型的緩衝區:常量緩衝區(cbuffers)和紋理緩衝區(tbuffers)。關於紋理緩衝區,咱們不在這討論。
常量緩衝區容許C++端將數據傳遞給HLSL中使用,在HLSL端,這些傳遞過來的數據不可更改,於是是常量。常量緩衝區對這種使用方式有所優化,表現爲低延遲的訪問和容許來自CPU的頻繁更新,所以他們有額外的大小、佈局和訪問限制。
聲明方式以下:
cbuffer VSConstants { float4x4 g_WorldViewProj; fioat3 g_Color; uint g_EnableFog; float2 g_ViewportXY; float2 g_ViewportWH; }
因爲咱們寫的是原生HLSL,當咱們在HLSL中聲明常量緩衝區時,還須要在HLSL的聲明中使用關鍵字register
手動指定對應的寄存器索引,而後編譯器會爲對應的着色器階段自動將其映射到15個常量緩衝寄存器的其中一個位置。這些寄存器的名字爲b0
到b14
:
cbuffer VSConstants : register(b0) { float4x4 g_WorldViewProj; fioat3 g_Color; uint g_EnableFog; float2 g_ViewportXY; float2 g_ViewportWH; }
在C++端是經過ID3D11DeviceContext::*SSetConstantBuffers
指定特定的槽(slot)來給某一着色器階段對應的寄存器索引提供常量緩衝區的數據。
若是是存在多個不一樣的着色器階段使用同一個常量緩衝區,那就須要分別給這兩個着色器階段設置好相同的數據。
綜合前面幾節內容,下面演示了頂點着色器和常量緩衝區的用法:
cbuffer ConstantBuffer : register(b0) { float4x4 g_WorldViewProj; } void VS_Main( in float4 inPos : POSITION, // 綁定變量到輸入裝配器 in uint VID : SV_VertexID, // 綁定變量到系統生成值 out float4 outPos : SV_Position) // 告訴管線將該值解釋爲輸出的頂點位置 { outPos = mul(inPos, g_WorldViewProj); }
上面的代碼也能夠寫成:
cbuffer ConstantBuffer : register(b0) { float4x4 g_WorldViewProj; } struct VertexIn { float4 inPos : POSITION; // 源自輸入裝配器 uint VID : SV_VertexID; // 源自系統生成值 }; float4 VS_Main(VertexIn vIn) : SV_Position { return mul(vIn.inPos, g_WorldViewProj); }
有關常量緩衝區的打包規則,建議在閱讀到時索引緩衝區、常量緩衝區一章時,再來參考雜項篇的HLSL常量緩衝區的打包規則。
DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。