用C语言画颗心(二)——光照模型

在上一篇文章用C语言画颗心(一)——心形曲面中,我们已经能用字符拼出一个心形图案。这一次,我们通过模拟光照模型,让心形表现出立体感。

说到光照(辐射)模型,博主学过一点点,什么照度,亮度,立体角,反射率,概念挺多,我也记不全。知乎原贴用的兰伯特体(Lambertian)假设,那我也用用吧。

Lambertian表面最主要的性质为在各个方向上的辐射相等,如上图所示,\(L_1 = L_2 = L_3\);并且,Lambertian表面完全反射,这就使得物体表面的双向表面函数是一个常数,如下图所示

更重要的一个结论是,表面亮度与入射方向和物体表面法向量的夹角余弦成正比,即$$L(x,y,z) \propto \alpha = \cos(\vec{n}(x,y,z),\vec{s})$$在应用时,我们直接令\(L = \max(\alpha,0)\)。

如何将亮度映射到字符上呢,偷懒抄的知乎原贴,用”.:-=+#%@”用这些字符区分亮度等级,亮度从小到大。一切准备就绪,可以准备敲代码了。

一,算梯度。梯度的算法在上一篇中已经提到,这里回忆一下$$\nabla f(x,y,z) = \left[\frac{\partial f}{\partial x},\frac{\partial f}{\partial y},\frac{\partial f}{\partial z}\right]$$上一次我们只确定了\(x\)和\(z\)的取值范围,我们通过二分法去解\(y\):

double compute_y(double x, double z){
	double z_min = 0.0, z_max = 1.5;
	double eps = 1e-6, r = 1.0, y;
	while(r>eps){
		y = (z_min + z_max) / 2.0;
		if (f(x,y,z) == 0)
			return y;
		if (f(x,y,z) > 0)
			z_max = y;
		else
			z_min = y;
		r = z_max - z_min;
	}

	return 0.5*(z_max + z_min);
}

二,画图。需要注意的一点,由于图像坐标系和数学坐标系的\(z\)轴方向相反,需要按照递减的方式循环,

int main(int argc, char const *argv[])
{
	double y, nx, ny, nz, a;
	// 光源方向(入射光方向)
	double lx = 1, ly = 1, lz = -1;
	char *l = ".:-=+#%@";
	for (float z = 1.5; z > -1.5; z-=0.1)
	{
		for (float x = -1.5; x < 1.5; x+=0.05)
		{
			// 点在心形图内
			if (f(x,0,z) < 0)
			{
				// 计算y
				y = compute_y(x,z);
				// 计算梯度(法向量)
				nx = fx(x,y,z);
				ny = fy(x,y,z);
				nz = fz(x,y,z);
				// light source direction (1,1,-0.5)
				a = (lx*nx + ly*ny + lz*nz)/sqrtf(lx*lx+ly*ly+lz*lz)
					/sqrtf(nx*nx+ny*ny+nz*nz);
				// 显然,正面的夹角余弦为正值
				// 根据入射光与法线夹角余弦值计算光强
				putchar(l[(int) ((0.5 * a + 0.5)*6)]);
			}
			else
				putchar(' ');
		}
		putchar('\n');
	}
	return 0;
}

完整的程序戳这里。效果图如下

未经允许不得转载:Charlie小站 » 用C语言画颗心(二)——光照模型

赞 (0)
分享到:更多 ()