1. 程式人生 > 其它 >【計算機圖形學】Cylinder rendering

【計算機圖形學】Cylinder rendering

技術標籤:計算機圖形學實驗opengl

文章目錄


本部落格基於課程"計算機圖形學",教材使用為計算機圖形學(第4版) [Computer Graphics with OpenGL, Fourth Edition],部分程式碼模板便來自於此教材,並且有所改動

實驗思路

程式碼思路

  1. drawsurface()unitSlice:每次增加的圓周塊,unitHeight:每次增加的高度塊。總體繪製路線是,在每個圓周塊上,繪製完整個高度的塊,然後繼續繪製下一個圓周塊,知道圓周塊組成一個圓。所以第一個for
    迴圈用來依次增加圓周塊的位置,第二個迴圈用來依次增加高度。每個圓柱以原點為中心,所以第二個迴圈的起點為正的高度的一半,終點為負的高度的一半。每次放置畫素點時,為了實現光照,先用glNormal3f設定法向量,xy座標值與畫素點保持一致,z座標始終為0,並且為了可以實現GL_QUAD_STRIP繪製模式效果,需要在同一迴圈中放置圓周上相鄰的兩個畫素點
  2. drawDisk():變數unitSlicedrawsurface()函式中的,unitRadius是各個同心圓(三角)的半徑增長單位,首先繪製最中心的一圈三角形,使用GL_TRIANGLE_FAN繪製模式,也就是繪製一圈距離初始點(0,0,0)距離unitRadius
    的畫素點。接著用GL_QUAD_STRIP繪製模式繪製多個四邊長條,需要用到兩個迴圈,一個用來對半徑遞增,一個用來對相同半徑的環進行畫素點放置

問題及解決方法

  1. GL_QUAD_STRIP繪製模式中,其繪製功能是填充面(每兩個點構成一條線,每兩個線構成一個四邊形),繪製四邊形的繞法是N型,並不是簡單的順時針型,在此實驗中,放置點的順序是:右下點->左下點->右上點->左上點。所以在繪製側面時,迴圈的初始值和終止值可以是nstack/2-nstack,也可以互換,但是互換之後需要調換放置畫素點ii+1先後順序

  2. GL_TRIANGLE_FAN則是用於繪製填充三角形,填充三角形(以第一個點為頂點,之後每兩個點合起來圍成的三角形進行填充,相鄰的點之間填充

    ),所以在建立第一個圓中的多個三角形時,需要先在原點放置一個畫素才能使之連起來
    GL_QUAD_STRIP繪製模式示意圖GL_TRIANGLE_FAN繪製模式示意圖

  3. 在繪製多個四邊長條時,由於迴圈設定的原因,會出現多了一個圓環的現象(如右圖),在這裡插入圖片描述
    ~所以需要加入一個if (nowRadius + unitRadius <= radius),然後再進行放置nowRadius + unitRadius圓環~直接修改for迴圈的退出條件即可

實現程式碼

核心程式碼及關鍵步驟註釋

void drawsurface(float radius, float height, int nslice, int nstack)
// nslice --- Number of subdivision around z-axis
// nstack --- Number of subdivision along z-axis
{
	//Write your code here
	const float unitSlice = 2 * PI / nslice;
	const float unitHeight = height / nstack;

	for (int i = 0; i <= nslice; i++) {
		glBegin(GL_QUAD_STRIP); //填充面(每兩個點構成一條線,每兩個線構成一個四邊形)
		for (int j = nstack / 2; j >= -nstack / 2; j--) {
			glNormal3f(radius * cos(unitSlice * (i + 1)), radius * sin(unitSlice * (i + 1)), 0);
			glVertex3f(radius * cos(unitSlice * (i + 1)), radius * sin(unitSlice * (i + 1)), unitHeight * j);

			glNormal3f(radius * cos(unitSlice * i), radius * sin(unitSlice * i), 0);
			glVertex3f(radius * cos(unitSlice * i), radius * sin(unitSlice * i), unitHeight * j);
		}
		glEnd();
	}
}

void drawDisk(float radius, int nslice, int nring) //半徑  圓周向細分數  半徑向細分數
// nslice --- Number of subdivision around z-axis
// nring  --- Number of concentric rings on each end face
{
	glNormal3f(0, 0, 1);
	// Draw quads
	//Write your code here
	const float unitSlice = 2 * PI / nslice;
	const float unitRadius = radius / nring;

	glBegin(GL_TRIANGLE_FAN);
	glVertex3f(0, 0, 0);
	for (int i = 0; i < nslice; i++) {
		glVertex3f(unitRadius * cos(unitSlice * i), unitRadius * sin(unitSlice * i), 0);
	}
	glEnd();
	// Draw triangles around center
	//Write your code here
	glBegin(GL_QUAD_STRIP);
	float nowRadius = 0;
	for (int i = 0; i < nring; i++) {
		for (int j = 0; j <= nslice; j++) {
			glVertex3f(nowRadius * cos(unitSlice * j), nowRadius * sin(unitSlice * j), 0);
			if (nowRadius + unitRadius <= radius)
				glVertex3f((nowRadius + unitRadius) * cos(unitSlice * j), (nowRadius + unitRadius) * sin(unitSlice * j), 0);
		}
		nowRadius += unitRadius;
	}
	glEnd();
}

全部程式碼

// ====== Computer Graphics Experiment #9 ======
// |            Cylinder rendering             |
// =============================================
//
// Requirement:
// (1) Implement cylinder rendering function.
// (2) Change polygon drawing mode and face culling parameters
//     and observe the effects.
// (3) Change smooth shading to flat shading and observe the effects
// (4) Carefully read and understand the rest of the source code

#include <GL/glut.h>
#include <math.h>
#include <windows.h>

#define PI 3.14159265
float xrotate, yrotate, zrotate;

void drawsurface(float radius, float height, int nslice, int nstack)
// nslice --- Number of subdivision around z-axis
// nstack --- Number of subdivision along z-axis
{
	//Write your code here
	const float unitSlice = 2 * PI / nslice;
	const float unitHeight = height / nstack;

	for (int i = 0; i <= nslice; i++) {
		glBegin(GL_QUAD_STRIP); //填充面(每兩個點構成一條線,每兩個線構成一個四邊形)
		for (int j = nstack / 2; j >= -nstack / 2; j--) {
			glNormal3f(radius * cos(unitSlice * (i + 1)), radius * sin(unitSlice * (i + 1)), 0);
			glVertex3f(radius * cos(unitSlice * (i + 1)), radius * sin(unitSlice * (i + 1)), unitHeight * j);

			glNormal3f(radius * cos(unitSlice * i), radius * sin(unitSlice * i), 0);
			glVertex3f(radius * cos(unitSlice * i), radius * sin(unitSlice * i), unitHeight * j);
		}
		glEnd();
	}
}

void drawDisk(float radius, int nslice, int nring) //半徑  圓周向細分數  半徑向細分數
// nslice --- Number of subdivision around z-axis
// nring  --- Number of concentric rings on each end face
{
	glNormal3f(0, 0, 1);
	// Draw quads
	//Write your code here
	const float unitSlice = 2 * PI / nslice;
	const float unitRadius = radius / nring;

	glBegin(GL_TRIANGLE_FAN);
	glVertex3f(0, 0, 0);
	for (int i = 0; i < nslice; i++) {
		glVertex3f(unitRadius * cos(unitSlice * i), unitRadius * sin(unitSlice * i), 0);
	}
	glEnd();
	// Draw triangles around center
	//Write your code here
	glBegin(GL_QUAD_STRIP);
	float nowRadius = 0;
	for (int i = 0; i < nring; i++) {
		for (int j = 0; j <= nslice; j++) {
			glVertex3f(nowRadius * cos(unitSlice * j), nowRadius * sin(unitSlice * j), 0);
			if (nowRadius + unitRadius <= radius)
				glVertex3f((nowRadius + unitRadius) * cos(unitSlice * j), (nowRadius + unitRadius) * sin(unitSlice * j), 0);
		}
		nowRadius += unitRadius;
	}
	glEnd();
}

// render a cylinder centered at the origin, with z as axis.
void MyCylinder(float radius, float height,
	int nslice, int nstack, int nring)
// nslice --- Number of subdivision around z-axis
// nstack --- Number of subdivision along z-axis
// nring  --- Number of concentric rings on each end face
{
	drawsurface(radius, height, nslice, nstack);
	glTranslatef(0, 0, height / 2);
	drawDisk(radius, nslice, nring);
	glTranslatef(0, 0, -height);
	glRotatef(180, 1, 0, 0);
	drawDisk(radius, nslice, nring);
}
class CVector3D {
public:
	float x, y, z;

	// Constructors
	CVector3D(void) {
		x = 0.0;
		y = 0.0;
		z = 0.0;
	}
	CVector3D(float x0, float y0, float z0) {
		x = x0;
		y = y0;
		z = z0;
	}
};

// View reference frame class
class CViewFrame {
public:
	float step; // step size
	float turn_a; // turn angle
	float pitch_a; // pitch angle
	float roll_a; // roll angle

	CVector3D P0; // View reference point
	CVector3D u; // unit vector in xv direction
	CVector3D v; // unit vector in yv direction
	CVector3D n; // unit vector in zv direction

	void move_up(void) {
	}

	void move_down(void) {
	}

	void move_left(void) {
	}

	void move_right(void) {
	}

	void move_forward(void) {
	}

	void move_backward(void) {
	}

	void turn_left(void) {
	}

	void turn_right(void) {
	}

	void look_up(void) {
	}

	void look_down(void) {
	}

	void roll_left(void) {
	}

	void roll_right(void) {
	}
};

CViewFrame view_frame;

int polygon_mode = 0;
// 0 --- GL_FILL, 1 --- GL_LINE

int cull_face_mode = 0;
// 0 --- Disable face culling, 1 --- Enable face culling

int face_to_cull = 0;
// 0 --- Cull back face, 1 -- Cull front face

// Program window width and height
int pw_width, pw_height;

// Initialization function
void init(void) {
	static GLfloat light_ambient[] = { 0.01, 0.01, 0.01, 1.0 };
	static GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
	static GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	static GLfloat light_pos[] = { 50.0, 50.0, 200.0, 0.0 };

	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_SMOOTH); // Set shading model

	// Set light source properties for light source #0
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos);

	glEnable(GL_LIGHTING); // Enable lighting
	glEnable(GL_LIGHT0); // Enable light source #0
	glEnable(GL_DEPTH_TEST); // Enable depth buffer test
	glEnable(GL_NORMALIZE); // Enable auto normalization
	// Enable two sided lighting
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);

	view_frame.P0 = CVector3D(500.0, 0.0, 100.0);
	view_frame.u = CVector3D(0.0, 1.0, 0.0);
	view_frame.v = CVector3D(0.0, 0.0, 1.0);
	view_frame.n = CVector3D(1.0, 0.0, 0.0);
	view_frame.step = 10;
	view_frame.turn_a = PI / 18;
	view_frame.pitch_a = PI / 18;
	view_frame.roll_a = PI / 6;

	xrotate = 0.0;
	yrotate = 0.0;
	zrotate = 0.0;
}

// Function to draw NULL-terminated string
void draw_string(void* font, char* str) {
	while ((*str) != '\0') {
		glutBitmapCharacter(font, (int)*str);
		str++;
	}
}

// Draw texts
void draw_texts(void) {
	static char* str_polygon[2] = {
		"Polygon Mode (Press p to change): GL_FILL",
		"Polygon Mode (Press p to change): GL_LINE"
	};
	static char* str_cull[2] = {
		"Face culling (Press c to change): Disabled",
		"Face culling (Press c to change): Enabled"
	};
	static char* str_face[2] = {
		"Face to cull (Press f to change): Back",
		"Face to cull (Press f to change): Front"
	};

	// Temporarily use orthographic projection
	// and set clipping window the same as program window
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0, pw_width, 0, pw_height);

	glColor3f(1.0, 1.0, 1.0);

	// Disable lighting before drawing texts
	glDisable(GL_LIGHTING);

	// Draw texts
	glRasterPos2i(5, pw_height - 20);
	draw_string(GLUT_BITMAP_9_BY_15, str_polygon[polygon_mode]);
	glRasterPos2i(5, pw_height - 40);
	draw_string(GLUT_BITMAP_9_BY_15, str_cull[cull_face_mode]);
	glRasterPos2i(5, pw_height - 60);
	draw_string(GLUT_BITMAP_9_BY_15, str_face[face_to_cull]);

	// Enable lighting after text drawing is finished
	glEnable(GL_LIGHTING);

	// Restore projection matrix
	glPopMatrix();
}

// Display callback function
void display(void) {
	GLfloat mat_color[] = { 0.91, 0.53, 0.14, 1.0 };

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Set material properties
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_color);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_color);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 64.0);

	// Set polygon drawing mode
	if (polygon_mode == 0)
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	else
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	// Set face culling mode
	if (cull_face_mode == 0)
		glDisable(GL_CULL_FACE);
	else
		glEnable(GL_CULL_FACE);

	// Define which faces to cull
	if (face_to_cull == 0)
		glCullFace(GL_BACK);
	else
		glCullFace(GL_FRONT);

	// Set matrix mode to model view
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix(); // Save current model view matrix

	CVector3D look_at;
	look_at.x = view_frame.P0.x - view_frame.n.x;
	look_at.y = view_frame.P0.y - view_frame.n.y;
	look_at.z = view_frame.P0.z - view_frame.n.z;
	gluLookAt(view_frame.P0.x, view_frame.P0.y, view_frame.P0.z,
		look_at.x, look_at.y, look_at.z,
		view_frame.v.x, view_frame.v.y, view_frame.v.z);

	// Render two crossing cylinders
	glRotatef(xrotate, 1.0, 0.0, 0.0); // rotate around x-axis
	glRotatef(yrotate, 0.0, 1.0, 0.0); //rotate around y-axis
	glRotatef(zrotate, 0.0, 0.0, 1.0); //rotate around z-axis

	MyCylinder(40.0, 200.0, 20, 10, 5);
	glRotatef(-90, 1.0, 0.0, 0.0);
	glTranslatef(0, 100, 0);
	MyCylinder(40.0, 200.0, 20, 10, 5);

	glPopMatrix(); // Restore model view matrix

	draw_texts(); // Draw texts

	glutSwapBuffers();
}

// Reshape callback function
void reshape(int w, int h) {
	pw_width = w;
	pw_height = h;

	// Set viewport as the entire program window
	glViewport(0, 0, w, h);

	// Set symmetric perspective projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (float)w / (float)h, 10.0, 100000.0);

	// Reset modelview transformation matrix to identity
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

// Keyboard callback function
void keyboard(unsigned char key, int x, int y) {
	switch (key) {
	case 27:
		exit(0);
		break;

	// Press P to change polygon drawing mode
	case 'p':
	case 'P':
		polygon_mode = 1 - polygon_mode;
		glutPostRedisplay();
		break;

	// Press C to change face culling mode
	case 'c':
	case 'C':
		cull_face_mode = 1 - cull_face_mode;
		glutPostRedisplay();
		break;

	// Press F to choose which faces to cull
	case 'f':
	case 'F':
		face_to_cull = 1 - face_to_cull;
		glutPostRedisplay();
		break;

	case 'w':
		view_frame.move_forward();
		glutPostRedisplay();
		break;
	case 's':
		view_frame.move_backward();
		glutPostRedisplay();
		break;
	case 'a':
		view_frame.move_left();
		glutPostRedisplay();
		break;
	case 'd':
		view_frame.move_right();
		glutPostRedisplay();
		break;
	case 'q':
		view_frame.roll_left();
		glutPostRedisplay();
		break;
	case 'e':
		view_frame.roll_right();
		glutPostRedisplay();
		break;
	}
}

// Special key callback function
void special_key(int key, int x, int y) {
	switch (key) {

	case GLUT_KEY_LEFT:
		view_frame.turn_left();
		glutPostRedisplay();
		break;
	case GLUT_KEY_RIGHT:
		view_frame.turn_right();
		glutPostRedisplay();
		break;
	case GLUT_KEY_UP:
		view_frame.look_up();
		glutPostRedisplay();
		break;
	case GLUT_KEY_DOWN:
		view_frame.look_down();
		glutPostRedisplay();
		break;
	case GLUT_KEY_PAGE_UP:
		view_frame.move_up();
		glutPostRedisplay();
		break;
	case GLUT_KEY_PAGE_DOWN:
		view_frame.move_down();
		glutPostRedisplay();
		break;
	case GLUT_KEY_HOME:
		xrotate += 30;
		glutPostRedisplay();
		break;
	case GLUT_KEY_END:
		yrotate += 30;
		glutPostRedisplay();
		break;
	case GLUT_KEY_INSERT:
		zrotate += 30;
		glutPostRedisplay();
		break;
	}
}

// Main program
int main(int argc, char* argv[]) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 800);
	glutInitWindowPosition(100, 50);
	glutCreateWindow("Draw cylinder");
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutSpecialFunc(special_key);
	glutMainLoop();
	return 0;
}