Skip to main content

渲染基础 - Fundamental of Graphics

· 10 min read

代码实现过程记录

具体代码见https://github.com/CanoeByGuitar/chenhui-lib

光线和各种物体求交

image-20230311143332619

![image-20230311143310242](assets/image-20230311143310242.png

image-20230311143322657

image-20230311143345703

上面那个把法线从[-1,1]映射到[0,255,255]

带有Anti-aliasing的多物体求交

image-20230311143820082

image-20230311143840947

Lambertian Shading

// light
vec3 light_pos(0,4,-2);
float intensity = 1.f;
Light light(light_pos, intensity);
// World
Material *m = new Material(vec3(100, 100, 100));
SurfaceList world(nullptr);
world.add(std::make_shared<Circle>(Circle(m, vec3(0, 0, -1), 0.5)));
world.add(std::make_shared<Circle>(Circle(m, vec3(0, -100.5, -1), 100)));
vec3 ray_color(SurfaceList &world, Ray r, Light light){
Intersection inter1;
vec3 color(0, 0, 0);
if(world.getIntersect(r, 0.f, INF, inter1)){
auto p = inter1.point;
auto n = inter1.normal;
auto light_ray = Ray(p, light.pos - p);
auto l = light_ray.direction();
auto k_d = inter1.m->k_d;
Intersection inter2;
if(world.getIntersect(light_ray, 0.f, INF, inter2)){
// light cannot arrive at obj
}else{
auto I = light.intensity;
color += k_d * max(n * l, 0.f) * I;
}
}
return color;
}

image-20230311142508493

检查代码 材质里的k_d应该是[0,1]区间而不是[0,255]

Material *m = new Material(vec3(0.7f, 0.7f, 0.7f));

image-20230311143039992

Blinn-Phong Shading

给输出color加了一个clamp,超过1的输出1,否则ppm会被大于255的数解析成黑的

image-20230311145558608

下图的参数为:

// World
auto *m1 = new Material(vec3(1, 0, 0), vec3(1, 0, 0), vec3(1, 0, 0));
auto *m2 = new Material(vec3(0, 1, 0), vec3(0, 1, 0), vec3(0, 1, 0));
auto *m3 = new Material(vec3(0.25, 0.25, 0.25), vec3(0.25, 0.25, 0.25), vec3(0.25, 0.25, 0.25));
SurfaceList world(nullptr);
world.add(std::make_shared<Circle>(Circle(m1, vec3(0.42, -0.08, -1.5), 0.4)));
world.add(std::make_shared<Circle>(Circle(m2, vec3(-0.35, -0.1, -1.3), 0.35)));
world.add(std::make_shared<Circle>(Circle(m3, vec3(0, -100.5, -1), 100)));

// light
std::vector<Light> light_list;
light_list.emplace_back(vec3(-4, 4, -3), 0.2f);
light_list.emplace_back(vec3(4, 4, -3), 0.5f);
light_list.emplace_back(vec3(0, 6, 0), 0.3f);
vec3 ray_color(SurfaceList &world, Ray r, const std::vector<Light> &light_list) {
Intersection inter1;
vec3 color(0, 0, 0);
if (world.getIntersect(r, 0.f, INF, inter1)) {
auto p = inter1.point;
auto n = inter1.normal;
auto v = -r.direction();
auto k_d = inter1.m->k_d;
auto k_s = inter1.m->k_s;
auto k_a = inter1.m->k_a;
// ambient shading
float I_a = 0.2;
color += k_a * I_a;
for (auto light: light_list) {
auto light_ray = Ray(p, light.pos - p);
auto l = light_ray.direction();
auto h = unit_vec((v + l));
Intersection inter2;
if (world.getIntersect(light_ray, 0.f, INF, inter2)) {
// light cannot arrive at obj
} else {
auto I = light.intensity;
color += k_d * max(n * l, 0.f) * I + k_s * I * pow(max(0.f, n * h), 10);
}
}
}else{
// background color
return vec3(0.847, 0.914, 0.996);
}
return clamp(color, 0, 1);
}

调整下面的p得到不同的材质

L = k_d* I * max(0,n.dot(l))+ k_s* I * max(0,n.dot(h))^p // p是为了加速从高光到消失的变化效果的

image-20230311162947638

image-20230311164147390

Math

点到直线距离

image-20230222153001768

计算行列式

LU分解 L下三角 U上三角

变换矩阵

首先注意,正交矩阵包括旋转和镜面反射两种情况

A=ATA=A^{T}

det(A) == 1 ==> rotation

det(A) == -1 ==> reflection

任意一个对成矩阵A,都表示沿着某一个方向(x'oy')进行放缩,x'oy'由特征向量组成

==symmetric matrices can be decomposed via eigenvalue diagonalization into a rotation times a scale times the inverse-rotation==

image-20230225021423520

相似对角化$ A = R S R^{T} $

旋转 ==> 放缩 ==> 转回来

3D 旋转矩阵

image-20230225221351128

SVD

singular value decomposition

==every matrix can be decomposed via SVD into a rotation times a scale times another rotation==

image-20230225021509148

如何解决正交矩阵不一定是旋转矩阵的问题?(Dynamic Deformables P181)

手算SVD分解:

A=UBVT==>AAT=UBTBUTA = UBV^T ==> AA^T = UB^TBU^T

A=UBVT==>ATA=VBTBVTA = UBV^T ==> A^TA = VB^TBV^T

对称矩阵正交相似化,求特征值、特征向量, 特征值开根就是B,原则上统一取B为正的。

极分解

任意一个矩阵A可以分解为 A=RSA = R S

其中R是 正交矩阵, S是对称半正定矩阵

分解过程借助于SVD

A=UBVT=(UVT)(VBVT)A=UBV^T = (UV^T) (VBV^T)

R=UVTR = UV^T, S=VBVTS = VBV^T

因为U V为正交矩阵,所以UVTUV^T也是正交矩阵(乘自己转置为E)

B是对角阵,且正定,所以S对称正定。

实现(JacobiSVD)

https://zhuanlan.zhihu.com/p/459369233

Matrix Computation(2013) p447

  • 先算极分解, A=RSA = R S,得到对称矩阵S
  • 对S进行Jacobi(Givens)旋转对角化,去掉非主对角的非零元素

f(z)z=if(z)z\frac{\partial f(z)}{\partial \Im z}=i \frac{\partial f(z)}{\partial \Re z}

齐次坐标(homogeneous coordinates

点 ==> (x, y, 1)

方向 ==> (x, y, 0)

Ray tracing

for each pixel do
compute view ray
find first hit object and its norml n
set pixel color from hit point, light, normal n

Gen rays

image-20230220215106252

p(t)=e+(se)tp(t)=e+(s-e)t

image-20230220215229532

image-20230220215237979

相机的view port是w的反方向(Opengl里也是如此)。

image-20230220221002172

Orthographic view

// input [l,r,b,t, nx, ny, u, v, w]
// pixel (i,j) ==> position coordinates
px = l + (r - l) / nx * (i + 0.5)
py = b + (t - b) / ny * (j + 0.5)

ray.direction = -w
ray.origin = e + px * u + py * v

Perspective Views

px = l + (r - l) / nx * (i + 0.5)
py = b + (t - b) / ny * (j + 0.5)

ray.direction = -d * w + px * u + py * v;
ray.origin = e;

Ray-Object intersection

Ray-Sphere

image-20230221213907621

image-20230221213919969

Ray-Triangle

image-20230221215048656

image-20230221215210083

// ray-triangle intersection
compute t
if(t < t0 || t > t1) return false;
compute gamma;
if(gamma < 0 || gamma > 1) return false;
compute beta;
if(beta < 0 || beta > 1 - gamma) return false;
return true;
Ray-Polygon

image-20230221221501295

image-20230307233719862

2D判断p在多边形内/外

ray from p intersects with polygon boundary
count the hit number h
if(h is odd) return inside
else return outside

image-20230221221516596

A group of Objects
// input [t0, t1] ray
intersection{
bool hit = false
for(each object o: group){
if(o intersets ray in t && t >= t0 && t < t1){
hit = true;
hit_obj = o;
t1 = t;
}
}
return hit
}

Shading

important variables:

  • light direction l

  • view dirction v

  • image-20230222221603312

Lambertian Shading

落在surface上的光的量只和入射角度相关(view independent 不考虑人眼位置看到的光的不同)

  • 垂直的时候 光最多

  • 相切的时候 光为0

  • 两者之间时,和入射光与face normal的夹角theta成正比。

    L = k_d * I * max(0, n.dot(l)); 

    // k_d: diffuse coef / surface color 3 channels
    // I: light intensity 3 channels
    // n: unit face normal
    // l: unit light dir

Blinn-Phong Shading

在Lambertian的基础上额外考虑高光(Phong和Blinn两个人分别完成和改进)

image-20230223012824894

h = (v + l).norm()
// Labert diffusion + specular
L = k_d* I * max(0,n.dot(l))+ k_s* I * max(0,n.dot(h))^p // p是为了加速从高光到消失的变化效果的

Ambient Shading

然而在早期的blinn-phong模型下,没有被光源直接照射的地方就是纯黑的,因为blinn-phong不考虑间接光,所以一个讨巧的方法是,

给surface表面默认附上一个颜色,叫ambient shading

以上三块整体构成blinn-phong模型

L = diffusion + specular + ambient(环境光)
L = k_d* I * max(0,n.dot(l))+ k_s* I * max(0,n.dot(h))^p + k_a * I_a // 可以所有的surface k_a都相同,也可以不一样

image-20230311145636904

Multiple Point Lights

image-20230223013800906

疑问:II怎么设定, 不会产生过爆吗?

A ray tracing program

for each pixel{
compute viewing ray r
if(r hits a object with t in [0, +inf]){
compute normal n;
evaluating shading and set color to the pixel
}else{
set pixel to background color
}
}

OOP类设计

class surface: geometry{
virtual bool hit(ray r+td, real t0, real t1, hit_record rec);
virtual box bounding_box()

private material *mtl;
}

class sphere:surface{
box bounding_box(){
vec3 min = center - vec3(radius, radius, radius);
vec3 max = center + vec3(radius, radius, radius);
return box(min, max);
}
}

Shadows

image-20230223191948973

shadow ray(区分与view ray) 用来判断是否在阴影中(如果hit了object,则在阴影中)

p+t*l, t in [0,+inf)

// 实践中通常为
t in [kesi, +inf] // 防止数值误差导致intersect with p本身在的平面。

image-20230223205039759

至此,更新ray tracing中计算某个像素颜色的算法

raycolor(e+t*d, t0, t1){
if(hit(e + t*d, t0, t1, &rec)){
p = e + (hit_record).t * d;
color c = rec.k_a * I_a; // ambient light

// 判断能否被光源照到
if(not hit(p + s*l, kesi,inf, &second_record)){
// 没有阴影
vec3 h = (l.norm() + (-d).norm()).norm();
c = c + rec.k_d*I*max(0, n*l) + rec.k_s * I * (n * h)^(rec.p);
}else{
// 有阴影不做其他处理
}
}else{
// 没有看到物体
return background;
}
}

Viewing

Object Space ==> World Space ==> Camera Space ==> Canonical View Volume[1,1]2[-1,-1]^2 ==> Screen Space

M V P viewport transformation

一些别称:

Canonical View Volume: Clip Space, Normalized device coordinates(NDC

image-20230309004657706

Viewport Transformation

[1,1]2==>[0.5,nx0.5]×[0.5,ny0.5][-1,1]^2 ==> [-0.5, nx-0.5] \times [-0.5,ny-0.5]

image-20230309010736288

加上z轴,让z保持不变

image-20230309010805176

Projection transformation

Orthographic

[l,r]×[b,t]×[f,n]==>[1,1]3[l, r] × [b, t] × [f, n] ==> [-1,1]^3

前者取决于用户设定

image-20230309013611534

e.g:

n = -0.01

f = -150

image-20230309013717698

Projective

View(Camera) transformation

world space[x,y,z] ===> camera space[u, v, w]

[u, v, w] can be generated from camera info:

  • eye position: e
  • gaze direction: g
  • view-up vector: t 只是把观察者的头部分成左右两个部分

image-20230310003101303

image-20230310005027077

image-20230310005147083

可以理解成先移动-e到原点再旋转

M=MvpMorthMcamM = M_{vp}M_{orth}M_{cam}