学完了插值,咱们来学习在场景里面添加一个立体彩色球(三维插值)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(); }
效果图就是开篇那个
感谢您的阅读,生活愉快~