Games101笔记 【Blinn-Phong(布林冯)反射模型】的C语言实现(1)

最近学习了Games101,听到Blinn-Phong反射模型的时候就想着自己动手写一下,加深理解,于是,有了本文。

本文的代码避开OpenGL的门槛,直接使用C语言,最终展示结果的时候用了OpenCV,所以需要C的入门知识以及一个OpenCV的开发环境(没有也不影响代码的阅读和原理的理解)。

1、渲染场景如下,在坐标系的某个位置上有一个球体,如下图

2、实现过程

(1)首先,定义环境参数,包括球体的位置、大小、颜色,光照的位置、颜色等,当然还有Blinn-Phong模型中的几个参数:环境光系数、漫反射系数、镜面反射系数等


    // 首先定义场景中存在一个球体,其球心和半径如下
    double ballCenter[3] = { 250, 250, 500 };
    double ballRadius = 150;
    unsigned char ballColor[3] = { 255, 255, 0};
    // 显示背景颜色
    unsigned char bkColor[3] = { 0, 0, 0 };
    // 定义光线的各类参数
    double lightPos[3] = { 0, 0, 0}; // 光源位置,这里使用点光源(即锥形光),但是只要将光源距离拉的足够大,其实就是平行光源
    unsigned char lightColor[3] = { 255, 255, 255 };           // 光的颜色
    double La, Ka = 0.3;                // 环境光系数和强度
    double Ld, Kd = 0.5;                // 漫反射系数
    double Ls, Ks = 0.2;                // 镜面反射系数
    double LSum;
    double normalLightAndEye[3];

 (2)定义一张用于显示的OpenCV图片,


    // 显示平面在Z=0的XY平面上,使用平行投影
    _mainMatImg = cv::Mat(500, 500, CV_8UC3);

(3)接下来所做的工作就是将上一步的图片填完整即可,核心代码在两个for循环中执行

for (int y = 0; y < _mainMatImg.rows; ++y)
{
	for (int x = 0; x < _mainMatImg.cols; ++x)
	{
		// 先将其填成背景色
		_mainMatImg.data[_mainMatImg.step * y + x * _mainMatImg.channels() + 0] = bkColor[0];
		_mainMatImg.data[_mainMatImg.step * y + x * _mainMatImg.channels() + 1] = bkColor[1];
		_mainMatImg.data[_mainMatImg.step * y + x * _mainMatImg.channels() + 2] = bkColor[2];

		if ((x - ballCenter[0]) * (x - ballCenter[0])
			+ (y - ballCenter[1]) * (y - ballCenter[1]) > ballRadius * ballRadius)
		{
			continue;
		}

		LSum = 0;
		// 1、计算环境光的效果
		La = Ka;
		LSum += La;
		// 2、计算漫反射
		double z = ballRadius * ballRadius - (x - ballCenter[0]) * (x - ballCenter[0])
				   - (y - ballCenter[1]) * (y - ballCenter[1]);
		z = -sqrt(z) + ballCenter[2];    // 求得[x,y]坐标下球表面的z坐标
		// 计算点乘
		Ld = (x - ballCenter[0]) * (lightPos[0] - x) + (y - ballCenter[1]) * (lightPos[1] - y) + (z - ballCenter[2]) * (lightPos[2] - z);

		if (Ld < 0)
		{
			Ld = 0;
		}

		// 计算夹角
		Ld /= (ballRadius * sqrt(pow(x - lightPos[0], 2) + pow(y - lightPos[1], 2) + pow(z - lightPos[2], 2)));
		Ld *= Kd;
		LSum += Ld;
		// 3、计算镜面反射
		// 计算光线和观察点([0,x,y])的平分线向量
		normalLightAndEye[0] = (x - x) + (lightPos[0] - x);
		normalLightAndEye[1] = (y - y) + (lightPos[1] - y);
		normalLightAndEye[2] = (0 - z) + (lightPos[2] - z);
		Ls = normalLightAndEye[0] * (x - ballCenter[0]) + normalLightAndEye[1] * (y - ballCenter[1]) + normalLightAndEye[2] * (z - ballCenter[2]);
		Ls /= sqrt(pow(normalLightAndEye[0], 2) + pow(normalLightAndEye[1], 2) + pow(normalLightAndEye[2], 2));
		Ls /= sqrt(pow(x - ballCenter[0], 2) + pow(y - ballCenter[1], 2) + pow(z - ballCenter[2], 2));

		if (Ls < 0)
		{
			Ls = 0;
		}

		// 将Ls进行指数级乘,此处为128次方处理,缩小高光区域
		for (int i = 0; i < 7; ++i)
		{
			Ls *= Ls;
		}

		Ls *= Ks;
		LSum += Ls;
		//
		_mainMatImg.data[_mainMatImg.step * y + x * _mainMatImg.channels() + 0] = ballColor[0] * LSum * lightColor[0] / 255;
		_mainMatImg.data[_mainMatImg.step * y + x * _mainMatImg.channels() + 1] = ballColor[1] * LSum * lightColor[1] / 255;
		_mainMatImg.data[_mainMatImg.step * y + x * _mainMatImg.channels() + 2] = ballColor[2] * LSum * lightColor[2] / 255;
	}
}

(4)最后,将其展示出来即可

cv::imshow("Blinn-Phong Model", _mainMatImg);

3、显示结果如下

4、补充说明:

(1)实现的过程基于Games101中闫老师的介绍,想要看明白漫反射和镜面反射的计算,可能需要 一定的向量计算的知识,例如向量夹角的计算,已知入射光线和反射光线计算其夹角平分线的向量等。这部分知识可以从Games101中获取;

(2)在计算球体的Z坐标的时候,每一对(x,y)应该能求得符号相反的两个在z值,因为屏幕在Z=0位置上,球体在Z=500的位置上,所以只取其较小值,我们将在下一篇中使用ZBuffer解决这个问题

5、致谢

感谢闫令琪老师和Games101!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值