一、预备知识
- 图形学变换——平移、旋转和缩放 https://blog.csdn.net/zhanxi1992/article/details/106532991
- 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键退出
四、预览
欢迎关注个人公众号,实时推送最新博文!