# 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
{
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);

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

// nslice --- Number of subdivision around z-axis
// nring  --- Number of concentric rings on each end face
{
glNormal3f(0, 0, 1);
const float unitSlice = 2 * PI / nslice;

glBegin(GL_TRIANGLE_FAN);
glVertex3f(0, 0, 0);
for (int i = 0; i < nslice; i++) {
}
glEnd();
// Draw triangles around center
for (int i = 0; i < nring; i++) {
for (int j = 0; j <= nslice; j++) {
}
}
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
{
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);

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

// nslice --- Number of subdivision around z-axis
// nring  --- Number of concentric rings on each end face
{
glNormal3f(0, 0, 1);
const float unitSlice = 2 * PI / nslice;

glBegin(GL_TRIANGLE_FAN);
glVertex3f(0, 0, 0);
for (int i = 0; i < nslice; i++) {
}
glEnd();
// Draw triangles around center
for (int i = 0; i < nring; i++) {
for (int j = 0; j <= nslice; j++) {
}
}
glEnd();
}

// render a cylinder centered at the origin, with z as axis.
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
{
glTranslatef(0, 0, height / 2);
glTranslatef(0, 0, -height);
glRotatef(180, 1, 0, 0);
}
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);

// 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();
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);
gluPerspective(60.0, (float)w / (float)h, 10.0, 100000.0);

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

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

``````