摘要:本文主要與您討論我是怎樣從零開始學習計算機圖形學知識的?我想向您描述我心中的圖形學世界,我所看到的圖形學邏輯知識圖譜,以及我給您的一些學習建議!


各位渲染愛好者:

你們好!

您可能是一名遊戲引擎/渲染器研發工程師,遊戲開發者,技術美術,圖形學研究者,老師或者學生等,您的工作可能涉及影視動畫製作,遊戲(引擎)開發,物理模擬,醫學成像,室內設計,虛擬現實,以及其他視覺相關的工作等,但不管怎樣,你們都有一個共同的願望,那就是希望瞭解怎樣將一個數字場景渲染成一幅符合物理的,非常逼真的,令人賞心悅目的圖片,就像本文的封面圖片,這幅圖片來自皮克斯的電影《尋夢環遊記》,也許您觀看過這部電影,並曾經或者還在驚嘆於它使用的圖形技術,您能想像那個夢中的場景有826 5557個點光源嗎?

那麼我是誰呢?可能大部分朋友還/並不認識我。

但是很多朋友可能聽說過我寫的一本即將出版的關於渲染的圖書:《全局光照技術:從離線到實時渲染》,這是一本純聚焦於渲染的圖形學圖書,我花了三年多全職的時間去寫作這本書。我於2016年就開放了一些這本書的試讀內容,到目前為止百度網盤的文件下載量超過了2000多次,還不包括一些QQ羣內無法統計的數字;我於2017年中發起了半本草稿書的眾籌(《全局光照技術》- 原來圖形技術的世界美如她所創造的世界),甚至有將近500多人購買了一本只包含一半內容的同時包含很多未矯正文法錯誤的半成品圖書,這在中文出版史上可能是第一次;我再次於2017年底發起全書的正式預訂(《全局光照技術》出版在即!揭祕工業中最前沿的渲染技術 - 摩點 - 文化創意眾籌社區),在距離正式出版還有長達半年之久,有800多名讀者就已經預訂了這本圖書!

那麼今天我想與您說些什麼呢?坦白講我是為了宣傳新書,這個我得首先向您坦誠,因為寫作了三年,我當然希望它有一定的市場,每名作者也希望自己的創作能夠被接受和認可。但是今天,我更想與您討論的內容是:我是怎樣從零開始學習計算機圖形學知識的?我想向您描述我心中的圖形學世界,我所看到的圖形學邏輯知識圖譜,以及我給您的一些學習建議!

我的圖形學學習歷程

我並不是計算機專業畢業,在大學也就是自己學了點編程知識(那時主要是C#和Ruby on Rails),以及旁聽了一點計算機專業的基礎課程,畢業後的頭兩年也就是做企業軟體開發,那時候其實我還沒有一點關於計算機圖形學的概念,甚至我在大學基本上不玩遊戲。

我進入遊戲開發行業是一個偶然的機會,那時候Cocos2d-x團隊希望與我所在的OpenXLive公司合作,將其移植到Windows Phone平臺上,這個項目也是我加入OpenXLive的原因,那個時候我還在另一家公司,我跟Cocos2d-x的創始成員之一(我的好朋友)楊文生先生以及OpenXLive的老闆在一起吃了次飯,商定由我來負責這個項目的移植。

其實那個時候(2011年底)我還沒有一點關於3D渲染的概念(所以您可以猜到我全部的計算機圖形學知識是從2012年才開始學習的),我之所以被委任這個項目,主要是因為我那時C#還算比較不錯,以及他們對我做事態度的認可。後來我進入新公司後,我帶領2名實習生,在Cocos2d-x團隊的協助下,大概一個月左右的時間發布了一個預覽版,然後大概3個月發布了正式版(cocos2d/cocos2d-x-for-xna),您可能會覺得僅僅是語言的移植會很簡單,但其實這仍然涉及到很多工作,如果你不去學習瞭解3D知識,會遇到很多困難。

cocos2d-x-for-xna是我進入3D領域的一個時間點,儘管那個時候移動遊戲開始逐漸興起,很長一段時間我還是沒有找到對遊戲開發的激情,還記得我說過我在整個大學期間幾乎沒有怎麼玩過遊戲,我確實覺得玩遊戲會浪費時間,所以Cocos2d-x項目之後我還是回到了傳統的應用和企業開發。

直到後來有幸玩到了一個遊戲:Superbrothers: Sword & Sworcery EP(Sword & Sworcery - NEWS),我才真正被遊戲的魅力征服,從此開始全身心投入遊戲行業,開啟了我一發不可收拾的計算機圖形學學習之旅。

後面的路可能給你們中的大部分朋友一樣,瘋狂地購買和閱讀各種遊戲開發的書,OpenGL圖形編程之類的書等,到後來寫作這本《全局光照技術》時大量閱讀渲染方面的論文。我將把這些學習經驗和方法留到最後一部分介紹。

我心中的渲染知識圖譜

如果你看過 @Milo Yip 的 遊戲程序員的學習之路(中文版),僅考慮圖形部分,這裡列出的還是比較優秀的圖形學圖書,也有60多本;如果你到《Real-time Rendering》官網查看一個圖書列表(Real-Time Rendering Graphics books Page),這裡甚至列出了多達數百本渲染相關的圖書;如果你搜索渲染相關的論文,所有論文的數量甚至是以萬計數的。

面對如此浩瀚的知識和技術,我們該如何著手,從哪裡開始,這些不同的知識通向哪裡,怎麼判斷你走在了正確並且最快捷的道路上?現在我就視圖從我的知識系統出發,給您描述一副渲染的知識圖譜,以幫助您更高效地作出選擇和學習。

渲染的問題,本質上是一個積分的問題。對於無法通過解析的方式求解的積分,我們通常使用一些數值方法,最常用的就是蒙特卡洛方法,它通過在被積函數的定義域內隨機採樣,並通過將該樣本代入到函數中計算其貢獻值來近似該積分的計算。積分的結果是一個數值,但是如果我們存儲了函數各個定義域處的貢獻值,那麼我們就能還原被積函數的分佈,在渲染中也就是最終渲染圖像的像素顏色值,這就是渲染的原理。

然而,攝像機描述的圖像對應分佈函數的形式並不是已知的,我們所要想了解的每個像素的顏色,源於光線從光源發出,然後在空中傳播,並經過與場景表面的一系列交互,例如反射或折射,最後進入攝像機的過程,因此我們的被積函數的形式隱藏於這種光照傳輸當中,因此渲染的問題本質是也是一個光照傳輸的問題,為了描述被積函數的形式,我們需要研究光照的傳輸。

最直觀的描述光照傳輸的方法就是光線追蹤,它是表述光子從光源出發到達攝像機的整個傳輸過程,我們記錄下每個到達攝像機的光線的能量以及其在圖像空間中的位置,那麼我們就得到了整個圖像的分佈。因此光線追蹤的思路很簡單,就是在圖像空間中每個位置處發射一條光線,然後計算該光線的光照貢獻。我們也稱一條光線為一個樣本,一條完整的光線也稱為一個光線路徑,因此跟蹤這個光照傳輸的過程也稱為路徑採樣,基於路徑採樣的光線追蹤演算法也稱為路徑追蹤。

然而,一個數字場景通常包含了太多複雜的細節,以至於我們需要非常巨量的光線又能夠比較真實的近似場景的實際視覺特徵,比如每一幀可能需要數以億計的光線,這樣根據使用演算法的不同,渲染一幀可能需要數分鐘至數小時甚至數天的時間。

因此,路徑追蹤技術的主要聚焦點就在於尋找更有效的路徑採樣方法,因此光照傳輸的問題本質上又是採樣的問題。在採樣中,一個樣本是一個隨機數,由於隨機數本身具有方差,因此這導致整個積分的估計也具有方差,並且在蒙特卡洛方法中,我們需要4倍多的樣本數量才能換來2倍的方差減少,因此在路徑採樣問題中,我們又主要將關注點集中在減少估計的方差上。

方差是怎麼產生的呢?首先要明白,方差是一種羣集屬性,它是指一系列隨機樣本的平均值與每個樣本誤差的綜合。蒙特卡洛方法的直觀形式,其實是根據某個分佈函數採樣來產生基礎樣本,然後將這些樣本代入帶被積函數以計算樣本的貢獻值,所有的貢獻值最後被平均,最後通過統計某個區域內樣本的數量來估計該區域的函數值,所以如果這些樣本的貢獻值越是相似或接近,那麼估計的方差就越小。

因此,理想的方法就是使用圖像的分佈函數為採樣函數,這樣每個樣本的貢獻值將是絕對相同的,估計的誤差為0。然而這確實不可能實現的,因為被積函數的分佈正是我們試圖去求解的結果。但是儘管我們無法知道整個圖像分佈函數的顯示形式,但是我們知道它的部分表達式,例如BRDF分佈函數,如果選取這些部分子函數作為採樣的分佈函數,那麼估計的方差也可以被大大減小,這就是所謂的重要性採樣,它幾乎被用在所有需要採樣的地方。

與重要性採樣方法緊密相關的是複合重要性採樣,這種方法基於這樣一種理論:即對於同一個樣本,它可以使用不同的分佈函數進行採樣,在上述的重要性採樣方法中,函數的不同部分通常對其中的一些區域有著更好的擬合,但是對另一些區域擬合效果就會差得多,由於同一個樣本可以來自於不同的分佈函數,因此如果我們使用對個分佈函數進行採樣,然後將它們組合起來,讓來自不同分佈函數的樣本去更好地擬合相應的函數部分,那麼整個估計的方差也會被進一步降低。

那麼來自兩個不同分佈函數的樣本應該怎樣被組合在一起呢?前面已經說過,蒙特卡洛方法的原理是假設每個樣本的貢獻值是相同的,最後僅僅涉及對樣本數量的統計,因此如果我們使得來自不同分佈函數樣本的貢獻值是相同的,那麼它們就可以被放到一起進行統計計數,畢竟最終我們需要的只是數量麼,並不在乎它是通過什麼分佈函數進行採樣的,這就是複合重要性採樣方法的核心。

需要注意的是,在路徑採樣中,一個採樣分佈函數是與特定的路徑長度相關的,對於不同長度(或深度)的路徑,我們通常需要使用不同的採樣方法。另一方面,在雙向路徑採樣中,一條路徑中間會有一條子路徑時通過直接連接(而不是採樣)得到的,對於一條已經採樣完畢的路徑,我們可以直接通過解析的方法得到該路徑對應的多個採樣分佈函數,這可以僅僅通過在不同的子路徑之間執行連接操作即可,這個過程甚至不涉及其它昂貴的路徑追蹤過程,例如直線光線與場景的相交計算。

儘管重要性採樣方法能夠大大降低估計的方差,但是運用在渲染中仍然面臨著另一個非常困難的問題:由於圖像的分佈函數不是已知的,因此它依賴於光線追蹤來執行路徑採樣,然而由於路徑是隨機採樣的,這使得一些路徑樣本很難被採樣到,例如,兩個被強隔離的房間,中間只有很小的一個通道,其中一隻有一間房間內有光源,在這種情況下,另一件房間唯一的光照來自於那個狹小的通道,而要想渲染該房間內的圖像,每一條光線都必須要經過那個通道才能達到光源。對於這種場景的渲染,大部分隨機採樣的路徑都會無效,使得估計收斂的速度非常慢。

因此,對於有些場景,重要性採樣方法仍然是無效或者低效的。離線的情況下,我們會需要一個完全正比於圖像分佈函數的採樣函數纔能夠克服這樣的問題,而梅特波利斯演算法正是這樣的一種採樣方法,這種方法可以不要求事先知道被積函數的形式,它只需要我們能夠計算每個樣本的函數值,然後通過比較兩個相鄰樣本的函數值,最後就能讓樣本的分佈正比於目標被積函數。這種方法的的原理,就是利用了一個穩定狀態空間的狀態轉移原理,在一個處於穩態的系統中,某種度量在各個狀態之間的轉移在宏觀上是不變的,因此我們只要知道了每兩個狀態之間的轉移關係,那麼我們就可以在各個狀態之間執行轉移而保持整個系統的平衡。

梅特波利斯演算法就是利用了這樣的原理,儘管我們不知道被積函數的形式,但是我們知道每個樣本的函數值,通過對當前樣本執行一個擾動以產生一個新的樣本,並通過比較該新的樣本在其位置處的函數值,我們就知道這個新的樣本是否位於函數區域內,通過這種比較來選擇或拒絕該樣本,在多個樣本的效應下,就相當於我們會按照函數的分佈來產生下一個樣本,這就相當於產生了一個從當前位置到下一個位置的複合被積函數的轉移函數,因此梅特波利斯演算法能夠產生複合被積函數的樣本,這樣的方法在用於克服上述的採樣問題。同時由於採樣函數正比於被積函數,因此其估計的方差也大大降低。

雙向路徑追蹤演算法有一個主要的缺陷,那就是當兩條來自光澤表面的子路徑相聚於一個漫反射表面時,它們幾乎無法連接成為一個完整路徑,這種路徑也就是經典的焦散效果,我們也幾乎從發從漫反射表面隨機尋找到一個朝向一個光澤表面的方向,這樣的路徑幾率太低,以至於在我們有限的時間要求下可以認為是不可能的。為了克服這個問題,隨機的方向採樣顯然無法再被使用,於是人們提出一種稱為光子映射的方法,這種方法的分為兩個渲染通道,第一個通道從光源出發,其首先經過與光澤表面的交互,最終落於第一個漫反射表面上,其光出的光線不再是攜帶輻射亮度值,而是一個能量密度值,因此這條光線稱為一個光子,這些光子最終落於場景中的漫反射表面上,當足夠數量的光子散落於漫反射表面時,我們就可以使用回歸分析的方法擬合出漫反射表面的光子密度分佈,從而可以得出任意位置處的能量密度。

當漫反射表面的任意位置都知道能量密度時,後續的路徑追蹤通道,來自光澤表面的路徑就不需要與其它路徑「連接」,因為它擊中的任何位置都可以計算出光照貢獻。通過這種方式,光源子路徑與攝像機子路徑之間不再是「連接」,而是以一種「合併」的方式生成一條路徑,一條攝像機子路徑實際上與該點附近的多條光源子路徑「合併」產生,儘管每條合併的路徑的光照貢獻被非參數回歸使用的核函數加權從而大大變小,但是這種方法巧妙地解決了焦散路徑的採樣,並且由於第一通道生成的光子圖可以被重用,這也在一定程度上彌補了路徑貢獻率低的問題。

我們以及介紹了目前為止離線渲染領域三大經典採樣方法:(雙向)路徑追蹤,梅特波利斯光照傳輸,以及光子映射,它們分別具有各自的優勢,能夠刪除處理特定環境下的路徑採樣。那麼這三種方法能不能被組合在一起呢,答案當然是可以的,因為上述每一種方法都是一種路徑採樣的方法,讓我們得到一個樣本時,我們能夠按照複合重要性採樣的思路把他們全部組合在一起,這樣我們的整個渲染流程就有了一個統一的演算法架構。

使用更好的採樣方法就足夠了麼?顯然還不夠,上述這些方法的重心在於減少估計的方差,但是我們仍然需要數以億計的光線,畢竟場景包含那麼多的細節,上述的方法使得樣本的分佈更加符合圖像的分佈,這是依靠樣本的數量正比於圖像的分佈來實現的。然而考慮圖形分佈函數的那些平滑的區域,這些區域由於頻率變化極低,因此我們只需要在這樣平滑的區域適應極少的樣本就能很好地近似圖像分佈。

因此路徑採樣的另一個方向是分析圖像的頻率分佈,使得我們僅需要對高頻的區域適應更密集的路徑樣本,而在平滑的區域減少路徑採樣的樣本,這種方法顯然能夠大大提高路徑追蹤的效率。

這方面的方法包括頻率域分析,流形探索,和自適應採樣方法,前兩者這裡不再詳述,請讀者日後參考《全局光照技術》,裡麪包含大量的篇幅,我們這裡僅說明一下比較好理解的自適應採樣方法。

只適應採樣方法的思路很簡單,整個採樣採樣過程是迭代式的,首先演算法根據某種基礎採樣方法得到少量的路徑,例如上面介紹的那些方法,這些少量的路徑樣本形成比較高噪的圖像分佈,由於這些樣本可以看做一些已知的函數值,因此我們可以用它們來擬合出一個「真實」的分佈圖像,這正是回歸分析的思路,最後我們從這個擬合的圖像分析其頻率分佈,然後在下一個迭代階段,對那些高頻的區域放置更多的樣本,而減少那些低頻區域的樣本數量,通過這樣的方式來大大減少樣本的數量。並且這樣的擬合隨著樣本數量的增加而越來越精確。更多關於自適應採樣的介紹可以參見我的另一個回答:

光線追蹤渲染中遇到了自適應方法嗎??

www.zhihu.com圖標

對於上述的路徑採樣,如果場景中的物體都是絕對光滑的鏡面表面,那麼路徑在傳輸過程中的軌跡就絕對由反射和折射定理決定,採樣過程能做的事情就是隨機決定光線從攝像機或者光源發出的方向。

然而,當我們開始考慮表面的粗糙度時,事情就變得複雜起來!

首先我們需要明白的是,粗糙度到底是什麼?它指的其實是表面的微觀結構,那麼要明白這種微觀結構,以及怎樣表述這種微觀結構,我們需要明確一個「像素」的意義。

在渲染中,像素並不僅僅是指物體表面某個點的位置,當我們通常在討論「像素」概念這個概念的時候,我們更多的是在描述它的位置坐標,然而像素最重要的特徵其實是它的尺寸,當我們在對一個像素進行著色的時候,其實我們是在計算物體表面某一個尺寸範圍內的平均顏色,隨著距離攝像機的距離發生改變,一個像素的尺寸範圍也在發生著改變,只不過因為有了如多級紋理過濾這樣的硬體特徵,我們在概念中忽略了像素的尺寸特徵。

那麼像素的尺寸是什麼,它是跟圖像的解析度,以及我們的應用程序所能處理的幾何尺寸相關的,當我們將攝像機的解析度確定,並且選擇了攝像機所能覆蓋的空間面積時,像素的尺寸就被確定下來,在這個尺寸之下,一個像素就是應用程序所能計算的最小範圍單元!

從這裡可以看出,像素的尺寸是在發生變化的,因此一個像素的尺寸不能代表表面的微觀結構尺寸,要想讓一個像素對應這種微觀結構尺寸,屏幕的解析度就會變得非常巨大,而我們的計算量也變得非常驚人。

那怎樣克服圖形應用程序的著色尺寸與物體表面微觀結構尺寸之間的不對等呢?答案就是使用一中抽象的概念來描述微觀結構,也就是在描述一個像素所覆蓋的那些微觀結果時,我們使用一個關於這些微面元的分佈函數,該分佈函數說明,當光線從某個方向進入這些表面時,它有多少幾率從觀察反向反射出來,這就是Microfacet BRDF的本質。

Microfacet BRDF是一些經驗模型,它們是對物體真實微觀結構的分析得出的一些近似模型,這些模型可以以簡單的方向函數的形式表述,例如遊戲當中比較常用的GGX模型。Microfacet BRDF通常使用三個部分的乘積來表述微面元的反射特徵:菲涅爾係數,微面元的法線分佈函數,以及考慮微面元之間相互遮擋的遮擋函數,遮擋函數的出現是為了近似微面元的空間結構,因為法線分佈函數是基於平面的,即它假設所有的微面元處於同一個平面上,因此缺乏微觀結構的凸凹特徵。

由於同一種物體的表面微觀結構通常都是均勻的,所以我們可以認為表面的每個像素都可以使用同一個Microfacet BRDF函數進行表述,因此每個表面的反射特徵就僅僅被記錄在像素著色器中的一個數學公式,這大大避免了表述物體表面微觀結構的內存開支。

Microfacet BRDF的思想其實不光可以用來表述這種小於像素級別的微觀結構,它其實提供了一種基本思路:即當應用程序處理的單位尺寸遠遠大於其覆蓋的實際尺寸,並且這些實際的「微小」尺寸的圖形特徵非常重要時,我們均可以用一個表述這些「微小」尺寸圖形特徵的分佈函數,來為這個宏觀的應用程序處理尺寸提供一個代理表述。

舉個例子,在VXGI中,我們處理的是一個個體素,一個體素表述的是一個3D的幾何區域,這些區域的尺寸可能遠遠大於一個像素的尺寸,這就是我們選擇使用體素的原因,我們通過增加一個處理單元所能處理的空間面積(或體積),來減少計算量。這也好比我們使用像素級別的處理單元,來計算微面元級別的反射特徵。因此在這裡,為了能夠反映每個體素內的表面的光照特徵,我們也將該體素內所有像素的法線分佈聯合起來,組成一個宏觀的法線分佈函數,這就是VXGI中能夠使用體素來渲染表面的精妙所在。

有了上述關於使用一個分佈函數來描述著色級別的「微觀結構」分佈的思路(這裡「微觀結構」不再是指表面真實的微面元結構,而是指相對於著色尺寸來講,那些小得多的尺寸,例如相對於一個體素來講,這種微觀結構可能就是一個像素),我們就會思考,能不能將這些分佈函數與物體表面的幾何表述結合起來,例如當物體離攝像機很近時,我們使用表面實際的微面元分佈;當物體離攝像機較遠一點時,我們能否將表面的頂點級別的分佈與Microfacet BRDF結合起來,形成一個新的分佈函數;當物體離攝像機更遠時,我們能夠把場景中物體網格之間的幾何分佈或者網格的LOD數據與Microfacet BRDF結合起來,形成一個更大範圍的分佈函數。這種幾何表述與Microfacet BRDF的組合,不僅大大提升了計算的效率,還能夠提升圖像的精確度。

所以,你現在明白「像素」的意義了嗎?同時明白了分佈函數相對於應用程序處理的範圍尺寸之間的關係了嗎?更多關於Microfacet BRDF的知識,可以參見我的另一篇文章:

秦春林THEGIBOOK:More DETAILS! PBR的下一個發展在哪裡??

zhuanlan.zhihu.com
圖標

當我們把目光轉向實時渲染時,實時的積分計算就變得非常奢侈,實時的光柵化管線甚至只考慮直接光照,而將間接光照留給其它的一些近似方法,下面我們將討論實時渲染中計算間接光照的兩種重要的基本思路。

首先說明一下,當前的大多數實時渲染中的全局光照演算法,都大大地依賴於環境光照貼圖,環境貼圖是將攝像機置於環境中的某個位置,然後渲染整個360度的場景,這個環境光照貼圖最後被用於提供間接光照,且它被認為是與位置無關的,因此對其採樣是只需要提供方向就行。

所以,當我們需要計算一個表面的間接光照時,我們需要做什麼事情呢?它實際上就是表面的BRDF分佈函數與環境貼圖光照乘積的一個積分,想像一下,每個像素都需要計算這樣一個半球空間內的積分,這顯然是非常奢侈的。

這裡BRDF可以看做是一個核函數,因為它是歸一化的,這是基於物理的Microfacet BRDF模型能量守恆的特徵,因此這個積分可以看做是對環境貼圖的一次過濾操作。但是如果這個BRDF核函數本身是各向同性的,那麼對於每個反射方向的這種過濾操作都是相同的,於是我們可以將這個BRDF函數在環境貼圖上做一個卷積計算,形成一個預過濾(pre-filtering)的環境貼圖,它相當於把這個過濾的積分計算提前到預處理階段,這樣在實時階段,像素的著色計算就是對預過濾的環境貼圖執行一次紋理查詢而已。

而萬幸的是,當前工業中使用的GGX模型正是各向同性的,或者它被普遍使用的原因正是因為源於預過濾等計算的需求。

預過濾是一種非常重要的實時渲染方法,它的優點是能夠將積分計算簡化為單次紋理讀取操作,但是其缺點是僅能處理圓對稱的核函數,否則我們需要更高維度的紋理來存儲預過濾的數據結果。

如果我們將預過濾的思想從環境貼圖中提取出來,我們就會推斷,所有關於使用包含一個圓對稱核函數的積分,都可以借鑒這種預過濾的思路。例如在前面介紹的VXGI中,在對體素進行採樣時仍然會涉及到BRDF函數與體素中的光照分佈的積分計算,如果使用各向同性的BRDF函數,這個積分計算仍然可以被轉化為預積分,實際上這也是VXGI的思想之一。

而預過濾的另一個在實時渲染中的重要運用,可能被大多數人忽視,或者說沒有意識到,那就是多級紋理(mimap)。為什麼要使用多級紋理,那就是因為當採樣尺寸遠遠大於物體表面的細節時,例如那些離攝像機比較遠的表面,使用單個表面的採樣點會導致走樣,所以我們希望在這個範圍內使用多個採樣點來計算該區域的平均值,或者按照某個核函數對這些採樣點執行加權平均,這實際上就是一個積分了,但是和上面的原因一樣,我們仍然不可能在實時渲染去,去對每一個像素執行一個積分計算,考慮到這個加權的核函數一般是圓對稱的,所以我們直接將該核函數與表面的紋理中的紋素執行預過濾操作,這就是多級紋理的原理和思路。

實時渲染中幾乎一般以上的全局光照演算法,基本上都涉及到預過濾的計算,因為它是實現簡化積分計算的主要手段和工具,《全局光照技術》中有大量的篇幅介紹各種預過濾技術,例如階層式預過濾,基於重要性採樣的預過濾,以及一些能夠實現實時計算的預過濾方法。

實時渲染的另一個重要的思路是投影近似,投影近似的概念可以使用傳統的矢量投影的概念進行描述。考慮一個3D空間的矢量,它的各個分量x,y,z,如果現在我們考慮只有一個1D的空間,要想在這個1D空間內最找到一個對3D空間中矢量的最佳描述,我們可以怎麼做呢,答案就是將這個3D的矢量投影到這個1D空間,為什麼它是最佳近似呢,因為原始3D矢量和這個投影矢量之間的距離最近。

現在我們將這個投影的概念擴展到函數上,假設我們有一組互不相同的基函數,並且某個空間的任意一個函數都可以表述為這些基函數的一個線性組合,那麼組基函數就構成了一個函數空間,有了這組基函數,我們就可以用一個由這些基函數的係數構成的矢量來描述任意一個函數。傅裏葉變換就是基於這樣的概念,其中那些正餘弦函數就構成一組基函數,我們可以用這些正餘弦函數的線性組合來描述任意一個函數。

如果我們將傅裏葉變換的基函數的維度由單位圓上擴展單位球面上,這就形成了所謂的球諧函數,所以球諧函數的方法基本上和傅裏葉變換的方法是一致的,因為他們本質上是相似的概念,只是基函數的維度不一樣。

在傅裏葉變換或球諧函數中,每個基函數都代表了函數的不同頻率特徵,理論上傅裏葉基函數或球諧函數的維度是無限的,因此我們就需要無限個係數來表述一個函數。但是如果某個函數是低頻的,例如間接光照,那麼我們能否去掉高頻部分的基函數部分,僅僅保留有限個基函數的係數來近似一個函數呢?答案顯然是可以的!

實時渲染中有很多這有的處理,例如環境貼圖在某些情況下也可以表述為低階的球諧函數,特別是那些涉及到每像素計算時,或者每一幀需要處理多個方向分佈函數時,函數投影近似就是一種非常重要且必要的手段,例如環境中可能需要在多個位置存儲於位置有關的環境貼圖,以向動態運動的物體提供間接光照,或者某些地方不能預計算,只能實時地執行積分計算,這時候更是需要使用更少量的係數來表述一個方向分佈函數。

更多關於渲染中的數學知識可以參見本專欄的文章:

秦春林THEGIBOOK:渲染中的數學知識?

zhuanlan.zhihu.com
圖標

更多關於渲染的概念,可以參見本專欄的文章:

秦春林THEGIBOOK:在《硬影像》與羅登導演聊渲染技術?

zhuanlan.zhihu.com
圖標

以上只是簡單介紹一些渲染中的一些重要方法的基本原理和概念,更多更詳細的探討,可以參見《全局光照技術》圖書的內容。

我的學習方法建議

第一部分我已經說過,我的起點並不高,我甚至沒有經過計算機科學的專業訓練,我入行3D渲染領域也大概只有6年多的時間,跟你們中大多數的朋友一樣,我也經歷過在書海中苦苦求索的經歷,我也曾希望尋求一些好的學習方法建議,好的參考資料推薦,我也是一點一點慢慢去摸索過來的,那麼我是如何自學併到現在寫出《全局光照技術》這本圖書的呢?

我覺得計算機很多分支學科知識的學習會包括三個階段:

  1. 學習編程語言及工具
  2. 大量編程實踐
  3. 深入研究理論

計算機科學需要較好的編程基礎及能力,考慮到它的基礎地位,我會建議將編程能力的培養放到你所在學科的最前面。比如在第一個階段,你只需要學習最簡單的語言及相關開發工具,這個時候不需要將編程涉及到您的複雜專業知識中,你可以選擇你所在行業中一些比較簡單的示例用來輔助編程語言及工具的學習。

以我自己的經歷,我在大學的時候開始自學ASP.NET,C#和Ruby on Rails,那個時候我還不知道怎樣用ASP.NET去實現複雜的企業應用以及各種高端的架構知識,因此也就不會糾結於過於繁雜而導致選擇迷茫,在這個階段我就是使用ASP.NET和RoR做了一些簡單的示例性的項目,這使我能夠快速地入門一種語言和工具。當然的具體語言選擇並不重要,編程的很多思想都是想通的。

是不是掌握了基本的編程能力就開始解決或者實現專業問題呢?我的答案是否定的,拿計算機圖形學為例,很多演算法和技術都是和對編程的理解有聯繫的,比如當我們在說針對硬體的優化的時候,你需要了解語言的指令怎樣在處理器中被執行,變數如果由主存到緩存再到寄存器,最後被用於執行指令;比如當我們在討論加速結構的時候,你需要了解這些結構為什麼在內存中的訪問是高效的等等。很多這些知識都可以在脫離特定複雜的領域問題背景下被學習,所以你應該首先具備非常紮實的編程能力,然後你在學習和處理領域問題的時候才會變得更加順手。

怎樣才能訓練你的編程能力呢?答案當然是大量的實踐,這個沒有其他方法,你不可能靠看一本書來提高編程能力。這個沒什麼可說的,但是我想強調的時候,這種大量的編程實踐不能寄希望於公司的項目開發,在軟體工程中,公司的項目開發往往都會出於效率的考慮,公司會有大量的架構和編程實踐及規範,你是在這些架構和規範下工作,這就像工廠裏的一名流水人員,因此你所做的事情多半也是非常簡單和重複性的事情,你在這樣的背景下其編程能力的提升會非常慢和低效。

要想快速提升編程能力,你必須有一個自己的相對比較複雜的個人項目,或者說你在公司有機會從零去構建一個項目。自己的項目因為需要考慮方方面面的事情,你不但在鍛煉軟體工程的思維,你可以有更多編程實踐的機會,並且你在實踐過程中,各個細小的細節會扔給你非常多你不曾瞭解的問題,然後你需要去學習來解決這些問題,這個成長的非常會非常的快和高效。

比如我在畢業第一年的時候,那時候我想在Web瀏覽器中實現一個類似App Store的東西,於是我要了解怎麼託管用戶的App,怎樣在app之間調度,我希望開發者能夠基於Windows Azure來使用傳統的流程開發app,於是我需要去學習Azure,然後發現Azure不能夠分配太多賬號,用戶需要自己單獨購買Azure非常麻煩,於是我深入分析Azure REST API的格式,實現了一個完全的反向代理伺服器,它完全映射了當時Azure的30多個API,使得我可以將Azure虛擬成無數個賬號供開發者使用。最後整個項目下來,我學習到了非常多的知識,並且編程能力得到大大提升,我的那個項目可以參考這裡:

藝街開放平臺開源計劃 - 秦春林 - 博客園?

www.cnblogs.com
圖標

最後,當你具備紮實的編程能力之後,你對專業領域知識的學習就會更加高效。在計算機領域,很多朋友都會很推崇閱讀源代碼的學習方法,我其實不太贊成,至少在圖形學領域我覺得這種方法並不是最高效的,可能在傳統的企業軟體開發中,代碼實現本身並不涉及太多專業的知識,唯有代碼本身可能就是最好的解釋。但是在圖形學領域,圖形學知識本身有比代碼實現更複雜的理論,這些理論,有時候你聽過閱讀代碼本身是很難理解的,所以最高效的方法應該是首先從理論著手,對理論的原理理解之後再去閱讀代碼實現。所以這也是我為什麼強度編程能力在先的原因,因為你必須在閱讀那些技術論文的時候,對其所涉及的關於實現部分的介紹,不要有任何理解上的障礙,否則這種障礙也會阻止你對理論的理解。

經常會有人問這樣的問題:新手適合看什麼書?有哪些書值得推薦的?《全局光照技術》適合萌新嗎?需要什麼基礎才能閱讀《全局光照技術》?

通常對於這種問題,我都不想去回答,或者說我其實真的不知道該怎麼去回答,在我的心中,我確實對於圖書沒有很嚴格的質量劃分,或者等級劃分,除了大家都公認的那少數幾本書,大多數對我來說都是一樣的。

對於圖書的選擇,我有兩個觀點:首先是不要對一本圖書寄予太多期望,只要一本圖書能夠真正幫助你解決某個比較大的困惑和理解,你就已經賺到了;其次,不要視圖去給圖書和自己定級,你唯一需要考慮的是「這本書是不是某個領域最好的書」?

對於第一個觀點,同類圖書之間通常都有很大的重合度,所以你不可能希望某本書的全部內容都是對你幫助巨大的;其次,一本書的作者通常都有不同的專註點和擅長的層面,每本書在某些方面都可能有著非常獨到的解釋和邏輯,但是在其它方面可能並不是很通俗易懂。所以針對這種情況,你有時候需要交叉閱讀,哪怕是相同的內容,不同的圖書可能給你不同的啟發。所以當我在開始學習一種新的技術或概念的時候,我通常都會買幾本同類圖書,交叉閱讀和理解,我基本上不會去區分哪些圖書更好,只要能給我理解上帶來幫助,我覺得就不錯了,畢竟和知識相比,那個書價真的就是不值一提。

第二個觀點是不要嘗試給自己定級,給自己畫一個圈圈!很多朋友會有很強烈的自覺性,很嚴格地把自己劃分為初級中級和高級,當然這裡面最多的是第一種情況,所以你看到整個世界都是萌新,我認為這是一種非常不利於成長的習慣。在我整個計算機知識自學的過程中,我幾乎從來不會去思考說哪本書是適合新手或者說我這個級別的,我一般只會去詢問哪本書是這個方面最好的參考資料。這裡有兩個原因,首先如果你將自己劃到某個小圈圈裡面,那麼你就把自己與一些非常好的學習資料隔離起來;其次,對於一名圖書作者來講,他通常都不會去嚴格地把圖書的內容分級,比如說完全針對高級開發者,這是因為知識的理解需要背景也需要連貫性,所以每本圖書基本上都要從很基本的概念講起,所以是除非是向論文這種完全不考慮讀者背景而僅專註研究內容的介紹,幾乎大部分圖書都是適合所有讀者的,或者說至少初學者也能從中收穫不少知識的。那既然是這樣,為什麼不直接選擇最好的資料呢,要知道這些圖書的作者對知識的理解更加深刻,他們討論的內容可能更加高級,但是他們對知識的邏輯理解可能會讓你進步的更快。

更多關於我的學習心得,還是留到我的技術巡講中吧?寫這麼多文字還是有點累哈!

誠摯邀請您來參加我的渲染技術巡講活動

看了這些描述,你是不是有些心動了呢?《全局光照技術》不僅包含所有上述這些內容,還包含遠遠多於這些的深度內容!

更重要的是,我即將於2018年5-6月期間在全國舉行一次線下的渲染技術巡講,您將能夠聆聽到來自我親身的技術講述,這可能是國內目前為止在渲染方面最專業最全面的技術分享,如果您看了這些內容很感興趣,就趕緊去瞧瞧吧!

渲染的祕密:《全局光照技術》線下技術巡講暨眾籌預訂 - 摩點 - 文化創意眾籌社區?

zhongchou.modian.com

祝您學習愉快!

秦春林

推薦閱讀:

相关文章