2018年4月30日 星期一

ShadowMap的心得

ShadowMap的心得

前言

  最近剛完成ShadowMap,這邊把製作的的過程和一些心得在此做個紀錄。

內容

  ShadowMap的基本原理是以光源的位置為基準繪製一張深度貼圖,而光源分為DirectionalLight與PointLight,DirectionalLight的實作會需要一個光源位置與方向,並且用一個OrthogonalMatrix來決定可視範圍與可視深度,來繪製深度貼圖,PointLight的作法則一樣需要位置,再來透過6個PrespectiveMatrix來分別繪製到CubeMap,所以一個物件要繪製6次,由於這樣會有相當高的繪製成本,所以通常使用DirectionalLight的ShadowMap。
 
 DirectionalLight的ShadowMap示意圖
  在繪製好深度貼圖後,接著要在繪製影子的Shader中參照深度貼圖,這部分本以為很困難,但實作後比想像種簡單一些,以下以DirectionalLight的ShadowMap來解說,實作時需要一個矩陣來完成投射,這個矩陣是以光源與方向來合成ViewMatrix,在來是之前使用來畫深度貼圖的OrthogonalMatrix來作為ProjectionMatrix,最後會需要一個空間轉換的矩陣,這個矩陣會將-1~+1的數值空間(繪圖buffer的數值空間)轉換成0~1空間(UV的數值空間),將這3個矩陣合成後,在VertexShader將所有vertex乘上這個矩陣後,再傳送到PixelShader,這樣就可以得到該Pixel參照到ShadowMap的深度,如果該Pixel的深度值比ShadowMap的深度還深究表示被遮蔽了,相反的狀況則表示沒被遮蔽。
參照ShadowMap的示意圖

後記

  本篇只講了基礎,如果只是照著實作會碰到一些問題,而這些問題就等下一篇在解釋。


參考資料

Tutorial 16 : Shadow mapping

2018年4月23日 星期一

使用Electron開啟檔案

使用Electron開啟檔案

前言

  這是第2次使用Electron,這次要實作在商用軟體常用的的開啟檔案,在此就做個紀錄。

內容

  這次要在Electron中使用node.js的模組(module),使用fs模組來開啟檔案與使用dialog模組來選取檔案。

  在Electron使用模組時不能直接透過require()來取得該模組,要使用的話要改一下方法,載入的程式碼如下

 const remote = require('electron').remote;
 const fs = remote.require('fs');
 const dialog = remote.dialog ;

可以看到的是和node.js不一樣,在node.js只須直接require()即可,但Electron需要透過Electron模組的remote模組來取得fs模組與dialog模組。

  開啟選取檔案視窗的用法是透過dialog.showOpenDialog()來開啟,使用的範例如下

dialog.showOpenDialog(properties: ['openFile']},function(fileURLList){
  if(!fileURLList){
    console.log("No file to selected!");
    return;
  }
  for(var i=0;i<fileURLList.length;i++){
    console.log("Select file:"+fileURLList[i]);
  }
});

這裡要注意因為選取檔案視窗是可以選取多個檔案的,所以得到的結果是個List。如果想知道更多的用法可以到dialog模組,取得更多的資訊。

  fs模組用起來和node.js是一樣的,開啟的範例如下

  
fs.readFile("c:\test.txt",function(err,dataBuf){
    if(err){
      console.log("readFile error:" + err);
      return;
    }
    //
  });

開啟後會得到一個dataBuf,它是一個型別為Buffer的資料,要注意的是Buffer並不是javascript定義的型別,它是node.js定義的,要得到更多的資訊請到Buffer。這個方法是用binary的方法讀取檔案,如果要用文字的方式讀取範例如下

fs.readFile("c:\test.txt","utf-8",function(err,str){
    if(err){
      console.log("readFile error:" + err);
      return;
    }
    //
  });

用起來只須在參數多加一個"utf-8",接收資料的參數就會變成string。

參考資料

remote模組
dialog模組
Buffer

2018年4月16日 星期一

初探Electron

初探Electron

前言

  目前的javascript遊戲引擎一直有個問題,就是沒有編輯器,要寫的話在檔案存取的部分要花上不少的時間,在偶然間,看到Electron,它可以把網頁變成本地的App,這樣就可以直接存取本地的檔案系統,看起來似乎是一個解決檔案存取的好方案,在此就先研究看看。

內容

  使用Electron需要node.js,請先安裝,安裝好後建一個資料夾,它會作為專案的root,之後在該資料夾輸入以下命令

npm init

全部用預設值後會產生package.json,接著一樣在專案的root輸入以下命令

npm install --save-dev electron

如果要安裝成全域套件,輸入以下命令

npm install electron -g

接著在專案的root產生一個檔案main.js,內容如下

const {app, BrowserWindow} = require('electron')
  const path = require('path')
  const url = require('url')
  
  // 將這個 window 物件記在全域變數裡。
  // 如果沒這麼做,這個視窗在 JavaScript 物件被垃圾回收時(GC)後就會被自動關閉。
  let win
  
  function createWindow () {
    // 建立瀏覽器視窗。
    win = new BrowserWindow({width: 800, height: 600})
  
    // 並載入應用程式的 index.html。
    win.loadURL(url.format({
      pathname: path.join(__dirname, 'index.html'),
      protocol: 'file:',
      slashes: true
    }))
  
    // 打開 DevTools。
    win.webContents.openDevTools()
  
    // 視窗關閉時會觸發。
    win.on('closed', () => {
      // 拿掉 window 物件的參照。如果你的應用程式支援多個視窗,
      // 你可能會將它們存成陣列,現在該是時候清除相關的物件了。
      win = null
    })
  }
  
  
  // 當 Electron 完成初始化,並且準備好建立瀏覽器視窗時
  // 會呼叫這的方法
  // 有些 API 只能在這個事件發生後才能用。
  app.on('ready', createWindow)
  
  // 在所有視窗都關閉時結束程式。
  app.on('window-all-closed', () => {
    // 在 macOS 中,一般會讓應用程式及選單列繼續留著,
    // 除非使用者按了 Cmd + Q 確定終止它們
    if (process.platform !== 'darwin') {
      app.quit()
    }
  })
  
  app.on('activate', () => {
    // 在 macOS 中,一般會在使用者按了 Dock 圖示
    // 且沒有其他視窗開啟的情況下,
    // 重新在應用程式裡建立視窗。
    if (win === null) {
      createWindow()
    }
  })
  
  // 你可以在這個檔案中繼續寫應用程式主程序要執行的程式碼。 
  // 你也可以將它們放在別的檔案裡,再由這裡 require 進來。

接著一樣在專案的root產生一個檔案index.html,內容如下

<!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8">
      <title>Hello World!</title>
    </head>
    <body>
      <h1>Hello World!</h1>
      我們用了 node <script>document.write(process.versions.node)</script>,
      Chrome <script>document.write(process.versions.chrome)</script>,
      以及 Electron <script>document.write(process.versions.electron)</script>.
    </body>
  </html>

接著修改package.json,修改內容如下

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^1.8.4"
  }
}
最後在專案的root輸入以下命令

npm start

接著就會看到HelloWorld視窗如下圖
HelloWorld視窗

後記

  用起來比想像中還簡單許多,看起來似乎是用chrome作為視窗,連debugger都叫得出來!這次只是初探,以後有機會再多多研究。

參考內容

寫你第一個 Electron 應用程式

2018年4月9日 星期一

初探CubeMap

初探CubeMap

前言

  以前CubeMap總是在書上看到範例但卻因為覺得不實用所以總是被我略過,但若想要實現PBR(physically based rendering)的話,其中dynamic environment mapping就需要用到CubeMap,
所以最近開始學習CubeMap的使用,在此做個紀錄。

內容

  在創建紋理資源時,不論是Direct3D或OpenGL都是建6張紋理後來創建,注意的是Direct3D創建時是用陣列的方式給予,而順序是+X、-X、+Y、-Y、+Z與-Z,請照順序放入即可,不用考慮是左手或右手座標系!
  接著是在Shader使用時,HLSL的資料型別是"TextureCube",依舊是透過Sample()來進行取得Texel,GLSL的資料型別是"samplerCube",取得Texel則是透過textureCube()來取得。在GLSL當有使用textureCube()時,請加入以下程式碼在開頭

#extension GL_NV_shadow_samplers_cube : enable

如果不加上面的程式,在GLSL編譯時會有以下錯誤訊息

error c7531:global function textureCube requires "#extension GL_NV_shadow_samplers_cube:enable" before use

還有以前取Texel都採用UV座標(float2),在CubeMapg時要採用UVW座標(float3)來取Texel,其實它就是個"方向",可以想像在Cube的中心用一個"方向"來取Texel。

後記

  這次實作CubeMap遽然意外的順利,只有碰到GLSL的編譯問題!CubeMap使用起來並不難,日後在實作dynamic environment mapping後應該會再寫一篇。

參考資料

D3D11: Creating a cube map from 6 images
Opengl's cubemaps
Fix glsl's c7531 error

2018年4月2日 星期一

PerspectiveMatrix的種類

PerspectiveMatrix的種類

前言

  在整合Direct3D與OpenGL前,我一直認為PerspectiveMatrix只有分左手與右手差別,但在實際整合後發現Direct3D與OpenGL的深度範圍是不一樣的,所以還要再區分一次,最後你會發現PerspectiveMatrix有4種,在此將它紀錄下來。

內容

  PerspectiveMatrix的種類在入門時,因為只要考慮一個繪圖函示庫(Direct3D或OpenGL),所以只要分成左手與右手兩種,但是當需要整合Direct3D與OpenGL時就要考慮到深度範圍不一樣的問題,Direct3D的深度範圍是 0 ~ +1,OpenGL的深度範圍是 -1 ~ +1,因此種類會分為以下:
1.左手,深度範圍是 0 ~ +1
2.右手,深度範圍是 0 ~ +1
3.左手,深度範圍是 -1 ~ +1
4.右手,深度範圍是 -1 ~ +1
在整合引擎時,當你下達產生一個PerspectiveMatrix時,會產生一種呢?首先整合引擎的座標本身就有預設好是左手與右手,但深度範圍就要分函示庫了,這裡整合引擎時要想出一套辦法知道目前使用的函示庫來決定要產生哪一種PerspectiveMatrix。
  接著來說怎麼產生這4種PerspectiveMatrix,Direct3D的部分可以查詢
D3DXMatrixPerspectiveFovLHD3DXMatrixPerspectiveFovRH,說明的下方有矩陣的產生,接著是OpenGL的部分就麻煩了點,必須要看code,首先要有glm(OpenGL Mathematics),它是OpenSource,下載後下列檔案看code怎麼產生:
(glm's root directory)\glm\glm\gtc\matrix_transform.inl

參考資料

OpenGL Mathematics
Projection Transform