首页 > 代码库 > 从头开始绘制一个球体

从头开始绘制一个球体

好久都没有更新博客了,最近在研究OpenGL图形编程,写了一些有趣的程序,分享一下. 废话少说,开始吧.

球体作为基本的几何图形在游戏程序中应用广泛,其中最为人所知的是可以作为Sky Dome模拟天空,比起Sky Box来说更加细致一些,即使加上别的特殊效果也不容易穿帮.

我们在中学的数学课上应该学过球体的参数方程

x = r * sin(angZ) * cos(angXY)

y = r * sin(angZ) * sin(angXY)

z = r * cos(angZ)

angZ是纵向夹角,angXY是横向夹角,r是半径. 想起来了吧,用这个方程可以表示球体表面上的点,对了用这个方程就可画出球体模型.

先让看一下需要定义哪些变量与函数:

#ifndef PI
#define PI 3.1415926//这个不用解释了
#endif
#ifndef PI2
#define PI2 6.2831853//2PI
#endif

class Sphere {
private:
	GLuint* vboId;
	GLuint vert,texcoord;
	GLfloat* verts;//保存顶点与法向量的指针
	GLfloat* texcoords;//保存纹理坐标的指针
	int vertNum;
public:
	Sphere(int m,int n);//m是纵向细分程度,n是横向细分程度
	~Sphere();
	void render();//渲染球体!
};

其中构造函数里面的m与n分别表示纵向与横向的细分程度,值越大则球体看上去越精细. GLuint,GLfloat是OpenGL的变量类型,相当于C++中的unsigned int和float.


接着来看一下构造球体最主要的部分:

	vertNum=m*n*4;//顶点总数
	verts=new GLfloat[vertNum*3];//每个顶点有xyz三个分量,因此*3
	texcoords=new GLfloat[vertNum*2];//每个顶点的纹理坐标有uv两个分量,因此*2

	float stepAngZ=PI/m;//纵向角度每次增加的值
	float stepAngXY=PI2/n;//横向角度每次增加的值
	float angZ=0.0;//初始的纵向角度
	float angXY=0.0;//初始的横向角度

	int index=0;
	int indexTex=0;
	for(int i=0;i<m;i++) {
		for(int j=0;j<n;j++) {
                        //构造一个顶点
                        float x1=sin(angZ)*cos(angXY);
			float y1=sin(angZ)*sin(angXY);
			float z1=cos(angZ);
			verts[index]=x1; index++;
			verts[index]=y1; index++;
			verts[index]=z1; index++;
			float v1=angZ/PI;
			float u1=angXY/PI2;
			texcoords[indexTex]=u1; indexTex++;
			texcoords[indexTex]=v1; indexTex++;

			float x2=sin(angZ+stepAngZ)*cos(angXY);
			float y2=sin(angZ+stepAngZ)*sin(angXY);
			float z2=cos(angZ+stepAngZ);
			verts[index]=x2; index++;
			verts[index]=y2; index++;
			verts[index]=z2; index++;
			float v2=(angZ+stepAngZ)/PI;
			float u2=angXY/PI2;
			texcoords[indexTex]=u2; indexTex++;
			texcoords[indexTex]=v2; indexTex++;


			float x3=sin(angZ+stepAngZ)*cos(angXY+stepAngXY);
			float y3=sin(angZ+stepAngZ)*sin(angXY+stepAngXY);
			float z3=cos(angZ+stepAngZ);
			verts[index]=x3; index++;
			verts[index]=y3; index++;
			verts[index]=z3; index++;
			float v3=(angZ+stepAngZ)/PI;
			float u3=(angXY+stepAngXY)/PI2;
			texcoords[indexTex]=u3; indexTex++;
			texcoords[indexTex]=v3; indexTex++;

			float x4=sin(angZ)*cos(angXY+stepAngXY);
			float y4=sin(angZ)*sin(angXY+stepAngXY);
			float z4=cos(angZ);
			verts[index]=x4; index++;
			verts[index]=y4; index++;
			verts[index]=z4; index++;
			float v4=angZ/PI;
			float u4=(angXY+stepAngXY)/PI2;
			texcoords[indexTex]=u4; indexTex++;
			texcoords[indexTex]=v4; indexTex++;

			angXY+=stepAngXY;
		}
		angXY=0.0;//每次横向到达2PI角度则横向角度归0
		angZ+=stepAngZ;
	}


通过增加固定角度来构造球体网格,至于为什么球体的法向量与球体表面的顶点坐标是一致的,请看以下手绘:


明白了吧,p点上的法向量就是从原点到p点的向量,简单吧.
接着是纹理坐标的计算,既然横向夹角的范围是[0,2PI],纵向夹角的坐标是[0,PI],纹理坐标的范围是[0,1],那么把每个顶点的横向与纵向的夹角对应的值按比例变到[0,1]就能够计算出每个顶点的纹理坐标了. 
嗯, 既然顶点,法向量,纹理坐标都有了,那么把这些数据都提交到显存中去,然后渲染出图像吧.



嗯,结果就是这样. 圆吧?
以上结果使用OpenGL渲染,使用DirectX也能够实现相同的效果.

从头开始绘制一个球体