OpenGL入门三——变换进阶

一、预备知识

  1. 图形学变换——平移、旋转和缩放 https://blog.csdn.net/zhanxi1992/article/details/106532991
  2. OpenGL入门二——变换 https://blog.csdn.net/zhanxi1992/article/details/106653096

二、实现

使用OpenGL变换模拟太阳系(Solar)。给地球 23.44度的倾斜,模拟地轴偏角,这种倾斜使地球产生了四季。注意地球的轨道不要倾斜,仅仅是地球、同步卫星这一系统的整体倾斜。因此该倾斜的方向总是相同的(例如朝向 x 轴正向),相对于太阳,倾斜的角度不是一成不变的。

1、模型构建

  • 构建一个球体
// 用于生成一个中心在原点的球的顶点坐标数据(南北极在z轴方向)
// 返回值为球的顶点数,参数为球的半径及经线和纬线数
GLsizei BuildSphere(GLfloat radius, GLsizei columns, GLsizei rows)
{
	int index = 0;	// 数组索引
	point3* vertices = new point3[(rows + 1) * (columns + 1)]; // 存放不同顶点的数组

	for (int r = 0; r <= rows; r++)
	{
		float v = (float)r / (float)rows;  // [0,1]
		float theta1 = v * (float)M_PI;	   // [0,PI]

		point3 temp(0, 0, 1);
		point3 n = temp;
		GLfloat cosTheta1 = cos(theta1);
		GLfloat sinTheta1 = sin(theta1);
		n.x = temp.x * cosTheta1 + temp.z * sinTheta1;
		n.z = -temp.x * sinTheta1 + temp.z * cosTheta1;

		for (int c = 0; c <= columns; c++)
		{
			float u = (float)c / (float)columns; // [0,1]
			float theta2 = u * (float)(M_PI * 2); // [0,2PI]
			point3 pos = n;
			temp = n;
			GLfloat cosTheta2 = cos(theta2);
			GLfloat sinTheta2 = sin(theta2);

			pos.x = temp.x * cosTheta2 - temp.y * sinTheta2;
			pos.y = temp.x * sinTheta2 + temp.y * cosTheta2;

			point3 posFull = pos;
			posFull *= radius;

			vertices[index++] = posFull;
		}
	}

	/*生成最终顶点数组数据*/
	if (sphere)
	{
		delete[] sphere;	// 如果sphere已经有数据,先回收
	}
	NumVertices = rows * columns * 6; // 顶点数
	sphere = new point3[NumVertices];

	int colLength = columns + 1;
	index = 0;
	for (int r = 0; r < rows; r++)
	{
		int offset = r * colLength;

		for (int c = 0; c < columns; c++)
		{
			int ul = offset + c;						// 左上
			int ur = offset + c + 1;					// 右上
			int br = offset + (c + 1 + colLength);	// 右下
			int bl = offset + (c + 0 + colLength);	// 左下

			// 由两条经线和纬线围成的矩形
			sphere[index++] = vertices[ul];
			sphere[index++] = vertices[bl];
			sphere[index++] = vertices[br];
			sphere[index++] = vertices[ul];
			sphere[index++] = vertices[br];
			sphere[index++] = vertices[ur];
		}
	}

	delete[] vertices;

	return NumVertices;
}
  • 构建一个轨道
void BuildRing(GLfloat radius, GLsizei num)
{
	int index = 0;	// 数组索引
	/*生成最终顶点数组数据*/
	if (ring)
	{
		delete[] ring;	// 如果 ring 已经有数据,先回收
	}
	ring = new point3[num]; // 存放不同顶点的数组
	NumRing = num;

	for (int i = 0; i < num; i++)
	{
		float v = (float)i / (float)num;  // [0,1] 当前第几段
		float theta1 = v * 2.0 * (float)M_PI;	   // [0,PI]

		point3 n(0, 0, 0);
		GLfloat cosTheta1 = cos(theta1);
		GLfloat sinTheta1 = sin(theta1);
		n.x = radius * cosTheta1;
		n.y = radius * sinTheta1;

		ring[i] = n;
	}

}

2、矩阵栈

OpenGL已经弃用的功能,这里使用 MatrixStack 要来保存变换矩阵,方便实现各种嵌套变换,如果地球公转影响地球卫星及月球的变换,此时可将地球公转的变换矩阵保存下来,直接用于卫星等的变换,可省去重复构建矩阵的代码。

class MatrixStack 
{
	int    _index;
	int    _size;
	mat4* _matrices;

public:
	MatrixStack(int numMatrices = 32) :_index(0), _size(numMatrices)
	{
		_matrices = new mat4[numMatrices];
	}

	~MatrixStack()
	{
		delete[]_matrices;
	}

	void push(const mat4& m) 
	{
		assert(_index + 1 < _size);
		_matrices[_index++] = m;
	}

	mat4& pop(void) 
	{
		assert(_index - 1 >= 0);
		_index--;
		return _matrices[_index];
	}
};

3、太阳系的绘制

  • 地球
    重点在于实现地球倾斜23.44度,并需要抵消公转对自身倾斜方向的影响,保证公转后,仍然向右倾斜。
/*对地球系统定位,绕太阳放置它*/
// 用DayOfYear来控制其绕太阳的旋转
mv *= Rotate(360.0 * DayOfYear / 365.0, 0.0, 1.0, 0.0);
mv *= Translate(4.0, 0.0, 0.0);

/*下面开始在地球系统的小世界坐标系下考虑问题*/
// 绘制地球,地球的自转不应该影响月球
mvStack.push(mv); // 保存矩阵状态

// 抵消公转对自身倾斜方向的影响,保证公转后 仍然向右倾斜
mv *= Rotate(-360.0 * DayOfYear / 365.0, 0.0, 1.0, 0.0);

// 地球向 右倾斜 23.44度
mv *= Rotate(ErothAxialAngle, 0.0, 0.0, 1.0);
// 地球自转,用HourOfDay进行控制
mv *= Rotate(360.0 * HourOfDay / 24.0, 0.0, 1.0, 0.0);
mv *= Rotate(90.0, 1.0, 0.0, 0.0);
// 最后,画一个蓝色的球来表示地球
glBindVertexArray(vaoSphere);
glUniformMatrix4fv(MVPMatrix, 1, GL_TRUE, proj * mv * Scale(0.4, 0.4, 0.4)); // 传模视投影矩阵
glUniform3f(uColor, 0.2, 0.2, 1.0);  // 蓝色
glDrawArrays(GL_TRIANGLES, 0, NumVertices);
mv = mvStack.pop(); // 恢复矩阵状态
  • 同步卫星
    位于赤道平面,且旋转速度与地球相同,这里同样需要抵消地球公转对轨道平面的影响。卫星轨道与这同理,这里不赘述。
// 绘制地球同步卫星轨道
mvStack.push(mv);
// 抵消地球公转对自身倾斜方向的影响,保证公转后 仍然向右倾斜
mv *= Rotate(-360.0 * DayOfYear / 365.0, 0.0, 1.0, 0.0);

mv *= Rotate(ErothAxialAngle, 0.0, 0.0, 1.0);
mv *= Rotate(90.0, 1.0, 0.0, 0.0);
glBindVertexArray(vaoRing);
glUniformMatrix4fv(MVPMatrix, 1, GL_TRUE, proj * mv * Scale(0.5, 0.5, 0.5));
glUniform3f(uColor, 0.5, 0.0, 0.5);  // 紫色
glDrawArrays(GL_LINE_LOOP, 0, NumRing);
mv = mvStack.pop();
  • 月球
/*画月球*/
// 用DayOfYear来控制其绕地球的旋转
mv *= Rotate(360.0 * 12.0 * DayOfYear / 365.0, 0.0, 1.0, 0.0);
mv *= Translate(0.7, 0.0, 0.0);
mv *= Scale(0.1, 0.1, 0.1);
mv *= Rotate(90.0, 1.0, 0.0, 0.0);
glBindVertexArray(vaoSphere);
glUniformMatrix4fv(MVPMatrix, 1, GL_TRUE, proj * mv); // 传模视投影矩阵
glUniform3f(uColor, 0.3, 0.7, 0.3);
glDrawArrays(GL_TRIANGLES, 0, NumVertices);

三、控制

  • 按 “r” 键来启动和停止动画
  • 按 “s” 键单步执行动画
  • 按 “t” 键切换透视和俯视图
  • 方向键 上下箭头 用于控制动画中每一帧的时间间隔,每次按键时间间隔乘2或除2
  • 按ESC键退出

四、预览

欢迎关注个人公众号,实时推送最新博文!
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值