2021年11月28日 星期日

為什麼要在4D space裁切三角形

原本我覺得要在camera space作裁切
(用視錐的6個平面)

可再想想
我們使用數學庫生成projection Matrix時
zFar和zNear(in camera space)
其實是被編碼在Matrix的2個component上面的。


而傳給Verext Shader 的,可以有下面2種情況

第1種:MVP分開

第2種:MVP乘在一起

第1種情況
如果OpenGL知道你傳的是那一種Projection Matrix
可以用Projection的2個component
重新解出zNear和zFar(但這也是Camera Space的)
但OpenGL其實不知道你傳那一種

第2種情況
我猜不行

由上可知
它不需要知道zNear和zFar

projection space ( or clip space)

乘上projection Matrix之後,視椎體的fov變成了一定是90度角
(原來在camera space 時不一定是90度,但除tanHalfFov項之後就一定是90度了)
這樣一來裁切平面就變成有4個是固定的
(和y軸夾45度角的2個平面,和x軸夾45度角的2個平面)

備註:
線段和這4個平面的交點
平面方程式是 dot( (P-C),N )= 0
和y軸夾45度角的2個平面,其中1個是
N=(1,0,-1),C=(0,0,0)所以該平面是 x=z

三角形 Vo、V1、V2
其中的一邊所在的射線為
Vo+t(V1-Vo)

代入 x=z 平面
解出t就能算出交點了
 

能不能6個平面都在NDC裡作裁切?

4個平面不能在NDC裡裁切,原因是:
如果A點和B點構成線段L
而L和其中一個裁切平面的交點為S
在projection space空間裡,A、B、S三點共線
但在NDC space,這3點不能保證共線
(這一段是在OpenGPU論壇看到的,我只是把前人說的話,畫成圖)

✖️是和y軸夾45度角的2個平面
(x , y , z , w)是Projection space的點
(1)畫的是(x , y , w)
(2)畫的是(x/w , y/w , 1)
(3)畫的是(x/w , y/w , z/w)

這張圖的NDC z是位在0~1之間
你會發現B的位置跑掉了

projection space ( or clip space)

仔細看clipping裡的條件
它說6個平面都可以在
projection space判定是否需要clip
為什麼?

以Directx為例

model view projection 全部使用左手座標
view space的頂點為Vv =(x, y, zv)
projection space的頂點為Vp = (x, y, zp , wp)
z= a zv + b
w= zv
NDC space z= z/ wp

當Vp在近平面和遠平面之間
zn會落在0和1之間
0 ≤ zn ≤ 1
0 ≤ z/ wp ≤ 1
0 ≤ zp ≤ w式A

當Vp在遠平面外面
zn > 1 
z/ wp > 1 
zp > wp 

當Vp在近平面外面
zn < 0
z/ wp < 0
zp < wp

所以用式A就可以判定是否需要clip
完全不需要知道近遠平面是什麼!
(OpenGL也是類似的道理)

當三角形的2個頂點V0和V1需要被裁切
對線段做插值就可以得出裁切平面(遠平面)上的點
(視錐的其他5個平面也可以套用類似的方法)

NDC之後還有1次z的remapping

 
參考資料

寫完軟光柵後


圖 Screen Space
先解出t,s找到點hit,就能算出α

發現其實不必在Clip space裁切左、右、上、下
因為在Screen Space 光柵化三角形前
會找出包圍三角形的矩形
而這個矩形又會受限於Screen Space的大小
效果等於在Clip space裁切左、右、上、下

但是不裁切的話
矩形超出Screen外面的部分
也會在loop裡
雖然沒做任何事

實作中發現的有趣效果


2021年11月27日 星期六

透視校正

假設三角形的3個頂點
在modle space是Am、Bm、Cm

頂點乘上Model View Projection Matrix(MVP)後
變換到Projection space
MVP(Am) = A = ( A(x),A(y),A(z),A(w) )
MVP(Bm) = B = ( B(x),B(y),B(z),B(w) )
MVP(Cm) = C = ( C(x),C(y),C(z),C(w) )

P是位在三角形ABC上的1點
那麼P在Projection space的插值是 (見下面的圖P)
P(x) = A(x)α'+B(x)*β'+C(x)*γ'
P(y) = A(y)α'+B(y)*β'+C(y)*γ'
P(w) = A(w)α'+B(w)*β'+C(w)*γ'

圖P


透視校正

(a α' + b β' + c γ')/P(w) = α a/A(w)+β b/B(w)+γ c/C(w)
意思是:「先內插的結果再 / w」 = 「先各自 / w再內插」 見上圖

把上式左右交換
α a/A(w)+β b/B(w)+γ c/C(w) = (a α' + b β' + c γ')/P(w)

再同乘P(w)後有
(α a/A(w)+β b/B(w)+γ c/C(w))*P(w) = (a α' + b β' + c γ')

意思是:
把「先各自 / w再內插」的結果 * w 
就能還原為「先內插的結果」

舉例

A,B,C 除w 之後 會變成An、Bn、Cn
An = A/ A(w)、Bn = B/ B(w)、Cn = C/ C(w)
An(uv) = A(uv)/ A(w)

這時An、Bn、Cn的插值是Pn
Pn = An*α + Bn*β + Cn
Pn(uv)  = An(uv) *α + Bn(uv) *β + Cn(uv) *γ

從圖P可以看出來 P/P(w) = Pn
反過來就是
P = Pn * P(w)
P(uv) = Pn(uv) * P(w)  
這樣就還原到/w之前的空間了


備註:P(z)是什麼?

P/P(w) 的幾何意義是,把P投影到z=1的平面上
對映到的是 ( P(x),P(y),P(w) ) / P(w) = ( P(x)/P(w) , P(y)/P(w) , 1 )
因為P(w)/P(w)永遠都是1 
所以需要P(z)/P(w)來解決深度排序

從projection矩陣可以知道
如果Pview 是 camera space的P
P(z) = Pview(z)*a+b

投影矩陣對z的修正(我用的是Directx的版本,NDC的z會落在0~1之間)

2021年11月17日 星期三

用ASM實現Navigator效果

Unity的ASM硬幹Navigator

ASM(Animation State Machines)


stacknavigator和tabnavigator

其實就是在處理狀態的轉移

所以用ASM就可以搞出一樣的效果


下面的狀態機是參考自這個頁面的UI配置

當時在練習使用Unity的UI ( 還不是為了 😍 錢錢




區塊代表的意義

  • 深橘色是大狀態,用來達到tabnavigator的劃分功能。
  • 淺橘色是子狀態,用來達到stacknavigator的回朔和記憶(memory)功能。

線段代表的意義
  • 淺藍、深藍線代表由上而下,比如說「Selector」到「音樂庫(1)」;
  • 或是「memory」到「全部樂曲」;只要從「memory」出發的就會是深藍線。
  • 紅線代表由下而上,比如說「全部樂曲」返回「音樂庫」。

舉個例子
  • User正在「全部樂曲」Panel。此時他點擊分頁按鈕「我的清單」,會產生goTop訊息給State Machine,「Any」子狀態收到goTop後會轉移到「Selector」狀態;「Selctor」狀態依據target會前往「我的清單(2)」狀態,並進入「memory」子狀態;假設之前已經進入過我的清單,而且是停在「清單樂曲」,就會由「memory」直接轉移到「清單樂曲」。
  • User正在「清單樂曲」Panel,這時他點擊其中一首曲子播放,於是「播放曲目」Panel會更新正在播放的曲目資訊。接著他點擊「播放曲目」Panel上的編輯按鈕,會產生goTop訊息,「Any」子狀態收到goTop後會轉移到「Selector」狀態;「Selector」跟據target會前往「編曲(3)」狀態。當他編曲結束後按某個按鈕會產生goTop,然後轉移到「Selector」。由於from有記錄他之前是在「我的清單(2)」狀態,「Selector」會把他導向那裡。進入「memory」後因為有過去的記錄,接著又導向「清單樂曲」狀態。
  • User正在「清單樂曲」Panel,此時他點擊分頁按鈕「編曲」,會產生goTop訊息,「Any」子狀態收到goTop後會轉移到「Selector」狀態;以下的Flow就不再重述,但是這個動作會產生新的編曲,而不是編輯舊的曲目。

DB的部分

那時傻傻的,沒想過可以直接用json做假資料
結果就把db的部分也硬幹出來了

UI長這樣

果然還是需要專業的配色呢

想了一下ig可能怎麼實作



UDP VS TCP

 如果1個程式是1個隨身碟;那usb插槽就是1個port

假設現在有隨身碟Server(S)、隨身碟Client(C)


UDP:

當要從S傳檔案A.txt到C,就要自己從S剪下,找到隨身碟C之後再貼上

(使用UDP,必須要提供另一端的ip和port)


TCP:

當C和S連線之後,會在S裡建立一個資料夾捷徑L(指到C裡的F資料夾)

當要從S傳檔案A.txt到C,只要剪下A.txt直接貼到L,這樣F裡就有A.txt了

(TCP在連線之後,已經記下了對方的ip和port)


TCP Sample Code

http://cs0.wikidot.com/chatbox

上面的程式碼

newsock.Bind(ipep);

newsock.Listen(10);

Socket client = newsock.Accept();

Socket client會設定2個port

localPort就是newsock使用的port (=20)

remotPort則是連進來的遠端程式使用的port

所以Accept得到的Socket並沒有佔用新的port

http://blog.csdn.net/tanyjin/article/details/69403220


WebSocket?

https://blog.zengrong.net/post/2199.html

Port

之前不懂
Server和Client程式為什麼可以在跑在同一台電腦上?

原來是因為:Server和Client是使用不同的port


比如說當程式和port 5555綁定

就可以透過5555來接收和傳送訊息;

但沒有限制一個程式只能和一個port綁定

https://zhidao.baidu.com/question


LinkA http://cs0.wikidot.com/udp
上面的程式碼

sender.Port

可以把sender的Port印出來,會發現它不是5555(而是發送端的port)

Console.WriteLine("Port:"+sender.Port);


在bind時使用Any的原因

(為了讓內網和外網的ip都可以被連到)

http://blog.csdn.net/farmer_worker


如何改成可以雙向傳送訊息?

Client-> Server

Server-> Client


Client也必須在1個port上綁定

這樣Server就知道要傳訊到那個port


發現比起用Socket,有個更方便的UDPClient

system.net.sockets.udpclient


非同步版本的接收/發送

zh-tw/library/h0kz38kh


2021年11月12日 星期五

半球積分與面光源積分 🐞

➡️ 目錄

開端

故事的開端是,我看了網上的LearnOpenGL CN 光照
在讀程式碼的時候,嗅到了1些不對勁
程式碼片段,來自LearnOpenGl CN 光照

於是我開始展開調查
這麼說來,我小時候的夢想,就是想做個警探呢...
(有點像Jacky的電影那樣,不是像現在這種版本)
如果你覺的這隻馬很面熟
對他叫熱鬥小馬,不是我原創的

後來我漸漸明白,「L的定義」再加上「BRDF的定義」,
從這2個地方出發,可以得到渲染方程

L是什麼?

我們從L的定義出發,真實世界只有面光源
但遊戲世界充滿了點光點,所以先看點光源

再來是面光源


除以cos(θo),是為了讓每個方向的L都是定值
計算光照時,會再考慮cos(θo)的影響

入射光源的Cos(θi)項


ΔAs 是 shading point的微小面積
能全部擊中shading point dAs的是dEi

Integrals over Area 式(5.6)


Integrals over Area
E = ∫ dE
出處

i = cos(θo) dA / r 2 
dE⊥ = L dωi
dE = cos(θi) L dωi



使用Lo = Lc * cos(θo
dEi =cos(θi) Lo dω
dω = dA / Ri 2 
也能推導出同樣的結果

渲染方程半球積分的dω

當初被點光源帶偏了
怎麼都想不通為什麼dω要從ΔAs的視角出發
於是我認為半球積分裡需要 (1 / Ri 2) 項


半球積分裡多了 (1 / Ri 2) 項 🐞

修正 🐞:

現在,讓我們回到故事的開端

重新檢視LearnOpenGL CN 光照的程式碼
橘色圈起來的那個
其實應該叫irradiance (會隨距離衰減的Ei)
程式碼片段,來自LearnOpenGl CN 光照

面光源的L (Radiance的cos項?)

➡️ 目錄

面光源的L


上圖來自advanced global illumination

魔改一下會變成


Lc cos(θ) = dE / dω 式①


會發現面光源的radiance在每個射出方向並非定值
  • Lc 為定值
  • L = Lc * cos(θo)

上面的式子畫起來就像這樣

利用半球面積投影到法向量平面 = 圓面積
可知 ∫ cos(θ) dω= π

如果把式移項
Lc  = dE / ( dω cos(θo) )
我們就能讓radiance在每個射出方向為定值
這樣日後再使用的時候會比較方便

舉例

現在給你1個面光源Φ
面光源的面積A 
Φ在表面上均勻分佈
就可以算出 L = Φ / A / π

2021年11月1日 星期一

對函數進行旋轉

 


用座標變換 對函數進行旋轉




轉45度也可以 只是GeoGebra不能畫
要用別的軟體畫