Example 6 : Bounding Volume Heirarchy And Bump Lit

建立 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];
}


效果图:







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值