作为一个围棋爱好者兼程序员,多年以来开发过很多与围棋有关的软件,诸如围棋打谱软件、棋谱管理软件、围棋棋谱下载软件、围棋网站下载软件……而其中,围棋打谱软件开发的次数最多,读书的时候就编写过一个简易的围棋打谱软件作为编程的作业。编程水平逐渐提高之后,又开发过新的围棋打谱软件。曾经买过一个Windows Mobile的手机,到处寻觅棋谱阅读和直播软件而不得,也曾经开发过Window Mobile上的围棋打谱软件。最近突然想学习3D开发(基于种种原因,我选择的是OpenGL而不是D3D),我想以后免不了我又会开发一个基于OpenGL的围棋打谱软件,因此一个3D围棋子的绘制是大概也是免不了的。

绘制3D围棋子的过程,我觉得是很有意思的,也可以学到很多的东西。

以下分享一下我绘制棋子的过程。

在我以前使用GDI或者GDI+进行围棋子绘制的时候,如果使用纯粹的点、线、形状的绘制,绘制出的棋子顶多就是一个黑色或者白色的圆圈,大不了加上消锯齿,圆圈更加平滑。要达到更漂亮的棋子绘制,只能采用棋子图片辅助绘制。在进行3D棋子绘制的时候,我脑海中有过使用棋子的3D模型的念头,但瞬间被自己否决了——依赖3D模型也太没成就感了。

由于才开始学习OpenGL不久,而以前绘制过的最复杂的3D图形也就是一个立方体,因此心中还是有些打鼓,不知道自己的数学水平能否胜任这个绘制工作。不知为何,第一感就是觉得这个工作可能需要立体几何基础等数学基础,而已经快十年没碰过数学了,几乎所有的东西都还给老师了。

由于心中打鼓,寻觅曾经的立体几何的相关书籍无果,结果找到一本电子书——《3D数学基础图形与游戏开发》。决心在正式绘制之前进行刻苦攻读一番,读了几个小时,读到"四元数"、"欧拉角"的时候,感觉自己彻底晕菜了。于是快速浏览了剩下的内容……的目录,然后开始实战演习。这本书,阅读之后的唯一收获,就是让自己摒弃了对于绘制3D棋子的畏惧感。至少我相信,绘制一个3D的棋子,不可能比理解四元数和欧拉角更复杂。

中国围棋子中最有代表性的是云子,云子分"单面凸"和"双面凸"两种,我比较喜欢前者,它只有一面凸起,另一面是平的。因此我下面的绘制,将绘制的是"单面凸"的围棋子。下面这个图,是几颗实际的云子放在棋盘上的照片。

接下来对云子结构进行一下剖析:云子的实际形状,应该是顶部一个弧顶,和一个平底,平滑衔接到一起。围棋子是一个中心对称的几何体,因此一个棋子的切面,是可以完全体现这个棋子形状的。

下面的图,是一个围棋子的从侧面观察的图。本想用实际照片,奈何总是难以拍摄出理想的效果,因此下面使用的,是我绘制的一个3D棋子的侧面观察图。

进一步分析,这个侧面图,可以用4个线条组合而成:顶部一个大圆弧,左右各一个与之相切的小圆弧,底部一条与小圆弧相切的直线。

下面这个图应该很能说明问题(注意实线部分就是一个围棋的轮廓)。

对于上面这个原理图,其中实线的部分是棋子轮廓,虚线部分都是辅助理解的线条。

说起上面这个图,我实在忍不住有几句想说。由于本人的PS水平属于非常初级的水准,为完成上面这个图形,可费了不少功夫。画笔和Photo shop轮番上阵,但还是没能绘制出我在白纸上绘制的一个草图(Look,我的草图就是上面那个图)。最后被逼无奈,我写了一段程序,用GDI+绘制了3个圆圈和5条线段,然后用PS的画笔鼓捣了几下以示虚线(请原谅我拙劣的PS水平吧……)。

再认真看一下上面这个图,顶部是一个大圆的圆弧,左右是两个小圆的圆弧,下面是一条相切的直线。就这么简单,一个棋子的轮廓就出来,而绕着中轴线转一圈,就是一个3D的围棋子了。

图中有a、b、c三个参数,其中a是底面的半径,b是底面和大圆弧圆心之间的距离,c是侧面小圆弧的半径。通过这3个参数,可以唯一确定一个围棋子的形状和大小。其中a、b、c的比例将决定这个围棋子的形状,比如决定这个棋子是比较凸还是比较扁平。

绘制的原理就是这么简单。

下面研究一下实际的绘制过程。

通过对OpenGL辅助库中的各种形状绘制的学习和研究(其实也就是把那些几何体以线条形式展现,然后放大无数倍之后仔细观察),初步有了设计方案。

我的想法是:沿着Y轴旋转一圈,将棋子切成M片,然后再横向切上N刀,然后就可以将围棋子的整个表面,分割成M*N个矩形。然后把这些矩形逐一绘制出来,那么一个棋子就绘制出来了。

按照这个思路,我绘制了我的第一个棋子,源码以及效果可以参见鄙人拙作"使用OpenGL绘制一颗围棋子"。当然,以我现在的目光看来,当时的代码,无论是绘制效率、代码可读性、绘制效果还是功能方面,都是有所欠缺的。但当时从无到有的突破,已经让我比较开心了。

后来,我在阅读Richard Wright的gltDrawSphere函数时,看到里面为绘制的球设置了纹理,因此我也想为自己的棋子绘制加入纹理映射。在思考纹理映射的过程中,感觉到上面的绘制方案有所不妥:当棋子被纵向切片之后,那一片的棋子,是可以用一系列的三角形带组成,没必要用一个个独立的矩形来表示。

顺便说一下,我之所以想重构棋子绘制算法,除了上面这个启发之外,还有一个原因,是因为我绘制的棋子,在顶部和侧面融合的时候,在某些角度观察时,我看见有一条光影(如下图)。而按照我的理解,如果两个面是相切的,应该是没有这条光影线的。我一直怀疑是代码有细微瑕疵造成的,而我对代码仔细的审查,却发现不了任何问题。我甚至把棋子的所有法线画出来,观察是否是法线方向有疏忽,也没能发现问题。期间有一个同事教了我一招判别法线方向是否平滑过渡的方法,我觉得非常有用也很有创意:将法线分量作为顶点的颜色分量,然后观察颜色是否平滑过渡

当然,最终我把法线当作颜色信息输出后,发现赫然也有一条光影,但我将这个光影放大无数倍,研究到底误差在什么地方的时候,却发现法线是平滑过渡的,但缩小为原始尺寸看,却又分明有一条光影,难道这是我眼睛的一种错觉?

关于这个光影线,我最终也没明白原因。我尝试过提高切片的密度,也尝试过让切片在边长上均匀,也尝试让切片在角度上均匀,似乎均没有改善。难道绝对的平滑过渡,必须曲率也不发生变化吗?

题外话到此为止,毕竟这个不影响主题,还是回归正途。下图是我新的绘制方案的基本原理图。首先进行纵向切片,然后用三角带完成每个切片的绘制。看着下面这个图,结合后面的代码,很容易想象实际是怎么完成的。(为了突出正反面,我加入了一点光照。)

整个棋子的绘制,源码如下(其中的参数a、b、c的几何意义,在上面的原理图中也有体现)。源码中我加入了非常详尽的注释,应该很容易理解。

#include <math.h>
#include <stdlib.h>
#include <GL/glut.h>
#pragma comment(lib,"glut32.lib")/***************************************************************************//**
* 函数名称:    DrawChess
* 功能描述:    绘制一个围棋子。
* 参 数:    a    >> 底部半径;
* 参 数:    b    >> 底部距离圆心距离;
* 参 数:    c    >> 侧面半径;
* 参 数:    n    >> 分割粒度;
* 返回值:
* 其它说明:
* 修改日期        修改人            修改内容
* ------------------------------------------------------------------------------
* 2011-08-28    Cloud         创建
*******************************************************************************/
// void DrawChess(double a, double b, double c, int n)
// {
//  const double PI = 3.14159265358979323846;
//  double fRange1 = PI - atan(a / (b + c));            //侧面弧度区间
//  double R = sqrt(a * a + (b + c) * (b + c)) + c;        //大圆顶半径
//  double fRange2 = atan(a / (b + c));                    //顶部弧度区间
//  double vPos[3], vNormal[3];                            //顶点位置和法线方向
//
//  for (int i=0; i<n; i++)
//  {
//      for (int j=0; j<n; j++)
//      {
//          ;//底面
// #define FILL1(n1, n2) \
//          {\
//              vPos[0] = (-a * (n1) / n) * cos((n2) * (2.0 * PI) / n);\
//              vPos[1] = b;\
//              vPos[2] = (-a * (n1) / n) * sin((n2) * (2.0 * PI) / n);\
//              vNormal[0] = 0;\
//              vNormal[1] = -1.0;\
//              vNormal[2] = 0;    \
//          }\
//
// #define DRAW_TRIANGLE(x) \
//          {\
//          glBegin(GL_TRIANGLE_STRIP);\
//          FILL##x(i, j);            glNormal3dv(vNormal); glVertex3dv(vPos);\
//          FILL##x(i + 1, j);        glNormal3dv(vNormal); glVertex3dv(vPos);\
//          FILL##x(i, j + 1);        glNormal3dv(vNormal); glVertex3dv(vPos);\
//          FILL##x(i + 1, j + 1);    glNormal3dv(vNormal); glVertex3dv(vPos);\
//          glEnd();\
//          }\
//
//          DRAW_TRIANGLE(1);
//
//          //侧面
// #define FILL2(n1, n2) \
//          {\
//              vPos[0] = (-a - c * sin((n1) * fRange1 / n)) * cos((n2) * (2.0 * PI) / n);\
//              vPos[1] = b + c - c * cos((n1) * fRange1 / n);\
//              vPos[2] = (-a - c * sin((n1) * fRange1 / n)) * sin((n2) * (2.0 * PI) / n);\
//              vNormal[0] = -sin((n1) * fRange1 / n) * cos((n2) * (2.0 * PI) / n);\
//              vNormal[1] = -cos((n1) * fRange1 / n);\
//              vNormal[2] = -sin((n1) * fRange1 / n) * sin((n2) * (2.0 * PI) / n);\
//          }\
//
//          DRAW_TRIANGLE(2);
//
//          //顶部
// #define FILL3(n1, n2) \
//          {\
//              vPos[0] = (-R * sin(fRange2 - fRange2 * (n1) / n)) * cos((n2) * (2.0 * PI) / n);\
//              vPos[1] = R * cos(fRange2 - fRange2 * (n1) / n);\
//              vPos[2] = (-R * sin(fRange2 - fRange2 * (n1) / n)) * sin((n2) * (2.0 * PI) / n);\
//              vNormal[0] = -sin(fRange2 - fRange2 * (n1) / n) * cos((n2) * (2.0 * PI) / n);\
//              vNormal[1] = cos(fRange2 - fRange2 * (n1) / n);\
//              vNormal[2] = -sin(fRange2 - fRange2 * (n1) / n) * sin((n2) * (2.0 * PI) / n);\
//          }\
//
//          DRAW_TRIANGLE(3);
//      }
//  }
// }
/***************************************************************************//**
* 函数名称:    DrawChess
* 功能描述:    绘制一颗围棋子。
* 参    数:    a        >> 底部半径;
* 参    数:    b        >> 底部距离圆心距离;
* 参    数:    c        >> 侧面半径;
* 参    数:    nSlice    >> 纵向分割的粒度;
* 参    数:    nStack    >> 环形分割的粒度;
* 返 回 值:
* 其它说明:
* 修改日期        修改人            修改内容
* ------------------------------------------------------------------------------
* 2011-12-05    一片云雾              创建
*******************************************************************************/
void DrawChess(GLfloat a, GLfloat b, GLfloat c, GLint nSlice, GLint nStack)
{const GLfloat PI = (GLfloat)(3.141592653589);                //圆周率PIGLfloat fYRotStep = 2.0f * PI / nStack;                        //沿着Y轴旋转的步长GLfloat fRange = atan(a / (b + c));                            //顶部圆弧的角度(单位为弧度)GLfloat R = sqrt(a * a + (b + c) * (b + c)) + c;            //大圆顶半径GLint nSlice1 = nSlice;                                        //底部纵向分片数量GLint nSlice2 = (GLint)(nSlice * (PI - fRange) * c / a);    //侧面纵向分片数量GLint nSlice3 = (GLint)(nSlice * R * fRange / a);            //顶部纵向分片数量GLfloat fStep1 = a / nSlice1;                                //顶部步长GLfloat fStep2 = (PI - fRange) / nSlice2;                    //侧面步长(弧度)GLfloat fStep3 = fRange / nSlice3;                            //顶部步长(弧度)GLfloat dr = -0.5f / (nSlice1 + nSlice2 + nSlice3);            //纹理半径增加的步长GLint i = 0, j = 0;for (i=0; i<nStack; i++){GLfloat fYR = i * fYRotStep;                            //当前沿着Y轴旋转的弧度GLfloat fZ = -sin(fYR);                                    //Z分量比率GLfloat fX = cos(fYR);                                    //X分量比率GLfloat fZ1 = -sin(fYR + fYRotStep);                    //下一列的Z分量比率GLfloat fX1 = cos(fYR + fYRotStep);                        //下一列的X分量比率GLfloat rs = 0.5f;                                        //纹理半径的起点glBegin(GL_TRIANGLE_STRIP);//底部for (j=0; j<nSlice1; j++){GLfloat r = fStep1 * j;glTexCoord2f(0.5f + rs * fX, 0.5f + rs * fZ);glNormal3f(0.0f, -1.0f, 0.0f);glVertex3f(r * fX, b, r * fZ);glTexCoord2f(0.5f + rs * fX1, 0.5f + rs * fZ1);glNormal3f(0.0f, -1.0f, 0.0f);glVertex3f(r * fX1, b, r * fZ1);rs += dr;}//侧面for (j=0; j<nSlice2; j++){GLfloat r = a + c * sin(fStep2 * j);GLfloat y = b + c - c * cos(fStep2 * j);GLfloat nr = sin(fStep2 * j);GLfloat nY = -cos(fStep2 * j);glTexCoord2f(0.5f + rs * fX, 0.5f + rs * fZ);glNormal3f(nr * fX, nY, nr * fZ);glVertex3f(r * fX, y, r * fZ);glTexCoord2f(0.5f + rs * fX1, 0.5f + rs * fZ1);glNormal3f(nr * fX1, nY, nr * fZ1);glVertex3f(r * fX1, y, r * fZ1);rs += dr;}//顶部for (j=0; j<=nSlice3; j++){GLfloat r = R * sin(fRange - j * fStep3);GLfloat y = R * cos(fRange - j * fStep3);GLfloat nr = sin(fRange - j * fStep3);GLfloat nY = cos(fRange - j * fStep3);glTexCoord2f(0.5f + rs * fX, 0.5f + rs * fZ);glNormal3f(nr * fX, nY, nr * fZ);glVertex3f(r * fX, y, r * fZ);glTexCoord2f(0.5f + rs * fX1, 0.5f + rs * fZ1);glNormal3f(nr * fX1, nY, nr * fZ1);glVertex3f(r * fX1, y, r * fZ1);rs += dr;}glEnd();}
}
static void init (void)
{glClearColor(0.0, 0.0, 0.0, 0.0);/* 显示窗口颜色为白色*/glShadeModel(GL_SMOOTH);glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glEnable(GL_POLYGON_MODE);
}
void display()
{glClear(GL_COLOR_BUFFER_BIT);glTranslatef(0.0, -2.0, -6.0);glColor3f(1.0, 1.0, 0.0);DrawChess(2, 1, 0.8, 16, 32);glFlush();
}void reshape(int newWidth,int newHeight)
{glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(60, 1, 0.01, 100.0);glClear(GL_COLOR_BUFFER_BIT);
}void main( int  argc, char** argv)
{glutInit(&argc,argv);glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);glutInitWindowPosition(100, 100);glutInitWindowSize(500, 500);glutCreateWindow("chess point");init();glutDisplayFunc(display);glutReshapeFunc(reshape);glutMainLoop();
}

使用这个函数,我绘制了两颗棋子,加上了一点光照,效果如下。

上面这个绘制效果图,是没有加入纹理映射的。为了让绘制功能更加强大,我又加入了纹理映射(上面的代码已经是最终代码,包括了纹理映射),毕竟gluSphere这样的几何体绘制,也是能够设置纹理映射的。而且,有了纹理映射,以后如果想实现更加独特的围棋子效果,也会更加简易。

关于纹理的映射,我起初的设想,是将棋子顶部映射到纹理上方,棋子底部映射到纹理下方。下面是一个效果图:

起初看见这个效果的时候,还感觉挺兴奋,觉得效果挺炫的。但后来越来越感觉不对劲,首先,纹理贴上棋子之后,纹理失真非常严重,纹理上部和中部原本是均匀的,现在上面被严重的挤压到了一起。其次,纹理的左边和右边,在棋子上转了一圈之后相遇了,形成一条边界线,如果希望平滑过渡,势必要找到左右能无缝拼接的纹理,对图片要求提高了,相应丧失了灵活性。

后来在某天下班回家的路上,想到了目前采用的纹理映射方案:也就是将围棋顶部映射到纹理的中央,底部的中央对应纹理的四周。下图是按照新的方案,加上了纹理之后的效果图,我个人感觉比上面那个效果要好J。

以上是我绘制漂亮的围棋子的全过程。此外,真正的云子,黑棋是碧透的,也就是对着光线看,会有幽幽的绿光,煞是好看,如何绘制这种碧透的效果,我还在继续研究中……

http://www.cnblogs.com/acloud/archive/2011/12/12/DrawChess.html

OpenGL: 绘制漂亮的围棋子相关推荐

  1. 如何用 OpenGL 绘制雪花?

    作者 | 许向武 责编 | 张红月 出品 | CSDN博客 看冬奥才知道,阿勒泰不但是中国的"雪都",还是"人类滑雪起源地".这个说法是否成立,姑且不论,阿勒泰 ...

  2. OpenGL绘制Triangle三角形

    OpenGL绘制Triangle三角形 前期知识准备 顶点输入 顶点着色器 编译着色器 片段着色器 着色器程序 链接顶点属性 顶点数组对象 我们一直期待的三角形 索引缓冲对象 前期知识准备 在Open ...

  3. 使用OpenGL绘制圆环体(Torus)

    本篇介绍一下使用OpenGL绘制圆环体的方法.程序是在C#和OpenTK环境下编译的. 代码: /// <summary> /// 绘制圆环体 /// </summary> / ...

  4. 【OpenGL】十三、OpenGL 绘制三角形 ( 绘制单个三角形 | 三角形绘制顺序 | 绘制多个三角形 )

    文章目录 一.绘制三角形 二.三角形绘制顺序 1.绘制正面 2.三个点逆时针方向排列 3.三个点顺时针方向排列 4.设置点的正面方向 三.绘制多个三角形 四.相关资源 一.绘制三角形 三角形绘制即绘制 ...

  5. 【OpenGL】十一、OpenGL 绘制多个点 ( 绘制单个点 | 绘制多个点 )

    文章目录 一.绘制单个点 二.绘制多个点 三.相关资源 在上一篇博客 [OpenGL]十.OpenGL 绘制点 ( 初始化 OpenGL 矩阵 | 设置投影矩阵 | 设置模型视图矩阵 | 绘制点 | ...

  6. 【OpenGL】十、OpenGL 绘制点 ( 初始化 OpenGL 矩阵 | 设置投影矩阵 | 设置模型视图矩阵 | 绘制点 | 清除缓冲区 | 设置当前颜色值 | 设置点大小 | 绘制点 )

    文章目录 一.初始化 OpenGL 矩阵 1.设置投影矩阵 2.设置模型视图矩阵 二.绘制点 1.清除缓冲区 2.设置当前颜色值 3.设置绘制点的大小 4.绘制点 5.将缓冲区绘制到前台 三.部分代码 ...

  7. 【OpenGL】九、OpenGL 绘制基础 ( OpenGL 状态机概念 | OpenGL 矩阵概念 )

    文章目录 一.OpenGL 状态机概念 二.OpenGL 矩阵概念 上一篇博客 [OpenGL]八.初始化 OpenGL 渲染环境 ( 导入 OpenGL 头文件 | 链接 OpenGL 库 | 将窗 ...

  8. OpenGL绘制二个不同颜色的三角形的实例

    OpenGL绘制二个不同颜色的三角形 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #include & ...

  9. OpenGL绘制带有索引的矩形的实例

    OpenGL绘制带有索引的矩形 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #include < ...

最新文章

  1. Windows Server 2012 网络负载均衡
  2. Apache Camel框架之事务控制
  3. 这五部关于宇宙的神级纪录片,带你探索未知的外太空世界
  4. 美国发布35页科技趋势报告!
  5. maven 引入war
  6. DataNode的流式接口
  7. S. gcc相关软件安装
  8. Oracle如何一次插入多条数据
  9. T61 拆机4短报警 续
  10. 漂浮式半潜风机(二)环境荷载
  11. TypeScript系列教程十一《装饰器》 -- reflect-metadata
  12. 修改layui绿色为蓝色
  13. redis指定配置文件启动不生效_Windows Redis默认配置文件,Redis配置不生效解决方案...
  14. 分库分表就能无限扩容吗
  15. 微软最爽命令行工具将成 Win11 默认终端
  16. php 属猪,属猪人的流年运程
  17. 关于kali中base64的加解密使用
  18. C#生成条形码图片的简单方法
  19. linux下驱动编译报错EEROR: *** [***.ko] undefined! 的错误原因和解决办法
  20. 再见, 软交换!又一个通信时代的落幕

热门文章

  1. Windows11使用Edge访问IE页面
  2. 安装问题:bokeh安装报错
  3. 3dmax第二次安装遇到的问题及解决方案
  4. AutoCAD Civil 3D-创建道路模型(2 道路的挖填方量计算及条件部件)
  5. 控制霍尔编码器电机(有刷)的一个思路
  6. python 打卡记录代码_python实现自动打卡的示例代码
  7. 如何了解上层管理者?
  8. 旅游定制服务|基于SSM实现旅游个性化定制网站平台
  9. 前端酷炫的n个UI设计效果网站
  10. 鸿蒙是另一种安卓吗,鸿蒙不是另一个安卓或者iOS!鸿蒙2.0上线倒计时