D3D9 Shader例項教程
啥是Shader?
Shader是一段執行在GPU上的小程式,是執行在GPU上的Pipeline上的特定的可程式設計單元的小程式。
從D3D9 API層面學習Shader程式設計
隨著Unity3D的流行,很多同學會把Unity ShaderLab和Shader這兩個概念搞混。 ShaderLab是Unity引擎中的Shader系統的名稱,它完成的是組織Shader、渲染狀態、渲染Pass,以及引數繫結等功能,有點像D3D SDK提供的D3D Effect。
如果想系統的學習Shader,就需要從圖形API的Pipeline開始。當然,現在理想的是去學習D3D 12,Valukan,Metal這樣的新一代API,至少也是D3D 11這樣的成熟API,或者Open GL ES,如果你專供移動端圖形的話。
下面這套程式碼,是我在2008年的時候,基於D3D9 API寫的一組教程程式碼,因為最近有同學問起,所以找出來重新發布。
程式碼已經上傳到GitHub:https://github.com/neil3d/myshaders
D3D9 Shader教程文件
實現基本光照
程式的基本思路
class SimpleD3DApp處理了win32視窗建立和D3D初始化的工作,然後響應選單,生成相應的ShaderSimpler派生類例項。 一個個ShaderSimpler的派生類為一個shader試驗小程式。
Effect的基本處理步驟
- 編輯一個純文字檔案,推薦字尾為fx. shader\SimpleDraw.fx是一個使用vertex shader和pixel shader的最簡單的Effect檔案:
- 使用D3DXCreateEffectFromFile函式載入生成一個ID3DXEffect物件;另外也可以使用fxc命令列工具把上述文字檔案編譯成二進位制檔案。
- 在渲染前呼叫ID3DXEffect::SetMatrix()設定effect中的引數;
- 使用ID3DXEffect::Begin(),BeginPass(),EndPass(),End()來控制渲染流程。
實現一個簡單omni燈光的計算
- 對於每個頂點進行燈光計算;
- 光照公式使用”距離衰減*(N.L)”;
參考
- Wolfgang Engel, Implementing Lighting Models With HLSL:
http://www.gamasutra.com/features/20030418/engel_01.shtml - ATI, FixedFuncShader – http://ati.amd.com/developer/samples/dx9/FixedFuncShader.html
Phong shading
這個小程式通過把頂點法線逐象素插值,再計算每個象素的diffuse,specular光照結果來簡單的實現phong模型。因為specular是view depended,所以Effect中增加了eyePos引數。
- 通過觀察box的渲染可以看出與basic lighting的明顯不同。
- 這段小程式沒有考慮效能的問題,為了提升速度,可以ps中的L,V,R放到vs中去計算。
- 鏡頭操作:WS前後,AD左右,ZX上下移動,滑鼠拖動為轉動。
卡通渲染
卡通渲染主要包括兩個效果:
1. 把普通光照的平滑亮度變化變成幾個強度級別。這是通過計算出光照強調,然後查詢一個貼圖來實現的,此貼圖儲存0~1的4個固定段。
2. 描出輪廓線。法線與視線垂直的點為輪廓,所以使用兩者的點積結果來查詢一個貼圖,然後乘以光照顏色來實現描邊,此貼圖0附近為全黑,其餘為全擺。
參考:
- Keith Lee, Cartoon Rendering with HLSL, http://www.booyah.com/article05-dx9.html
DX9SDK simple, BasicHLSL
環境對映
Cube Map環境對映特別適合與曲面,實現也很簡單--構造一個視點到頂點的向量,然後計算出相對與normal的反射向量,用此向量索引cube map貼圖即可。
順便比較了一下shpere環境對映的效果。Sphere環境對映的實現也很簡單,把頂點法向量變換到view space,然後使用貼圖座標(n.x/2+0.5,n.y/2+0.5)來索引sphere map貼圖即可。
參考
- DX9SDK simple, HLSL workshop, Goal 3
Bump Mapping(Normal mapping)
從貼圖生成normal map
normal map可以從一個顏色貼圖對應的高度圖生成。為了計算i,j點的normal,首先可以根據高度差計算出s,t方向的切向量:
S(i,j) = <1, 0, aH(i+1,j)-aH(i-1,j)>
T(i,j) = <0, 1, aH(i,j+1)-aH(i,j-1)>
其中a是一個縮放係數。normal可以由這兩個切向量得出
*N(i,j) = normalize(cross(S,T)) = cross(S,T)/length(cross(S,T)) = <-Sz, -Tz, 1> / sqrt(Sz^2 + Tz^2 +1)
然後可以把這個向量使用RGB編碼儲存到貼圖檔案中。原理是這樣,本例使用nvidia提供的工具生成normal map。
計算出Tangent space
這個計算稍微有些複雜。記得老早的時候nvidia有一個文件寫的很清楚,不過我怎麼也搜不出來了。哪位要是知道,請給我個連結。在Eric Lengyel的書裡面找到了另外一種解法,講得也很透徹。
本例子中簡單的呼叫了D3DXComputeTangent,也可以使用nvidia sdk中提供的nv_meshmender來計算。
光照計算
使用Bump map的光照計算與前面的PhongShading例子相比沒有什麼新鮮的。前面的例子中每個象素的normal只是簡單的從頂點normal插值而來,而使用bump map後,每個頂點的normal可以從貼圖中查詢;另外因為這個normal是存在與tangent space中,所以需要把light dir和view dir都轉換到tangent space再進行計算。
參考
- Eric Lengyel, 3D遊戲與計算機圖形學中的數學方法
- Normal Map貼圖使用nVidia Photoshop plugin生成 –
http://developer.nvidia.com/object/nv_texture_tools.html - Wolfgang Engel, Implementing Lighting Models With HLSL –
http://www.gamasutra.com/features/20030418/engel_01.shtml
Parallax Mapping
Parallax map通過簡單的計算可以大幅提高per-pixel光照的效果。Parallax map的想法基於一個簡單的事實,那就是當我們用一個多邊形去表現一個凹凸不平的表面的時候,事實上隨著視線的變化,看到的實際的texel並不是頂點uv座標差值出來的那個結果,而是有一定的偏移。而這個偏移可以通過是個高度圖來計算。
在本例中,經過簡化,最後單個象素uv偏移量的公式為:
offset = <viewDir.x, viewDir.y>*(height*SCALE+BIAS)
其中的height就是高度圖在這個uv的高度值,然後根據實際需要進行縮放和偏移。SCALE和BIAS可以根據所要渲染的物體的單位來估計,例如要渲染一個1平方米的磚牆,那麼我們估計每個磚的起伏在幾釐米。在本例中使用這個offset將uv進行偏移之後,再去取樣bump map和color map。
- 程式執行時按字母鍵“P”可以開啟、關閉parallax map,用來比較效果。:)
參考
- Terry Welsh, Parallax Mapping, ShaderX3 p89
實現類似魔獸世界的“全屏泛光”
現在很多遊戲,像WOW、激戰都有這種“全屏泛光”的畫面效果,很多人以為是使用了HDR,其實就是一個post process效果。
PostProcess是一個很簡單又有用的概念,顧名思義,就是把渲染之後的場景,在影象空間進行一些處理。它可以做出各種各樣的效果。
全屏泛光的實現步驟如下
- 分配三個texture,F,A,B,其中F的大小與back buffer一致,A和B為back buffer面積的四分之一;
- 將場景渲染到F;
- 將F渲染到A - 執行down sample和high-pass filter;
- 將A渲染到B - 執行H blur;
- 將B渲染到A - 執行V blur,先橫向再縱向計算,把每個象素需要的計算由n*n變為n+n,其中n為blur區域的大小;
- 將F和A混合,渲染到back buffer,然後present。至於混合的公式,就可以自己發揮了。:)
參考
- nVidia FXComposer
初探Deferred shading
對於一個基於Shader的渲染系統如何來計算多個動態光源的光照呢?這是一個困擾我很久的問題。基本上有這樣三種解決方案:
1. 在單個pass中計算物體的所有光照。這種方案的主要缺點就是shader管理非常複雜。常見的思路是基於一套shader模版,自動生成n個光照shader,n往往能達到上百個;另外一個主要的缺點就是很多fragment經過複雜的光照計算之後又被z測試剔除,造成計算的浪費(可以先進行一個z only pass來優化);
2. 為每個燈光進行一個pass。這種方案也會有前者over draw的問題,另外還會造成draw call數量增加;
3. deferred shading,這種方案結構簡單,但是對顯示卡要求較高,需要multi-render targets, float point render target;還有就是對於透明面,目前還沒有很好的解決方法。
下面就介紹一下Deferred Shading的思路。它的想法非常的簡單、直接,簡單的說就是我們可以把光照計算需要的一些引數先通過一個pass渲染到一些render target中(稱為Attributes buffer或G-Buffer),然後可以為每個燈光進行一個pass來計算光照。
原始碼中演示了deferred shading的基本概念,實現了4個點光源的phong 光照渲染。為了能在Geforce 5600上跑,沒有使用multi-render targets,而是用了多個pass來生成G-Buffer,這樣也可以更清楚的演示deferred shading的概念。
首先程式建立了position,normal,diffuse三個render target,在渲染時使用3個pass,把場景的“世界座標”,“法線(世界座標系)”,“顏色”,分別渲染到這三個render target中;為了簡單起見,假設所有燈光都覆蓋整個螢幕――為每個燈光進行一個pass,渲染一個全螢幕的quad,然後為每個象素計算光照,並使用additive混合,把光照強度累加到螢幕緩衝中。
Position
Normal
Diffuse
Deferred shading with Multiple Render Targets
今天抽空是試驗了一下使用MRT進行deferred shading。基本是在上個例子的基礎上修改的,主要有一下幾個地方:
- 使用SetRenderTarget分別將各個render target設定好;
- 在pixel shader中使用“COLOR[n]”來寫入各個render target;
- 有一點需要注意的是,MRT的各個surface的bits必須是一致的,所以這個例子使用了兩個G16R16的float - texture來儲存position。
OK,就是這樣簡單。渲染效果與上個例子一樣。這個例子程式在geforce 7600GT上測試通過,其他顯示卡沒測過。
全文結束
相關文章
- OpenGL Shader例項,OpenGL 粒子系統
- Java例項教程Java
- ORM 例項教程ORM
- Luat例項教程:adc
- 深入 Laravel Nova 教程例項Laravel
- Web Components 入門例項教程Web
- 【FLASH例項1000教程】(4)上
- expand 與 unexpand 命令例項教程
- Luat例項教程:tcp短連線TCP
- 使用原生js實現選項卡功能例項教程JS
- Flutter繪製-05-shader專項Flutter
- CSS-Flex 佈局教程:例項篇CSSFlex
- PhpSpreadsheet中文文件 | Spreadsheet操作教程例項PHP
- C++設計模式+例項視訊教程C++設計模式
- 執行caffe自帶的mnist例項教程
- QT OPENGL 與 shader 繪製展示視訊程式碼例項 OPenGL直接顯示YUV資料QT
- Dreamweaver網頁製作教程:表格設計例項網頁
- Vue.js編輯文字--菜鳥教程例項Vue.js
- php例項化物件的例項方法PHP物件
- 原生js使用物件導向的方法開發選項卡例項教程JS物件
- 原生js拖拽功能製作滑動條例項教程JS
- 純原生javascript下拉框表單美化例項教程JavaScript
- OkHttpClient例項HTTPclient
- unittest例項
- jQuery 例項jQuery
- Xamarin Essentials教程實現資料的傳輸功能例項
- onethink構思不錯,好像沒有好的例項教程
- Activiti的流程例項【ProcessInstance】與執行例項【Execution】
- Python爬蟲教程-17-ajax爬取例項(豆瓣電影)Python爬蟲
- 最新SS園設計模式例項剖析與深入解讀教程設計模式
- 一文說清FISCO BCOS效能壓測(附例項教程)
- 《JavaScript前端開發與例項教程(微課影片版)》簡介JavaScript前端
- 前端基礎功能,原生js實現輪播圖例項教程前端JS
- 例項化list
- msfvenom使用例項
- 雙層 for 例項
- python socket例項Python
- pinctrl使用例項
- Draggable 拖拽例項