Computer graphics - Experiment: Cylinder rendering

Posted by kr3m3r on Mon, 10 Jan 2022 08:39:20 +0100

This blog is based on the course "computer graphics", and the textbook is Computer graphics (4th Edition) [Computer Graphics with OpenGL, Fourth Edition] , some code templates come from this textbook and have been changed

Experimental ideas

Code ideas

  1. drawsurface(): unitSlice: circumference block increased each time, unitHeight: height block increased each time. The overall drawing route is to draw a full height block on each circular block, and then continue to draw the next circular block until the circular block forms a circle. Therefore, the first for loop is used to increase the position of the circumferential block in turn, and the second loop is used to increase the height in turn. Each cylinder is centered on the origin, so the starting point of the second cycle is half of the positive height and the ending point is half of the negative height. Each time a pixel is placed, in order to achieve illumination, glNormal3f is used to set the normal vector, the x and y coordinate values are consistent with the pixel, the z coordinate is always 0, and in order to achieve GL_QUAD_STRIP painting mode effect requires two adjacent pixels on the circumference to be placed in the same cycle
  2. drawDisk(): the variable unitSlice is the same as that in the drawsurface() function. unitRadius is the radius growth unit of each concentric circle (triangle). First, draw a circle of triangles in the center and use GL_TRIANGLE_FAN drawing mode, that is, draw a circle of pixels away from the initial point (0,0,0) and unitRadius. Then use GL_QUAD_STRIP drawing mode draws multiple four sided strips, which requires two cycles, one for increasing the radius and the other for pixel placement of rings with the same radius

Problems and Solutions

  1. GL_ QUAD_ In strip drawing mode, its drawing function is to fill the surface (every two points form a line, and every two lines form a quadrilateral). The winding method of drawing quadrilateral is N-type, not a simple clockwise type. In this experiment, the order of placing points is: lower right point - > lower left point - > upper right point - > upper left point. Therefore, when drawing the side, the initial and end values of the cycle can be nstack/2 and - nstack, or they can be interchanged, but after the interchange, the order of placing pixels i and i+1 needs to be changed

  2. GL_TRIANGLE_FAN is used to draw filled triangles and fill triangles (take the first point as the vertex, then fill the triangles surrounded by every two points, and fill between adjacent points). Therefore, when creating multiple triangles in the first circle, you need to place a pixel at the origin to connect them

  3. When drawing multiple quadrilateral strips, there will be one more ring due to the cycle setting (as shown in the right figure),

# Implementation code
## Core code and key step notes
```cpp
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); //Fill surface (every two points form a line and every two lines form a quadrilateral)
		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) //Radial circumferential fraction radial fraction
// 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();
}
```
## All codes

```cpp
// ====== 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); //Fill surface (every two points form a line and every two lines form a quadrilateral)
		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) //Radial circumferential fraction radial fraction
// 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;
}

```