从0开始的图形学大师
标签(空格分隔):图形学
peter Shirley
<计算机图形学>
第5章 线性代数
大学知识。
三维行列式为平行六面体体积,体积为零,共面,线性相关。
特征向量不相等为正交。对称矩阵特征值是实数。
第6章 矩阵变换
##6.1 基本二维变换
- 缩放
[ s x 0 0 s y ] \begin{bmatrix} s_x & 0\\ 0 & s_y \end{bmatrix} [sx00sy]
- 切变
[ 1 s 水 平 s 垂 直 1 ] \begin{bmatrix} 1&s_{水平}\\ s_{垂直}&1 \end{bmatrix} [1s垂直s水平1]
圆在这时候会变换成椭圆
对切变的另一种解释是,相对垂直轴(水平轴)做旋转。
[
1
t
a
n
ϕ
垂
直
轴
t
a
n
ϕ
水
平
轴
1
]
\begin{bmatrix} 1&tan\phi_{垂直轴}\\ tan\phi_{水平轴}&1 \end{bmatrix}
[1tanϕ水平轴tanϕ垂直轴1]
- 旋转(默认逆时针)
[ c o s ϕ − s i n ϕ s i n ϕ c o s ϕ ] \begin{bmatrix} cos\phi&-sin\phi\\ sin\phi&cos\phi \end{bmatrix} [cosϕsinϕ−sinϕcosϕ]
旋转矩阵是正交矩阵
- 反射
Y 轴 反 射 [ − 1 0 0 1 ] X 轴 反 射 [ 1 0 0 − 1 ] Y轴反射 \begin{bmatrix} -1&0\\ 0&1 \end{bmatrix} X轴反射 \begin{bmatrix} 1&0\\ 0&-1 \end{bmatrix} Y轴反射[−1001]X轴反射[100−1]
- 二维变换组合与分解
组合没啥好说的,就是矩阵相乘。
分解(旋转-缩放-旋转)就是奇异值分解
分解(多个切变)没说,在光栅旋转时很有用。
6.2 基本三维变换
###6.2.1 任意三维旋转
如果想绕任意向量a进行旋转,可以先构造一个正交基w=a,把这个正交基旋转到标准基,然后绕z轴进行旋转,然后再把标准基旋转回uvw基。
###6.2.2 法向量变换
法向量经过与曲面上的点相同的M变换后,可能不再与变换后的表面垂直。
因此法向量的变换不同于平面的变换。
经过推导p103,我们知道法向量的变换 N = ( m − 1 ) T N = (m^{-1})^T N=(m−1)T
其实就是矩阵余子式不转置。(经常不转置的我-,-)
##6.3 平移(窗口变换)
我们可以用高维的矩阵实现低维矩阵相同的变换
窗口变换可以用三次矩阵相乘
6.4 变换矩阵的逆
介绍了几何方法求逆
6.5 坐标变换
我们可以把点的变换变为坐标系的变换
第7章 观察
7.1 绘制标准视体
绘制2d标准视体进行的变换(为了适应像素点而做的窗口变换),我们把像素扩展成为一个1x1x1的立方体。
$$
\begin{bmatrix}
1&0&\frac{n_x-1}{2}\
0&1&\frac{n_y-1}{2}\
0&0&1
\end{bmatrix}
\begin{bmatrix}
\frac{n_x}{2}&0&0\
0&\frac{n_y}{2}&0\
0&0&1
\end{bmatrix}
\begin{bmatrix}
\frac{n_x}{2}&0&\frac{n_x-1}{2}\
0&\frac{n_y}{2}&\frac{n_y-1}{2}\
0&0&1
\end{bmatrix}
\begin{bmatrix}
\frac{n_x}{2}&0&0&\frac{n_x-1}{2}\
0&\frac{n_y}{2}&0&\frac{n_y-1}{2}\
0&0&1&0\
0&0&0&1
\end{bmatrix}
$$
7.2 正射投影
绘制3d标准视体进行的变换(相当于三维窗口变换)
$$
\begin{bmatrix}
\frac{2}{r-l}&0&0&0\
0&\frac{2}{t-b}&0&0\
0&0&\frac{2}{n-f}&0\
0&0&0&1
\end{bmatrix}
\begin{bmatrix}
1&0&0&-\frac{l+r}{2}\
0&1&0&-\frac{b+t}{2}\
0&0&1&-\frac{n+f}{2}\
0&0&0&1
\end{bmatrix}
\begin{bmatrix}
\frac{2}{r-l}&0&0&-\frac{l+r}{2}\
0&\frac{2}{t-b}&0&-\frac{b+t}{2}\
0&0&\frac{2}{n-f}&-\frac{n+f}{2}\
0&0&0&1
\end{bmatrix}
为
了
在
正
视
投
影
(
即
x
y
平
面
)
中
画
投
影
,
我
们
把
7.1
和
7.2
得
到
的
矩
阵
合
并
。
为了在正视投影(即xy平面)中画投影,我们把7.1和7.2得到的矩阵合并。
为了在正视投影(即xy平面)中画投影,我们把7.1和7.2得到的矩阵合并。
M_0 =
\begin{bmatrix}
\frac{D-d}{A-a}&0&0&\frac{dA-Da}{A-a}\
0&\frac{E-e}{B-b}&0&\frac{eB-Eb}{B-b}\
0&0&\frac{F-f}{C-c}&\frac{fC-Fc}{C-c}\
0&0&0&1
\end{bmatrix}
$$
compute M0
for each line segment(ai,bi) do
p = M0ai
q = M0bi
drawline(xp,yp,xq,yq)
7.3 透视投影
这时我们要用到之前的第4维坐标,因为需要x,y中除z。实现的效果是把z=f上的点投影到与z=n一样大小的平面,而z=n上的点不变。
我们给出结论,变换矩阵为:
M
p
=
[
1
0
0
0
0
1
0
0
0
0
n
+
f
n
−
f
0
0
1
n
0
]
M_p = \begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&0&\frac{n+f}{n}&-f\\ 0&0&\frac{1}{n}&0 \end{bmatrix}
Mp=⎣⎢⎢⎡1000010000nn+fn100−f0⎦⎥⎥⎤
我们光看矩阵不知道有啥特点。我们试验一下效果。
$$
M_p
\begin{bmatrix}
x\y\z\1
\end{bmatrix}
\begin{bmatrix}
x\y\z\frac{n+f}{n}-f\\frac{z}{n}
\end{bmatrix}
\underrightarrow{齐次化}
\begin{bmatrix}
\frac{nx}{z}\ \frac{ny}{z}\n+f-\frac{fn}{z}\1
\end{bmatrix}
$$
我们把[x,y,z,1]换成[x,y,n,1],其次化结果还是[x,y,n,1],说明z=n上的点没有受到影响;我们把[x,y,z,1]换成[x,y,f,1],其次化结果是[nx/f,ny/f,f,1],说明z=f上的点被缩放了n/f倍;我们自然可以得出n<z<f上的点会缩放n/z倍;
由于齐次化可以消去任意常数,所以
M
p
M_p
Mp可以乘以n。
M
p
=
[
n
0
0
0
0
n
0
0
0
0
n
+
f
−
n
f
0
0
1
0
]
M_p = \begin{bmatrix} n&0&0&0\\ 0&n&0&0\\ 0&0&n+f&-nf\\ 0&0&1&0 \end{bmatrix}
Mp=⎣⎢⎢⎡n0000n0000n+f100−nf0⎦⎥⎥⎤
最终的
M
=
M
o
M
p
M
v
M = M_oM_pM_v
M=MoMpMv
compute M0
compute Mv
compute Mp
M=M0MpMv
for each line segment(ai,bi) do
p = Mai
q = Mbi
drawline(xp/hp,yp/hp,xq/hq,yq/hq)
我们把Mo与Mp相乘叫做投影矩阵,OpenGL的投影矩阵为:
M
O
p
e
n
G
L
=
[
2
∣
n
∣
r
−
l
0
r
+
l
r
−
l
0
0
2
∣
n
∣
t
−
b
t
+
b
t
−
b
0
0
0
∣
n
∣
+
∣
f
∣
∣
n
∣
−
∣
f
∣
2
∣
f
∣
∣
n
∣
∣
n
∣
−
∣
f
∣
0
0
−
1
0
]
M_{OpenGL} = \begin{bmatrix} \frac{2|n|}{r-l}&0&\frac{r+l}{r-l}&0\\ 0&\frac{2|n|}{t-b}&\frac{t+b}{t-b}&0\\ 0&0&\frac{|n|+|f|}{|n|-|f|}&\frac{2|f||n|}{|n|-|f|}\\ 0&0&-1&0 \end{bmatrix}
MOpenGL=⎣⎢⎢⎢⎡r−l2∣n∣0000t−b2∣n∣00r−lr+lt−bt+b∣n∣−∣f∣∣n∣+∣f∣−100∣n∣−∣f∣2∣f∣∣n∣0⎦⎥⎥⎥⎤
7.4 透视变换的性质
一些证明,有些没看懂。不过不影响
7.5 视域
视域不等于窗口
第8章 隐藏面消除
先看第九章-。-
8.1 BSP树
第9章 表面明暗处理
漫反射
不光滑的物体,被认为具有朗伯物体特性。
9.1.1 朗伯明暗处理模型
朗博余弦定理(1971):物品表面颜色c与表面法线和光线入射方向夹角的余弦成正比。
c ∝ cos θ c \propto \cos \theta c∝cosθ
或其单位向量表示
c ∝ n ∗ l c \propto n*l c∝n∗l
我们加上RGB的颜色和强度。
c
=
c
r
c
l
m
a
x
(
0
,
n
∗
l
)
或
者
c
=
c
r
c
l
∣
n
∗
l
∣
c = c_rc_lmax(0,n*l)或者c = c_rc_l |n*l|
c=crclmax(0,n∗l)或者c=crcl∣n∗l∣
9.1.2 环境明暗处理
这有个问题,由于我们法向量有两个方向,所有法向量被向光源的点将会变黑。于是我们添加环境项。
c
=
c
r
(
c
a
+
c
l
m
a
x
(
0
,
n
∗
l
)
)
c= c_r(c_a+c_lmax(0,n*l))
c=cr(ca+clmax(0,n∗l))
9.1.3 基于顶点的漫反射明暗处理
很多物体由三角形建模,我们把法向量放在三角形顶点上,避免出现小平面的情况。我们最简单就把一个顶点法线的平均值作为该顶点的法线值(最好处理完单位化一下)。
9.2 Phong 明暗处理
我们假设自然光的反射光为r,眼睛看的方向为e。当e=r时最亮,当e背离r程度越大越暗。
c
=
c
l
(
e
∗
r
)
c = c_l(e*r)
c=cl(e∗r)
点积为负可以添加一个if。这个公式产生的高光比现实生活看到的范围要大多了,我们提高幂次把它变窄一些。
c
=
c
l
max
(
0
,
e
∗
r
)
p
c = c_l\max(0,e*r)^p
c=clmax(0,e∗r)p
p被称为Phong指数
我们先要算r向量,原理图如下:
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \begin{aligned…
Phong的启发式模型我就不说了
。。。。。。
由于高光的光滑表面(是一个平面??)与相同几何形状的博朗表面相比颜色变化得更快,用法线向量进行明暗处理会产生伪像()。
我们不需要把三角形设得很小,我们用类似重心插值的方法进行插值,不必在每个三角形中插值。
n
=
α
n
0
+
β
c
1
+
γ
c
2
n = \alpha n_0 + \beta c_1 + \gamma c_2
n=αn0+βc1+γc2
9.3 艺术化明暗处理
本节介绍非真实感渲染
9.3.1 线图
- 共享一条边的两个三维三角形,如果一个朝向观察者一个背离观察者,那么公共边为轮廓线
- 这里提到隐藏面消除方法,我跳跳跳。。。直接看第10章。
#第10章 光线跟踪
这是一种生成真实感图像的方法。对于有大型物体的方法,它比较慢。
它计算阴影和反射比较简单。之后说光线跟踪适合“浏览”巨大的模型,和前面是否矛盾??
本章讨论分布式光线跟踪,它从图像每个像素发出多条随机光线,同时解决反走样、软阴影、模糊反射和景深问题。
10.1 基本光线跟踪算法
我们从眼睛发射一条光线到观察平面w = n(uvw系w相当于z)上,光线接触的第一个物体的像素就对于了观察平面上改点的像素。
10.2 计算观察光线
我们通过变换把[-0.5,nx-0.5]x[-0.5,ny-0.5]映射到[l,r]x[b,t]上
10.3 光线与物体相交
10.3.1 光线与球相交
球的方程:
(
x
−
x
c
)
2
+
(
y
−
y
c
)
2
+
(
z
−
z
c
)
2
−
R
2
=
0
(x-x_c)^2+(y-y_c)^2+(z-z_c)^2-R^2 = 0
(x−xc)2+(y−yc)2+(z−zc)2−R2=0
(1) ( p − c ) 2 ∗ ( p − c ) − R 2 = 0 (p-c)^2*(p-c)-R^2 = 0 \tag{1} (p−c)2∗(p−c)−R2=0(1)
直线方程:
(2)
p
=
e
+
t
d
p = e+td \tag{2}
p=e+td(2)
两式联立求解一元二次方程即可得到t。
10.3.2 光线与三角形相交
10.3.3 光线与多边形相交
10.4 光线追踪程序
不难理解
for each pixel do
compute viewing ray
if(ray hits an object with t>=0) then
Compute n
Evaluate lighting equation and set pixel to that color
else
set pixel color to background color
其中if(ray hits an object)用一个函数实现
hit = false
for each object o do
if(object is hit at ray parameter t and t0<=t<=t1) then
hit = true
hitobject = o
t1 = t
return hit
我们不用物体object这个术语表达广义物体基类,而用surface。
包围盒bounding-box是指把离散点集包围起来的几何体,可以分析碰撞。
另一个有用的类是材质。把物体和材质相关联的简单方法是在surface类中加一个指向材质的指针。(99行中是添加一个枚举)
纹理要不要作为材质的一部分11章会讲。
10.5 阴影
用到第九章知识
10.6 镜面反射
就用光照反射公式的变体。
/**
* @title 从0开始的图形学大师
* @author hezenggeng
* @code 镜面反射
*/
10.7 折射
折射定理(shell法则)
n
s
i
n
θ
=
n
i
s
i
n
ϕ
nsin\theta = n_isin\phi
nsinθ=nisinϕ
用
sin
2
θ
+
cos
2
θ
=
1
\sin^2\theta + \cos^2\theta = 1
sin2θ+cos2θ=1代入可求得
ϕ
\phi
ϕ
cos
2
ϕ
=
1
−
n
2
(
1
−
cos
2
θ
)
n
i
2
\cos^2\phi = 1-\frac{n^2(1-\cos^2\theta)}{n^2_i}
cos2ϕ=1−ni2n2(1−cos2θ)
n与
n
i
n_i
ni互换则
θ
\theta
θ与
ϕ
\phi
ϕ互换
如图,我们可以用正交基n,b描述t和d。
t
=
sin
ϕ
b
−
cos
ϕ
n
t = \sin\phi b- \cos \phi n
t=sinϕb−cosϕn
d = sin θ b − cos θ n d = \sin\theta b- \cos \theta n d=sinθb−cosθn
综合上述三个式子,我们得出t的公式,没有用到 θ 、 ϕ \theta、\phi θ、ϕ(见书上p146)
beer定理
光的强度在介质中会减小,从而改变玻璃颜色
I ( s ) = I ( 0 ) e − ln ( a ) s I(s) = I(0)e^{-\ln(a)s} I(s)=I(0)e−ln(a)s
/**
* @title 从0开始的图形学大师
* @author hezenggeng
* @code 折射
*/
#99行smallpt反向光线追踪讲解(C++)
参考链接
Milo的博客:[用JavaScript玩转计算机图形学(一)光线追踪入门][http://www.cnblogs.com/miloyip/archive/2010/03/29/1698953.html]
。。。
完整注释版c++
##(一)光线追踪
光线跟踪就是跟着光线画,画的光线越多,图像越细化。
光线追踪因为基于物理变换,它模拟光的传播,所以我们可以知道这个光线它反射的物体是谁,妙呀。
实际上叫反向光线追踪(backward raytracing),因为计算是从camera开始发射光线,而不是从光源发射光线。
一、光线跟踪的基本原理
光线跟踪(Ray-trace)是一种具有真实感的显示物体的方法,该方法由Appel在1968年提出。光线跟踪方法沿着到达视点的光线的相反方向跟踪,经过屏幕上每一象素,找出与视线所交的物体表面点 P0,并继续跟踪,找出影响P0点光强的所有的光源,从而算出P0点上精确的光照强度。
如上图所示,联结观察点和屏幕上的一个象素,即形成一根视线。因此,视线的数目等于象素的数目。对于每一根视线作如下处理:
计算视线V与各平面的交点。以距离最小的交点为可见交点P0。视线V在P0处产生反射和透射,所产生的反射线和透视线作为新的视线与各平面求交几时出新的交点P1、P2,并分别产生新的反射线和透视线,……。这样不断递归,直至所产生的视线射出场景。结果是得到视线跟踪轨迹上的一系列交点:P0、 P1、P2、…、Pn。这个过程可以表示为一棵光线跟踪树。
下图所示是一棵与上图对应的光线跟踪树。树的结点代表物体表面与跟踪线的交点。结点连线代表跟踪线。每个结点的左儿子代表反射产生的跟踪线(r),右儿子代表透射产生的跟踪线(T)。空箭头表示跟踪丝射出场景。P0处的光强是P0、P1、P2、P3点光强的合成。计算方法是以后序周游的算法遍历这颗光线跟踪树。在每一结点处,递归调用光照模型,算出跟踪射线方向的光强,并按两表面交点之间的距离进行衰减后,传递给父结点。如此上递,最后得出P0点处的光强,亦即得到屏幕象素处的亮度。
//获取射线ray的颜色,存入colour_out,最多跟踪traceNum次
void World::getColourForRay(const Ray& ray, Colour& colour_out,int traceNum)
{
if(traceNum != 0)
{
Object *obj;
Vec3 normal,hitpos,L,R,Ldir;
float dist,bf=0.5;//为了简化,这里将反射系数设为常数,其本来值与dist值有关
int i;
static int ligsize = m_lights.size();
obj = closestObject(ray,dist);//返回与光线ray相交的最近的物体,并将其距离存入dist中
if(obj)//检测光线是否与场景中的物体相交
{
Colour lightcolour,total1,total2,diffuse = Colour::black(),specular = Colour::black();
hitpos = ray.m_startPos;
hitpos.addMult(ray.m_unitDir,dist);
normal = obj->getGeometry().getNormalForPos(hitpos);
normal.normalise();
R = reflect(ray.m_unitDir,normal);//求出反射光线
for(i = 0;i < ligsize;i++)//求出每个光源对交点处光照的贡献
{
L = m_lights.at(i)->getPos() - hitpos;//求出阴影光线
L.normalise();
if(!closestObject(Ray(hitpos,L),dist))//检测阴影光线路径中是否存在遮挡物
{ //不存在遮挡物则累加上该光源对交点的散射和镜面光的贡献
lightcolour = m_lights.at(i)->getColour();
Vec3 H = (L - ray.m_unitDir)/2;
H.normalise();
diffuse += lightcolour * (L.dot(normal)>0?L.dot(normal):0);
specular += lightcolour * pow((H.dot(normal)>0?H.dot(normal):0),obj->getMaterial().ns);
}
}
total1 = ambient_lighting * obj->getMaterial().ka
+ diffuse * obj->getMaterial().kd + specular * obj->getMaterial().ks;
getColourForRay(Ray(hitpos,R),total2,traceNum-1);//递归计算下个交点的光照
colour_out = total1 + total2*bf;//累加
return;
}
}
colour_out = Colour::black();//其余情况返回黑色
}
##(二)
随机函数reand48
查找定义,在stdlib.h中
double erand48(unsigned short[3]);
网上说的
double erand48(unsigned short xsubi[3])
{
return (double)rand() / (double)RAND_MAX;
}
erand48()函数是linux下的随机函数,可以取[0, 1]的随机浮点数,在windows下必须自己写一个类似功能的函数。
inline double clamp(double x) { return x < 0 ? 0 : x > 1 ? 1 : x; }
inline int toInt(double x) { return int(pow(clamp(x), 1 / 2.2) * 255 + .5); } //四舍五入
这里的难点在于为什么要用pow(clamp(x), 1/ 2.2),查了资料才知道是gamma校正方法。
射线和圆/球求交
![Sphere Intersection](/Users/hezenggeng/Desktop/皂片/图形学/Sphere Intersection.png)
代码
// intersect求交点
// Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0
// returns distance, 0 if nohit
//求det时,因为我们求的b和原理图中表达式的b差了两倍,所以可以直接用
//最终的解有一个或两个(t=b-det或者t=b+det),选择t大于0(eps相当于0)并且两个中较小的t
double intersect(const Ray &r) const
{
Vec op = p-r.o; // 向量OP,表示原理图中的O-C
double t, eps=1e-4, b=op.dot(r.d);
double det=b*b-op.dot(op)+rad*rad;//表达式中根号里的 b^2-4ac
if (det < 0)
return 0;
else
det=sqrt(det);
return (t=b-det)>eps ? t : ((t=b+det)>eps ? t : 0);
}
绘制的图形(全是球!)
Sphere spheres[] = {
//Scene:radius,position,emission,color,material
Sphere(1e5, Vec( 1e5+1,40.8,81.6), Vec(),Vec(.75,.25,.25),DIFF),//Left
Sphere(1e5, Vec(-1e5+99,40.8,81.6),Vec(),Vec(.25,.25,.75),DIFF),//Rght
Sphere(1e5, Vec(50,40.8, 1e5), Vec(),Vec(.75,.75,.75),DIFF),//Back
Sphere(1e5, Vec(50,40.8,-1e5+170), Vec(),Vec(), DIFF),//Frnt
Sphere(1e5, Vec(50, 1e5, 81.6), Vec(),Vec(.75,.75,.75),DIFF),//Botm
Sphere(1e5, Vec(50,-1e5+81.6,81.6),Vec(),Vec(.75,.75,.75),DIFF),//Top
Sphere(16.5,Vec(27,16.5,47), Vec(),Vec(1,1,1)*.999, SPEC),//Mirr镜子
Sphere(16.5,Vec(73,16.5,78), Vec(),Vec(1,1,1)*.999, REFR),//Glas玻璃
Sphere(600, Vec(50,681.6-.27,81.6),Vec(12,12,12), Vec(), DIFF) //Lite光
};
1)用6个很大的球体当做平面(DIFF属性,只有漫反射),因为半径很大的话,你在近距离看起来,球面就很像一个平面。
作者这样做应该是为了避免去写平面求交,平面类等函数。 (为了99行的目标。。。)
2)用1个球表示光源,就是Lite,1个Mirr球(完全反射),1个Glass球(折射和反射都有)
球与光线交点
//为何用inline?待解决
inline bool intersect(const Ray &r, double &t, int &id)
{
double n = sizeof(spheres) / sizeof(Sphere), d, inf = t = 1e20;
for (int i = int(n); i--;)
if ((d = spheres[i].intersect(r)) && d < t)
{
t = d;
id = i;
}
return t < inf;
}
此光线射出去,在所有的球体中求交点。
求出距离camera最近的交点,这就是待会要绘制在屏幕上的主要的点。
camera
camera的位置是在(50, 52, 295.6), 往z轴的负方向看。
Ray cam(Vec(50, 52, 295.6), Vec(0, -0.042612, -1).norm());
for循环
遍历每个像素点,用随机采样的方式求得要射出的光线的方向d。
for (int y = 0; y < h; y++)//Loop cows
{
fprintf(stderr, "\rRendering (%d spp) %5.2f%%", samps * 4, 100. * y / (h - 1));
for (unsigned short x = 0; x < w; x++) // Loop cols
{
unsigned short Xi[3] = {0, 0, (unsigned short)(y * y * y)};
for (int sy = 0, i = (h - y - 1) * w + x; sy < 2; sy++) // 2x2 subpixel rows
{
for (int sx = 0; sx < 2; sx++, r = Vec())
{
// 2x2 subpixel cols
for (int s = 0; s < samps; s++)
{
double r1 = 2 * erand48(Xi);
double r2 = 2 * erand48(Xi);
double dx = r1 < 1 ? sqrt(r1) - 1 : 1 - sqrt(2 - r1);
double dy = r2 < 1 ? sqrt(r2) - 1 : 1 - sqrt(2 - r2);
Vec d = cx * (((sx + .5 + dx) / 2 + x) / w - .5) +
cy * (((sy + .5 + dy) / 2 + y) / h - .5) + cam.d;
r = r + radiance(Ray(cam.o + d * 140, d.norm()), 0, Xi) * (1. / samps);
}
c[i] = c[i] + Vec(clamp(r.x), clamp(r.y), clamp(r.z)) * .25;
}
}
}
}
光线追踪递归函数
- 判断是否相交,求交点x,求表面法向量n,nl
double t; // distance to intersection
int id = 0; // id of intersected object
if (!intersect(r, t, id))//是否相交
return Vec();
const Sphere &obj = spheres[id]; // the hit object
Vec x = r.o + r.d * t;//交点
Vec n = (x - obj.p).norm(), nl = n.dot(r.d) < 0 ? n : n * -1, f = obj.c;//法向量
double p = f.x > f.y && f.x > f.z ? f.x : f.y > f.z ? f.y : f.z; // max refl(为了99行)
if (++depth > 5)
if (erand48(Xi) < p)
f = f * (1 / p);
else
return obj.e; //R.R.
- 漫反射(DIFF)
//如果材质是漫反射,那么就随机生成一个方向进行漫反射。
if (obj.refl == DIFF)
{
double r1 = 2 * M_PI * erand48(Xi), r2 = erand48(Xi), r2s = sqrt(r2);
Vec w = nl, u = ((fabs(w.x) > .1 ? Vec(0, 1) : Vec(1)) % w).norm(), v = w % u;
Vec d = (u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1 - r2)).norm();
return obj.e + f.mult(radiance(Ray(x, d), depth, Xi));
}
M_PI 来自math.h
#define M_PI 3.14159265358979323846264338327950288 /*pi*/
以后会讲怎么写漫反射。。。
- 镜面反射(SPEC)
//计算镜面反射的方向,然后继续递归
else if (obj.refl == SPEC)
return obj.e + f.mult(radiance(Ray(x, r.d - n * 2 * n.dot(r.d)), depth, Xi));
- 反射和折射(REFR)
玻璃材质,有一部分光进行反射,有一部分光进行折射。
这里用到了轮盘赌方法。
Ray reflRay(x, r.d - n * 2 * n.dot(r.d));
bool into = n.dot(nl) > 0; // Ray from outside going in?
double nc = 1, nt = 1.5, nnt = into ? nc / nt : nt / nc, ddn = r.d.dot(nl), cos2t;
if ((cos2t = 1 - nnt * nnt * (1 - ddn * ddn)) < 0) // Total internal reflection
return obj.e + f.mult(radiance(reflRay, depth, Xi));
Vec tdir = (r.d * nnt - n * ((into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)))).norm();
double a = nt - nc, b = nt + nc, R0 = a * a / (b * b), c = 1 - (into ? -ddn : tdir.dot(n));
double Re = R0 + (1 - R0) * c * c * c * c * c, Tr = 1 - Re, P = .25 + .5 * Re, RP = Re / P, TP = Tr / (1 - P);
return obj.e + f.mult(depth > 2 ? (erand48(Xi) < P ? // Russian roulette
radiance(reflRay, depth, Xi) * RP : radiance(Ray(x, tdir), depth, Xi) * TP) :
radiance(reflRay, depth, Xi) * Re + radiance(Ray(x, tdir), depth, Xi) * Tr);
##(三)
透视投影的原理
如果是小孔成像的话,我们应该是投影在z=-d的平面上,但是这样做涉及到负值操作,还使图像反了。如果我们将投影平面移到z = d,这样做就可以避免负值问题。(但是有个问题就是,物体在摄像机和z=d平面之间的物体,电脑就无法绘制了)
cx与cy是什么?
Vec cx = Vec(w * .5135 / h), cy = (cx % cam.d).norm() * .5135;
及用到cx、cy来计算光线d的代码
Vec d = cx * (((sx + .5 + dx) / 2 + x) / w - .5) +
cy * (((sy + .5 + dy) / 2 + y) / h - .5) + cam.d;
其中sx,sy是[0,1],dx,dy的范围是[-1,1],dx,dy的分布函数
所以(sx + .5 + dx) / 2这个的值得范围是[-0.25, 0.75]。这个值主要是为了在随机采样时,对x进行偏移。我们如果把他忽略不计。
那么(((sx + .5 + dx) / 2 + x) / w - .5)的值其实是在[-0.5, 0.5]的。
当x = w-1, y = 0时,
(0.5cx)+(-0.5cy)+cam.d是为了计算最大角度的射出光线。
图例1, 如果我们的cx = (w / h)
图例2, 如果我们的cx = (w/h *0.5135),我们的最大角度肯定比图例1小。
验证结果
我的理解是:
0.5135这个参数就是为了设置视角大小的。
作者计算好了角度之后,才这样设置的。
例子1
double testVal = 1.0;
double cx = Vec(w*.testVal / h), cy = (cx.cross(cam.d)).norm()*.testVal;
例子2
double testVal = 0.5135;
double cx = Vec(w*.testVal / h), cy = (cx.cross(cam.d)).norm()*.testVal;
double testVal = 0.2;
double cx = Vec(w*.testVal / h), cy = (cx.cross(cam.d)).norm()*.testVal;
##(四)
###1. 光线跟踪详细解释
计算法向量,计算折射光线,反射光线等,可以查阅《3D数学基础,图形与游戏开发》
####1.1 计算部分
Vec radiance(const Ray &r, int depth, unsigned short *Xi) {
double t; // 与射线相交物体的距离
int id = 0; // 与射线相交物体的id
if (!intersect(r, t, id))
return Vec(); // 如果都没有相交,则返回emisson(0, 0, 0)
const Sphere &obj = spheres[id]; // the hit object
Vec x = r.o + r.d*t, n = (x - obj.position).norm(); // 计算n,球面法向量
Vec nl = n.dot(r.d) < 0 ? n : n*-1, f = obj.color;
double p = f.x>f.y && f.x>f.z ? f.x : f.y>f.z ? f.y : f.z; // 找出最大的值
if (++depth>5||!p)
if (erand48(Xi)<p)
f = f*(1 / p); //假设f为(0.5, 0.2, 0.2),那么作者假定这个表面的反射率为f*(1/0.5),也就是颜色值最大的反射率最高(1, 0.4, 0.4)
else
return obj.emission;
####1.2 漫反射
if (obj.refl == DIFF) { // Ideal DIFFUSE reflection
double r1 = 2 * M_PI*erand48(Xi), r2 = erand48(Xi), r2s = sqrt(r2); //取随机数
Vec w = nl, u = ((fabs(w.x)>.1 ? Vec(0, 1) : Vec(1)).cross(w)).norm(), v = w.cross(u); //w,v,u为正交基
Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1 - r2)).norm(); //求得一个随机的漫反射光线,继续迭代
return obj.emission + f.mult(radiance(Ray(x, d), depth, Xi));
}
####1.3 镜面反射光
else if (obj.refl == SPEC) // Ideal SPECULAR reflection
return obj.emission + f.mult(radiance(Ray(x, r.d - n * 2 * n.dot(r.d)), depth, Xi)); //这个比较简单,只要求得反射光的角度即可12
1.4 折射加反射
Ray reflRay(x, r.d - n * 2 * n.dot(r.d)); // 由平行四边形的方法求得反射光的direction
bool into = n.dot(nl)>0; // 判断光线是否是进入球体
double nc = 1, nt = 1.5, nnt = into ? nc / nt : nt / nc, ddn = r.d.dot(nl), cos2t;
if ((cos2t = 1 - nnt*nnt*(1 - ddn*ddn))<0) // 如果求得cos2t<0,就没必要进行折射了。
return obj.emission + f.mult(radiance(reflRay, depth, Xi));
Vec tdir = (r.d*nnt - n*((into ? 1 : -1)*(ddn*nnt + sqrt(cos2t)))).norm(); //折射角的角度
double a = nt - nc, b = nt + nc, R0 = a*a / (b*b), c = 1 - (into ? -ddn : tdir.dot(n));
double Re = R0 + (1 - R0)*c*c*c*c*c, Tr = 1 - Re, P = .25 + .5*Re, RP = Re / P, TP = Tr / (1 - P);
return obj.emission + f.mult(depth>2 ? (erand48(Xi)<P ? // 轮盘赌
radiance(reflRay, depth, Xi)*RP : radiance(Ray(x, tdir), depth, Xi)*TP) :
radiance(reflRay, depth, Xi)*Re + radiance(Ray(x, tdir), depth, Xi)*Tr);1234567891011
###2 加烟雾效果
学习完smallpt之后,看到图形学的书上提到了烟雾效果。
#define E_MATH 2.71828
Vector frog_color(0.7, 0.7, 0.7);
inline double f_atmo(double distance) {
return pow(E_MATH, -(0.01 * 0.01* distance * distance));
}
// 然后修改光线反射,折射中的代码
// 在这个实现中,我只在第一层的递归加了烟雾效果
// 修改漫反射的代码
if (depth <= 1)
{
double fTemp = f_atmo(distance);
return obj._emission + (radiance(Ray(hit_point, direct), depth)) * fTemp + frog_color*(1 - fTemp);
}
else
return obj._emission + f.mult(radiance(Ray(hit_point, direct), depth));
// 修改反射
if (depth <= 1)
{
double fTemp = f_atmo(distance);
return obj._emission + (radiance(Ray(hit_point, direct_refl), depth)) * fTemp + frog_color*(1 - fTemp);
}
else
return obj._emission + radiance(Ray(hit_point, direct_refl), depth);
// 修改折射
double fTemp = f_atmo(distance);
return ((radiance(ray_refl, depth) * Fe
+ radiance(ray_refr, depth) * Fr))*fTemp + frog_color*(1 - fTemp);
光线追踪入门
为何要映射到【-1,1】?
正负可以代表方向?