OpenGL遞歸細分四面體法繪製球體

OpenGL遞歸細分四面體繪製球體


遞歸細分四面體法繪製三維球體,其思路是首先繪製一個內接於單位球體的正四面體,然後遞歸地分割四面體的平面,所分割的點經過歸一化映射到球面上。經過足夠多次的遞歸,就可以用多面體來逼近球體。


1. 遞歸細分四面體

首先設定初始的四面體:內接於單位球體的正四面體,其四個頂點容易計算,結果如下:

GLfloat tetrahedron_vertex[][3] = {
	 0.0f,		 0.0f,		 1.0f,
	 0.0f,		 0.942809f, -0.333333f,
	-0.816497f, -0.471405f, -0.333333f,
	 0.816497f, -0.471405f, -0.333333f
};

在這裏插入圖片描述

隨後要對四面體每個面的三角形進行細分,如下圖所示有等分各角、尋找中心以及等分各邊等等方式的細分方法,本次代碼採用等分各邊的方式,將正三角形進一步細分爲四個正三角形。細分過後的四個正三角形和原本的三角形仍在同一平面,因此需要通過歸一化將其映射到單位球面上:將頂點到原點的距離從原距離放縮到1即可。設定好遞歸層次數depth之後,開始遞歸地進行細分,如果沒有達到層次數,則進一步細分三角形,如果depth降爲0則開始繪製最終的小三角形。這種方式可以通過控制depth的大小來控制最終球面的近似效果。爲了直觀感受近似效果,本次代碼只繪製了近似出的網格線。

void normalize(GLfloat* v)
{
	GLfloat d = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
	v[0] /= d; v[1] /= d; v[2] /= d;
}

void divide_triangle(GLfloat* a, GLfloat* b, GLfloat* c, int depth)
{
	if (depth > 0) {
		GLfloat ab[3], ac[3], bc[3];
		for (unsigned int i = 0; i < 3; i++)
			ab[i] = a[i] + b[i];
		normalize(ab);
		for (unsigned int i = 0; i < 3; i++)
			ac[i] = a[i] + c[i];
		normalize(ac);
		for (unsigned int i = 0; i < 3; i++)
			bc[i] = b[i] + c[i];
		normalize(bc);
		divide_triangle(a, ab, ac, depth - 1);
		divide_triangle(b, bc, ab, depth - 1);
		divide_triangle(c, ac, bc, depth - 1);
		divide_triangle(ab, bc, ac, depth - 1);
	}
	else {
		glBegin(GL_LINE_LOOP);
		glColor3f(sqrt(a[0]*a[0]), sqrt(a[1] * a[1]), sqrt(a[2] * a[2]));
		glVertex3fv(a);
		glVertex3fv(b);
		glVertex3fv(c);
		glEnd();
	}
}

2. 運行效果

下圖分別是遞歸層次設置爲3次、4次、6次的球體近似效果:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述


附錄:完整代碼

#include<GL/glut.h>
#include<math.h>
#include<iostream>
#define DEPTH 4
using namespace std;

GLfloat tetrahedron_vertex[][3] = {
	 0.0f,		 0.0f,		 1.0f,
	 0.0f,		 0.942809f, -0.333333f,
	-0.816497f, -0.471405f, -0.333333f,
	 0.816497f, -0.471405f, -0.333333f
};

void normalize(GLfloat* v)
{
	GLfloat d = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
	v[0] /= d; v[1] /= d; v[2] /= d;
}

void divide_triangle(GLfloat* a, GLfloat* b, GLfloat* c, int depth)
{
	if (depth > 0) {
		GLfloat ab[3], ac[3], bc[3];
		for (unsigned int i = 0; i < 3; i++)
			ab[i] = a[i] + b[i];
		normalize(ab);
		for (unsigned int i = 0; i < 3; i++)
			ac[i] = a[i] + c[i];
		normalize(ac);
		for (unsigned int i = 0; i < 3; i++)
			bc[i] = b[i] + c[i];
		normalize(bc);
		divide_triangle(a, ab, ac, depth - 1);
		divide_triangle(b, bc, ab, depth - 1);
		divide_triangle(c, ac, bc, depth - 1);
		divide_triangle(ab, bc, ac, depth - 1);
	}
	else {
		glBegin(GL_LINE_LOOP);
		glColor3f(sqrt(a[0]*a[0]), sqrt(a[1] * a[1]), sqrt(a[2] * a[2]));
		glVertex3fv(a);
		glVertex3fv(b);
		glVertex3fv(c);
		glEnd();
	}
}

void display()
{
	// 設置逆時針排列的點圍成的平面爲正面
	glFrontFace(GL_CCW);
	// 設置不繪製背面,節省算力同時不會出現背面覆蓋正面的情況
	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);
	// 設置背景爲白色
	glClearColor(1.0, 1.0, 1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);
	// 加載單位陣
	glLoadIdentity();
	// 設置相機的位置和視角
	// 有關gluLookAt:https://blog.csdn.net/Augusdi/article/details/20470813
	gluLookAt(2, 2, 2, 0.0, 0.0, 0.0, -1, -1, 1);
	divide_triangle(tetrahedron_vertex[0], tetrahedron_vertex[2], tetrahedron_vertex[1], DEPTH);
	divide_triangle(tetrahedron_vertex[0], tetrahedron_vertex[3], tetrahedron_vertex[2], DEPTH);
	divide_triangle(tetrahedron_vertex[0], tetrahedron_vertex[1], tetrahedron_vertex[3], DEPTH);
	divide_triangle(tetrahedron_vertex[1], tetrahedron_vertex[2], tetrahedron_vertex[3], DEPTH);

	glutSwapBuffers();
}

// 窗口大小自適應函數,使得窗口大小改變時仍保持圖形的比例不變
// 有關窗口自適應函數:http://blog.sina.com.cn/s/blog_5497dc110102w8qh.html
void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(2, 2, 2, 0.0, 0.0, 0.0, -1, -1, 1);
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	// 設置雙緩衝和RGB顏色模式
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	// 設置窗口大小、位置和名稱
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("sphere");
	// 設置繪製函數、窗口大小自適應函數
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	// 進入主循環
	glutMainLoop();
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章