write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie
OpenGL系列文章都已经写到3D部分了,但是感觉2D的例子还是少了点,特意弄个外篇,引入一些稍微复杂的例子,这些例子不适合作为概念介绍时写入此系列文章,只好独立成篇,自己完成一个就发布一个吧。
此次完成的是一个七巧板图形的绘制,绘制时参考了网上的七巧板程序的颜色( 此处 ),因为我仅仅是为了加深对OpenGL的学习,没有添加具体的真实游戏代码,仅仅只有OpenGL图形绘制的演示。
另外,因为作为学习OpenGL例子之用,自然要使用OpenGL来绘制啦,虽然这么简单的图形根本用不着OpenGL,别说我用大炮打苍蝇,用牛刀杀鸡-_-!因为是学习OpenGL嘛,所以在2D坐标系中还是使用了OpenGL的坐标系,并且,为了追求显示的完美(主要是旋转的),我选取了所有图形的重心作为每个图形的坐标系的原点,这样旋转时的中心就是每个图形的中心,这样比使用rgnbox获取边框然后取中心旋转效果要好,具体的旋转效果需要自己添加代码去尝试了,Rotate和RotateTo的实现已经有了。添加到绘制部分即可。
下面简单介绍一下所有源代码。
所有的七巧板图形全部继承自CGLShape,此类实现如下
// 此类实现移动,旋转,
class CGLShape
{
public :
CGLShape( void )
{
mfPosX = 0.0;
mfPosY = 0.0;
memset(mfvColor, 0, sizeof (mfvColor));
mfDegree = 0.0;
mfSize = 1.0;
}
virtual ~CGLShape( void ) { }
void SetSize(GLfloat afSize) { mfSize = afSize; }
void SetColor(GLfloat afRed, GLfloat afGreen, GLfloat afBlue, GLfloat afAlpha = 1.0)
{
mfvColor[0] = afRed;
mfvColor[1] = afGreen;
mfvColor[2] = afBlue;
mfvColor[3] = afAlpha;
}
// 相对偏移(外部坐标系长度)
void Move( int aiPosX, int aiPosY)
{
mfPosX += 2.0f * aiPosX/( float )WIDTH;
mfPosY += -(2.0f * aiPosY/( float )HEIGHT);
}
// 移动到的位置(Windows坐标系点)
void MoveTo( int aiPosX, int aiPosY)
{
mfPosX = (2.0f * aiPosX - ( float )WIDTH) / ( float )WIDTH;
mfPosY = -(2.0f * aiPosY - ( float )HEIGHT) / ( float )HEIGHT;
}
// 旋转,以逆时针为正方向
void Rotate(GLfloat aiDegree) { mfDegree += aiDegree; }
// 旋转到
void RotateTo(GLfloat aiDegree) { mfDegree = aiDegree; }
// OpenGL坐标系
void SetPos(GLfloat afPosX, GLfloat afPosY) { mfPosX = afPosX; mfPosY = afPosY; }
void GetPos(GLfloat& afPosX, GLfloat& afPosY) { afPosX = mfPosX; afPosY = mfPosY; }
void Draw()
{
glLoadIdentity();
glColor4fv(mfvColor);
glPushMatrix();
glTranslatef(mfPosX, mfPosY, 0.0);
glRotatef(mfDegree, 0.0, 0.0, 1.0);
DrawImp();
glPopMatrix();
}
protected :
// 重绘时需要调用的函数,由各子类实现
virtual void DrawImp() = 0;
// 位置信息,没有用POINT是为了将来方便移植
GLfloat mfPosX;
GLfloat mfPosY;
// 颜色
GLfloat mfvColor[4];
// 旋转度数
GLfloat mfDegree;
// 大小
GLfloat mfSize;
};
大小,颜色,旋转角度都以成员变量的方式保存在CGLShape中,并且在Draw函数中已经实际的完成了使用,具体的DrawImp交由子类实现,完成具体图形的绘制。(此处使用template模式)具体的各个实现按照七巧板的要求如下:
三角形:
class CGLTriangle : public CGLShape
{
public :
CGLTriangle() { }
virtual ~CGLTriangle() { }
// 重绘时需要调用的函数,由各子类实现
virtual void DrawImp()
{
static GLfloat fvTop[3] = { 0.0, (1.0/3.0), 0.0};
static GLfloat fvLeftBottom[3] = { -0.5, -(1.0/6.0), 0.0};
static GLfloat fvRightBottom[3] = { 0.5, -(1.0/6.0), 0.0};
GLfloat fvSizeTop[3] = {fvTop[0], fvTop[1] * mfSize, fvTop[2]};
GLfloat fvSizeLeftBottom[3] = {fvLeftBottom[0] * mfSize, fvLeftBottom[1] * mfSize, fvLeftBottom[2]};
GLfloat fvSizeRightBottom[3] = {fvRightBottom[0] * mfSize, fvRightBottom[1] * mfSize, fvRightBottom[2]};
glBegin(GL_TRIANGLES);
glVertex3fv(fvSizeTop);
glVertex3fv(fvSizeLeftBottom);
glVertex3fv(fvSizeRightBottom);
glEnd();
}
};
实际上DrawImp仅仅关心绘制一个三角形而已,这里因为其他图形只有一个,所以不关心大小,而三角形在七巧板中有多个,所以将大小的计算放在三角形中,独立计算大小,事实上更通用的办法是在CShape的Draw函数中用glScale实现。三角形的3个顶点的坐标相对来说还比较容易计算。
矩形:
class CGLRectangle: public CGLShape
{
public :
CGLRectangle() { }
virtual ~CGLRectangle() { }
// 重绘时需要调用的函数,由各子类实现
virtual void DrawImp()
{
static GLfloat fLength = sqrt(2.0)/4.0;
static GLfloat fLengthHalf = fLength/2.0;
static GLfloat fvLeftTop[3] = { -fLengthHalf, fLengthHalf, 0.0};
static GLfloat fvRightTop[3] = { fLengthHalf, fLengthHalf, 0.0};
static GLfloat fvLeftBottom[3] = { -fLengthHalf, -fLengthHalf, 0.0};
static GLfloat fvRightBottom[3] = { fLengthHalf, -fLengthHalf, 0.0};
// TODO:因为七角板此例中方形只有一个不需要考虑大小,暂时不计算大小
glColor4fv(mfvColor);
glPushMatrix();
glBegin(GL_QUADS);
glVertex3fv(fvLeftTop);
glVertex3fv(fvRightTop);
glVertex3fv(fvRightBottom);
glVertex3fv(fvLeftBottom);
glEnd();
glPopMatrix();
}
};
很简单,不多说了,DrawImp实现一个矩形的绘制而已。四个顶点的坐标时最容易计算的。
平行四边形:
class CGLParallelogram: public CGLShape
{
public :
CGLParallelogram() { }
virtual ~CGLParallelogram() { }
// 重绘时需要调用的函数,由各子类实现
virtual void DrawImp()
{
static GLfloat fRightBottomX = 0.375;
static GLfloat fHeight = 0.25;
static GLfloat fvLeftTop[3] = { -fRightBottomX, fHeight/2.0, 0.0};
static GLfloat fvRightTop[3] = { 0.5-fRightBottomX, fHeight/2, 0.0};
static GLfloat fvLeftBottom[3] = { fRightBottomX-0.5, -fHeight/2.0, 0.0};
static GLfloat fvRightBottom[3] = { fRightBottomX, -fHeight/2.0, 0.0};
// TODO:因为七角板此例中四边形只有一个不需要考虑大小,暂时不计算大小
glColor4fv(mfvColor);
glPushMatrix();
glRotatef(mfDegree, 0.0, 0.0, 1.0);
glBegin(GL_QUADS);
glVertex3fv(fvLeftTop);
glVertex3fv(fvRightTop);
glVertex3fv(fvRightBottom);
glVertex3fv(fvLeftBottom);
glEnd();
glPopMatrix();
}
};
绘制是同样简单,但是在以平行四边形的重心为原点的坐标系中,计算平时四边形的4个顶点的坐标需要一定的计算量,上述那几个数值可不是随便掰出来的啊。
将上述图形组合在一起
CGLTriangle gTriBTop; // 上方的大三角形
CGLTriangle gTriBRight; // 右边的大三角形
CGLTriangle gTriSLeft; // 左上方的小上角形
CGLTriangle gTriSMid; // 中间的小三角形
CGLTriangle gTriMLeft; // 左下方的中三角形
CGLRectangle gRectangle; // 唯一的正方形
CGLParallelogram gParal; // 唯一的平行四边形
//OpenGL初始化开始
void SceneInit( int w, int h)
{
GLenum err = glewInit();
if (err != GLEW_OK)
{
MessageBox(NULL, _T( "Error" ), _T( "Glew init failed." ), MB_OK);
exit(-1);
}
gTriBTop.SetColor(1.0, 0.0, 0.0); // 上方的大三角形,红色,
gTriBTop.SetPos(0.0, 1.0/3.0);
gTriBTop.RotateTo(180.0);
gTriBRight.SetColor(0.0, 0.0, 1.0); // 右边的大三角形,蓝色
gTriBRight.SetPos(1.0/3.0, 0.0);
gTriBRight.RotateTo(90.0);
gTriSLeft.SetColor(1.0, 1.0, 0.0); // 左上方的小上角形,黄色
gTriSLeft.SetSize(0.5); // 小三角形只有大的一半大
gTriSLeft.SetPos(-(5.0/12.0), 0.25);
gTriSLeft.RotateTo(-90.0);
gRectangle.SetColor(1.0, 175.0/255.0, 175.0/255.0); // 唯一的正方形,不知道什么颜色
gRectangle.SetPos(-0.25, 0.0);
gRectangle.RotateTo(45.0);
gTriSMid.SetColor(0.0, 1.0, 0.0); // 中间的小三角形,绿色
gTriSMid.SetSize(0.5); // 小三角形只有大的一半大
gTriSMid.SetPos(0.0, -1.0/6.0);
gTriMLeft.SetColor(0.0, 1.0, 1.0); // 左下方的中三角形
gTriMLeft.SetSize( sqrt(2.0) / 2.0 ); // 以底边计算,2分之根号2倍
gTriMLeft.SetPos(-0.5 + 1.0/6.0, -0.5 + 1.0/6.0 );
gTriMLeft.RotateTo(135.0);
gParal.SetColor(1.0, 0.0, 1.0); // 唯一的平行四边形,紫色
gParal.SetPos(0.125, -0.375);
}
void ReShape( unsigned auWidth, unsigned auHeight)
{
glViewport(0, 0, auWidth, auHeight);
}
//这里进行所有的绘图工作
void SceneShow(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲区
gTriBTop.Draw();
gTriBRight.Draw();
gTriSLeft.Draw();
gRectangle.Draw();
gTriSMid.Draw();
gTriMLeft.Draw();
gParal.Draw();
glFlush();
}
上述代码中程序逻辑非常简单,无非就是设定好每个图形的大小,位置,旋转角度(三角形需要)等,每个图形的绘制都是以其重心为原点,所以计算的实际是七巧板每个图形的重心的位置,有一定的计算量,上述每个数值都是精确计算出来的,所以最后的图形才能完美,实际上也可以通过移动所有图形到一起,然后记录下每个值的方式来完成,但是此方式可能没有完整的数学描述精确,效果也就没有那么完美了。显示的截图如下:
最后演示一下每个图形的旋转效果:
只需要将代码改成如下形式即可:
//这里进行所有的绘图工作
void SceneShow(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲区
gTriBTop.Draw();
gTriBRight.Draw();
gTriSLeft.Draw();
gRectangle.Draw();
gTriSMid.Draw();
gTriMLeft.Draw();
gParal.Draw();
gTriBTop.Rotate(1.0);
gTriBRight.Rotate(1.0);
gTriSLeft.Rotate(1.0);
gRectangle.Rotate(1.0);
gTriSMid.Rotate(1.0);
gTriMLeft.Rotate(1.0);
gParal.Rotate(1.0);
glFlush();
}
见视频:
此文演示的完整代码在博客代码的JTTangram目录。有兴趣的可以将整个七巧板游戏完成然后再告诉我。最后,开始实际的完成稍微复杂点的图形的绘制时就会发现,就会发现技术的学习都是比较简单的,但是,实际的使用全靠数学撑着,此例算是比较简单了,但是平面几何,三角函数等东西不懂一点根本无法计算各个图形的位置-_-!数学还是得学好啊。。。。。。。。。。。
本OpenGL系列其他文章
1. 《 Win32 OpenGL 编程(1)Win32下的OpenGL编程必须步骤 》
2. 《 Win32 OpenGL编程(2) 寻找缺失的OpenGL函数 》
3. 《 Win32 OpenGL编程(3) 基本图元(点,直线,多边形)的绘制 》
4. 《 Win32 OpenGL编程(4) 2D图形基础(颜色及坐标体系进阶知识) 》
5. 《 Win32 OpenGL编程(5)顶点数组详细介绍 》
6.《 Win32 OpenGL编程(6) 踏入3D世界 》
完整源代码获取说明
由于篇幅限制,本文一般仅贴出代码的主要关心的部分,代码带工程(或者makefile)完整版(如果有的话)都能用Mercurial在Google Code中下载。文章以博文发表的日期分目录存放,请直接使用Mercurial克隆下库:
https://blog-sample-code.jtianling.googlecode.com/hg/
Mercurial使用方法见《 分布式的,新一代版本控制系统Mercurial的介绍及简要入门 》
要是仅仅想浏览全部代码也可以直接到google code上去看,在下面的地址:
http://code.google.com/p/jtianling/source/browse?repo=blog-sample-code
原创文章作者保留版权 转载请注明原作者 并给出链接
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie