├── Makefile ├── Presentation.pptx ├── README.md ├── bokeh.cpp ├── bokeh45.cpp ├── flower1.jpg ├── flower2.jpg ├── flower3.jpg └── yakei.jpg /Makefile: -------------------------------------------------------------------------------- 1 | CXX=g++ 2 | CXXFLAGS=`pkg-config opencv --cflags` 3 | LDLIBS=-lglut -lGL -lGLU `pkg-config opencv --libs` 4 | WITH_OPENGL=ON 5 | -------------------------------------------------------------------------------- /Presentation.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akakikuumeri/OpenCV-bokeh/52b7bbf2465e66f57ff82082c06b98272cce7931/Presentation.pptx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenCV-bokeh 2 | Using OpenCV to add a depth-of-field bokeh effect to 3D scenes rendered by OpenGL. Uses some optimizations and tricks such as dividing the Z-axis into slices that get coarser the further they are from focus, and some faux HDR by spreading very bight points more. 3 | 4 | ##Demo 5 | ![example1](https://i.imgur.com/vZSiCRD.png) 6 | 7 | ![example2](https://i.imgur.com/vK0xHGI.png) 8 | 9 | ##Behind the scenes 10 | Here is the plain render of the scene, and a sample of the depth buffer. 11 | 12 | ![example3](https://i.imgur.com/LXU2FtW.png) 13 | 14 | The program slices the image into different depths based on the pixel information in the depth buffer, and applies variable blur to different depths. Optionally, very bright spots can be blurred more to achieve a more natural effect. 15 | -------------------------------------------------------------------------------- /bokeh.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace cv; 7 | using namespace std; 8 | 9 | int main(int argc, char *argv[]) { 10 | int INIT_TIME = 50; 11 | int width, height; 12 | double B_PARAM = 1.0 / 50.0; 13 | double T_PARAM = 1.0 / 200.0; 14 | double Zeta = 10.0; 15 | 16 | int blurvalue = 19; 17 | double bokehthreshold = 0.8; 18 | int bokehthresholdint = 253; 19 | double bokehthreshold2; 20 | int bokehthresholdint2 = 248; 21 | int dilation_size = 10; 22 | int dilation_size2 = 7; 23 | 24 | VideoCapture cap; 25 | Mat frame, result, baseblur, highlights, bokeh, mediums; 26 | Mat avg_img, sqm_img; 27 | Mat lower_img, upper_img, tmp_img; 28 | Mat dst_img, msk_img; 29 | 30 | if (argc >= 2) { 31 | cap.open(argv[1]); 32 | }else{ 33 | cap.open(0); 34 | } 35 | if (!cap.isOpened()){ 36 | printf("Can not open video.\n"); 37 | exit(0); 38 | } 39 | 40 | cap >> frame; 41 | frame = imread("yakei.jpg"); 42 | 43 | Size s = frame.size(); 44 | 45 | //namedWindow("Input", 1); 46 | //namedWindow("FG", 1); 47 | namedWindow("Result",1); 48 | namedWindow("Controls",1); 49 | createTrackbar("Blur size", "Controls", &blurvalue, 40, 0); 50 | createTrackbar("Bokeh Threshold", "Controls", &bokehthresholdint, 255, 0); 51 | createTrackbar("Medium bokeh threshold", "Controls", &bokehthresholdint2, 255, 0 ); 52 | createTrackbar("Bokeh Size", "Controls", &dilation_size, 50, 0); 53 | createTrackbar("Medium bokeh size", "Controls", &dilation_size2, 50, 0); 54 | //namedWindow("Bokeh", 1); 55 | 56 | bool loop_flag = true; 57 | while (loop_flag) { 58 | //cap>>frame; 59 | //if (frame.empty()) break; 60 | 61 | // BEGIN FILTER 62 | 63 | frame.convertTo(tmp_img, tmp_img.type()); 64 | 65 | //GaussianBlur(frame, tmp_img, Size(0,0), 5, 5, 0); 66 | 67 | medianBlur(frame, baseblur, (blurvalue / 2) * 2 + 1); 68 | //brightness range 69 | baseblur.copyTo(result); 70 | 71 | Mat temp; 72 | //convert to grayscale for thresholding 73 | cvtColor(frame, temp, CV_BGR2YCrCb); 74 | vector planes; 75 | split(temp, planes); 76 | Mat bnw = planes[0]; 77 | 78 | bokehthreshold = bokehthresholdint; 79 | threshold(bnw, highlights, bokehthreshold, 0.5, THRESH_TOZERO);//take only highlights 80 | bokehthreshold2 = bokehthresholdint2; 81 | threshold(bnw, mediums, bokehthreshold2, 0.5, THRESH_TOZERO); 82 | threshold(mediums, mediums, bokehthreshold, 0.5, THRESH_TOZERO_INV);//also shave off too bright ones 83 | equalizeHist(highlights, highlights); 84 | equalizeHist(mediums, mediums); 85 | 86 | Mat dilationelement = 87 | getStructuringElement(MORPH_ELLIPSE, 88 | Size(2*dilation_size + 1, 2* dilation_size + 1 ), 89 | Point( dilation_size, dilation_size) ); 90 | 91 | dilate( highlights, temp, dilationelement); 92 | 93 | dilationelement = getStructuringElement(MORPH_ELLIPSE, 94 | Size(2*dilation_size2 + 1, 2* dilation_size2 + 1 ), 95 | Point( dilation_size2, dilation_size2) ); 96 | dilate (mediums, mediums, dilationelement); 97 | 98 | //imshow("Bokeh", temp); 99 | 100 | Mat bokeh; 101 | temp.convertTo(bokeh, frame.type()); 102 | 103 | //convert grayscale bokeh image to RGB: 104 | vector channels; 105 | channels.push_back(temp); 106 | channels.push_back(temp); 107 | channels.push_back(temp); 108 | merge(channels, bokeh); 109 | vector channels2; 110 | channels2.push_back(mediums); 111 | channels2.push_back(mediums); 112 | channels2.push_back(mediums); 113 | Mat mediumbokeh; 114 | merge(channels2, mediumbokeh); 115 | //RGB conversion over 116 | 117 | //blur bokeh a little bit: 118 | GaussianBlur(mediumbokeh, mediumbokeh, Size(0,0), 3, 3, 0); 119 | blur(bokeh, bokeh, Size(2,2)); 120 | /******************************* 121 | 122 | NOTE FOR NEXT TIME: 123 | for the highligh blur, and maybe for medium blur as well, 124 | make a loop that goes through a number of brightness values. For example 125 | 100% brightness to 98%, then 97% to 96% and so on, and apply seperate bokeh 126 | effect on all. This way bokeh circles dont merge as much. 127 | Maybe even add seperate sprites for the absolute white case only. 128 | 129 | make the granularity and number of passes controllable. 130 | 131 | ******************************/ 132 | 133 | 134 | //add(baseblur, bokeh, result); 135 | 136 | addWeighted(mediumbokeh, 0.3, baseblur, 1.0, 0.0, temp, -1); 137 | addWeighted(bokeh, 0.5, temp, 0.9, 0.0, result, -1); 138 | 139 | 140 | imshow("Result", result); 141 | 142 | char key = waitKey(10); 143 | if(key == 27) { 144 | loop_flag = false; 145 | } 146 | } 147 | return 0; 148 | } 149 | -------------------------------------------------------------------------------- /bokeh45.cpp: -------------------------------------------------------------------------------- 1 | //Akaki Kuumeri 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define WINDOW_NAME "test3" 12 | #define astcount 100 13 | #define SW 600 14 | #define SH 500 15 | #define NEAR 3.0 16 | #define FAR 30.0 17 | #define TEXHEIGHT 650 18 | #define TEXWIDTH 650 19 | GLuint TextureHandle[3]; 20 | 21 | using namespace cv; 22 | using namespace std; 23 | 24 | void OnIdle(void); 25 | void init(void); 26 | void glut_display(void); 27 | void glut_keyboard(unsigned char key, int x, int y); 28 | void glut_mouse(int button, int state, int x, int y); 29 | void glut_motion(int x, int y); 30 | void draw_pyramid(void); 31 | void draw_orbit(double r); 32 | void set_texture(void); 33 | 34 | double Angle1 = 3.0; 35 | double Angle2 =0.1; 36 | double Distance = 10.0; 37 | bool LeftButtonOn = false; 38 | bool RightButtonOn = false; 39 | double mytime = 500.0; 40 | double timerate = 0.6; 41 | double astangle1[astcount]; 42 | double astangle2[astcount]; 43 | double astangle3[astcount]; 44 | double astr[astcount]; 45 | double astspeed[astcount]; 46 | double aststart[astcount]; 47 | double astsize[astcount]; 48 | 49 | Mat frame, baseblur, highlights, bokeh, mediums, temp; 50 | 51 | int blurvalue = 20; 52 | int dilation_size = 3; 53 | int dilation_size2 = 3; 54 | int granularity = 3; 55 | int opacity = 10; 56 | int overlap = 13;//how much to take from the previous z slice. 57 | int focus = 8000; 58 | int bokehthreshold=150; 59 | 60 | int no_passes = 8; 61 | 62 | GLUquadricObj *Ring; 63 | 64 | Mat flipped; 65 | Mat img(SH, SW, CV_8UC3); 66 | Mat depth(SH, SW, CV_32F); 67 | Mat result(SH, SW, CV_8UC3); 68 | Mat depthtemp(SH, SW, CV_32F); 69 | 70 | int main (int argc, char *argv[]) 71 | { 72 | namedWindow("Controls",1); 73 | createTrackbar("Focus", "Controls", &focus, 10000, 0); 74 | createTrackbar("Blur size", "Controls", &blurvalue, 40, 0); 75 | createTrackbar("Granularity", "Controls", &granularity, 20, 0 ); 76 | createTrackbar("Depth overlap", "Controls", &overlap, 100, 0); 77 | createTrackbar("Number of Passes", "Controls", &no_passes, 20, 0); 78 | createTrackbar("Bokeh Threshold", "Controls", &bokehthreshold, 300, 0); 79 | //namedWindow("Bokeh", 1); 80 | 81 | 82 | for (int i = 0; i < astcount; i++){ 83 | astangle1[i] = (rand() % 50)*0.1 - 0.05; 84 | astangle2[i] = (rand() % 50)*0.1 - 0.05; 85 | astangle3[i] = rand() % 360; 86 | astr[i] = (rand()%40)*0.01 + 3.7; 87 | astspeed[i] = astr[i] / 8.0; 88 | aststart[i] = rand() % 360; 89 | astsize[i] = (rand()%10)*0.002; 90 | } 91 | Ring = gluNewQuadric(); 92 | gluQuadricDrawStyle(Ring, GLU_FILL); 93 | gluQuadricOrientation( Ring,GLU_OUTSIDE); 94 | 95 | 96 | 97 | 98 | glutInit(&argc, argv); 99 | glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE); 100 | 101 | 102 | 103 | glutInitWindowSize(SW,SH); 104 | glutCreateWindow(WINDOW_NAME); 105 | //glutCreateWindow("Hello"); 106 | init(); 107 | 108 | glutIdleFunc(OnIdle); 109 | glutDisplayFunc(glut_display); 110 | glutKeyboardFunc(glut_keyboard); 111 | glutMouseFunc(glut_mouse); 112 | glutMotionFunc(glut_motion); 113 | glutPassiveMotionFunc(glut_motion); 114 | 115 | glutMainLoop(); 116 | 117 | return 0; 118 | } 119 | 120 | void OnIdle(void){ 121 | static int counter = 0; 122 | 123 | if (counter ==0){ 124 | glBindTexture(GL_TEXTURE_2D, TextureHandle[0]); 125 | }else if(counter == 1000){ 126 | glBindTexture(GL_TEXTURE_2D, TextureHandle[1]); 127 | }else if(counter == 2000){ 128 | glBindTexture(GL_TEXTURE_2D, TextureHandle[2]); 129 | } 130 | 131 | counter++; 132 | if (counter > 3000) counter = 0; 133 | 134 | 135 | 136 | mytime += timerate; 137 | glutPostRedisplay(); 138 | 139 | //get image: 140 | glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3)?1:4); 141 | glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize()); 142 | glReadPixels(0, 0, img.cols, img.rows, GL_BGR_EXT, GL_UNSIGNED_BYTE, img.data); 143 | //get depth buffer: 144 | glPixelStorei(GL_PACK_ALIGNMENT, (depth.step & 3)?1:4); 145 | glPixelStorei(GL_PACK_ROW_LENGTH, depth.step/depth.elemSize()); 146 | glReadPixels(0, 0, depth.cols, depth.rows, GL_DEPTH_COMPONENT, GL_FLOAT, depth.data); 147 | //cv::Mat flipped(img); 148 | flip(img, img, 0); 149 | flip(depth, depth, 0); 150 | //imwrite("snapshot.png", img); 151 | img.convertTo(temp, CV_8UC3); 152 | depth.convertTo(depthtemp, CV_32F); 153 | 154 | //Mat thresholdresult(depthtemp); 155 | //threshold(depthtemp, thresholdresult, bokehthresholdint/10000.0, 1, THRESH_TOZERO); 156 | //imshow("thresholdresult",thresholdresult); 157 | //Mat tempmask; 158 | Mat blurs; 159 | blurs = Mat::zeros(SH,SW,CV_8UC3); 160 | result = Mat::zeros(SH, SW, CV_8UC3); 161 | //img.copyTo(result); 162 | //thresholdresult.convertTo(tempmask, CV_8UC1); 163 | //temp.copyTo(result, tempmask); 164 | //boxFilter(temp, baseblur, -1, Size(blurvalue + 1, blurvalue + 1)); 165 | 166 | 167 | //convert to grayscale 168 | Mat bnw1channel, bnwtemp; 169 | cvtColor(temp, bnwtemp, CV_BGR2YCrCb); 170 | vector planes; 171 | split(temp, planes); 172 | bnw1channel = planes[0]; 173 | 174 | double thrhigh= 1.0; 175 | double thrlow = 1.0; 176 | //FAR FOCUS: 177 | 178 | for (int i = 0; i < no_passes; i++) { 179 | //go through each pass and threshold out a slice 180 | thrhigh = thrlow + (overlap-10)/10000.0;;//take the previous low 181 | thrlow -= (thrlow - focus/10000.0)/granularity;//bring the bottom down, progressively slower 182 | Mat thresholdresult; 183 | thresholdresult = Mat::ones(SH, SW, CV_8UC1); 184 | threshold(depthtemp, thresholdresult, thrlow, 1, THRESH_TOZERO); //drop evrything below to zero 185 | threshold(thresholdresult, thresholdresult, thrhigh, 1, THRESH_TOZERO_INV); //drop evrything above to zero 186 | //equalizeHist(thresholdresult, thresholdresult); 187 | //use thresh as mask and copy only that portion of image: 188 | Mat tempmask; 189 | highlights = Mat::zeros(SH, SW, CV_8UC3); 190 | thresholdresult.convertTo(tempmask, CV_8UC1); 191 | temp.copyTo(highlights, tempmask); 192 | 193 | 194 | int blursize = 1.0*(no_passes-i)/no_passes*blurvalue+1; 195 | Mat blurresult; 196 | 197 | //blursize = (blursize % 2 == 1) ? blursize : blursize+1; 198 | boxFilter(highlights, blurresult, -1, Size(blursize, blursize)); 199 | //medianBlur(blurresult, blurresult, blursize); 200 | //blur(dilationresult, dilationresult, Size(i,i)); 201 | //GaussianBlur(dilationresult, dilationresult, Size(0,0), blursize, blursize, 0); 202 | 203 | int dsize = blursize/2; 204 | Mat dilationelement = 205 | getStructuringElement(MORPH_ELLIPSE, 206 | Size(2*dsize + 1, 2* dsize + 1 ), 207 | Point( dsize, dsize) ); 208 | 209 | Mat dilationresult, dilationmask;// = Mat::zeros(SH, SW, CV_8UC1); 210 | 211 | bnw1channel.copyTo(dilationmask, tempmask); 212 | threshold(dilationmask, thresholdresult, bokehthreshold, 1, THRESH_TOZERO_INV); 213 | equalizeHist(thresholdresult, thresholdresult); 214 | if (sum(thresholdresult)[0] > 0) {//if any bokeh is found at all 215 | Mat dilatables; 216 | dilatables = Mat::zeros(SH, SW, CV_8UC3); 217 | highlights.copyTo(dilatables, thresholdresult); 218 | 219 | dilate(dilatables, dilationresult, dilationelement); 220 | if (blursize < 9) blur(dilationresult, dilationresult, Size(3,3)); 221 | add(dilationresult, result, result); 222 | } 223 | add(blurresult, result, result); 224 | } 225 | { 226 | //NEXT THE PINTO 227 | thrlow = thrhigh - (overlap-10)/10000.0;//take the previous top 228 | thrhigh= overlap/10000.0;//prepare for the for loop 229 | for (int i = 0; i < no_passes; i++){ 230 | thrhigh += (focus/10000.0 - thrhigh)/granularity;//get the final near focus thresh 231 | } 232 | Mat thresholdresult; 233 | thresholdresult = Mat::ones(SH, SW, CV_8UC1); 234 | threshold(depthtemp, thresholdresult, thrhigh-(overlap-10)/10000.0, 1, THRESH_TOZERO); //drop evrything below to zero 235 | threshold(thresholdresult, thresholdresult, thrlow, 1, THRESH_TOZERO_INV); //drop evrything above to zero 236 | //equalizeHist(thresholdresult, thresholdresult); 237 | //use thresh as mask and copy only that portion of image: 238 | Mat tempmask; 239 | highlights = Mat::zeros(SH, SW, CV_8UC3); 240 | thresholdresult.convertTo(tempmask, CV_8UC1); 241 | temp.copyTo(highlights, tempmask); 242 | 243 | //no blurring 244 | 245 | add(highlights, result, result); 246 | } 247 | 248 | 249 | //NEXT THE NEAR BLUR 250 | thrhigh= overlap/10000.0;//start from 0 251 | 252 | 253 | for (int i = 0; i < no_passes; i++) { 254 | //go through each pass and threshold out a slice 255 | thrlow = thrhigh - (overlap-10)/10000.0;//take the previous top 256 | thrhigh += (focus/10000.0 - thrhigh)/granularity;//bring the threshold half the way nearer to the focus 257 | Mat thresholdresult; 258 | thresholdresult = Mat::ones(SH, SW, CV_8UC1); 259 | threshold(depthtemp, thresholdresult, thrlow, 1, THRESH_TOZERO); //drop evrything below to zero 260 | threshold(thresholdresult, thresholdresult, thrhigh, 1, THRESH_TOZERO_INV); //drop evrything above to zero 261 | //equalizeHist(thresholdresult, thresholdresult); 262 | //use thresh as mask and copy only that portion of image: 263 | Mat tempmask; 264 | highlights = Mat::zeros(SH, SW, CV_8UC3); 265 | thresholdresult.convertTo(tempmask, CV_8UC1); 266 | temp.copyTo(highlights, tempmask); 267 | 268 | //Mat dilationresult; 269 | //dilate(highlights, dilationresult, dilationelement); 270 | int blursize = 1.0*(no_passes-i)/no_passes*blurvalue+1; 271 | Mat blurresult; 272 | //blursize = (blursize % 2 == 1) ? blursize : blursize+1; 273 | boxFilter(highlights, blurresult, -1, Size(blursize, blursize)); 274 | //blur(dilationresult, dilationresult, Size(i,i)); 275 | //GaussianBlur(dilationresult, dilationresult, Size(0,0), blursize, blursize, 0); 276 | add(blurresult, result, result); 277 | } 278 | //add(result, blurs, result); 279 | 280 | imshow("test3", result); 281 | imshow("z buffer", depth); 282 | waitKey(1); 283 | //imshow("Hello", flipped); 284 | 285 | } 286 | 287 | 288 | void init(void) { 289 | glClearColor(0.0,0.0,0.0,0.0); 290 | 291 | glGenTextures(3, TextureHandle); 292 | 293 | for (int i = 0; i < 3; i++){ 294 | glBindTexture(GL_TEXTURE_2D, TextureHandle[i]); 295 | 296 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 297 | 298 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TEXWIDTH, TEXHEIGHT, 299 | 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); 300 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 301 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 302 | } 303 | 304 | set_texture(); 305 | } 306 | 307 | void glut_keyboard(unsigned char key, int x, int y){ 308 | switch(key){ 309 | case 'q': 310 | exit(0); 311 | } 312 | glutPostRedisplay(); 313 | 314 | } 315 | 316 | void glut_mouse(int button, int state, int x , int y){ 317 | if (button == GLUT_LEFT_BUTTON){ 318 | if (state == GLUT_UP){ 319 | LeftButtonOn = false; 320 | } else if (state == GLUT_DOWN){ 321 | LeftButtonOn = true; 322 | } 323 | } 324 | 325 | if (button == GLUT_RIGHT_BUTTON){ 326 | if (state == GLUT_UP){ 327 | RightButtonOn = false; 328 | } else if (state == GLUT_DOWN){ 329 | RightButtonOn = true; 330 | } 331 | } 332 | } 333 | void glut_motion(int x, int y){ 334 | static int px = -1, py = -1; 335 | 336 | if (LeftButtonOn == true){ 337 | if(px >= 0 && py >= 0){ 338 | Angle1 +=(double) - (x - px) /20; 339 | Angle2 += (double) (y - py)/20; 340 | } 341 | px = x; 342 | py = y; 343 | 344 | }else if (RightButtonOn){ 345 | if (px >= 0 && py >= 0){ 346 | Distance += (double)(y -py)/20; 347 | } 348 | px = x; 349 | py = y; 350 | }else{ 351 | px = -1; 352 | py = -1; 353 | } 354 | 355 | glutPostRedisplay(); 356 | } 357 | 358 | void glut_display(void){ 359 | glMatrixMode(GL_PROJECTION); 360 | glLoadIdentity(); 361 | gluPerspective(30.0,1.0 * SW / SH ,NEAR,FAR); 362 | 363 | glMatrixMode(GL_MODELVIEW); 364 | glLoadIdentity(); 365 | gluLookAt(Distance*cos(Angle2) * sin(Angle1), 366 | Distance * sin(Angle2), 367 | Distance * cos(Angle2) * cos(Angle1), 368 | 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); 369 | 370 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 371 | glEnable(GL_DEPTH_TEST); 372 | 373 | glTranslatef(2.0,0.0,0.0); 374 | 375 | //sun 376 | glPushMatrix(); 377 | glColor3d(1.0,1.0,0.2); 378 | glutSolidSphere(0.7, 30, 20); 379 | //draw_pyramid();//for reference 380 | glTranslatef(0.0,-0.7,0.0); 381 | glScalef(0.3,0.3,0.3); 382 | draw_pyramid(); 383 | glPopMatrix(); 384 | 385 | //extra pyramids 386 | glPushMatrix(); 387 | glScalef(0.3, 0.3, 0.3); 388 | glTranslated(-100.0, -2.0, -100.0); 389 | for (int i = 0; i < 50; i ++){ 390 | glTranslatef(4.0,0.0,0.0); 391 | for (int j=0; j< 50; j++) { 392 | glTranslatef(0.0, 0.0, 4.0); 393 | draw_pyramid(); 394 | } 395 | glTranslatef(0.0, 0.0, -200.0); 396 | } 397 | glPopMatrix(); 398 | 399 | //venus 400 | glPushMatrix(); 401 | draw_orbit(1.3); 402 | glColor3d(0.4,0.2,0.7); 403 | glRotatef(mytime*2.0, 0.0, 1.0, 0.0); 404 | glTranslatef(1.3, 0.0, 0.0); 405 | glutSolidSphere(0.03, 15,10); 406 | glPopMatrix(); 407 | 408 | //earth 409 | glPushMatrix(); 410 | draw_orbit(2.0); 411 | glRotatef(mytime, 0.0, 1.0, 0.0); 412 | glTranslatef(2.0,0.0,0.0); 413 | //glRotatef(time, 1.0, 0.0, 1.0); 414 | glColor3d(0.0,1.0,0.5); 415 | glutSolidSphere(0.1,15,10); 416 | //glPopMatrix(); 417 | //moon 418 | glRotatef(mytime*12, 0.0, 1.0, 0.0);//12 months in a year 419 | draw_orbit(0.4); 420 | glTranslatef(0.4, 0.0, 0.0); 421 | glColor3d(0.2,0.2,0.2); 422 | glutSolidSphere(0.02, 10,10); 423 | glPopMatrix(); 424 | 425 | //mars 426 | glPushMatrix(); 427 | glRotatef(mytime*0.7, 0.0, 1.0, 0.0); 428 | draw_orbit(3.0); 429 | glTranslatef(3.0,0.0,0.0); 430 | glColor3d(8.0,0.0,0.0); 431 | glutSolidSphere(0.08,15,10); 432 | //glPopMatrix(); 433 | glPopMatrix(); 434 | 435 | //asteroids 436 | for (int i = 0; i