學完了插值,咱們來學習在場景裏面添加一個立體彩色球(三維插值)html
按照慣例,先看效果:學習
Chapter4: Adding a sphereui
咱們又一次面臨圖形學的主要任務。spa
咱們須要再次回顧coord1.1的座標系統,若是有一個球,在眼睛和屏幕中間,那麼如何將其繪製到屏幕上
3d
假設咱們沿着y負方向垂直往下看:code
diagram 4-1orm
掃描屏幕位置的視線(有眼睛發出指向屏幕特定位置)的生成在上一節講述完畢。
htm
咱們的方法是經過逐行掃描屏幕的每個位置,而後發出對應的視線,檢測視線是否與其餘物體相交進而肯定屏幕位置的像素值(rgb)。blog
那麼,在此案例中,可能出現的三種視線如上ip
line1 視線不與球相交,暢通無阻,看到的應該是背景,因此,在屏幕上對應的位置爲背景色
line2 視線與球體相交,穿過
line3 視線與球相切
那麼,這就比較好辦了
咱們只須要推出判斷直線與球是否相交的公式,此題得解
書上的最後的斷定公式推錯了,可是書上公式處代碼是對的,咱們本身推一遍。
依據符號約定,向量用粗斜體小寫字母,矩陣用粗斜體大寫字母,標量用正常體大寫或小寫字母
************************* 數學分割線 ***************************
球體方程:x*x + y*y + z*z = r * r.
若是球心爲{hx,hy,hz},那麼方程以下:
(x - hx)*(x - hx) + (y - hy)*(y - hy) + (z - hz)*(z - hz) = r*r.
咱們設 p = (x,y,z), h = (hx,hy,hz).
則方程表示形式爲:(p - h)·(p - h) = r * r
咱們須要肯定咱們的光線p(t) = a + t * b. 是否曾經碰撞過這個球體
若是它碰撞過,那麼存在係數t知足方程。
因此咱們求下述公式是否知足
(p(t) - h)·(p(t) - h) = r * r
即: (a + t*b - h)·(a + t*b - h) = r * r
通過化簡獲得:
t*t* b·b + 2t* b·(a-h) + (a-h)·(a-h) - r * r = 0
化簡過程也很簡單,你先把a-h組合到一塊兒,而後進行點乘分配律便可
************************* End ***************************
全部的向量都是已知量,咱們要求取 t 是否存在,依此來斷定屏幕上的點是不是背景色
因此,二次方程求根法, delt不小於0,說明存在,即視線遇到了球體
那麼咱們假定眼睛仍然在(0,0,0),球心在(0,0,-1),半徑爲0.5
看一個橘黃色的球
代碼以下:
#define LOWPRECISION #include <fstream> #include "ray.h" //https://www.cnblogs.com/lv-anchoret/p/10182002.html using namespace lvgm; #define stds std:: bool hit(const ray::vec_type& Center, const precision R, const ray& sight) { ray::vec_type trace = sight.origin() - Center; precision a = dot(sight.direction(), sight.direction()); precision b = 2.0 * dot(trace, sight.direction()); precision c = dot(trace, trace) - R*R; precision delt = b*b - 4. * a*c; return delt > 0.; } const ray::vec_type lerp4(const ray& sight) { ray::vec_type sphereHeart{ 0.,0.,-1. }; if (hit(sphereHeart, 0.5, sight)) return ray::vec_type{ 1.0f,0.6f,0.f }; ray::vec_type unit = sight.direction().ret_unitization(); precision _t = 0.5*(unit.y() + 1.); return (1.0 - _t)*ray::vec_type{ 1.0f,1.0f,1.0f } +_t * ray::vec_type{ 0.5f,0.7f,1.0f }; } void build_4_1() { stds ofstream file("graph4-1.ppm"); size_t W = 400, H = 200; if (file.is_open()) { file << "P3\n" << W << " " << H << "\n255\n" << stds endl; ray::vec_type eye{ 0.,0.,0. }; ray::vec_type horizontal{ 4.,0.,0. }; ray::vec_type vertical{ 0.,2.,0. }; ray::vec_type start{ -2.,-1.,-1. }; for (int y = H - 1; y >= 0; --y) for (int x = 0; x < W; ++x) { vec2<precision> para{ precision(x) / W,precision(y) / H }; ray sight = { eye, start + para.u() * horizontal + para.v() * vertical }; ray::vec_type color = lerp4(sight); int r = int(255.99 * color.r()); int g = int(255.99 * color.g()); int b = int(255.99 * color.b()); file << r << " " << g << " " << b << stds endl; } stds cout << "complished" << stds endl; file.close(); } else stds cerr << "open file error" << stds endl; } int main() { build_4_1(); }
固然,你能夠把球心的位置先後上下左右移動試試,它在屏幕上會進行對應的平移和放縮
不過,不管你怎麼移動,你看到的背面和正面是同樣的,下面咱們就來解決區分背面和正面的方法
Chapter5-1: Surface normals and multiple objects.
這一篇,咱們先講表面法線,下一篇講剩餘部分
咱們有了上一章節的知識,如今來實現咱們最初的那張圖,就很簡單了。
還記不記得咱們在上一篇文章中的二維插值,是根據平面中的二維位置座標來進行區分和特化的。
那麼咱們若是要在三維座標系中像上次那樣,賦予每個像素點獨一無二的特徵,咱們也能夠採起三維座標位置,可是它不是很好解決上一章節Chapter4末尾提出的那個問題,因此咱們還須要找出可以代表球體上某一點的另一個獨一無二的特徵且可以區分出背面。那就是——Surface normals(表面法線)
引用書中一張圖:
我仍是畫一個吧
以前就說過,h的三維座標就是視線到h的向量,p點座標就是視線向量,由於eye的座標爲(0,0,0),這個應該沒問題吧
好,這就好辦了,關於平面上某一點的法線,就是該點所在的切平面的法向量,而對於球來講,表面某點p的切平面的法向量平行於向量(h->p),因此p - h 即爲p點表面法線(向外),p - h,你能夠理解爲座標相減獲得的向量,也能夠理解爲兩個向量相減(eye->p,eye->h)獲得的向量,結果是同樣的
因此,咱們目前的任務就是求出p點
即,咱們真正要求的是實線
結合
視線公式 p(t) = a + t * b
與
斷定相交 t*t* b·b + 2t* b·(a-h) + (a-h)·(a-h) - r * r = 0
求出t係數,就求出了圖中的實線向量
代碼:
#define LOWPRECISION #include <fstream> #include "ray.h" //https://www.cnblogs.com/lv-anchoret/p/10182002.html using namespace lvgm; #define stds std:: precision hit(const ray::vec_type& Heart, const precision R, const ray& sight) { ray::vec_type trace = sight.origin() - Heart; precision a = dot(sight.direction(), sight.direction()); precision b = 2.0 * dot(trace, sight.direction()); precision c = dot(trace, trace) - R*R; precision delt = b*b - 4. * a*c; if (delt < 0.) return -1.; else return (-b - sqrt(delt)) / (2.*a); //根,正、背面 } const ray::vec_type lerp5(const ray& sight) { ray::vec_type sphereHeart{ 0.,0.,-1. }; precision _t = hit(sphereHeart, 0.5, sight); if (_t > 0.) //相交 { ray::vec_type normUnit = (sight.go(_t) - sphereHeart).ret_unitization(); //單位法線 -1~1 return 0.5*ray::vec_type{ normUnit.x() + 1.f ,normUnit.y() + 1.f,normUnit.z() + 1.f }; //映射到 0~1 } //背景色 ray::vec_type unit = sight.direction().ret_unitization(); _t = 0.5*(unit.y() + 1.); return (1.0 - _t)*ray::vec_type{ 1.0f,1.0f,1.0f } +_t * ray::vec_type{ 0.5f,0.7f,1.0f }; } void build_5_1() { stds ofstream file("graph5-1.ppm"); size_t W = 400, H = 200; if (file.is_open()) { file << "P3\n" << W << " " << H << "\n255\n" << stds endl; ray::vec_type eye{ 0.,0.,0. }; ray::vec_type horizontal{ 4.,0.,0. }; ray::vec_type vertical{ 0.,2.,0. }; ray::vec_type start{ -2.,-1.,-1. }; for (int y = H - 1; y >= 0; --y) for (int x = 0; x < W; ++x) { vec2<precision> para{ precision(x) / W,precision(y) / H }; ray sight = { eye, start + para.u() * horizontal + para.v() * vertical }; //屏幕位置 ray::vec_type color = lerp5(sight); int r = int(255.99 * color.r()); int g = int(255.99 * color.g()); int b = int(255.99 * color.b()); file << r << " " << g << " " << b << stds endl; } stds cout << "complished" << stds endl; file.close(); } else stds cerr << "open file error" << stds endl; } int main() { build_5_1(); }
效果圖就是開篇那個
感謝您的閱讀,生活愉快~