2020年1月21日 星期二

用Git來移除Source Code以外的文件

有時想把整個Project folder複製到另外一部電腦或者USB碟時,整個資料夾往往很大,因為包含了許多Binary files(可能是程式的執行檔,也有可能是IDE自己產生的cache),而這些Binary files其實是可以用Source Code重新產生的,所以在複製的時候會想只複製有用的Source Code就夠了。

我通常習慣在Project folder開個Git repo,然後放個.gitignore,再commit其餘的文件,這樣我們就有了一個track list,也就是必要的Source Code和檔案。

然後在複製文件的時候,可以用git clean去清除不必要的檔案:

git clean -dfx
-d代表directories(資料夾),-f代表files(檔案),-x代表.gitignore內所標識的檔案

可以先用dry run來確認一下哪些檔案會被刪除
git clean -n -dfx

2019年7月15日 星期一

檢查DLL Dependencies

以下的cmd的目的是檢查一個exe或dll需要哪些其他的DLL才能順利執行。如果有安裝Visual Studio便能使用這個方法。

"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\dumpbin.exe" /dependents PATH-TO-EXE

另外也可以用該exe dump file,就能一次過查看包括dependent dll在內所有dll的dependencies。

2019年6月3日 星期一

從pcap中過濾部分資料,做一個新的pcap


tshark -F libpcap -Y "ip.dst==1.2.3.4 and udp.port==110" -r "Input.pcap" -w "Output.pcap"


解釋:
-F libpcap 以pcap作為檔案格式(而非pcapng,因為部分軟件無法處理這種格式)
-Y "display filter" 新pcap中只需要符合filter的資料
-r "Input.pcap" 原本的pcap
-w "Output.pcap" 新的pcap位置


參考:

2018年11月16日 星期五

Shader常用資料和連結

無論寫甚麼程式都經常要看網上資源和Documentation,Shader也一樣,但Shader的網上資源很鬆散,很多時連在Google搜尋也找不回自己以前見過的資料,於是我把一些常用的資料連結放在這裡,方便日後查閱。

Semantics

https://docs.unity3d.com/Manual/SL-ShaderSemantics.html

Microsoft對HLSL支援的每個Semantics有較詳細的解釋,但要注意Unity只支援一部分:
https://docs.microsoft.com/zh-tw/windows/desktop/direct3dhlsl/dx-graphics-hlsl-semantics

Semantics定義了一個Shader的輸入和輸出中包含了甚麼資訊。

常用的vertex shader的輸入Semantics包括:
  • POSITION:vertex在model space的位置,一般用float4
  • TANGENT:vertex在model space的tangent(切線),一般用float3
  • NORMAL:vertex在model space的normal(法線),一般用float4
  • TEXCOORD0-3:材質座標0-3(之後會用作uv mapping),一般用float2或4
  • COLOR:vertex顏色,一般用fixed4或float4

一邊來說vertex shader的輸出要有SV_POSITION (代表了vertex在clip space的位置),就算fragment shader裡面不需要用vertex位置來計算也要有,否則兩個Shader之間的步驟就無法計算vertex在screen上的位置。

如果fragment shader還需要其他資訊的話,就可以在vertex shader的輸出加多一點variable並配上適當的semantics(例如COLOR0-1代表顏色、TEXCOORD0-7代表uv mapping)。這些semantics的用途則完全是由我們決定(而非像vertex shader輸入,每個semantics代表了從Unity輸出的特定資訊)。

fragment shader的輸出一般都是SV_TARGET (每一粒pixel的顏色)。

CG datatype: float、half還是fixed?


到底應該用float、half還是fixed?

首先要知道每個datatype都有其範圍:
float:-3.4E38到3.4E38,7 significant digits
half:–60000到60000,3 significant digits
fixed:-2到2,1/256精準度(可以儲存color或unit vector)

如果你要儲存的數的範圍會超過的話就要升級成高級一點的datatype。一般來說可以用fixed就用fixed,不能再升級half或float,因為在某些較弱較舊的GPU上(尤其手機)fixed所需的memory是最低的(但實際上現在的手機一般half和fixed都用同樣的memory,而在PC上甚至三者也可能用一樣memory)。

ShaderLab Properties對應的CG datatype

https://docs.unity3d.com/Manual/SL-PropertiesInPrograms.html

Color、Vector:float4 half4 fixed4 (也可以只拿1-3)
Range、Float:float half fixed
2D、Cube、3D:sampler2D、samplerCube、sampler3D

Unity Shader內建library

library文件列表:https://docs.unity3d.com/Manual/SL-BuiltinIncludes.html
UnityCG.cginc的function列表:https://docs.unity3d.com/Manual/SL-BuiltinFunctions.html
下載原始碼:https://unity3d.com/get-unity/download/archive (選Unity版本後按Downloads→Built-in Shaders),在自己的電腦上也有(C:\Program Files\Unity\Editor\Data\CGIncludes)

HLSLSupport.cginc(包含跨平台用macro)和UnityShaderVariables.cginc(包含很多global variable)會自動加入,不用自己include。
UnityCG.cginc則有很多常用function,需要手動include。其他的文件多是在寫surface shader時會用到。

UnityCG.cginc中預先定義的struct

library文件列表(部分列出):https://docs.unity3d.com/Manual/SL-BuiltinIncludes.html

常用的struct:
appdata_base:vertex POSITION、normal NORMAL、texcoord TEXCOORD0
appdata_tan:vertex POSITION、tangent TANGENT、normal NORMAL、texcoord TEXCOORD0
appdata_full:vertex POSITION、tangent TANGENT、normal NORMAL、texcoord TEXCOORD0、texcoord1-3 TEXCOORD1-3、color COLOR
appdata_img:vertex POSITION、texcoord TEXCOORD0
v2f_img:pos SV_POSITION、uv TEXCOORD0

我自己寫Shader時一般不會用這些預先定義好的struct,因為不能讓人一看就知道這些struct中包含哪些semantics,有時候你想賦予的semantics名稱和它不一樣,要增加或減少semantics時也會麻煩,最好還是自己親自寫好每個semantics並命名。不過這些預先定義好的struct在library裡原始碼倒是會經常用到,所以如果你會經常查看library原始碼的話就要好好了解這些struct代表哪些semantics。

跨平台問題

一些常見的平台之間的差異:https://docs.unity3d.com/Manual/SL-PlatformDifferences.html

2018年10月28日 星期日

座標空間的轉換

座標轉換matrix的填法

設有一個parent space P和一個child space C,要把一個C下的vector Vc轉到P下,可以這樣表達:
Vp = (Mc→p) Vc

相反地,把一個vector Vp從P轉到C,就是: Vc = (Mp→c) Vp

Mc→p的inverse就是Mp→c,反之亦然。

至於中間的這個Transformation matrix要填甚麼呢?我們需要知道P和C之間的關係。
Mc→p做例子,我們需要知道C的座標是在P的位置

為甚麼不是求P在C的位置呢?試想想,原本我們是以C為中心,但如果要轉到P下面,以P為中心的話,那C肯定就飛到另外一個位置了。

Mc→p的填法一般為:

[  |     |    |     |
  XYc Zc Oc
   |     |    |     |
  0    0   0    1  ]

其中Xc Yc和Zc 是代表了C在P下的三個axis vector,而Oc是C在P下的origin。

按著上一篇所提到的affine matrix的結構來分析的話,就會知道左邊的Xc Yc Zc是做linear transform,而Oc是做translation。所以如果你只是需要做linear transform的話(即只是針對方向vector做轉換,因為方向vector不需要translate),就可以只用Mc→p中左上角3x3的部分。

快速求Inverse

還記得orthogonal matrix的特性嗎?
(1) M^T = M^-1
(2) 每個column vector和其他column vector是互相垂直的

因此,而如果我們知道這個座標的x y z axis是互相垂直的話,我們在計算這個transformation matrix的inverse的時候,直接把它transpose就可以了。

換言之,Mp→c就變成:
[  -  Xc  - 
    -  Yc  -
    -  Zc  -  ]

繪製過程涉及的座標空間

Model Space/Local Space/Object Space(模型空間/局部空間/物件空間)

以這個遊戲物件本身的其中一個中心點為origin。這在建立遊戲模型的時候應該就定好了。

World Space(世界空間)

以Unity scene view中的中心點為origin。

Camera Space/Eye Space/View Space(攝影機空間/眼睛空間/觀察空間)

以Unity中的camera的位置為origin。要注意的是Unity為了配合OpenGL的傳統,這個空間使用的是右手法則(right-hand rule),即是說攝影機前方的方向和z axis的正數方向是相反的。這是唯一使用右手法則的空間,之後的空間會重新轉回左手法則。

Clip Space(修改空間,又譯裁剪空間)

同樣以camera的位置做origin,但所有可視範圍以外的物件都會被剔除,只有一部分在可視範圍內的物件就會修改成只保留可視的部分,這個範圍叫view frustum(視椎體),是一個六面體。這裡也會看攝影機使用了正交投影(orthographic projection)還是透視投影(perspective projection)來決定view frustum的大小。

從camera space轉到clip space的matrix叫做clip matrix或者projection matrix。雖然名字叫做projection matrix,但嚴格來說只是在「準備」做projection,真正做projection的是在從clip space轉到screen space的部分。

下面列出的matrix假設camera space是用右手法則,轉換後就會變回左手法則(根據Unity的做法)。對於這些matrix的來歷有興趣的話可按此

正交matrix

[  1/(aspect*size)    0           0                             0
    0                         1/size    0                             0
    0                         0            -2/(Far-Near)   -(Far+Near)/(Far-Near)
    0                         0            0                             1]
這個matrix對一個vector的x y z都做了不同程度的縮放,也對z做了translation。
而做完正交matrix轉換後的view frustum會變成一個立方體(vertex的x y z w是-1至1)。

透視matrix

[   1/(aspect * tan (FOV/2))    0                                0                                            0 
      0                                        1/tan(FOV/2)            0                                            0
      0                                        0                              -(Far+Near)/(Far-Near)     -2(Near*Far)/(Far-Near)
      0                                        0                                -1                                           0]
這個matrix和正交matrix同樣對一個vector的x y z都做了不同程度的縮放,也對z做了translation。但不同的是,轉換完後的vector的w此時不再是1,而是-z。這時候,如果一個vexter的x y z均小於或等於w並大於或等於-w(-w<= x y z <= w),就代表這個vertex在view frustum裡面。

做完透視matrix轉換後的view frustum,其near panel的vertex的x y z w都會變成near或-near,而far panel的vertex的x y z w都會變成far或-far。

Screen Space(螢幕空間)

這裡已經表示了我們要在螢幕上畫些甚麼,因此這裡是一個2D Space。

從clip space轉換成2d space的步驟:

要做homogeneous division(齊次除法),也叫perspective division(透視除法),其實就只是把vector上的x y z除以w而已。對透視投影來說,除完之後就會發現view frustum從原本錐體變成了立方體(vertex的x y z w是-1至1,如果是DirectX則是0至1),而對正交投影就沒有影響(因為w是1)。要注意的是,除法是比較耗用CPU的,所以我們想盡量確保先做完clipping,把所有不需要的物件移除後,才對剩下必要的物件做除法。

做完除法後的座標在OpenGL叫做NDC(Normalized Device Coordinates)。

最後要用這些vertex的x和y來轉換成螢幕上的x和y(下方公式包含了homogeneous division):

screen x = clipx * pixelWidth / 2clipw + pixelWidth / 2
screen y = clipy * pixdelHeight / 2clipw + pixelHeight / 2

在這裡Unity依然遵照著左手法則(或者說是OpenGL傳統),因此螢幕左下是(0,0),右上是(pxielWidth, pixelHeight)。

Normal (法線) 的空間轉換

要留意的是Vertex可能會帶有Normal vector(光源用),這些normal vector在空間轉換的時候不能用和vertex一樣的matrix,否則會「走樣」(尤其因為縮放比例不一,和三角平面的角度可能變得不太準確了)。設轉換vertex的matrix為M,那麼轉換normal的matrix是M的inverse transpose matrix(即 (M^-1) ^T)。

如果M本身是orthogonal的,那麼(M^-1)^T會等於回M,因此可以直接用M來轉換normal。
之前有提過rotation matrix無論是單一還是複合都是orthogonal的,所以只帶有rotation的matrix可以直接用來轉換normal。
如果M還包含了scaling的話,設scaling constant為k,那麼matrix就是 (1/k)M。
其他情況就可能真的需要慢慢計算inverse transpose matrix了。

這些空間和Shader的關係

其實上述的各類空間描述了繪製管線中的Geometry stage中的流程,而當中有一部分的流程我們是可以自己寫程式控制的(這些就是Shader),有些則由GPU完全操控。那麼讓我們重提一下Vertex Shader和Fragment Shader負責甚麼吧。

Vertex Shader

重溫:Vertex Shader的主要作用是把vertex從model space轉到clip space。

還記得mul(UNITY_MATRIX_MVP, v.vertex)嗎?UNITY_MATRIX_MVP中的MVP代表了:
Model:Model Space → World Space
View:World Space → View Space
Projection:View Space → Clip Space

UNITY_MATRIX_MVP代表了上述三個transformation matrix結合出來的matrix,Unity會自動幫你計算這個matrix是甚麼。因此這句程式碼其實就是把vertex從model space轉到clip space。

Unity還提供了其他多種matrix組合的變數給你選擇,只需要搞清楚你到底是想從哪個space轉到哪個space,再選用適當的matrix便可。例如你只想計算從Model Space轉到View Space,那就只用UNITY_MATRIX_MV即可。

問:為甚麼沒有UNITY_MATRIX_I_MV?
雖然不清楚確實原因,但其實我們只要把UNITY_MATRIX_IT_MV做transpose之後就能得出UNITY_MATRIX_I_MV了。又或者連transpose也不用而是直接改變mul的次序(即mul(v.vertex, UNITY_MATRIX_IT_MV)),這樣的結果和mul(transpose(UNITY_MATRIX_IT_MV), v.vertex)是一模一樣的,因為Unity會根據argument中vector和matrix的先後次序而決定要用row vector還是column vector(但不會幫matrix做transpose)。

Fragment Shader

重溫:Fragment Shader的主要作用是決定該物件在每個pixel上的顏色。

Fragment shader其實位於比Screen space更後的位置。雖然Screen space中決定了每個vertex在螢幕中的位置,但我們還沒把它們轉成pixel去針對每個pixel做一些處理。因此中間還需要經過Triangle Setup(三角設定,負責計算三角邊的像素坐標)和Triangle traversal(三角檢查,負責檢查每個pixel是否被一個由vertex組成的triangle mesh覆蓋)後,知道了哪些pixel(準確點叫fragment或potential pixel,因為我們還沒知道那個pixel實際上會否被畫上去)是在這個物件下的,然後就每個pixel去執行一次fragment shader,來決定到底在這個pixel上畫甚麼顏色。

2018年10月20日 星期六

Unity上常用的基本矩陣計算 (Matrix)

常見的Matrix種類

Identity Matrix單位矩陣,簡稱I):左上到右下的對角線是1,其他都是0,MI = IM = M

Transposed Matrix轉置矩陣,用上標T表示):把Matrix的Row變成Column,Column變成Row地反轉,(AB)^T = (B^T)(A^T)

Inverse Matrix反矩陣,用上標-1表示):M * M^-1 = M^-1 * M = I

Orthogonal Matrix正交矩陣):M * M^T = M^T * M = I ,即 M^T = M^-1

判斷一個matrix是否orthogonal,一般教科書都是直接用其定義來判斷,即計算Matrix乘以自己的transposed matrix是否等於I。但是在寫遊戲程式的時候,計算matrix相乘其實會用很多時間,因此我們想用一個更快的方法來做判斷:

把orthogonal matrix拆成每個每個column來檢查的話,會發現它們都是unit vector(因為這些vector和自己的dot product是1),而和matrix中除自己外的其他column是垂直的(因為dot product是0)。因此我們在用vector們建構一個matrix的時候,已經可以透過vector本身的特質來判斷建構出來的matrix是否orthogonal。

(參考:How to identify an orthogonal(orthonormal matrix)?

這一特點在做座標轉換的時候非常有用,因為我們一般都是使用正交座標(即xyz axis互相垂直),而由這三個axis的vector形成的matrix就是orthogonal的。如果我們知道一個matrix是orthogonal的話,那麼當我們想要計算它的inverse時,就可以直接用它的transpose,可以減少很多繁複的計算。

另外,多於一個orthogonal matrix互相相乘的結果也是orthogonal。

(參考:Why is the matrix product of 2 orthogonal matrices also an orthogonal matrix?

Vector和Matrix相乘的先後次序

兩個Matrix如果相乘的先後次序不一樣,結果也會不同(即AB =/= BA)。如果我們需要對一個Vector進行transformation,那麼究竟是要用row Vector乘以matrix,還是用matrix乘以column vector呢?在Unity中一般是matrix乘column vector,不過如果涉及多個matrix相乘時我們可以做一點optimization讓相乘更快:

例如我們要把一個vector v做三次transformation:
CBAv

如果從左至右開始計算(即((CB)A)v),那麼每次乘完後的matrix都會很大(不止一個column),但如果從右邊開始計算(即(C(B(Av))),那麼每次乘完的matrix都只有一個column。

你也可以把ABC做transpose,這樣就能把v放在左邊了:
v(A^T)(B^T)(C^T)

到底是橫列直行還是橫行直列?

經常會看見「列矩陣」和「橫矩陣」這兩個詞,但「列矩陣」到底是指大小1 x n(打橫n個數字)還是 n x 1(打直n個數字)的matrix?

我發現不同的教材和討論之間有分歧,似乎在不同地區或者社群中對於列是指橫還是直都有不同的定義。我甚至見過在同一份教材中列矩陣有時是指橫、有時是指直的情況。在英文中就不會有這個問題,Row從來都是橫的,Column永遠是直的,Row Matrix就是橫著三個數字(1x3)。

大概只能根據上下文中的例子來推斷到底該文章是使用橫列直行抑或橫行直列了。

Matrix Transformation (矩陣轉換)的種類

在遊戲中我們經常要對vector進行轉換(transform),例如移動或者縮放,又或者是轉到另外一個世界,最容易做轉換的方式就是用一個matrix來乘以這個vector(也就是單靠這個matrix已經能表達出我們想做的轉換),這種方式能做到大部分我們想做的效果。

Linear Transformation(線性轉換)

線性轉換的定義是符合下面兩個特性:
Vector加:  f(x)+f(y)=f(x+y)
Scalar乘:  kf(x) = f(kx)

線性轉換只需要和vector同一dimension的matrix就足以表達(2D遊戲是2x2,3D遊戲是3x3)。

以下列出常見的線性轉換。下列的matrix都是以origin為中心進行轉換,如果你是需要以遊戲物件的中心進行轉換的話,就需要先把vector本身轉成物件的local position再進行轉換,然後再轉回world position。

Scaling (縮放)

matrix的左上到右下的對角線上的數字為縮放比例k
[ kx 0 0
  0 ky 0
  0 0 kz]

Rotation(旋轉)

以x axis為中心逆時針旋轉:
[1    0     0
 0  cosθ  -sinθ
 0  sinθ  cosθ]

以y axis為中心逆時針旋轉:
[cosθ   0  sinθ
   0      1     0
 -sinθ  0  cosθ]

以z axis為中心逆時針旋轉:(一般來說2D遊戲中的旋轉都是用這個)
[cosθ    -sinθ    0
 sinθ      cosθ    0
   0           0       1]

要點:

  • Rotation matrix都是orthogonal的。
  • 這些matrix的inverse就是向順時針方向旋轉同樣的角度。
  • 如果需要進行多於一個axis的旋轉,要留意Unity定義的預設旋轉順序是MzMxMy

Shear(錯切,例如把一個長方形扭成平行四邊形)


Mirroring/Reflection(鏡像)


Orthographic projection(正交投影)


Translation Transformation(平移轉換)

簡單來說translate就是把一個物件從一個位置移動到另外一個位置。

以公式表達的話一般是這樣:
f(x) = x + c
因為有constant的存在,所以並不符合線性轉換的定義。

想用一個matrix來表示translation的話,需要比原本的vector多一個dimension(2D遊戲需要3x3,3D遊戲需要4x4)。

參考:Why do 2D transformations need 3x3 matrices?

Affine Transformation(仿射轉換)

Affine其實就是包括了線性和平移的轉換,即是一個matrix就能同時表示兩種轉換(當然也可以只表示一種)。
既然包含了平移轉換,那就需要比vector多一個dimension的matrix,然後在相乘之前,把vector也擴充到相同的dimension(我們稱為homogeneous coordinate,齊次座標)。我們把多出來的那個dimension的數叫做w,即整個vector是(x,y,z,w)。

首先要看看該vector是哪一種vector:
位置(點):把w設為1(代表以該點為中心進行轉換)
方向:把w設為0(代表以origin為中心進行轉換)

而matrix的結構如下:
[ M 3x3   t 3x1
   0 1x3    1      ]
M代表線性轉換,t則代表平移(試試乘一下就能發現t單純是加在位置vector上,而對方向vector就不會有任何影響)
如果不要做線性轉換,把M設成Identity Matrix即可
如果不要做平移轉換,把t設成0即可(又或者只做3x3的matrix multiplication)

在此結構下,可以用一個matrix便能表達數次複合的轉換。一般來說按「縮放→旋轉→平移」的次序進行轉換最不容易出錯。如果不按這個順序的話可能會讓後面的轉換影響到前面的轉換(例如先平移再縮放的話,會導致原本平移的距離也一起被縮放,容易讓人混淆並質疑是不是真的要這樣做)。(參考:What is the correct order to multiply scale, rotation and translation matrices for a proper world matrix?

例如想對一個vector v先縮放再旋轉再平移,那麼要計算轉換後的vector v'的公式就是:
v' = (Mtranslation)(Mrotation)(Mscaling)v

因為Unity是使用column vector,要放在最右邊,所以公式也要從右開始寫。
而把 (Mtranslation)(Mrotation)(Mscaling)這三個matrix相乘便能得出一個結合了三次轉換的matrix。




2018年10月13日 星期六

Unity上常用的基本向量計算 (Vector)


甚麼是Vector(向量)?簡單來說就是幾個代表了各自維度的數字的組合,例如2D世界的Vector就是(x,y)兩個數字,3D世界的Vector就是(x,y,z)。不過比起這些數字上組合,數學上對待Vector比較重視其magnitude(模)和direction(方向)這兩個特點的組合。

在一個遊戲世界的coordinates中,一個遊戲物件通常會有位置方位,而這兩個要素都可以分別用Vector表達。在Unity中寫程式時,假如是2D遊戲的話,就用Vector2這個datatype來表達,3D遊戲則是用Vector3。雖然位置看起來似乎並不是一個向量(因為它不是方向),但你可以想像它是由原點(0,0,0)指著遊戲物件所在的位置,而Unity為了方便計算所以統一把位置和方向都用Vector來表達,然後提供了許多方便你做Vector計算的函數庫。不過要留意之後在做Matrix Transformation的時候,對於位置Vector和方向Vector的處理方式會不太一樣。

Magnitude (模)

Vector的magnitude其實就是它的長度。要計算兩個物件之間的直線距離的話,就要相減它們的位置(先後次序無所謂),得出一個Vector(代表從其中一個物件指向另外一個物件),再計算這個Vector的magnitude。

Vector V的magnitude的計算方法: |V| = sqrt(Vx^2 + Vy^2 + Vz^2)  (如果是2D就不用加Vz^2,之後的公式也是)

其實這個公式就是來自勾股定理(Pythagorean theorem),也能看得出Magnitude不會有負數。

Unit Vector/Normalized Vector (單位向量)

Unit vector(又叫Normalized vector)是指magnitude只有1的vector,可以說這個unit vector只有direction而沒有magnitude。只要把一個Vector內的每個值除以它的magnitude,就能得到Unit vector了,這個過程叫做Normalization(歸一化)。Unit vector是其中一種表達物件方向的方法(尤其在3D世界內難以用角度來表達方向)。

要判斷一個vector是否unit vector,最快的方法是看其和自己的dot product是否1(下面會詳細講解)。

Dot Product (點積)

Dot product的計算方法: A.B = AxBx + AyBy + AzBz
可以看得出兩個Vector的Dot product只是一個數而非Vector。

一個常用的角度公式:
A.B = |A| |B| cos (A和B的夾角)

Dot product的常見用途:

計算投影

一個Vector A和一個Unit Vector B的Dot product代表了A在B這個方向上的投影的長度。

另外,一個Unit vector和自己的dot product是1(可以想像兩個一模一樣的vector出來的投影肯定也和自己一樣),因此要判斷一個vector是否unit vector最快的方法是計算dot product是否1(而非使用浪費時間的sqrt來計算vector的magnitude)。

比較方向

如果兩個Vector的Dot product大於0,代表這兩個Vector間的角度小於90度(我稱為指著大致相同方向)。
如果等於0,代表兩個Vector互相垂直。
如果小於0,代表兩個Vector的角度大於90度(我稱之為相反方向)。

判斷視線範圍

在很多遊戲中,一個怪物會有牠的視線範圍,當你步入了這個範圍中牠就會攻擊你。一個常用的視線範圍判定方法是:以怪物面向的方向為一個Vector(這個Vector的長度代表了怪物能觀察的距離),然後設定觀察角度(即怪物只能看到Vector左邊角度除以2和右邊角度除以2內的物件)。

那麼要如何判定玩家是否在怪物的視線範圍內?條件為以下兩項同時符合:

  • 觀察距離:玩家和怪物間的距離 <= 怪物面向Vector的magnitude
  • 觀察角度:怪物指著玩家的Vector 與 怪物面向Vector間的夾角 <= 觀察角度/2
如何計算上述提到的夾角?可以利用上面提到過的角度公式:

設「怪物指著玩家的Vector」為A,「怪物面向Vector」為B
(留意A的magnitude其實就是玩家和怪物間的距離)
A.B = |A| |B| cos (A和B間的夾角)
夾角 = arccos (  (A.B) / (|A| |B|)   )

另外要注意,怪物指著玩家的Vector是 玩家的位置 - 怪物的位置 (而非相反)
(如果你覺得很容易搞混相減的次序,試想想從怪物指著玩家,即是怪物是原點,這樣就知道是要玩家減怪物了)

Cross Product (叉積)

兩個Vector的Cross product是一個新的Vector,而非像dot product般只是一個數。

Cross product的計算方法:A X B = (AyBz - AzBy, AzBx - AxBz, AxBy - AyBx)
(尤其注意y點是先z後x,而非先x後z)

另外,A X B = -(B X A)
(從公式就能看得出,因為減法次序的改變,所以A X B和B X A的關係是正負相反)

一個常用的角度公式:
|A X B| = |A| |B| sin (A和B的夾角)

Cross product的用途:

3D遊戲:計算垂直Vector

在3D遊戲的情況下,A X B得出來的Vector會同時和A和B垂直(若A和B平行的話會得出0,0,0)。
能和A和B同時垂直的方向有兩邊,那麼怎麼知道這個Vector是指著哪邊?
Unity內的Scene View用的是Left-hand rule,在Left-hand rule下A是拇指、B是食指的話,A X B就是中指指著的方向。

若你想要的是相反方向的垂直Vector,使用B X A計算或者直接用-(A X B)即可。

假如我們需要知道3D世界內一個三角形的平面對著哪個方向,可以用三角形的三個邊的其中兩個作為Vector,便能計算出與這個平面垂直的Vector。

2D遊戲:計算面積

在2D世界的情況下,A X B得出來的Vector的magnitude是A和B形成的平行四邊形的面積,除以二的話就是三角形的面積。

2018年10月8日 星期一

在CentOS 7上設立SFTP server

想必大家都有聽過FTP (File Transfer Protocol),能讓你像檔案總管一樣瀏覽伺服器上面放置的檔案。但FTP本身是個純文字的協議,並沒有任何加密功能,所以傳送的內容很容易會被人輕易看到。而FTPS則是FTP的延伸,幫FTP加了一層TLS/SSL加密,不過本身需要開多個port分別負責驗證、傳送檔案等不同的功能。

SFTP則是另一套完全不同的傳輸協議。它的作用雖然和FTP一樣能讓你瀏覽伺服器上的檔案,但技術上的協議內容運作模式完全不一樣(使用了更加節省bandwidth的binary protocol),因此一個程式支援FTP或FTPS不代表它支援SFTP。由於SFTP是經SSH(兩部電腦之間加密通訊的協議,一般用於遠端控制Linux主機)運行的,所以我比較喜歡以SSH File Transfer Protocol作為SFTP的全名,更能反映SFTP的特性。

如果想要設立SFTP伺服器的話,建議在Linux上設立,因為Windows對於SSH的支援比較差,一般需要安裝額外軟件,連帶一堆麻煩的設定,而Linux則對SSH和SFTP均有原生的支援,一般來說不必安裝額外軟件,只要小心設定便可。

這次我們以CentOs 7作例子安裝SFTP伺服器。一般來說CentOs 7安裝的時候會自動帶有SSH功能,如果不肯定可以用以下Command檢查一下:

rpm -qa|grep ssh

(-qa代表query all,列出所有已安裝的package,grep ssh則是列出帶有ssh這三個字的package)

看到有libssh2、opeenssh(包括server和clients)基本上就沒問題了。

接下來我們會建立一個只能存取SFTP的user:(記得把myusername改成自己想要的賬戶名稱。)

groupadd sftpusers
useradd -g sftpusers -d /upload -s /sbin/nologin myusername
passwd myusername

(-g sftpusers代表把user加到這個sftpusers群組內)
(-d /upload代表設立user的main directory為/upload)
(-s /sbin/nologin代表把user加進nologin這個shell,讓他無法登入這部電腦,確保只能存取SFTP)

然後我們可以建立這個user專用的資料夾,並設定權限:(同樣記得把myusername改成自己想要的賬戶名稱)
mkdir -p /data/mysftpuser/upload
chown -R root:sftpusers /data/myusername
chown -R mysftpuser:sftpusers /data/myusername/upload


最後,在/etc/ssh/sshd_config這個設定檔下新增一些設定:
Match Group sftpusers
ChrootDirectory /data/%u
ForceCommand internal-sftp

(Match Group sftpusers代表下方的設定套用到sftpusers群組身上)
(ChrootDirectory代表更改user的root directory,讓他只能使用我們指定的目錄)
(ForceCommand internal-sftp代表啟用SFTP subsystem)

最後重啟SSH:

service sshd restart

安裝完成,你可以直接試試打sftp來試試連接自己電腦的SFTP server,或者用FileZilla之類的SFTP client軟件來連接。

參考資料:
https://www.howtoforge.com/tutorial/how-to-setup-an-sftp-server-on-centos/

2018年9月20日 星期四

Shader(著色器)新手入門筆記 - 初次接觸

最近一段時間在使用Unity開發一款遊戲,想做一些特別的視覺效果,在網上做了一些資料蒐集後,似乎寫Shader(著色器)是最好的選擇。在兩年前用Unity開發遊戲的時候並沒有需要用到特別的視覺效果,因此並沒有接觸過Shader,所以要從零開始學起。

不過我剛開始打算自學Shader的時候遇到了很大的問題,就是網上的教學很少也很分散(可能是因為這項技術本身比較專門),入門門檻很高。雖然Unity官方有提供一篇入門教學(A Gentle Introduction to Shaders),但看了很久也不太明白,可能是因為這篇文章面向的對象並非完全是新手,同時也沒有清楚解釋一個Shader程式碼內每個部分的意義。

經過一段時間的自學後,總結一下自己對Shader的初步理解:(自己也還只是新手,若有錯誤歡迎指正)

甚麼是Shader?

Shader(著色器)是一種電腦程式,簡單來說主要負責進行圖像處理,一般都在GPU上執行。

如果想講得準確點的話,要把遊戲的各個物件從模型到畫在螢幕上的過程有很多個步驟,這個過程我們叫Rendering Pipeline(繪圖管線)。當中有一些步驟我們可以寫程式控制,有一些步驟我們可以做一些設定,其他步驟則甚麼也不能做(只能交由GPU處理)。可以寫程式控制的那些程式就叫Shader,不同的步驟就是交由不同種類的Shader來負責。不過一般我們最常接觸的是Vertex shader和Fragment shader,下面會講到這兩種shader實際上負責甚麼。

Shader使用甚麼程式語言?

和Unity使用C#作為程式語言不一樣,Shader是使用另外一種專門為圖像處理而設計的程式語言。Unity可以用的Shader程式語言有兩種,分別為Cg/HLSL(DirectX)和GLSL(OpenGL)。一般來說都會使用跨平台的Cg/HLSL(Unity會根據目標平台再幫你轉成DirectX或OpenGL),網上的Shader資源大多也是使用這個語言。

HLSL是微軟的DirectX的所使用的語言,GLSL則是OpenGL所使用的語言,而Cg是Nvidia開發出來的語言,而因為Cg和HLSL有合作,所以兩者的語法非常相似,但又有一些不同。

Cg全名為C for graphics,語法上有點參考了C但又不太一樣,原本由Nvidia負責維護但現在已經不再支援(depreciated)。Unity官方之所以把Unity Shader使用的程式語言會叫Cg/HLSL,是因為它實際上並不完全等同於Cg或HLSL,而較像是它們的變種,因為有些Cg和HLSL支援的語法或函數在Unity中並不支援(你可以理解為Unity在參考了Cg和HLSL後自己又弄了一個很像它們的語言)。但為了簡單起見,我們就把Unity所使用的這個Cg/HLSL變種語言直接簡稱為Cg。

要注意的是,Unity的Shader程式碼中,要到「CGPROGRAM」和「ENDCG」之間的程式碼才是Cg語言,其他的部分則是使用叫ShaderLab的宣告式語言(即是只是定義,而沒有任何程式邏輯的部分)。

Unity的Shader就是一個Shader嗎?

Unity Shader其實指的是Unity內一個附檔名為.shader的檔案,而裡面實際上是把很多不同步驟所涉及的Shader包裝在一起。這個檔案可以附加在材質(Material)上面,然後我們就可以把材質附加在Game Object上,讓其顯示出我們想要的效果。

簡單來說,Unity Shader為了方便你管理,是以Material為單位,讓你寫該Material下所需的各種Shader。

Unity Shader的結構?

一個Unity Shader下會有多個Sub Shader,GPU會視乎你為其加的標籤而選用其中一個Sub Shader。

而Sub Shader裡面可以再定義不同種類的Shader,常見的有兩種Shader組合:

第一種是Vertex Shader + Fragment Shader,算是比較基礎的Shader,也是我一開始學習的組合,會比較容易了解Shader的原理並進行基礎的運算,也容易做Optimization,確保只計算自己需要的效果,不計算其他多餘的東西。

第二種是Surface Shader(表面著色器),是Unity為了方便你而設立的一個簡化版Shader,實際上它還是會再幫你將其轉為Vertex Shader + Fragment Shader。據說比較容易用簡單的程式碼寫出比較好的效果(尤其是涉及光源的效果),但可能當中的函數會幫你計算一些多餘的東西,較難做Optimization或者一些獨特的效果。

除了Sub Shader以外,還有Property(弄一些可以在Unity Editor內調節的variables給Shader用)、Tag(定義Shader的一些特性,供GPU參考)和Fallback(如果所有Sub Shade都用了一些GPU不支援的功能的話就轉用另外一個低級的Shader)等。

甚麼是Vertex Shader和Fragment Shader?

在了解這兩種Shader是甚麼之前,建議先了解整個Rendering Pipeline的流程,這樣你會比較清楚他們當中會擔任甚麼工作。在Vertex Shader和Fragment Shader之間其實還隔了很多個步驟,所以不要以為Vertex Shader的output就是Fragment Shader的input。

Vertex Shader(頂點著色器)會每個Vertex(即是你的遊戲模型的一點)運行一次。網上經常只說Vertex Shader可以讓你針對Vertex的位置或者Normal做調節,但卻經常說漏了最重要的作用,就是做Matrix transformation,把vertex的coordinates從Model Space(模型空間)轉成Clip Space(修改空間),我稱之為空間轉換。

(Model Space又稱Local Space局部空間或Object Space物件空間)

本來每個Game Object都是獨自一個個體,有自己的coordinates,而這個轉換就是要把這些Game Object都放在同一個世界內。你如果在Shader檔案內有看過類似mul(UNITY_MVP, v.vertex)這樣的語法(Unity建議簡化成UnityObjectToClipPos(v.vertex)),它們就是在做空間轉換。

因此我對Vertex Shader的理解是,主要負責做空間轉換,但你也可以選擇針對vertex再加多一點調節,例如要模擬水面的扭曲效果,就是需要把vertex按一定的規律進行移動。

Fragment Shader(片段著色器)(有人叫Pixel Shader但我覺得不太準確),則是每個Fragment(亦即是可能會畫在螢幕上的一點,但Fragment本身包含了較多資訊,Pixel就真的只是在螢幕上的一個點)運行一次。Fragment Shader主要做的就是利用每一粒Fragment中不同的資訊,決定每一粒Pixel上的顏色

你可以想像如果你的螢幕大小是1920x1200,那就可能要運行兩百萬次,而為了確保遊戲能保持60fps的速度,一個Fragment Shader需要在最多8.33納秒(nanoseconds,即10的負九次方)內就執行完畢,因此要使用最快的計算方法以及最少的Memory來做Optimization。因此你會用到half和fixed這種比float使用更少Memory的floating point data type,一般會使用2 Bytes但會根據不同GPU硬件而有所不同。

Shader data types:
https://docs.unity3d.com/Manual/SL-DataTypesAndPrecision.html

有時候如果真的做不到更快速計算的話,可以試試以下方法:

  • 在某些GPU上停用該Shader(通常會設定該Shader的LOD,即Level of details,當總體LOD低於某個值就會自動停用Shader)
  • 調低預設fps(可固定在30或更低)

想自己寫一個Shader

我覺得第一個Shader可以試試自己從頭到尾寫,以了解Shader的結構,不過在之後進行開發的時候多數都是複製一個現成的Shader修改程式碼(現成的無論是自己的還是別人的都可以,只要你大致看得懂),這樣就能省卻許多syntax的問題,專心弄想要的效果。

對於Shader的概念有基本的認識後,想自己動手寫一個Shader試試看的話,我覺得以下的影片是個不錯的入手點:
Shaders 101 - Intro to Shaders

Shader難寫嗎?

簡單的Shader很容易寫,但稍微複雜一點的Shader就會難寫很多。主要是因為不同GPU的種類和能力都差很遠,通常好的Shader需要能夠支援不同等級的GPU,於是往往要定義很多Pass或者Fallback來處理GPU不支援的情況。

此外,相比起Unity本身的程式或者其他程式來說,Shader的網上資源很少,討論不多,就連Documentation也是近乎沒有(對於平時非常依賴Documentation的程式員來簡直說是無法想像),想知道一個function在做甚麼往往都只能在一些分散的Tutorial或者社群文章中找到。

Shader因為會涉及許多數學運算,想做到自己想要的效果往往要自己想一條formula出來,如果數學不好的話大概要惡補一下數學了(尤其是Graph和Matrix)。

參考資料

這篇教學很親切地講解了Unity Shader的結構:
貓都能學會的Unity3D Shader入門指南(一)(二)

也可以看看在巴哈的這篇文章,C4Cat的Colin Leung在留言下解答了許多新手常見的問題:
https://home.gamer.com.tw/creationDetail.php?sn=2867450

另外我也在圖書館借過《遊戲大師天堂路:只有Unity Shader才能超越Unity》這本教材,內容由淺入深且相當詳細,也讓我知道了原來Shader可以做的事情比想像中多,不過要注意當中一些專用術語的中文翻譯前後不一,因此個人還是喜歡用回英文術語


2018年8月19日 星期日

於PHP7安裝Coppermine

Coppermine是一個已經有很久歷史的Image Gallery Web應用,特點為讓多位用家登入並發表自己的相片,有點類似phpBB之類的論壇風格。目前的最新穩定版本為1.5.x,不過development版本已經到了1.6.x,可在GitHub下載:

https://github.com/coppermine-gallery
https://github.com/coppermine-gallery/cpg1.6.x/releases

1.6.x的其中一個特點是把database的API抽象化,可用mysqli或者PDO,改善了1.5.x之前只能用mysql的問題。因為PHP7一定要用mysqli或PDO,所以變相要在PHP7上安裝Coppermine的話只能安裝1.6.x的版本。

另外,1.6.x的安裝精靈可以直接用root權限來新增database,不過我還是建議自己開一個database並創立一個專屬user,這樣就不需要把root權限交給安裝精靈了。

另外在複製文件夾的時候,需要把文件夾的Owner改成web server,並設定755權限:

sudo chown -R www-data:www-data /coppermine
sudo chmod -R 755 /coppermine


不過目前好像還沒找到支援1.6.x的主題(theme),而且官方教學中也還沒有加上把1.5.x的主題upgrade上1.6.x的方法,可能需要在社群上發問或者自己研究一下如何做了。

2018年5月24日 星期四

解決mediawiki的上傳檔案時出現的錯誤

雖然在安裝Mediawiki的時候已經能選擇是否啟用檔案上傳功能,但我發現在啟用後在上傳檔案時會出現錯誤,經過一番調查之後發現原來是web server沒有權限編輯images資料夾(上傳的檔案會放在images資料夾)。

解決方法是把這個資料夾的Owner轉成www-data,亦即是Ubuntu上的web server(例如Apache和Nginx)所使用的user。

sudo chown -R www-data:www-data images/

這個技巧在其他應用程式也能用到。如果有程式需要編輯某個資料夾,一般都需要特地為其加上權限。如果不肯定是哪個資料夾就需要Recursively地把所有該程式會用到的資料夾都加上權限或轉Owner。

參考:
https://www.mediawiki.org/wiki/Manual:Configuring_file_uploads
https://askubuntu.com/questions/873839/what-is-the-www-data-user

2018年4月29日 星期日

在Linux上啟用php的mail()

在Linux上安裝了PHP後,一般來說是沒辦法是用php來寄電郵的,mail()會return nothing。若要啟用的話,需要先指定php如何寄電郵。

最簡單的方法是在其他網站註冊一個電郵賬號(例如gmail),然後用ssmtp以該電郵賬號的身份寄電郵。一般來說在自己的伺服器上架一個電郵伺服器(mail server)是個不實際的做法 ,一來相當費功夫,二來寄出去的電郵很可能會被當成spam而被擋掉,無論你怎麼設定都無法解決這個問題。這篇文章很詳細地解釋不應自己架電郵伺服器的原因:https://www.digitalocean.com/community/tutorials/why-you-may-not-want-to-run-your-own-mail-server

1. 取得電郵賬號的SMTP資料

需要知道的資料:
SMTP server的domain和port (一般來說SSL/TLS會用465)
賬號和密碼

2. 安裝ssmtp

sudo apt-get update
sudo apt-get install ssmtp

修改/etc/ssmtp/ssmtp.conf,設定以下的參數:

root=user@example.com
mailhub=smtp.example.com:465 FromLineOverride=YES AuthUser=user@example.com AuthPass=password UseTLS=YES

然後可以試試寄電郵:
echo "Hello world!" | ssmtp recipient@example.com

有些電郵伺服器指定要有From之類的header,那就先打以下的command line:
ssmtp recipient@example.com
按Enter後打電郵內容,打完按Ctrl+D,注意SUBJECT之後要空一行,再打電郵內容:
TO: recipient@example.com
FROM: <from@example.com>
SUBJECT: Testing subject

Hello world!

3. 修改php.ini

PHP5的位置: /etc/php5/apache2/php.ini
PHP7的位置: /etc/php/7.0/apache2/php.ini

修改該sendmail_path(原本這句應該是被comment掉的)

sendmail_path = /usr/sbin/ssmtp -t

重啟Apache後應該就可以用PHP的mail()了。如果電郵伺服器要求有From之類的header的話,在使用mail()時記得也要給header。

2018年4月25日 星期三

在Mediawiki上安裝navbox

看到Wikipedia的navbox非常好用,使用率也很高,所以也想在自己的Mediawiki上面安裝,但發現網上幾乎沒有這方面的教學,又或者相當模糊,讓自己花了好多時間才摸索到安裝的方法,因此記錄一下自己安裝的過程:

1. 安裝Scribunto插件

Scribunto插件的主要目的是可以讓你建立Module:XXX的頁面,然後在上面寫lua script。因為MediaWiki的navbox是用lua script寫出來的,需要建立一些Module頁面並貼上lua代碼,所以想用的話就先要安裝Scribunto。

安裝步驟:https://www.mediawiki.org/wiki/Extension:Scribunto#Installation
大致上就是下載後把資料夾放在extensions資料夾內,再於LocalSettings.php加上以下代碼:
wfLoadExtension( 'Scribunto' );
$wgScribuntoDefaultEngine = 'luastandalone';
幫Lua程式加上執行權限,(雖然我試的時候已經有執行權限了):
chmod a+x /path/to/extensions/Scribunto/engines/LuaStandalone/binaries/yourOS/lua
記得把yourOS換成自己的作業系統(我的是lua5_1_5_linux_64_generic)

如果想有syntax highlighting(代碼上色),需要先安裝SyntaxHighlight_GeSHi插件,然後再於LocalSettings.php加上:
$wgScribuntoUseGeSHi = true;

安裝SyntaxHighlight_GeSHi的步驟請看:https://www.mediawiki.org/wiki/Extension:SyntaxHighlight#Installation

不過當初我嘗試加上的時候,遇到了個問題:在自己的測試機(Ubuntu 16.04 Desktop)是可以順利啟用代碼上色的,但到了正式機(Ubuntu 16.04 Server)卻無法上色,查了很久才發現是/usr/bin內沒有以python為名的程式(只有python3.5),而測試機則已經有一個python link指著另一個python版本的程式,於是我也在正式機內加上一條link,這樣SyntaxHighlight_GeSHi執行pygmentize的時候就能找到python了。

2. 在MediaWiki上建立頁面


建立以下頁面,可以從MediaWiki把原始碼抄過來(不要用Wikipedia的,因為需要加更多額外的Module)
Template:Navbox
Module:Navbox
Module:Navbar
Module:Arguments

建立以下頁面(預設會有不過是空的),從Wikipedia把原始碼抄過來
MediaWiki:Common.css
MediaWiki.Common.js

建立完畢後就可以用navbox了。範例代碼:

{{Navbox
|name = Navbox/doc
|state = uncollapsed
|image = {{{image}}}
|title = {{{title}}}
|above = {{{above}}}
|group1 = {{{group1}}}
|list1 = {{{list1}}}
|group2 = {{{group2}}}
|list2 = {{{list2}}}
|list3 = {{{list3}}} ''without {{{group3}}}''
|group4 = {{{group4}}}
|list4 = {{{list4}}}
|below = {{{below}}}
See alternate navbox formats under: [[#Layout of table|''Layout of table'']]
}}

2018年4月19日 星期四

用dumpcap抓取網絡封包 (Capture network packets)

dumpcap是Wireshark套裝的其中一個小軟件,用來抓取你電腦上的網絡封包並存成檔案,方便之後分析或者是重播。如果用比喻來說就是在幫你電腦上的網絡活動「錄影」。

安裝Wireshark後,在Wireshark的文件夾(一般是C:\Program Files\Wireshark\)內會有dumpcap.exe。可以在command line內啟動裡面的dumpcap.exe,或者把Wireshark的文件夾加到PATH裡面,這樣就可以直接在cmd內打dumpcap開啟。

dumpcap只會錄影不會主動干涉,所以如果你想錄某個multicast group的封包但電腦本身沒有在接收這個group的話,dumpcap是不會錄到這個group的。所以記得在錄影前要先用其他軟件接收這個group。

範例用法:

dumpcap -P -i InterfaceName -a duration: DurationInSeconds -b filesize: MaxFileSizeForEachFile -f Filter & -w FilePath


詳情:https://www.wireshark.org/docs/man-pages/dumpcap.html

asp 網頁轉交 (web page forwarding)

之前試過要在web server上寫一個asp,目的是做網頁轉交 (web page forwarding)。情況是這樣的:我想存取某一個網頁,但因為某些原因我不能直接存取(例如那個網頁是在一個內部伺服器內,我不能直接透過網絡存取那個伺服器),所以我要弄一個asp放在web server,而這個web server是可以存取內部伺服器的。
這個asp會把我的GET和POST request都轉交給目標網頁,再把目標網頁的內容轉交給我。
寫法如下:

1. 先設好CodePage

CodePage用來指定IIS用哪個代碼頁來解碼的,這裡我們用UTF-8(65001)
Session.CodePage = 65001

2. 轉交網頁內容

先取得用家的POST request,還可以附帶你自己加上的ExtraPostData
Dim strPostData
strPostData = Request.form
If strPostData <> "" Then strPostData = strPostData & "&"
strPostData = strPostData & ExtraPostData
然後開啟XMLHTTP,把你的目標網頁(URLTarget)和GET request拼在一起,再連同POST request一起發送出去
SET httpRequest = Server.createObject("MSXML2.XMLHTTP")
With httpRequest
    .Open "POST", URLTarget & "?" & Request.QueryString , FALSE
    .SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
    .Send strPostData
End With
最後再把取得的網頁內容寫出來,別忘了在Response header加上Character set,否則有些瀏覽器可能會用了錯的character set來解碼
Response.charset = "UTF-8"
Response.write(httpRequest.ResponseText)

不過這個asp只能轉交網頁,並不能轉交網頁附帶的資源(例如存放在內部伺服器的圖片和和連結)。

3. 錯誤處理

visual basic並沒有像C++之類的語言有exception handling機制,一般來說是先加入下面的代碼,這樣就算遇到錯誤不是馬上停止而是繼續下去:
On Error Resume Next
之後每當你想檢查剛剛的代碼有沒有出錯時,可以檢查Err.Number:
If Err.Number <> 0 Then
  Response.write Err.Description
  Err.Clear
End If

2018年4月17日 星期二

Ubuntu 16.04上安裝LAMP

OS版本:Ubuntu 16.04
可參考DigitalOcean的教學:https://www.digitalocean.com/community/tutorials/how-to-install-linux-apache-mysql-php-lamp-stack-on-ubuntu-16-04
一篇中文的文章,雖然部分內容已過時:http://www.codingstartup.com/how-to-setup-lamp-on-ubuntu/

0. 更新軟件清單

凡是在安裝任何新軟件前都應該先執行這一步,確保自己的軟件清單是最新的:

sudo apt-get update

1. 安裝Apache

sudo apt-get install apache2
安裝完畢後便可直接打開lcaolhost,看到Apache的歡迎頁面。
預設網頁目錄在/var/www/html
這時候你應該可以在有隨便一部連上網絡的電腦上打http://ServerIP/看到你的首頁。如果你本身開啟了防火牆,記得允許Apache通過。

重啟Apache:
sudo service apache2 restart

/var/www/html的預設權限是755,如果不想每次改東西都打sudo的話可以幫資料夾加上所有人都可以修改的權限:

sudo chmod o+w /var/www/html

如果要允許使用.htaccess,可以更改/etc/apache2/sites-available/000-default.conf
至於https則是更改/etc/apache2/sites-available/default-ssl.conf
在conf內加上以下代碼:(記得重啟Apache)

<Directory "/var/www/html">
    AllowOverride All
</Directory>

假如文件夾沒有index.html之類的檔案,Apache預設會顯示該資料夾下的所有檔案。如果不想給人看的話,可在.htaccess加上以下代碼:
Options -Indexes


到了正式機的時候,可以安裝SSL,這樣別人便能用https來上你的網站,確保用家和你網站之間的交流是加密的。
前幾年要安裝SSL依然是非常麻煩的,而且也要付錢,但現在已經有各種免費的SSL簽發者了,比較有名的是Let's Encrypt,安裝起來也相當簡單。不過要留意安裝SSL前必須先在Apache上設定Virtual Host(讓Apache能根據request的domain name來判斷給予哪個網頁,這樣便能在同一個伺服器上設立多個不同domain name的網站)。
教學:https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04

2. 安裝MySQL

sudo apt-get install mysql-server
中途會要求你設定root的密碼

mysql_secure_installation
幫你設定一些安全選項,但如果只是想測試可以選擇不做某些指令,例如密碼強度(Password Policy)可以選LOW,或者乾脆不安裝validate password plugin,否則每次都要打複雜的密碼只會為自己帶來麻煩

安裝完畢後可以測試一下,看MySQL是否順利地運行中:
systemctl status mysql.service

版本資訊:
mysqladmin -p -u root version

登入MySQL:
mysql -u root -p

開啟/關閉MySQL:
service mysqld start
service mysqld stop

3. 安裝PHP

請注意Ubuntu 16.04預設只能安裝PHP7,如果要PHP5請參考:

安裝PHP7:
sudo apt-get install php libapache2-mod-php php-mcrypt php-mysql
你也可以選擇安裝 php5-curl (讀取外部網站)和 php5-gd (圖像處理)

4. 安裝phpMyAdmin

在Command line執行mysql指令或者更新database或者table可能會相當麻煩,可以安裝phpMyAdmin,這樣便能直接以網頁的方式更容易管理數據庫。記得要安裝完MySQL和PHP後才安裝此項。

sudo apt-get install phpmyadmin php-mbstring php-gettext

安裝的時候會叫你選擇web server,選apache,記得按Space打勾,再按Tab和Enter。

需要啟用的php模組:(裝完後要重啟Apache)
sudo phpenmod mcrypt
sudo phpenmod mbstring

網址:localhost/phpmyadmin

記得要幫localhost/phpmyadmin加入額外一層密碼,加多一重保障。詳情可看:
https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-phpmyadmin-on-ubuntu-16-04   (Step Two — Secure your phpMyAdmin Instance)

更安全的做法是只允許在本機存取,在/etc/phpmyadmin/apache.conf加上下列代碼:(可能需要重啟Apache)
Order Allow,Deny
Deny from All
Allow from localhost


2018年3月28日 星期三

關於Server時區的問題

工作上經常會接觸很多部Server,而每個Server雖然時間都是接近或者一樣的,但時區設定可能會各有不同,尤其是面對國際的Server更是。因此如果在開發Server程式而需要用電腦時間來執行某些工作的時候,就要小心時區兼容的問題。即使你的程式是以香港時間為準,千萬不要假設了你的程式一定會在香港時區的電腦運行

當你在程式內想取得電腦現在的時間時,需要小心你取得的是本地時區的時間 (Localtime),還是世界協調時間 (UTC/GMT,雖然在科學的定義上UTC和GMT有少許不一樣,但一般在寫程式上會視兩者為同一物)。寫程式很容易犯的錯誤就是沒有考慮到底取得了哪個時區的時間。如果運行程式的電腦(尤其是伺服器)有機會是處於不同時區,而你想取得的是準確的時間(尤其是要計算或顯示時、分、秒),那就千萬別拿本地時區時間。

假設我們身處香港,時區是UTC+8,有一個伺服器程式會在每天香港時間晚上六點執行一次任務。如果這個程式在一部UTC(+0)的電腦上執行,就會變成香港時間凌晨兩點才執行。也許你會覺得只要能夠自行設定這個時間而不是寫死(hardcode)在程式內,再看看目標電腦是哪個時區而更改設定,就能解決這個問題。但一來我們有時候並不知道目標電腦的時區是甚麼,二來有一些程式邏輯可能假定了執行任務時的日期,當出現跨日期的情況就更難處理。

其實最簡單的方法就是永遠只取得世界協調時間,再將其調整成你自己想要的時區。例如在香港的話,取得世界協調時間後直接加上八小時。這樣無論電腦是設置成哪個時區,我們都能取得正確的香港時間。當然視乎情況也可以一律只使用UTC時間作準

C++程式碼例子:
//Get UTC+8 Time (Windows MFC library)
CTime ctNow = CTime::GetCurrentTime();
tm tmHongKongTime;
(ctNow  + CTimeSpan(0, 8, 0, 0)).GetGmtTm(tmHongKongTime);
return tmHKT;

// Get UTC+8 Time (Standard library)
time_t t = time(NULL);
struct tm *tmHKT = gmtime(&t); //still GMT, not yet HKT
tmHKT->tm_hour += 8; //add 8 to GMT
mktime(tmHKT); //recompute the values to make it valid
return tmHKT;

// Print time
printf("Now is %d/%d/%d %02d:%02d:%02d HKT",
 tmHKT->tm_year + 1900, tmHKT->tm_mon + 1, tmHKT->tm_mday,
 tmHKT->tm_hour, tmHKT->tm_min, tmHKT->tm_sec);
c#程式碼例子:
C#的DateTime可以直接在上面設定時區,然後無論是Property還是Print出來的時間都會自動幫你調成該時區的時間:
// Assume now is 2018:08:23 16:27:04 Hong Kong Time

// UTC Time: 2018-08-23 08:27:04 Utc 8 636706096247972300
DateTime dtUTC = DateTime.UtcNow;
Console.WriteLine("{0} {1} {2} {3}", dtUTC, dtUTC.Kind, dtUTC.Hour, dtUTC.Ticks);

// Local Time (now Hong Kong Time): 2018-08-23 16:27:04 Local 16 636706384247972300
DateTime dtLocal = dtUTC.ToLocalTime();
Console.WriteLine("{0} {1} {2} {3}", dtLocal, dtLocal.Kind, dtLocal.Hour, dtLocal.Ticks);

// Absolute Hong Kong Time (ignore machine timezone): 2018-08-23 16:27:04 Unspecified 16 636706384247972300
DateTime dtHKT = TimeZoneInfo.ConvertTimeFromUtc(dtUTC, TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
Console.WriteLine("{0} {1} {2} {3}", dtHKT, dtHKT.Kind, dtHKT.Hour, dtHKT.Ticks);
JavaScript程式碼例子:
//JavaScript
function GetAbsoluteHKTime()
{
    var now = new Date();
    now.setUTCHours(now.getUTCHours() + 8);
    return {
        "year" : now.getUTCFullYear(),
        "month" : now.getUTCMonth(),
        "date" : now.getUTCDate(),
        "hour" : now.getUTCHours(),
        "minute" : now.getUTCMinutes(),
        "second" : now.getUTCSeconds()
    };
}

var j = GetAbsoluteHKTime();
console.log("Now is " + j.year + "/" + j.month + "/" + j.date + " "
+ ('0' + j.hour).slice(-2) + ":" 
+ ('0' + j.minute).slice(-2) + ":" 
+ ('0' + j.second).slice(-2) + " HKT");



C++程式碼例子:(以下代碼嚴格來說並不正確,因為沒有跟標準,CTime的GetHour()之類的函數會回傳本地時區的時間,如果想要拿UTC時間就要用GetGmtTm)

// Get UTC+8 Time (Windows MFC library)
SYSTEMTIME st;
GetSystemTime(&st);
CTime ctHongKongTime = CTime(st) + CTimeSpan(0, 8, 0, 0)

2018年1月8日 星期一

JSON Hijacking

JSON Hijacking是利用JSON array可以執行的特性,把用戶的cookie送到其他網站的漏洞。如果你的網站需要用戶發送GET請求取回一個JSON array,便有機會中招。

要避免這個漏洞,最簡單的方法就是網站直接傳回一個JSON object {}而不是array []。另外也可以在傳回值的前面加上while(1);避免後面的代碼被執行。

參考資料
https://stackoverflow.com/questions/2669690/why-does-google-prepend-while1-to-their-json-responses?rq=1
https://haacked.com/archive/2009/06/25/json-hijacking.aspx/

2017年9月22日 星期五

Windows的傾印檔 (Dump File)

甚麽來的?

這是一個程式在某個瞬間的快照(Snapshot),可把當時程式的運行狀態以及使用的記憶體像照片一樣拍下來,之後便可在開發軟件內嘗試重現當時的情況。在程式不知道為何出錯時,如果能取得它當時的Dump file,對於debug找出問題所在是很有用的。

下面介紹在Windows取得傾印檔的方法。

如何取得

當程式在運行途中出現錯誤而被終止運行(Crash/Terminated)的時候,作業系統有時會幫它儲存一個傾印檔(Dump File)。你也可以選擇自己開啓Task manager,在該程式名稱上按右鍵選擇Create Dump File,又或者使用微軟提供的procdump.exe來監視一個程式:

procdump -t PID

記得填入該程式的Process ID,可在程式開啟後Task Manager查到。
procdump還有許多功能,可看:https://docs.microsoft.com/en-us/sysinternals/downloads/procdump

取得之後如何使用


在取得Dump File後,可以開啓Visual Studio或者WinDbg之類的軟件來打開。
只要載入正確的Symbols檔(一般是pdb,可以幫你指出exe中的某行是source code的哪一行),再加上source code,便可得悉程式是在運行到哪一行時出錯,方便debug。

注意:

  1. 如果程式是使用Release模式編譯的,因為經過最佳化(Optimization),不一定能夠和source code一行一行對應,因此頂多只能知道大概的位置,而不能準確指出是哪一行出問題。
  2. 有時候程式或者函式庫會自帶Exception handler,即是某行出錯時會被handler抓到,然後在handler function內執行後再把Exception交給上面,最後才終止運行。這種時候我們需要查找Exception object或pointer中的Stack Trace,它上面會寫原本出問題的是哪一行。可參考:https://support.microsoft.com/en-us/help/313109/how-to-find-the-problem-exception-stack-when-you-receive-an-unhandlede
  3. 記得在編譯程式的時候保留當時的source code以及pdb!pdb是用來儲存debug information,包括exe中的machine code對source code的mapping。一定要保留在編譯exe時一起編譯的pdb,因為只要source code稍有改動後編譯出來的exe已經是完全不同的東西,不配對的pdb是無法debug的。

2017年7月24日 星期一

類別成員函數 (Class member variables) 常見的分類

在編寫Class的時候需要define很多member variables(又叫fields),供Class的member functions(又叫method)使用。但經常會看到一些比較大的Class被人加了許多member variables(從數十個到上百個都有),而且都是亂排,只看到一堆排列得沒有意義的variable會讓人很難了解這個class想做甚麼。寫Class寫多了見多了的時候會覺得其實member variable來來去去也是那幾個作用,所以我自己寫Class時通常會把member variable分成下面四類,同類的會放在一起,方便以後查看,也能提醒自己別漏了initialize某些variable:

Data Containers

裝著一些程式所需要用到的資料,尤其是一些複雜的資料結構(Data Structure),如map、list、queue。

Resources

一般是一些來自Library Class 的 Instance,當中常需要用到一些作業系統的API,或者IO用。
例子:

  • Socket (供UDP/TCP連接用)
  • Event
  • Critical section
  • File

States

用來記錄目前程式的狀態。這些variable一般都需要在Constructor initialize,避免之後第一次存取的時候出錯。
例子:

  • 上一次執行某程序的時間
  • 現在程式運行到哪一個階段 (Stage)
  • 目前是否連接著伺服器

Config

程式一開始的時候會載入的設定,一般來説之後較少或不會更改