03 2月

axis-aligned bounding boxes碰撞检测

作者:will 网址:http://ymuhua.com/2016/02/03/aabb/

再讲加速算法之前,我要来介绍bounding box的概念,中文翻译成边界盒子,一般简称bbx。为什么要引入bbx呢。因为bbx可以代表一个物体体积范围,在模型很复杂的时候,如果每根光线都要和复杂模型进行碰撞运算会非常的慢,所以,一个快速简单的方法就是先和这个代表物体体积范围的bbx先进行快速碰撞检测,如果没碰到就不需要进行复杂模型的碰撞检测,这要大大减少了碰撞检测的运算量。加快了速度。

bbx

axis-aligned bounding box必须满足它是和轴向对齐,也就是每个面都满足垂直末个轴向。

如何求一根射线和一个box相交呢?我们先考虑2d情况。

我们还记得光线方程: p=o+td;

bbx2

图(a)x0,x1,y0,y1这四根是从box表面无线延伸的直线,图(b)一根光线射出,和x0,x1,y0,y1四根线分别产生四个交点,t0,t1,t2,t3。t0,t1表示一个线段x,t2,t3表示一个线段y,我们发现当x和y重叠的时候,光线就和box相交了。我们看下图。

bbx3

所以我们算法就是找这两断线段有没有重叠。

除了以上的规律外,我们还要考虑特殊情况,如下图。

bbx4

黑点表示的光线的起点。这是4种特殊情况,图(a)虽然他满足重叠规律,但是光线并没有穿过box,这时候所有的t满足,t<0。图(b)光线从物体表面和内部出发。

代码:

碰撞检测

转载请注明作者和出处,谢谢。http://ymuhua.com/2016/02/03/aabb/

 

 

02 2月

摄像机,透视,镜头畸变

作者:will 网址:http://ymuhua.com/2016/02/02/camera/

我们之前的渲染都是基于正交视图,是没有透视关系,何为透视,透视更符合我们平时眼睛观察,近大远小就是我们观察这个世界事物最常见的。所以我们需要在三维场景中也要实现这个效果。

在计算机图形学,我们知道所有的三维模型场景从显示器中显示出来都需要进行3d到2d的投射转换。所以我们需要一个观察屏幕view plane,也称为画布,而摄像机是一个观察点,通过这个摄像机观察点射出光线,然后打到模型,然后在这些光线的路径上我们放上一个画布view plane如果光线遇到模型,那么我们就会在屏幕上留下一个印记,然后,屏幕上就能显示出一个模型了。

persperct

那么透视是怎么产生的呢?我们用张图来解释。

perspect2

a图是透视的结果,b图是我们从一个顶视图俯瞰,小黄点是摄像机,虚线是光线,然后虚线和黑实线(view plane)相交的点就是在屏幕上所成图像的由来了。

这个也同样。

perspect3

 

知道了透视关系后,我们的摄像机作为透视的重要工具需要哪些属性呢。

观察屏幕:长,宽,分辨率,在《渲染第一张图片》已讲。

焦距:摄像机距离画布的距离(view plane)。如下图的d来表示

focallengthfocal

如上图,我们发现在保持摄像机相对物体位置不变,和画布大小不变的情况下,摄像机的焦距(d)越大,可视范围就越小。这个玩过摄影的人都知道,焦距越小,它的捕捉场景的范围反而越大,这个和相机的原理是相似的。因为你的感光器就是你的画布,而你的感光器的大小对于一台相机来说是定死。

摄像机的位置,方向:方便入门,我们将摄像机放在原点位置,方向是朝着z轴方向。那么画布也垂直于z轴。

这是摄像机类定义:

camera

这个是houdini中摄像机的关系图

spherecam

那么我们如何来确定从摄像机射出的光线方向呢,对比上面两张图,左图是正交试图的光线,右图是透视射出的光线,发现区别了吧。

左图的光线的方向永远是ray.dir=(0,0,1)。

但是右图的光绪需要将每个画布上的像素中心点减去摄像机的坐标,就能确定一个方向(向量位置相减)。viewplane

还记得这张图吗,我们是用来求每个像素中心点是用到的,假定现在的摄像机在坐标原点(0,0,0)。光线的方向公式:camdir

s:每个像素的size大小,pixel_size=Aperture/Hres(前一章介绍)

c,r: 当前长,宽,第几个宽像素。

d: 摄像机的方向,是指向z轴。

那么,很简单,我们的代码只需要改两句话。

下面是三张不同焦距的渲染一堆球。

f10

焦距:10

f50

焦距:50

f200

焦距:200

这个结果和之前分析的是一样的。焦距越大可视范围反而越小。同时我们发现在焦距等于10的情况下。圆球变成了椭圆形,尤其是图片边缘的球越发明显,我们称之为镜头畸变。

镜头畸变

镜头畸变是怎么产生的呢?请看下图。

distortion

引起透视变形的原因就是我们的画布(view plane)是平的。那为什么我们人眼没有透视畸变呢,就是因为人的眼球是个圆球。从上图可以发现,当焦距非常小的时候,可视范围是非常大。只有在z轴上的物体保持的是圆形,离开z轴越远,球失圆就越严重。所以照相机的鱼眼镜头镜片非常的凸就是为了避免镜头畸变的原因了。

转载请注明作者和出处,谢谢。http://ymuhua.com/2016/02/02/camera/
19 1月

Jetter Sampling Antialiasing 随机采样抗锯齿(二)

在抗锯齿第一章我们讲了均匀采样。如,将一个像素点分成5*5的子格子分别进行光线采样。

organiti

他的缺点就是太平均了,可能会出现一些奇怪的波纹。大自然都是无规律的形状,所以我们也要随机话。

jettersample

a图,在一个像素点随机取25个点进行光线采样,缺点是点很不平均。

b图,更均匀的分布,原理如c图将一个像素分割成5*5的格子,然后在每个子格子中采样点进行光线采样。即随机,又均匀。

代码修改很简单。只要将前一章的

q+0.5   p+0.5 替换成 q+rand_float()  p+rand_float(),rand_float()范围是0到1

 

15 1月

抗锯齿 Antialiasing(一)

抗锯齿,也译为抗锯齿或边缘柔化、消除混叠、抗图像折叠有损等。它是一种消除显示器输出的画面中图物边缘出现凹凸锯齿的技术。

这个技术是为了在保证低分辨的情况下,把图形的边缘变得更平滑,过渡更自然。

其实我最先遇到这个技术还是在小时候玩游戏的时候遇到的。尤其是那些大型三维游戏。那时候家里穷,买不起牛逼的显卡,但是又想玩,所以只能把游戏的分辨率调到最低,然后在显示选项里面把抗锯齿给关了,然后忍受的满屏的狗牙还玩的乐此不疲。最佩服自己的还是在一台只有两兆显存的电脑上,把生化危机3给通关了,全程帧速率不足10帧,就像在玩幻灯片一样,有个好处是你有足够的反映时间,里面那个追击者很难追上你,很怀念小时候的时光啊。

言归正转,以下三张图,最左边是没有抗锯齿,右边两张图是经过抗锯齿的,我们可以看到右边两张图是经过虚化处理。看起来边缘的过渡更平滑。

antitri

我先来介绍实现抗锯齿最简单的一个方法。

我们知道光线追踪是根据点采样产生的,我们每采样一个点,就在那个点上射出一根光线,之前我们介绍的是我们从每个像素的中心点作为一个光线的起点射出,虽然点在空间中是可以无限小的点,但是他在空间中也必须代表一块有限大小的面积。我们上一章一个中心点就代表这整个像素的结果,如果它射出去遇到红色的球,那么一整块像素就是红色的,但是这么做是很不精确的,尤其是在你像素点很大的情况下。

aniti1 anti2

上面两张图左图是理想状态下,右图是根据实际像素所采样的结果,图a的A像素,我们看到他2/3的面积是被黑色的背景覆盖,而1/3的面积是被黄色所覆盖,但是图b的A像素的中心点是在背景中,所以沿着中心点射出,我们必定遇不到黄色的模型。所以是黑色。

现在我们想,如果让这个A像素显示一个1/3的黄色的话不是更能够代表这个像素的真实情况吗。就像下图。

anti

那我们就需要将一个像素在分割成更小的像素。

aniti1G

如上图,像素A又被分割成9个小像素,然后我们再从这9个像素射出9个光线,然后,我们将这9个像素射出返回的值求他们的一个平均值给到像素A。我们就能得到一个光滑的边缘了。

anti

然后,我们来改一下代码。
假设我们采样3*3,也就是将每个像素点再划分成9小格,横向3个,纵向3个,定义n=3,n*n=9,也就是说我们在每次循环再增加9次光线碰撞计算,

Done

resultanit

当然了,这是最最基础抗锯齿的算法,速度也是最慢的,但是不关怎么着,我们有了第一步的图像优化!

接下俩又是思考题,如果多个物体的抗锯齿,尤其是叠加在一起的物体怎么算?

multanit

留给大家思考,有问题欢迎联系我

09 1月

渲染第一张图片

在这一章节,我来教大家怎么来用光线追踪来渲染第一张图片,把场景中的一个球渲染出来,我们假设这个球的颜色是红色。

sphere

我们要渲染一张图片,需要将三维的模型投射到一个平面上,我们先称它为画布,画布就是观察者的屏幕,实体化的画布就是电脑的显示屏,我们需要将画布在三维场景里面表示出来。所以我们定义:

画布的位置,朝向: 我们先定义画布在世界坐标的原点pos(0,0,0),面朝Z轴方向dir(0,0,1)。

画布的大小:这个画布都宽高多少,我们先定义画布宽(Aperture):41.4214m(houdini默认的摄像机的宽度)。画布高度这里是根据像素的宽高比来算出来。

注意,宽高不是指的是像素,这不是一个概念,像素指的是画布宽高在一个指定长度里面被划分成多少格数,一个格子代表一个像素。

像素:Hres8宽,Vres6高(如下图)

像素大小:因为像素一般都是一个正方形,所以我们只要求出像素宽就行了。

pixel_size=Aperture/Hres=41.4214/8(m)

viewplane

好了,现在每个有8*6个像素点,每个像素点我要射出去一根光线来判断我这个像素到底显示什么内容,如果这个线射出去碰到了场景中的红球,那么对应的像素点就填上红色。如果没有碰到红球,相对应的就显示黑色(背景)。

那么问题来了,我现在有48个像素对应要射出48根光线,那么这48根光线从哪里?方向?射出呢?

我们先不考虑透视的关系,现在我们假定每根线光线的方向都是沿着z轴的方向,定义ray.dir=(0,0,1);

下面,我们来确定他们的位置,如何确定光线的位置就相当于确定每个像素点的位置。我们来规定画布的中心正好在世界坐标的原点(0,0,0)。

画布的左下角等于:(0-8/2, 0-6/2)=(-4,-3) 单位是像素格。

画布的右上角等于: (3,2)   左上角:(-4,2) 右下角:(3,-3) 单位是像素格。

注意,这些数字现在都是以每个像素点的左下角为起点的。我们现在想让光想从每个像素的中心点出发。那么我们分别要向右和向上移动半格像素:

左下角(-3.5,-2.5) 右上角等于: (3.5,2.5)   左上角:(-3.5,2.5) 右下角:(3.5,-2.5)  单位是像素格。

既然我们在之前求出了像素的大小(pixel_size)那么我们可以知道每个像素的中心点的坐标了。

例:左下角:(-3.5,-2.5)*pixel_size=(-18.13,-12.95) 单位(m)

那么每个光线的位置ray.pos和方向ray.dir=(0,0,1)我们都已经确定了。遍历所有像素的

输出ppm:

结果如果下,球的位置坐标为(0,0,10),半径为1:

first1

大家感到很奇怪,为什会是个正方形?下面我们增加像素再来渲染看看。

middlefirsthigh

通过上面三张图比较,我们知道。像素越高,这个圆的还原度就越好。如果像素太低,那么图像的细节就会丢失很多,当然像素越高图像所花的渲染时间也就月多。

8*6:48根光线

80*60:4800根光线

800*600:4800000根光线

这是处理数据量的对比。

 

好了。这章我们讲完了。下面留下课后思考:如果场景中有多个球怎么办?并且他们从摄像机看去还有重叠关系。如何区分前后关系?留给大家思考吧

think

有任何问题,欢迎来信或者留言,我很乐意提供解答和帮助。

谢谢

 

08 1月

ppm 图片格式文件

好了,经过前面两章的介绍,我们知道了,如何来判断从观察者射出的光线是否能和圆碰到。接下来我们要继续讲下去去之前,要先介绍用什么图片格式来保存我们的数据。

我们平时用到最多的格式恐怕就是jpg图了,当然搞cg的同学平时渲染会输出现在的主流格式exr,当然用maya还会有tiff。不管什么格式的图片格式他们做的事情都是保存图片信息,不同是压缩算法的不同,看哪家的格式占用空间最少,图片信息丢失最少。这些都不是我们现在要关心的,我们现在关心的哪个图片格式最容易入门,那当然是ppm格式。

为什么说他容易,因为你可以用电脑自带的记事本做一个图片,对,就是那么的容易。

现在大家打开记事本贴写以下几行。

然后另存为test.ppm。好了,一张图片创建好了。

pixelppm

ppm像素的排列顺序是从左到右,从上到下。

对了。因为我是在Linux系统下工作环境,所以linux是直接支持ppm的格式图片。

如果大家是在windows平台下面,那么要看到ppm就需要安装看图软件ACDSEE

下面我们通过一个Rgb类来输出Rgb这三个颜色通道。

注意,我在内部进行数据操作的时候R,G,B的范围是0-1,理论上是可以大于1的,但是大于1之后,人眼分辨不出,但是可能以后后期较色需要这些信息,这里我们假定是[0,1]范围
还有,我们在写入到ppm文件是,需要将R,G,B值域范围映射到[0,255]的范围。如果值大于1了,限定等于255(clamp)。

写入ppm文件

 

07 1月

你可能说要知道的知识

光 颜色 Light and Color

 

我们知道,计算机中三维模型都需要投射到二维屏幕上。那么投射到二维屏幕上的每个像素点的颜色是由谁来决定的呢。

一个物体的颜色、明暗是由场景中灯光照射到物体上和这个物体本身的材质有关。

光——是由千千万万个光子(传递电磁相互作用的基本粒子)所组成,也就是说这些粒子他们带有能量,他们会震荡,就好比声波在空气中传播一样,他们是直线传播的。.光子是从光源中发射的,太阳,就是我们身边最大的一个光源。如果一组光子打到一个物体上。那么会发生以下三种情况:被吸收,反弹,穿过。不同的材质这三个情况发生的概率是不一样的。所以,每个不同材质的物体在我们眼里他们表现的质感也是不同的。但是,有一个规则是所有材质都相同的。那就是他们遵循能量守恒的原则,设入的光子总量=吸收+反弹+穿过的光子总和。

在科学界上,我们将材料总归为两类,导体和非导体。导体:金属类。非导体:玻璃,塑料,木头。非导体,可以是透明或不透明的(如下图塑料球和玻璃球)。

同样的,材质是可以合成在一起的,比如下图的彩色球,和颜色偏深的玻璃球一样

下面我们来考虑一下漫反射,一束光照射到一个球上,为什么他会显示红色?我们简单假设一束白光是由,红,绿,蓝三种颜色光子组成,现在我们照射一个红球上,现在蓝色和绿色光被这个球所吸收(过滤)了。而红光(光子)反射到我们眼睛里面,所以我们看到的是红色。为什么我们能看到五颜六色的物体,就是因为大自然界中的光源发射能量不同(颜色不同)的光子,然后这些光子在空间中来回穿梭,有的被吸收了,有的被反弹,有的穿过了,这也就是为什么天有时候是蓝的,有时候是橘红色,太阳光被大气过滤。然后不同光子从不同物体上反射到不同角度上,到达我们眼睛就是我们所看到不同的颜色。所以说,人眼配合上人脑是非常非常牛逼的渲染器,他要处理无数个光子是我们现在电脑处理器所远远达不到的运算速度。

06 1月

从一个球开始

好了,我们定义好光线了,那么接下来我们需要拿些物体来检测了光线碰撞了,我们会介绍光线和平面交互,光线和box,光线和三角平面,光线和圆。

很多人会问,为什么不介绍复杂模型碰撞检测呢,比如说茶壶,龙。因为在三维中所有的三维模型都能用三角面组成,所以只要讲光线和三角面碰撞检测,我们就能融会贯通了。

首先我们使用圆来进行。为什么用圆来做碰撞检测呢。因为圆最简单。(为什么最简单,等以后你就知道了)

我们在初中学过圆的方程:

(x-c)²=r²

光线的方程:

p=o+t*d;  (  t>0  )

当光线和圆交互也就是有交点的时候,点p是在圆上面。得到

(p-c)²=r²;

(p-c)²-r²=0;

将光线代入:

(o+t*d-c)²-r²=0;

现在我们唯一不知道的是未知数t:

所以就变成解二元一次方程解:

d²t²+[2(o-c)d]t+(o-c)(o-c)-r²=0;

简化方程:

at²+bt+c=0;

a=d²

b=2(o-c)d

c=(o-c)(o-c)-r²

那么得到t的解是:

b_4abc

我们确定有没有解,就要判断开根号里面的值是否>0

所以,我们要判断试着个等式

d=b²-4ac;

如果,d>0,那么t有两个解,如果d=0,那么t有一个解。如果d<0则无解。我们用一张图来表示

sphere

这是第一步。因为如果我求出d>0,不等于就一定就有交互。我们还要看t的值。t值必须大于0,因为光线只会向前进,不会倒着走。

ok,求出了p的值之后,那么再将t代入p=o+t*d,我们就能知道撞击点的位置了。

Hitpos=0+t*d

现在我们来看看圆的类

以下是碰撞检测函数,

*这里来解释一下为什么t要大于0.0001而不是0,以后章节会提到,我这里简单说一下,之后为了判断这个点在不在阴影里面还要从这个点再继续射出一根shadow_ray。为了避免碰到自己本身=0的极端值,用着个方法把碰撞点往外推了一点点。

 

 

 

 

06 1月

Rays 光线

介绍光线之前,我们来讲讲场景,The World

我们的渲染器首先是在一个场景进行,这个场景会包含模型物体,灯光,摄像机,光线,这些物体都是在一个统一的世界坐标系统中。坐标原点为(0,0,0)。具体的坐标系统变换我们会在以后的章节中讨论。

光线

一根光线就是一根射线,我在年轻的时候学过射线就是从一个起点出发射向无穷远。所以,我们定义o为光线的起点,dir为光线的方向。为了能够参数化这个光线射出多远。我们用引入t这个变量。当t=0在起点。

ray

那么根据下图这个光线的表达式: p=o+td;

光线的种类又可分为:

主光线:从摄像机射出的光线

间接光线:反弹光线

阴影光线:从物体表面射出的光线是和材质有关的

灯光光线:从灯光射出的,在特定场合会用到,比如全局光。

光线定义:

 

06 1月

Vector 向量

首先的首先,我们来讲向量,向量是有方向和大小的量。在三维中是必不可少。我们一般已x,y,z来表示三个维度

Vector由分为位置向量:比如表示点的位置Positon。

和方向向量;比如表示速度Velocity。

我们在C++中这么定义

dot :点乘,我们可以用他来判断两个向量的夹角
cross: 差乘,我们可以求得垂直于两个向量所组成平面的向量
length:向量的大小
normalize: 向量的归一话,得到长度等于1的向量

我省略了一些代码,为了能保持整洁同时也不让大家过于纠结c++。