1. 程式人生 > >南京代孕一般多少

南京代孕一般多少

南京代孕一般多少█ 微信 同號█:138-0226-9370██████南京代孕包成功,代孕包男孩,供卵代孕,三代試管嬰兒選性別,供卵試管嬰兒,十年老品牌代孕公司,

本文主要講解從紋理中生成法線貼圖的基本方法,並在 Unity 中進行實現和測試。

  預備知識

  法線貼圖和基本的圖形學知識,基本的向量和極限的知識。

  高度圖或灰度圖

  一張二維紋理有兩個維度 u 和 v,但其實,高度(h)可以算第三個維度。有了高度,一張二維紋理就可以想象成一個三維的物體了。

  先來考慮只有 u 方向的情況,如圖所示, A 和 B 是紋理中的兩個點, uv 座標分別是 (0, 0) 和 (1, 0),上方黑線表示點對應的高度,那麼顯然,只要求出 u 方向上的高度函式在某一點的切線,就能求出垂直於他的法線了。同理, v 方向也是如此。也就是說,如果有紋理的高度資訊,那麼就能計算出紋理中每一個畫素的法線了。

  所以計演算法線需要一張高度圖,它表示紋理中每一個點對應的高度。

  但其實並不需要求出每個紋理畫素上 uv 方向各自的法線,只需要求出 uv 方向上高度函式的切線,再做一個叉積,即可計算出對應的法線了。

  如果沒有高度圖,也可以用灰度圖代替,灰度圖就是把 rgb 三個顏色分量做一個加權平均,有很多種演算法提取灰度值,這裡用一個比較常用的基於人眼感知的灰度值提取公式。

  color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722

  這個公式是由人眼對不同顏色敏感度不同得來的,這裡無需過多計較,直接把提取出來的灰度值作為高度值即可。

  計算方法

  當需要求一個點的函式影象切線的時候,只要求出該點的函式斜率即可,即是導數,這需要和它相臨的點進行計算。顯然,兩個點越接近,結果越精確。所以有如下公式:

  求出切線後,就得到了兩個方向上的切線向量和。之所以是這種形式的二維向量,是因為這裡是按照 uoh 平面和 voh 平面分別計算的,具體的向量形式需要根據實際情況去組合。這裡可以做一個優化,在求導數的時候公式裡做了一個除法,因為法線最終會歸一化,切線向量長度不影響叉積後的結果向量方向,所以其實可以直接把求導數時候的除法去掉。

  接下來是將兩個向量做叉積,叉積的順序會影響計算出的法線的方向,這個要根據實際情況去決定。

  例項

  這個例子使用 Unity Shader 去動態的生成一張紋理中每一個畫素的法線,並當作顏色輸出出來,最終在螢幕上會看到一張動態生成的法線貼圖。將紋理放置成平行於螢幕的方向,如下圖所示:

  整張紋理處於世界空間 XOY 平面,並且朝向 -Z 軸(Unity 使用左手座標系,且 Z 軸朝向螢幕裡)。

  由於沒有高度圖,所以提取出灰度值來當作高度圖,演算法根上面描述的一樣,函式名為 GetGrayColor。

  然後可根據高度圖的值來計算 uv 兩個方向的高度函式切線。

  上面程式碼分為 3 段,前兩段為計算 uv 各自方向的高度函式切線,最後一段計算最終法線。

  先看第一段,計算 u 方向的高度函式切線。首先,確定步長的大小。

  MainTexTexelSize 是 Unity Shader 內建的一個變數,儲存著紋理大小相關的資訊,是一個 float4 型別的值,具體為 (1 / width, 1 / height, width, height)。

  _DeltaScale 是一個控制步長縮放的變數,在這個例子中為 0.5,乘以 _DeltaScale 是用來控制法線生成的精確度的,就如之前所說, 越小,生成的法線就越精確。通常我們會向當前取樣點兩側去取樣,以獲得更精準的結果,這個方法叫做中心差分法。然後可以根據步長分別取當前畫素左右兩側的高度值(在這個例子裡就是灰度值),在按照上面提到的計算方法計算切線即可。註釋掉的程式碼是原始程式碼,下面沒註釋的是優化後的程式碼,這個也是上面提到的。

  有一個問題是,為什麼計算出來的切線向量是 (x, 0, z) 的形式,而不是其他?這是因為前面提到整張紋理是處於 XOY 平面的,而高度是第三個維度,因為 u 和 v 自然是按照 x 和 y 軸處理方便,所以高度 h 就按照 z 軸來處理了。

  還有一個可能的疑問是,當 _DeltaScale 特別小的時候,取兩側的畫素實際上都是單前畫素,則高度差都是 0 了。但實際上這個情況只有在取樣過濾方式為 point 取樣時才會出現,具體取樣過濾方式是如何處理的可以查閱其他資料。

  同理,第二段可以計算出 v 方向的高度函式切線,兩個切線向量,做叉積,再歸一化,即可獲得當前畫素點表面的法線向量。叉積的順序很重要,因為紋理是朝向 -z 軸的,所以一般來說會讓法線也順著表面所在的朝向,這就是為什麼是 cross (tangentv, tangentu) 而不是 cross (tangentu, tangentv) 的原因。

  現在將法線當作顏色輸出出來看一下,當然不能直接輸出,因為法線向量可能包含著負值,可能看到的都是黑色,所以需要轉換一下,這個轉換對於瞭解過法線貼圖的讀者應該很熟悉了。

  fixed4 color = normal * 0.5 + 0.5

  直接輸出這個 color,如下圖所示:

  看起來跟常見的法線貼圖有些不一樣,常見的是偏藍色的那種。為什麼是偏藍色的呢,因為常見的法線貼圖都是切線空間的。

  基於切線空間的法線貼圖,z 也就是 b 通道的值都是 0.5 到 1,而 x 和 y 也就是 r 和 g 通道都是 0 到 1,所以看起來會偏藍一些,當然不是絕對。而上面計算出來的法線貼圖,由於叉積的順序,z 分量是朝向 -z 軸的,所以 b 通道都是 0 到 0.5,不信可以用截圖工具看下顏色值。在這個例子裡,想要變成切線空間下的法線貼圖是非常簡單的,只需要將 z 分量乘以 -1 即可。

  normal.z *= -1;

  fixed4 color = normal * 0.5 + 0.5

  結果如下圖:

  根上一張圖比,確實偏藍一些了,但是依然不夠藍。這並不是因為這張紋理特殊,而是還有一些校正的步驟沒有做。

  在計算切線向量的時候,是直接用高度差和值做計算的,這其實是不合理的,因為是非常非常小的,一張 1024 * 1024 大小的圖,只有 1 / 1024 = 0.00097656,但是高度差卻是 0 到 1 之間某兩個數的差,例如高度為 0.6 和高度為 0.2,正常來說是遠大於的,這就導致了切線向量很接近 -z 軸,計算出的法線就很接近於 xoy 平面了,這樣就看起來有很多紅色和綠色,因為 x 和 y 的分量更大。為了解決這個問題,需要引入一個 _HeightScale 變數,來控制高度差的比例。

  當這個值為 _HeightScale 值為 0.01 時,法線貼圖結果如下:

  這張法線貼圖看起來正常了,而且仔細觀察可以發現,每一個磚塊的上側是偏綠的,因為 y 對應於 g,右側是偏紅的,因為 x 對應於 r。

  可以不用中心差分法嗎

  可以使用有限差分法,即不取畫素兩邊相鄰的點,而是隻取一個方向上相鄰的點與當前畫素比較,這種方法想想也知道效果一般不如中心差分法的好。

  除了高度差縮放,還有別的引數可以調節嗎

  有,這裡簡單列舉兩個,因為修改都很簡單,而且效果不適合這裡講的例子,所以不在本文實現了。

  凹凸值

  圖中每一個磚塊,是凹進去的還是突出來的呢?要改變這個屬性,只需要調整法線 xy 的正負即可,就會改變原有的凹凸方向,稍微想象一下應該就能想出來。

  粗糙度

  可以在原來的法線題圖基礎上,進一步修改法線貼圖的粗糙度。其實之前的高度差縮放,也是處理粗糙度,但是當你有一張已經生成好的法線貼圖時,想修改就需要做額外的處理了。也很簡單,對法線的 xy 分量進行縮放,然後再重新計算即可。

  加上光照

  法線是為了光照服務的,所以這裡再演試一下加上一個平行光之後的漫反射的效果,並與沒加法線貼圖的效果做一下對比(預設法線為 -z 軸方向)。

  首先是沒有法線貼圖的情況。

  最終的結果如下圖所示:

  這是將光源繞 x 軸和 y 軸都旋轉了 60 度並且使用預設法線得到的 diffuse 結果,和原來沒有光照的原圖比較,有了明暗的變化,但依然只是一張平坦的圖。

  接下來是使用了上面演算法動態生成法線貼圖的情況。

  注意這裡的 normal.z 不再乘以 -1 了,因為這個例子一切都是在世界空間下計算的,正常情況下可能在切線空間算效率會更高一些,但這並不是本篇文章的內容。最終輸出的結果如下圖所示:

  可以看到,整張圖有了明顯的立體感,磚塊也顯得粗糙了,與之前有了極大的效果提升。再仔細觀察可以發現,每個磚塊左邊和上邊都被照亮,右邊和下邊都變暗了,這正符合平行光的旋轉角度,所以光照結果是正確的。

  最後的工作

  最後的工作就是把生成的法線貼圖儲存到硬碟上,這一步只需要呼叫引擎的相關 API 把渲染出來的法線貼圖儲存為資源即可,也可以直接在 cpu 上操作去生成一張,但這麼做就不方便用實時光照去檢視效果了。