上面這張圖主要是用來吸引人的考廉,跟本文內(nèi)容沒啥關(guān)系(偷笑~)
還是跟之前一樣秘豹,本文是閱讀《基于物理的渲染:從理論到實踐》的總結(jié),文中不會涉及到方方面面昌粤,只會整理筆者認為重要的一兩個點既绕。本文關(guān)注的重點是誤差,尤其是舍入誤差(Rounding Error)涮坐,主要來源書中的3.9節(jié)凄贩。
由于作者水平所限,文中如果有錯誤或者沒有解釋到位的地方袱讹,還請讀者不吝指正怎炊。
誤差的來源
首先我們必須認識到,沒有任何方法可以避免誤差廓译,我們能做的就只能是減少誤差评肆。為啥?從誤差的來源我們就可以看出來了非区。
誤差會來自兩個方面:
第一瓜挽,用于計算的數(shù)據(jù)(即輸入數(shù)據(jù))本身就不是精確的,即輸入數(shù)據(jù)本身就帶有誤差征绸。
第二久橙,保存數(shù)據(jù)的工具會產(chǎn)生誤差俄占。這個怎么理解?我們用計算機來保存數(shù)據(jù)淆衷,計算機是二進制的缸榄,我們用來保存數(shù)據(jù)的大小也有限制,不可能表示數(shù)學(xué)上類似于“實數(shù)”這種無限精度的數(shù)字祝拯。
從誤差的來源我們就可以看出甚带,誤差無法避免,無論是哪個方面的誤差我們都無法避免佳头,只能盡可能的減少誤差鹰贵。這就意味著,當(dāng)我們進行計算的時候(尤其是要進行光線和表面交點這種精度要求非常高的計算)康嘉,我們必須把誤差考慮進去碉输,否則渲染出來的場景會和理想中的場景相差很大。而輸入數(shù)據(jù)本身的精確性我們沒法控制亭珍,就當(dāng)它是精確的吧敷钾!我們要讓第二個誤差,也就是保存數(shù)據(jù)的工具產(chǎn)生的誤差減少肄梨!
舍入誤差
舍入誤差闰非,英文名叫rounding error,是用計算機保存實數(shù)的時候峭范,由于精度有限财松,導(dǎo)致的保存的數(shù)據(jù)和原來的數(shù)據(jù)的差距。這個差距纱控,就是舍入誤差辆毡。
就拿32位浮點數(shù)來說。IEEE的標準(下面講的內(nèi)容都是IEEE標準)是32位浮點數(shù)甜害,首位用來保存符號+/-(用s表示)舶掖,接下來的8位用來保存指數(shù)值e,最后的23位用來保存有效數(shù)字m(二進制0或者1)尔店,即:
于是眨攘,我們的數(shù)字可以表示成。如果你的觀察夠敏銳的話嚣州,會發(fā)現(xiàn)鲫售,這種表示方式無法表示0,這是不能容忍的该肴,所以IEEE又規(guī)定情竹,如果指數(shù)e是0,那么表示的數(shù)字將是匀哄,這樣秦效,如果m等于0的話雏蛮,我們就能表示0了,雖然有+0和-0之分阱州。當(dāng)然挑秉,這時候IEEE又跳出來了,規(guī)定說+0和-0的表現(xiàn)必須是一樣的苔货,這樣才有了唯一的0值犀概。
絕大多數(shù)情況下,我們的指數(shù)e不會等于0蒲赂,所以我們可以放心大膽地只考慮第一種情況阱冶〉蟊铮考慮這樣一個問題滥嘴,浮點數(shù)1和離1最近的比1大的浮點數(shù)之間的間隔是多少呢?
我們來看一下至耻,表示1的時候若皱,m值是00...00,然后e是127尘颓,這樣求出來走触。而比這個數(shù)稍微大點的數(shù)就是
在第23位的地方是1,這就比1大了疤苹,也是比1大的最小數(shù)互广,它比1大了
喻频。這也就說明了努咐,我們沒法表示
和
之間的數(shù)字棍矛。而這個
就是通常所說的機器精度(machine epsilon)娃圆,我們用
表示捂龄。
很少有數(shù)字正好落在浮點數(shù)能精確表示的位置上曹铃,不在這個位置上的數(shù)怎么辦呢绞佩?就比如說怎么表示冬阳?有兩種方法可以采用颤霎,一是多出來的精度我就不管了媳谁,直接舍棄。二是我先看看這個精確的數(shù)字更接近哪個數(shù)友酱,然后表示成那個數(shù)晴音。
和
的中間量是
,
顯然要比這個中間量小缔杉,所以它會被表示成
段多。
那么,正好落在中間的數(shù)怎么辦壮吩?比如說?這又是一個麻煩事进苍,還是得寄出IEEE大法:
IEEE舍入最近法則:
如果第24位,為1加缘,第24位后的所有已知位是0,那么如果第23位是1觉啊,就往第23位加1拣宏,如果第23位為0,就不加杠人。
如果第24位為1勋乾,第24位之后還有至少一個1,那么就往第23位上加1.
如果第24位為0嗡善,那么就舍棄之后的所有位數(shù)辑莫,保持第23位的數(shù)不變。
很繞的一個方法罩引,解釋一下各吨。
情況一、表示
因為這個數(shù)第24位是0袁铐,第25位是1揭蜒,根據(jù)IEEE舍入最近法則,第24位為0的話剔桨,保持第23位數(shù)不變的原則屉更,第25位會被舍棄,而這個數(shù)就會被舍入成洒缀。
情況二瑰谜、表示
這個數(shù)第24位是0,之后的所有數(shù)位都是0树绩,而的第23位是0萨脑,所以不會進位,導(dǎo)致它也會被表示成
葱峡。
情況三砚哗、表示
這個數(shù)的第24位是1,并且之后所有的數(shù)位都是0砰奕,而的第23位是1蛛芥,所以它會進位,導(dǎo)致最后表示成
军援。
神奇吧仅淑?
誤差的表示
知道舍入誤差產(chǎn)生的原因之后,接下來就要表示誤差了胸哥。在分析誤差的時候涯竟,我們需要兩個概念:絕對誤差(absolute error)和相對誤差(relative error)。
令
是精確值,而
是浮點值
絕對誤差表示為
即庐船,浮點值與精確值之差的絕對值银酬。
相對誤差表示為
即,絕對誤差與精確值絕對值之比筐钟。
由于舍入的規(guī)則揩瞪,精確值和浮點值之間的相對誤差不會超過半個機器精度,即篓冲,大小是
李破。
用表示將精確值
舍入成浮點值,舍入后的浮點值通常表示成
壹将,就是在上面加個波浪線嗤攻。然后,用符號來表示浮點算術(shù)運算:
誤差計算
確定了符號之后密任,緊接著進行誤差的計算颜启。標準的誤差計算模型是
每當(dāng)發(fā)生一次浮點運算的時候,引入一個的相對誤差浪讳,這個誤差的大小不超過機器精度的一半缰盏。這是一個非常合理的模型,用來分析誤差大小非常合適淹遵。
誤差的累積
考慮這樣一個計算:口猜,假設(shè)
都是精確值。在計算機中透揣,我們可以認為它的計算順序是這樣的
济炎,于是它的誤差可以表示成:
展開式子可得
這個式子的最大誤差是
但是這個表示過于繁瑣,我們考慮誤差不想這么復(fù)雜大計算這么一個長式子辐真,所以把它簡化為:
雖然擴大了誤差须尚,但是簡化了計算。還有就是要注意侍咱,這里必須要有絕對值耐床,因為如果中有負數(shù)的話,那么相加之后的誤差會減少楔脯,只有當(dāng)這些數(shù)的符號都相同的時候撩轰,它才是最大誤差,所以我們在這里加上絕對值符號。比如說堪嫂,考慮如下的情況:
假設(shè)有兩個數(shù)相加偎箫,
,計算表達式為:
皆串,我們可以得到最終結(jié)果是
镜廉,誤差是
,用絕對值限制的誤差結(jié)果是
愚战,真實誤差在絕對值限制誤差之內(nèi)娇唯,而如果沒有絕對值,得到的誤差會是
寂玲,顯然真實誤差要超過限制了塔插,這是不允許的。
這里要指出書上的一處錯誤拓哟,書上說如果中想许,a和b的符號相同的話,那么最大誤差為
断序,這很明顯太大了流纹。從它的實現(xiàn)代碼來看,它用的也不是這個范圍违诗,而是我上面說的那個漱凝。
對乘法的計算來說,考慮誤差的計算非常直觀:
因為本身就要引入
的誤差诸迟,所以最后的誤差指數(shù)需要
再加
茸炒。
除法的計算就不那么直觀了:
你沒看錯阵苇,是而不是
壁公,因為這里的
有正有負,它是一個范圍绅项,所以不能把它消掉紊册,它的誤差和相乘的誤差是一樣的!
一個簡潔的記號
根據(jù)從其他書上借來的引理快耿,如果囊陡,并且對正整數(shù)n有
,可以得到以下式子:
這樣关斜,就可以簡單地記作
了。
誤差計算的實例
根據(jù)上面的原理铺浇,我們可以看到pbrt中有正確的誤差痢畜,有錯誤的誤差,還有不知道是不是正確的誤差(-_-)!
先看正確的誤差:
用計算公式計算出球表面和光線的交點后丁稀,還需要執(zhí)行一步吼拥,把交點投射到球表面上去,因為由于誤差的緣故线衫,計算出來的交點可能不在表面上凿可。這個投射的過程是
是投射后的x坐標,投射y和z坐標的方式一樣授账。我們來分析一下這個操作的誤差
其中,操作引入的誤差為
枯跑,然后為了開根號,我們把分母內(nèi)的誤差擴大了
白热,使得最后的式子非常簡潔敛助。可以查看pbrt-v3的代碼屋确,我們可以看到:
// Compute error bounds for sphere intersection
Vector3f pError = gamma(5) * Abs((Vector3f)pHit);
沒錯纳击,就是。
再來看個錯誤的誤差:
在用矩陣轉(zhuǎn)換頂點的時候攻臀,代碼是這樣的
T xp = m.m[0][0] * x + m.m[0][1] * y + m.m[0][2] * z + m.m[0][3];
而書中的式子是這樣的
注意這里在后面加了個括號焕数,代碼中是沒有這個括號的,有了括號之后刨啸,計算出來的誤差和沒有括號計算的誤差是不一樣的堡赔。
我們先來看有括號之后的計算過程是什么
最終引入的誤差是。
不加括號是什么樣子呜投?
沒錯加匈,最終引入的誤差是存璃。而它的代碼中仑荐,引入的誤差卻仍然是
:
T xAbsSum = (std::abs(m.m[0][0] * x) + std::abs(m.m[0][1] * y) +
std::abs(m.m[0][2] * z) + std::abs(m.m[0][3]));
*pError = gamma(3) * Vector3<T>(xAbsSum, yAbsSum, zAbsSum);
最后是不知道正確不正確的誤差:
計算光線和三角形交點的時候,涉及到的計算非常復(fù)雜纵东,最終的計算公式是
但是里面的所有參數(shù)粘招,都是從別的地方計算出來的,也就是說這些參數(shù)本身就包含誤差在里面偎球。誤差的計算過程也弄不清楚洒扎,為啥要分別計算x,y衰絮,z分量的絕對誤差值袍冷,然后再計算t值的誤差,完全搞不懂懊怠胡诗!
總結(jié)
說了這么多,你一定覺得誤差非常恐怖煌恢。其實不然骇陈,因為不管誤差怎么累積,它的量級也不會太大瑰抵,除非你放大誤差你雌。跟分析誤差相比,更應(yīng)該關(guān)注的是算法的穩(wěn)定性和精確度二汛,因為如果算法不好婿崭,它會放大某些誤差,導(dǎo)致結(jié)果完全不一樣肴颊。所以逛球,更多地關(guān)注算法,在算法出現(xiàn)問題的時候苫昌,從誤差的角度去思考一下颤绕,是一個不錯的方法。
參考資料
Physically Based Rendering
pbrt源碼祟身,版本3
Accuracy and Stability of Numerical Algorithm
數(shù)值分析(第二版)
Rounding Errors in Algebraic Processes