建立 BVH:
思路:
类似二叉树,一个node有child0, child1, 那么就递归下去。
想象,有一个三角形列表(objs),三角形列表数量(num_objs),这个三角形列表的大包围盒(min, max),轴(axis)。
而且,每一个三角形自己有一个包围盒。
现在就是,把这个大包围盒划分为2个子包围盒,划分的规则就是,遍历每一个三角形,以一个轴为标准,例如x轴,比较三角形包围盒在大的包围盒中点的左边还是右边,确定好之后,就把在中点左边的所有三角形组成左子包围盒,在中点右边的所有三角形组成右子包围盒。
再把左子包围盒,右子包围盒分别执行刚才的操作,直到只有这个左子包围盒只剩下一个三角形为止。
// 当我们把一个指针做为参数传一个方法时,其实是把指针的复本传递给了方法,也可以说传递指针是指针的值传递。
// *p = **a, p = *a, p其实是一个地址,*p才是这个地址保存的的数据,那么改了,*a 改了,就是改了p,那么*p出来的东西也就不一样
void shape_build_bvh(shape_t **parent, shape_t *objs, int num_objs, vec3 mins, vec3 maxs, int axis)
{
int i, j;
float d;
shape_t tmp;
shape_t *p;
bvh_t *bvh;
vec3 diff;
vec3 bounds[2][2];
// 递归的终止条件
if (num_objs == 1)
{
(*parent) = objs;
return;
}
// 相当于(*parent) = new shape_t
(*parent) = p = malloc(sizeof(shape_t));
p->shader = NULL;
p->hitfunc = shape_hit_bvh;
p->shadefunc = shape_shade_bvh;
p->bboxfunc = NULL;
// new bvh_t
bvh = p->params.bvh = malloc(sizeof(bvh_t));
// 填充p->extremes,p->center
vec3_copy(mins, p->extremes[0]);
vec3_copy(maxs, p->extremes[1]);
vec3_sub(p->extremes[1], p->extremes[0], diff);
vec3_ma(p->extremes[0], 0.5f, diff, p->center);
// 左右子树的 min,max bbox
vec3_bounds_clear(bounds[0][0], bounds[0][1]);
vec3_bounds_clear(bounds[1][0], bounds[1][1]);
// partition the objects
d = p->center[axis];
// 这里的主要执行的就是,
// 1. 以axis轴为参考,把参数,min[axis],max[axis]的中点center[axis],当成了objs的分界线,
// 小于center[axis]就在左边,大于center[axis]就在右边
// 2. 确定左右两边的bbox的min,max
for (i=0,j=num_objs; i<j; i++)
{
if (objs[i].center[axis] > d)
{
// 在[i - num_objs]中,进行寻找一个合适的
for (j-=1; j>i; j--)
{
if (objs[j].center[axis] <= d)
{
// swap the two objects
memcpy(&tmp, &objs[i], sizeof(shape_t));
memcpy(&objs[i], &objs[j], sizeof(shape_t));
memcpy(&objs[j], &tmp, sizeof(shape_t));
vec3_bounds_expand(bounds[0][0], bounds[0][1], objs[i].extremes[0]);
vec3_bounds_expand(bounds[0][0], bounds[0][1], objs[i].extremes[1]);
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[j].extremes[0]);
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[j].extremes[1]);
break;
}
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[j].extremes[0]);
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[j].extremes[1]);
}
if (j == i)
{
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[i].extremes[0]);
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[i].extremes[1]);
break;
}
}
vec3_bounds_expand(bounds[0][0], bounds[0][1], objs[i].extremes[0]);
vec3_bounds_expand(bounds[0][0], bounds[0][1], objs[i].extremes[1]);
}
// no good partition - just arbitrarily split them
if ((i==0) || (i==num_objs))
{
i = num_objs / 2;
// recalculate the bounding boxes
vec3_bounds_clear(bounds[0][0], bounds[0][1]);
vec3_bounds_clear(bounds[1][0], bounds[1][1]);
for (j=0; j<i; j++)
{
vec3_bounds_expand(bounds[0][0], bounds[0][1], objs[j].extremes[0]);
vec3_bounds_expand(bounds[0][0], bounds[0][1], objs[j].extremes[1]);
}
for (j=i; j<num_objs; j++)
{
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[j].extremes[0]);
vec3_bounds_expand(bounds[1][0], bounds[1][1], objs[j].extremes[1]);
}
}
// recurse
shape_build_bvh(&bvh->children[0], objs, i, bounds[0][0], bounds[0][1], (axis+1)%3);
shape_build_bvh(&bvh->children[1], &objs[i], num_objs-i, bounds[1][0], bounds[1][1], (axis+1)%3);
}
遍历BHV:
每一个sample 会生成一条ray, 之后,就去检测是否碰到了一个三角形。
具体的思路就是:
类似二叉树,从root开始递归下去,递归每一个包围盒,直到判断ray是否与三角形相交了。
(其实具体的就是递归到每一个叶子节点,就判断在这个叶子节点中的三角形是否与ray相交,如果相交就表示碰撞成功)
bvh_root->hitfunc(bvh_root, &ray, t_samples[s], &hit)
hitfunc :
// 需要注意,在建立bvh的时候,中止条件就是,如果只有bbox只要一个shape的话,那么,shape的hitfunc就不会是shape_hit_bvh
// 而是,shape_hit_tri,
int shape_hit_bvh(shape_t *shape, ray_t *r, float time, hit_t *h)
{
int close;
int bh[2];
float th[2];
int bvhc[2];
hit_t hit2;
int bvhchild;
bvh_t *bvh;
bvh = shape->params.bvh;
bvhc[0] = (bvh->children[0]->hitfunc == shape_hit_bvh);
bvhc[1] = (bvh->children[1]->hitfunc == shape_hit_bvh);
// both children are bvh nodes
if (bvhc[0] && bvhc[1])
{
// 判断左右孩子的bbox是否碰撞到ray
bh[0] = shape_hit_bbox(bvh->children[0]->extremes, r, &th[0]);
bh[1] = shape_hit_bbox(bvh->children[1]->extremes, r, &th[1]);
// 左右孩子都碰撞到ray的情况
if (bh[0] && bh[1])
{
// first go into the closer one
// close : 1 表示的就是,ray首先碰到了右孩子bbox, 0 : ray首先碰到了左孩子bbox
close = (th[0] > th[1]);
// 递归 ray首先碰到的那个bbox
bh[close] = shape_hit_bvh(bvh->children[close], r, time, h);
// 碰到ray
if (bh[close])
{
// 如果ray 碰到孩子的t,要比碰到父亲的要大的话,就表示,之前的判断有问题了,
if (h->t > th[1-close])
{
// 进行另一个孩子的递归
bh[1-close] = shape_hit_bvh(bvh->children[1-close], r, time, &hit2);
// 如果另一个孩子的t都要比自己的父亲要大的话,就不递归下去了,直接返回计算的结构
if (bh[1-close] && (h->t > hit2.t))
memcpy(h, &hit2, sizeof(hit_t));
}
return 1;
}
else
{
// missed everything in the near bbox - return whatever the far hit is
return shape_hit_bvh(bvh->children[1-close], r, time, h);
}
}
// 只有左孩子bbox碰到ray,那么就是只递归左孩子bbox
else if (bh[0])
{
return shape_hit_bvh(bvh->children[0], r, time, h);
}
// 只有右孩子bbox碰到ray,那么就是只递归右孩子bbox
else if (bh[1])
{
return shape_hit_bvh(bvh->children[1], r, time, h);
}
// missed both boxes
return 0;
}
// one of the children is a bvh node, the other is a regular shape
else if (bvhc[0] ^ bvhc[1])
{
// bvhchild记录了哪一个才是bvh node
bvhchild = 1 - bvhc[0];
// see if we hit the bbox / shape
// 执行bvh node,和regular shape的hit func
bh[0] = shape_hit_bbox(bvh->children[bvhchild]->extremes, r, &th[0]);
bh[1] = bvh->children[1-bvhchild]->hitfunc(bvh->children[1-bvhchild], r, time, h);
// bvh node bbox 没有与ray进行碰撞,直接返回与regular shape的碰撞结果
if (!bh[0])
{
// didn't hit bbox - return the shape hit
return bh[1];
}
// bvh node bbox 与ray进行碰撞
else
{
// regular shape没有与ray发生碰撞,就直接递归这个bvh node
if (!bh[1])
{
return shape_hit_bvh(bvh->children[bvhchild], r, time, h);
}
// regular shape与ray也发生碰撞,但是bvh 更靠近ray的情况
else if (th[0] < h->t)
{
bh[0] = shape_hit_bvh(bvh->children[bvhchild], r, time, &hit2);
// 如果孩子bbox的比父亲bbox的更加靠近ray,那么就直接选孩子的
if (bh[0] && (hit2.t < h->t))
memcpy(h, &hit2, sizeof(hit_t));
}
return 1;
}
}
// both sides are regular shapes
else
{
bh[0] = bvh->children[0]->hitfunc(bvh->children[0], r, time, h);
bh[1] = bvh->children[1]->hitfunc(bvh->children[1], r, time, &hit2);
// 如果两个shape都碰撞的话,就看哪一个更接近ray
if (bh[0] && bh[1])
{
if (h->t > hit2.t)
memcpy(h, &hit2, sizeof(hit_t));
return 1;
}
// 只有右孩子碰到ray
else if (bh[1])
{
memcpy(h, &hit2, sizeof(hit_t));
return 1;
}
return bh[0];
}
return 0;
}
Bump Lit :
当遍历完BVH,发现了与一个三角形发生了碰撞,那么就可以进行光照,目前只考虑Bump Lit (凹凸)
// shape_hit_bbox : 判断ray 是否与 extremes[](bbox,min,max),发生碰撞
// 这里就是判断,ray是否与bvh_root下的shape发生了碰撞,如果是的话,才进行下面的操作
if (shape_hit_bbox(bvh_root->extremes, &ray, &dummy_t) &&
bvh_root->hitfunc(bvh_root, &ray, t_samples[s], &hit))
{
vec3_ma(ray.origin, hit.t, ray.dir, shader_params.ghitloc);
// 着色函数,
// shadefunc,如果是bvh node的话,就是shape_shade_bvh
// 如果是shape的话,就是shape_shade_tri,
// 大多数情况下,都是返回shape的shape_shade_tri
// shape_shade_tri 都是 为了填充shader_params,每一个sample的着色的参数
// 这里的shader_params.shader
// 就是 shaders[0].shader = shade_constant_dumb_lit
hit.obj->shadefunc(&hit, &ray, t_samples[s], &shader_params);
// 这里就是执行到shade_constant_dumb_lit中,着色
shader_params.shader->shader(&shader_params, &ray, t_samples[s], &shader_params.shader->params, color);
vec3_add(total, color, total);
}
// background
else
{
total[0] += 0.2f;
total[1] += 0.0f;
total[2] += 0.0f;
}
第一步,执行 hit.obj->shadefunc(&hit, &ray, t_samples[s], &shader_params);
这一步主要是跳到 shape_shade_tri 中,在这个函数中主要是填充shader_params_t ,shader_params_t 记录了每一个三角形的进行光照会涉及的信息,例如,法线,UV,
那么这个函数就负责计算shader_params_t 里面的属性。
void shape_shade_tri(hit_t *hit, ray_t *r, float time, shader_params_t *params)
{
float A, B, C, D, E, F, G, H, I, J, K, L,
EIHF, GFDI, DHEG, denom, beta, AKJB, JCAL, BLKC, gamma;
vec3 e1, e2;
tri_t *tri;
tri = hit->obj->params.tri;
vec3_sub(tri->verts[0]->v, tri->verts[1]->v, e1);
vec3_sub(tri->verts[0]->v, tri->verts[2]->v, e2);
A = e1[0];
B = e1[1];
C = e1[2];
D = e2[0];
E = e2[1];
F = e2[2];
G = r->dir[0];
H = r->dir[1];
I = r->dir[2];
J = tri->verts[0]->v[0] - r->origin[0];
K = tri->verts[0]->v[1] - r->origin[1];
L = tri->verts[0]->v[2] - r->origin[2];
EIHF = E*I-H*F;
GFDI = G*F-D*I;
DHEG = D*H-E*G;
denom = (A*EIHF + B*GFDI + C*DHEG);
beta = (J*EIHF + K*GFDI + L*DHEG) / denom;
AKJB = A*K - J*B;
JCAL = J*C - A*L;
BLKC = B*L - K*C;
gamma = (I*AKJB + H*JCAL + G*BLKC)/denom;
vec3_cross(e2, e1, params->normal);
vec3_normalize(params->normal);
params->texcoord[0] = 0;//w*tri->uv[0][0] + u*tri->uv[1][0] + v*tri->uv[2][0];
params->texcoord[1] = 0;//w*tri->uv[0][1] + u*tri->uv[1][1] + v*tri->uv[2][1];
params->shader = hit->obj->shader;
vec3_ma(params->ghitloc, 0.001f, params->normal, params->ghitloc);
}
这个函数主要是填充 shader_params_t
typedef struct shader_params_s
{
shader_t *shader;
vec3 ghitloc;
vec3 lhitloc;
vec2 texcoord;
vec3 normal;
} shader_params_t;
第二步,执行 shader_params.shader->shader(&shader_params, &ray, t_samples[s], &shader_params.shader->params, color);
这一步主要跳到了 shade_constant_dumb_lit中,shade_constant_dumb_lit 主要的就是变换三角形的法线,从[-1, 1] -> [0, 1],来实现凹凸的感觉。
shade_constant_dumb_lit :
void shade_constant_dumb_lit(shader_params_t *sparams, ray_t *ray, float time, void *vparam, vec3 color)
{
float d;
shader_constant_t *param = vparam;
// 就是将法线从[-1, 1] 变换到 [0, 1]
d = (sparams->normal[1] + 1.0f) * 0.5f;
//d = 1;
// 这里就是模拟 凹凸的感觉
color[0] = d*param->color[0];
color[1] = d*param->color[1];
color[2] = d*param->color[2];
}
效果图: