中文第一计算机图形学社区OpenGPU 版权所有2007-2018

 找回密码
 注册

扫一扫,访问微社区

搜索
查看: 10421|回复: 1

PBRT阅读:第二章 几何和变换 第2.1 - 2.6节

[复制链接]
发表于 2011-4-3 12:05:23 | 显示全部楼层 |阅读模式
(编号:PBRT-TEXT-02001)<br><br><span style="font-weight: bold;">第二章 几何和变换</span><br> <br>几乎所有图形软件都以几何类(geometric classes, 这里指c++类)为基础.这些类表示了诸如点,向量,光线等等的数学构件. 由于我们在系统中会到处用到这些类, 良好的抽象和有效的实现至关重要. 本章会讲解pbrt的几何基础的接口和实现.<br><br>几何类见文件 core/geometry.h 和core/geometry.cpp.<br>变换矩阵见文件 core/transform.h 和core/transform.cpp.<br> <br><span style="font-weight: bold;">2.1 坐标系统</span><br><br>pbrt用三个浮点数坐标值x,y,z来表示三维点,向量和法向量. 当然,这些值只有在一个给定的坐标系下才有意义: 给定一个原点和三个定义x,y,z轴的向量,就定义了这个坐标系(frame).<br><br>在n维空间中, 坐标系的原点P0和其n个线性无关的基向量定义了n维仿射空间(affine space).所有空间中的向量V可以被表达成为基向量(V1,V2, ..., Vn)的线性组合:<br><br>&nbsp; &nbsp;&nbsp;&nbsp;V = s1V1 + s2V2 + ... + snVn&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(s1, s2, ... sn是唯一存在的一组纯量, 被称为V关于基(V1,V2...Vn)的表达).<br>同样地, 对与点P而言, 它可用原点P0和基向量(V1,V2, ..., Vn)表达:<br><br>&nbsp; &nbsp;&nbsp;&nbsp;P = P0 +&nbsp;&nbsp;s1V1 + s2V2 + ... + snVn<br><br>以上讨论有点循环定义的味道: 要定义坐标系我们需要定义一个点和一组向量, 而点和向量只有在给定的一个坐标系下才有意义. 因此,我们需要一个标准坐标系, 其原点是(0,0,0), 基向量为(1,0,0), (0,1,0) 和(0,0,1).<br> <br><span style="font-weight: bold;">2.1.1 左/右手坐标系</span><br> <br>我们知道坐标系分左手坐标系和右手坐标系,pbrt用左手坐标系.<br> <br><span style="font-weight: bold;">2.2 向量</span><br><br> &lt;Geometry Delcarations&gt; =<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;class COREDLL Vector {<br>&nbsp; &nbsp; public:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Vector Public Methods &gt;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Vector Public Data &gt;<br>&nbsp; &nbsp; };<br><br> 一个向量表达了三维空间内的一个方向, 它由三个浮点数定义:<br>&lt;Vector Public Data&gt;=<br>&nbsp; &nbsp; float x, y, z;<br><br>&nbsp; &nbsp;x,y,z被定义为公共成员, 不太符合C++的封装原则, 但我们这样做是为了代码的清晰和效率.<br> <br>缺省情况下, (x,y,z)被设成0. 用户可以选择给定任意值:<br>&lt;Vector Public Methods&gt;=<br>&nbsp; &nbsp;&nbsp;&nbsp;Vector(float _x = 0, float _y = 0, float _z = 0)<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;: x(_x), y(_y), z(_z) {<br>&nbsp; &nbsp; }<br><br><span style="font-weight: bold;">2.2.1 向量运算</span><br><br>向量加法运算:<br><br>&nbsp; &nbsp;&nbsp; &nbsp; &lt;Vector Public Methods&gt; +=<br>&nbsp; &nbsp; Vector operator+(const Vector &amp;v) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return Vector(x+v.x, y + v.y, z + v.z);<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; Vector&amp; operator+=(const Vector &amp;v) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;x += v.x;&nbsp;&nbsp;y += v.y;&nbsp;&nbsp;z += v.z;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return *this;<br>&nbsp; &nbsp; }<br><br>向量减法运算与上类似, 略.<br> <br style="font-weight: bold;"><span style="font-weight: bold;">2.2.2 比例运算</span><br><br>比例运算是纯量乘法, 即是将向量每个分量乘以一个纯量, 从而改变了它的长度.<br><br>&nbsp; &nbsp;&nbsp; &nbsp; &lt;Vector Public Methods&gt; +=<br>&nbsp; &nbsp; Vector operator*(float f) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return Vector(f*x,&nbsp;&nbsp;f*y, f*z);<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; Vector&amp; operator*=(const Vector &amp;v) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;x&nbsp;&nbsp;*=f;&nbsp;&nbsp;y *= f;&nbsp;&nbsp;z&nbsp;&nbsp;*= f;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return *this;<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp;&nbsp; &nbsp;&lt;Geometry Inline Functions&gt; =<br>&nbsp; &nbsp; inline Vector operator*(float f, const Vector &amp;v) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return v*f;<br>&nbsp; &nbsp; }<br><br>&nbsp;&nbsp;类似地,我们可以定义纯量除法 "operator/" 和 "operator /=", 此略.<br> <br>&nbsp; &nbsp;Vector类还有一个"取负值"的单操作符定义, 用来返回一个方向相反的向量:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &lt;Vector Public Methods&gt; +=<br>&nbsp; &nbsp; Vector operator-() const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return Vector(-x, -y, -z);<br>&nbsp; &nbsp; }&nbsp;&nbsp;<br><br>&nbsp;&nbsp;下面两个函数可以用索引值0,1,2方便地使用向量的各个分量: v[0]­得到x值, v[1]­得到y值, v[2]­得到z值.<br><br>&nbsp; &nbsp;&nbsp; &nbsp; &lt;Vector Public Methods&gt; +=<br>&nbsp; &nbsp; float operator[](int i) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Assert(i &gt;= 0 &amp;&amp; i &lt;= 2);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return (&amp; x);<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; float &amp;operator[](int i) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Assert(i &gt;= 0 &amp;&amp; i &lt;= 2);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return (&amp;x);<br>&nbsp; &nbsp; }<br> <br><span style="font-weight: bold;">2.2.3 点积和叉积</span><br><br>对于两个向量V和W, 它们的点积(V . W)定义为:&nbsp;&nbsp;Vx*Wx + Vy*Wy + Vy*Wy.<br> <br>&nbsp; &nbsp;&nbsp; &nbsp;&lt;Geometry Inline Functions&gt; =<br>&nbsp; &nbsp; inline float Dot(const Vector &amp;v1, const Vector &amp;v2) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;<br>&nbsp; &nbsp; }<br> <br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;点积跟两向量的夹角关系是:<br><br>&nbsp; &nbsp; (v.w) = |v| |w| cosθ<br><br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;如果两个非退化(即非(0,0,0))的向量相互垂直, 则(v.w)为零; 反之,也成立. 两个或多个相互垂直的向量被称为是正交的(orthogonal), 一组正交的单位向量被称为规格化正交的(orthonormal).<br> <br>假定u,v,w是向量, s是纯量, 则有下列性质:<br><br>&nbsp; &nbsp; (u . v) = (v . u)<br>&nbsp; &nbsp; (su . v) = s(v . u)<br>&nbsp; &nbsp; (u . (v + w)) = (u . v) + (u . w)<br> <br>&nbsp;&nbsp;我们常常要计算点积的绝对值, 故有如下函数:<br> <br>&nbsp; &nbsp;&nbsp; &nbsp;&lt;Geometry Inline Functions&gt; =<br>&nbsp; &nbsp; inline float AbsDot(const Vector &amp;v1, const Vector &amp;v2) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return fabsf(Dot(v1,v2));<br>&nbsp; &nbsp; }<br>叉积是另一个很有用的向量操作. 给定三维空间的两个向量, 叉积v × w 是垂直于两者的向量. 注意这个新向量的朝向是由坐标系的左右手定则(handedness)决定的.给定两个正交的向量 v 和 w, 那么, (v, w, v × w) 就按照给定的左右手定则形成一个坐标系.<br>在左手系中, 叉积定义为:<br><br>&nbsp; &nbsp; (v × w)x = vy wz – vz wy<br>&nbsp; &nbsp; (v × w)y = vz wx – vx wz<br>&nbsp; &nbsp; (v × w)z = vx wy – vy wx<br><br>有一个帮助记忆的公式是计算下面矩阵的行列式值:<br> <br><br>其中i, j, k分别代表轴(1, 0, 0), (0, 1, 0), (0, 0, 1). 注意这只是一个记忆工具, 而不是严格的数学表达, 因为矩阵把纯量和向量混合在一起用了.<br><br>&lt;Geometry Inline Functions&gt; +=<br>&nbsp; &nbsp;&nbsp;&nbsp;inline Vector Cross(const Vector &amp;v1, const Vector &amp;v2) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; return Vector((v1.y * v2.z) – (v1.z * v2.y),<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; (v1.z * v2.x) – (v1.x * v2.z),<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; (v1.x* v2.y) – (v1.y * v2.z));<br>}<br><br>从叉积的定义中,我们得出:<br>&nbsp; &nbsp; | v × w | =&nbsp;&nbsp;| v | |w| sin θ&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;(θ是v和w的夹角)<br><br>从上式可以看出, 两个相互垂直的单位向量的叉积也是一个单位向量. 如果两个向量平行, 则它们的叉积是个退化的向量.&nbsp;&nbsp;另外, 可以看出, 以两个向量v1 , v2为边的平行四变形面积是| v1 × v2 |.<br><br><span style="font-weight: bold;">2.2.4 向量正规化</span><br><br>把一个向量变换成具有相同方向的单位向量就是向量的正规化, 方法是将向量的各个分量除以向量的长度:<br><br>&lt;Vector Public Methods&gt; +=<br>&nbsp; &nbsp;&nbsp;&nbsp;float LengthSquared() const { return x * x + y * y + z * z;}<br>&nbsp; &nbsp; float Length() const { return sqrtf(LengthSquared());}<br>&lt;Geometry Inline Function&gt; +=<br>&nbsp; &nbsp;&nbsp;&nbsp;inline Vector Normalize (const Vector &amp;v) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Return v / v.Length();<br>&nbsp; &nbsp; }<br> <br><span style="font-weight: bold;">2.2.5 由一个向量建立的坐标系</span><br><br>我们会经常用一个向量构造一个坐标系. 由于叉积跟两个向量垂直, 我们可以通过两次叉积(该向量跟任意一个向量叉积得到第二个向量, 第一和第二向量叉积得到第三个向量) 来得到三个相互垂直的向量,因而得到一个坐标系.<br><br>Pbrt所用的方法是: 给定一个正规化的向量v1, 把该向量其中一个分量置零并互换另外两个分量的值, 然后对之正规化,就得到第二个向量 v2 (可以验证v1和v2相互垂直), 再由v1和v2的叉积得到第三个向量:<br><br>&lt;Geometry Inline Functions&gt; +=<br>&nbsp; &nbsp; inline void CoordinateSystem(const Vector &amp;v1, Vector *v2, Vector *v3) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;if(fabsf(v1.x) &gt; fabsf(v1.y)) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;Float invLen = 1.f/sqrtf(v1.x * v1.x + v1.z * v1.z);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*v2 = Vector(-v1.z * invLen, 0.f, v1.x * invLen);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;else{<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;Float invLen = 1.f/sqrtf(v1.y * v1.y + v1.z * v1.z);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;*v2 = Vector(0.f,&nbsp;&nbsp;v1.z * invLen, -v1.y* invLen);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}&nbsp; &nbsp; <br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;*v3 = Cross(v1, *v2);<br>&nbsp; &nbsp; }<br><br><span style="font-weight: bold;">2.3 点</span><br><br>&lt;Geometry Declarations&gt; +=<br>&nbsp; &nbsp; Class COREDLL Point {<br>&nbsp; &nbsp; Public:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Point Methods&gt;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Point Public Data&gt;<br>&nbsp; &nbsp; };<br><br>点是三维空间的位置.虽然它跟向量一样也是用(x,y,z)三个坐标值表示, 但由于它们本质上的不同, 处理它们的方式也是不同的.<br><br>&lt;Point Public Data&gt; =<br>&nbsp; &nbsp; Float x, y, z;<br><br>跟Vector的构造器一样, Point构造器也是用可选的参数设置x,y,z的坐标值:<br><br>&lt;Point Mehods&gt; =<br>&nbsp; &nbsp; Point(float _x = 0, float _y = 0, float _z = 0)<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;: x(_x), y(_y), z(_z) {<br>&nbsp; &nbsp; }<br><br>有一些Point的函数返回一个Vector,或者用一个Vector作为参数. 比如, 把一个向量加到一个点上, 就是相当于将它在给定的方向上偏移而得到一个新的向量. 同样地, 两个点相减,得到它们之间的向量:<br><br>&lt;Point Methods&gt; +=<br>&nbsp; &nbsp; Point operator+ (const Vector &amp;v) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return Point(x + v.x, y + v.y, z + v.z);<br>&nbsp; &nbsp; }<br> <br>&nbsp; &nbsp; Point &amp;operator += (const Vector &amp;v) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;x += v.x; y += v.y; z += v.z;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;return *this;<br>}<br> <br>&lt;Point Methods&gt; +=<br>&nbsp; &nbsp; Vector&nbsp;&nbsp;operator- (const Point &amp;p) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return Vector(x - p.x, y -&nbsp;&nbsp;p.y, z&nbsp;&nbsp;- p.z);<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; Point operator- (const Vector &amp;v) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return Point(x -&nbsp;&nbsp;v.x, y - v.y, z - v.z);<br>&nbsp; &nbsp; }<br> <br>&nbsp; &nbsp; Point &amp;operator -= (const Vector &amp;v) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;x -= v.x; y -= v.y; z -= v.z;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Return *this;<br>}<br>下面是求两点之间距离的函数:<br>&lt;Geometry Inline Functions&gt; +=<br>&nbsp; &nbsp; inline float Distance(const Point &amp;p1, const Point &amp;p2) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return (p1&nbsp;&nbsp;- p2).Length();<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; inline float DistanceSquared(const Point &amp;p1, const Point &amp;p2) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return (p1&nbsp;&nbsp;- p2).LengthSquared();<br>&nbsp; &nbsp; }<br><br>虽然点乘以纯量不具数学意义, 但是Point类仍然支持纯量乘的定义, 用以求多个点的加权和. 其实现跟Vector中的实现类似, 从略.<br> <br><span style="font-weight: bold;">2.4 法向量</span><br><br>&lt;Geometry Declarations&gt;&nbsp;&nbsp;+=<br>&nbsp; &nbsp; class COREDLL Normal {<br>&nbsp; &nbsp; public:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Normal Methods&gt;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Normal Public Data&gt;<br>&nbsp; &nbsp; };<br><br>法向量是在给定点上垂直于表面的向量。它可以被定义成两个互相不平行的表面切向量的叉积。虽然法向量跟向量很相似,但是应知它们的不同:因为法向量是根据它跟特定的曲面来定义的,在某些情况下跟向量是不同的,特别是使用变换的时候。(见第2.8节)。<br><br>Normal和Vector的实现很相似,都是用三个浮点数x,y,z表示,并定义了法向量之间的加,减,纯量乘,正规化等运算。但是,法向量不能跟一个点相加,也不能取两个法向量的叉积。还有,法向量不一定是正规化的。<br><br>Normal提供了由一个Vector初始化一个Normal的构造器。由于Normal和Vector有细微的差别,我们不希望它们之间有隐性的转换。为此,C++的explicit关键词可以保证它们之间显性的转换。<br><br>&lt;Normal Methods&gt; =<br>&nbsp; &nbsp; explict Normal(const Vector &amp;v)<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;: x(v.x),&nbsp;&nbsp;y(v.y), z(v.z) {}<br>&lt;Vector Public Methods&gt; +=<br>&nbsp; &nbsp; explict Vector(const Normal &amp;n);<br>&lt;Geometry Inline Functions&gt; +=<br>&nbsp; &nbsp; inline Vector::Vector(const Normal &amp;n)<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;:x(n.x), y(n.y), z(n.z) { }<br><br>这样一来,如果声明了Vector&nbsp;&nbsp;v; Normal&nbsp;&nbsp;n; 那么 n = v 就是非法的,必须用显式的转换:n = Normal(v).<br>我们还重载了Dot()和AbsDot() 函数来覆盖求法向量和向量之间的求点积的各种组合情况, 另外,其它跟Vector类似的函数都不提及了。<br> <br><span style="font-weight: bold;">2.5 光线</span><br><br>&lt;Geometry Declarations&gt; +=<br>&nbsp; &nbsp; class COREDLL Ray {<br>&nbsp; &nbsp; public:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Ray Public Methods&gt;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;Ray Public Data&gt;<br>&nbsp; &nbsp; };<br>光线是一条由其原点和方向定义的射线。pbrt用Ray类来表达光线,其中用一个Point成员变量表示其原点,用一个Vector表示其方向:<br><br>&lt;Ray Public Data&gt; =<br>&nbsp; &nbsp; Point&nbsp;&nbsp;o;<br>&nbsp; &nbsp; Vector d;<br><br>光线的参数化形式是一个关于纯量t的方程:<br>&nbsp; &nbsp; r(t) = o + t d&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;≤t&nbsp;&nbsp;≤ ∞<br><br>Ray类还包含两个值mint和maxt,把光线限定在[r(mint), r(maxt)]区间之间。 它们声明为mutable, 这意味着即使它们所在的Ray是const, 也是可以被改变的。其目的就是方便光线/物体的求交, 因为在这过程中,总是要记录最近的交点所对应的t值。<br><br>&lt;Ray Public Data &gt; +=<br>&nbsp; &nbsp; mutable float mint, maxt;<br><br>为了模拟运动模糊效果, 每条光线还需要一个时间值:<br><br>&lt;Ray Public Data&gt; +=<br>&nbsp; &nbsp; float time;<br><br>Ray的构造器很简单明了:<br><br>&lt;Ray Public Methods&gt; =<br>&nbsp; &nbsp; Ray() : mint(RAY_EPSILON), maxt(INFINITY), time(0.f) {}<br>&nbsp; &nbsp; Ray(const Point &amp;origin, const Vector &amp;direction,<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;float start = RAY_EPSION, float end = INFINITY, float t = 0.f)<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;: o(origin), d(direction), mint(start), maxt(end), time(t) {}<br><br>&lt;Global Constants&gt; =<br>&nbsp; &nbsp; #define&nbsp;&nbsp;RAY_EPSILON&nbsp; &nbsp; 1e-3f<br><br>注意我们用一个极小的数(RAY_EPSILON)来初始化mint, 而不是用0, 原因是避免因浮点计算精度而引起的自相交的错误, 这是一个在光线追踪中的很典型的手法。<br><br>我们还重载函数操作符“()”, 来求和参数t对应的点:<br><br>&lt;Ray Public Methods&gt; +=<br>&nbsp; &nbsp; Point operator() (float t) const { return o + d * t;}<br><br>这样,我们可以很方便地写类似下面的代码:<br>&nbsp; &nbsp; Ray r(Point(0,0,0), Vector(1,2,3));<br>&nbsp; &nbsp; Point p = r(1.7);<br> <br><span style="font-weight: bold;">2.5.1 光线微分</span><br style="font-weight: bold;"><br>为了更好地利用第11章定义的纹理函数进行反走样,pbrt对每条被追踪的光线都保持着一些附加的信息。 在第11.1节, 这些信息用在Texture类中估算一小部分的场景在图像平面上的投影面积。这样,Texture类就可以计算出纹理在这个面积上的平均值,从而得到更好的图像。<br><br>RayDifferential是Ray的子类, 并包含两条辅助光线的附加信息。 这两条光线表示从主光线向x和y方向分别偏置一个像素而得到的相机光线。确定了这三条光线投射到被着色物体上的区域,Texture就可以估算出用于反走样的平均值。<br><br>&lt;Geometry Declarations&gt; +=<br>&nbsp; &nbsp; class COREDLL RayDifferential : public Ray {<br>&nbsp; &nbsp; public:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;RayDifferential Methods&gt;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;RayDifferential Public Data&gt;<br>&nbsp; &nbsp; };<br><br>&lt;RayDifferential Methods&gt; =<br>&nbsp; &nbsp; RayDifferential() { hasDifferentials = false;}<br>&nbsp; &nbsp; RayDifferential(const Point &amp;org, const Vector &amp;dir) : Ray(org, dir) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;hasDifferentials = false;&nbsp; &nbsp; <br>&nbsp; &nbsp; }<br><br>注意我们用到关键字explicit,防止不经意的Ray到RayDifferential的转换。 变量hasDifferentials被初始化为false, 表示相邻的两条光线还是未知的。<br><br>&lt;RayDifferential Methods&gt; +=<br>&nbsp; &nbsp; explicit RayDifferential(const Ray &amp;ray) : Ray(ray) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;hasDifferentials = false;&nbsp; &nbsp; <br>&nbsp; &nbsp; }<br>&lt;RayDifferential Public Data&gt; =<br>&nbsp; &nbsp; bool hasDifferentials;<br>&nbsp; &nbsp; Ray rx, ry;<br> <br style="font-weight: bold;"><span style="font-weight: bold;">2.6 三维包围盒</span><br><br>&lt;Geometry Declarations&gt; +=<br>&nbsp; &nbsp; class COREDLL BBOX {<br>&nbsp; &nbsp; public:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;BBOX Public Methods&gt;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&lt;BBOX Public Data&gt;<br>&nbsp; &nbsp; };<br><br>pbrt所要渲染的场景经常包含计算很费时的物体。 一个包含整个物体的三维包围体对很多操作而言都会非常有用。比如, 如果光线没有穿过包围盒, 就不必求光线和其中所包围的物体的交点了。<br><br>包围体的有效性跟两个因素有关:计算包围体的时间化费和包围盒包围物体的紧密程度。如果哦包围体太“宽松”了,就会浪费很多不必要的计算;反过来, 如果强求非常紧密的包围体,那么包围体很可能变得太复杂,时间耗费也会不菲。<br><br>包围体有很多种, pbrt用到沿轴的包围盒(axis-aligned bounding boxes, AABB). 其他的常见的选择包括沿方向的包围盒(oriented bounding boxes, OBB)和包围球。AABB可以由一个顶点和分别沿x,y,z轴方向的三个长度值来表示, 也可以由包围盒上两个相对的顶点来表示。pbrt就是用两点表示的,一个点的坐标是x,y,z的最小值,另一个是x,y,z的最大值。<br><br>BBOX缺省构造器把包围盒的范围定义成退化的 : pMin.x &gt;pMax.x, 即是空包围盒。<br><br>&lt;BBOX Public Methods&gt; =<br>&nbsp; &nbsp; BBox() {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pMin = Point (INFINITY, INFINITY, INFINITY);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pMax= Point (-INFINITY, -INFINITY, -INFINITY);<br>&nbsp; &nbsp; };<br>&lt;BBOX Public Data&gt; =<br>&nbsp; &nbsp; Point pMin, pMax;<br><br>有时我们用到包含一个点的包围盒:<br><br>&lt;BBOX Public Methods&gt; +=<br>&nbsp; &nbsp; BBox(const Point &amp;p) : pMin(p), pMax(p) {}<br><br>我们还可以用两个点p1, p2来构造BBOX, p1和p2不必满足p1.x &lt;= p2.x等条件, 构造器可以计算出最大、最小值:<br><br>&lt;BBOX Public Methods&gt; +=<br>&nbsp; &nbsp; BBox(const Point &amp;p1,const Point &amp;p2 )&nbsp;&nbsp;{<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pMin = Point(min(p1.x, p2.x), min(p1.y, p2.y), min(p1.z, p2.z));<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pMax = Point(max(p1.x, p2.x), max(p1.y, p2.y), max(p1.z, p2.z));<br>&nbsp; &nbsp; }<br><br>给定一个包围盒和一个点,BBox::Union()计算并返回一个包含该点和原包围盒的新包围盒:<br><br>&lt; BBox Method Definitions&gt; =<br>&nbsp; &nbsp; COREDLL BBox Union(const BBOX &amp;b, const Point &amp;p) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;BBox ret = b;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ret.pMin.x = min(b.pMin.x, p.x);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ret.pMin.y = min(b.pMin.y, p.y);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ret.pMin.z = min(b.pMin.z, p.z);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ret.pMax.x = min(b.pMax.x, p.x);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ret.pMax.y = min(b.pMax.y, p.y);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ret.pMax.z = min(b.pMax.z, p.z);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return ret;<br>&nbsp; &nbsp; }<br><br>同样地,我们可以构造一个包含两个包围盒的包围盒:<br><br>&lt;BBox Public Methods&gt; +=<br>&nbsp; &nbsp; friend COREDLL BBox Union(const BBox &amp;b, const BBox &amp;b2);<br>很容易判定两个包围盒是否重叠:<br><br>&lt;BBox Public Methods&gt; +=<br>&nbsp; &nbsp; bool Overlaps(const BBox &amp;b) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bool x = (pMax.x &gt;= b.pMin.x) &amp;&amp; (pMin.x &lt;= b.pMax.x);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bool y = (pMax.y &gt;= b.pMin.y) &amp;&amp; (pMin.y &lt;= b.pMax.y);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bool z = (pMax.z &gt;= b.pMin.z)&nbsp;&nbsp;&amp;&amp; (pMin.z &lt;= b.pMax.z);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return (x &amp;&amp; y &amp;&amp; z);<br>&nbsp; &nbsp; }<br><br>下面函数判定一个点是否在包围盒内:<br>&lt;BBox Public Methods&gt; +=<br>&nbsp; &nbsp; bool&nbsp;&nbsp;Inside(const Point &amp;pt)&nbsp;&nbsp;const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return (pt.x &gt;= pMin.x &amp;&amp; pt.x &lt;= pMax.x &amp;&amp;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;pt.y &gt;= pMin.y &amp;&amp; pt.y&lt;= pMax.y &amp;&amp;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;pt.z &gt;= pMin.z &amp;&amp; pt.z&lt;= pMax.z);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br> <br>BBox::Expand()用来扩张包围盒, BBox::Volume()用来计算包围盒的体积:<br><br>&lt;BBox Public Methods&gt; +=<br>&nbsp; &nbsp; void Expand(float delta) {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pMin -= Vector(delta, delta, delta);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pMax += Vector(delta, delta, delta);<br>&nbsp; &nbsp; }<br>&lt;BBox Public Methods&gt; +=<br>&nbsp; &nbsp; float Volume() const{<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Vector d = pMax - pMin;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return d.x * d.y * d.z;<br>&nbsp; &nbsp; }<br><br>BBox::MaximumExtent()返回最长的那个轴。在建造kd树时,我们用它决定沿那个轴划分。<br>&lt;BBox Public Methods&gt; +=<br>&nbsp; &nbsp; int BBox::MaximumExtent() const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Vector diag = pMax - pMin;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;if (diag.x &gt; diag.y &amp;&amp; diag.x &gt; diag.z)<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;return 0;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;else if (diag.y &gt; diag.z) return 1;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;else return 2;<br>&nbsp; &nbsp; }<br><br>BBox::BoundingSphere()返回包含该包围盒的球的中心和半径。 虽然包围球比对应的包围盒要宽松得多, 但有时仍是很有用的。在第15章, 我们用它得到包含整个场景的包围球,用以生成可能跟场景相交的随机光线。<br><br>&lt;BBox Public Methods&gt; +=<br>&nbsp; &nbsp; int BBox::BoundingSphere(Point *c, float *rad) const {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;*c = 0.5f * pMin + 0.5*pMax;<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;*rad = Distance(*c, pMax);<br>&nbsp; &nbsp; }<br> <br><br>

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

评分

1

查看全部评分

发表于 2018-11-20 17:48:48 | 显示全部楼层
你这发的 我还不如 看原版
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|关于我们|小黑屋|Archiver|手机版|中文第一计算机图形学社区OpenGPU

GMT+8, 2019-1-22 09:10 , Processed in 0.094058 second(s), 21 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表