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
- 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
- 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
-
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
-
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
-
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; } ```