874 233 50MB
English Pages 383 [535] Year 2019
"This book provides step-by-step instruction on modern 3D graphics shader programming in OpenGL with C++, along wit
2,353 293 38MB Read more
This book provides step-by-step instruction on modern 3D graphics shader programming in C++ and OpenGL. It is appropriat
947 184 29MB Read more
This book provides step-by-step instruction on modern 3D graphics shader programming in C++ and OpenGL. It is appropriat
3,967 839 38MB Read more
This new edition provides step-by-step instruction on modern 3D graphics shader programming in OpenGL with Java, along w
3,036 380 46MB Read more
This new edition provides step-by-step instruction on modern 3D graphics shader programming in OpenGL, along with its th
725 111 42MB Read more
This book brings together several advanced topics in computer graphics that are important in the areas of game developme
964 242 8MB Read more
Develop graphically sophisticated apps and games today!The smart phone app market is progressively growing, and there is
524 95 6MB Read more
Explore modern game programming and rendering techniques to build games using C++ programming language and its popular l
1,091 203 8MB Read more
Although numerous languages are currently being used to develop games, C++ remains the standard for fabricating expert l
4,520 1,074 8MB Read more
Table of contents :
CGP_C++_CH00-FM_2E_2pp.pdf
CGP_C++_CH01_2E_2pp.pdf
CGP_C++_CH02_2E_2pp.pdf
CGP_C++_CH03_2E_2pp.pdf
CGP_C++_CH04_2E_2pp.pdf
CGP_C++_CH05_2E_2pp.pdf
CGP_C++_CH06_2E_2pp.pdf
CGP_C++_CH07_2E_2pp.pdf
CGP_C++_CH08_2E_2pp.pdf
CGP_C++_CH09_2E_2pp.pdf
CGP_C++_CH10_2E_2pp.pdf
CGP_C++_CH11_2E_2pp.pdf
CGP_C++_CH12_2E_2pp.pdf
CGP_C++_CH13_2E_2pp.pdf
CGP_C++_CH14_2E_2pp.pdf
CGP_C++_CH15_2E_2pp.pdf
CGP_C++_CH16_2E_3pp.pdf
CGP_C++_CH17_2E_2pp.pdf
CGP_C++_CH18-AppA_2E_3pp.pdf
CGP_C++_CH19-AppB_2E_2pp.pdf
CGP_C++_CH20-AppC_2E_1pp.pdf
Computer Graphics Programming in OpenGL with C++ Second Edition
CGP_C++_CH00-FM_2E_2pp.indd 1
11/13/2020 4:25:31 PM
LICENSE, DISCLAIMER OF LIABILITY, AND LIMITED WARRANTY
By purchasing or using this book and its companion files (the “Work”), you agree that this license grants permission to use the contents contained herein, including the companion files, but does not give you the right of ownership to any of the textual content in the book / files or ownership to any of the information or products contained in it. This license does not permit uploading of the Work onto the Internet or on a network (of any kind) without the written consent of the Publisher. Duplication or dissemination of any text, code, simulations, images, etc. contained herein is limited to and subject to licensing terms for the respective products, and permission must be obtained from the Publisher or the owner of the content, etc., in order to reproduce or network any portion of the textual material (in any media) that is contained in the Work. Mercury Learning and Information (“MLI” or “the Publisher”) and anyone involved in the creation, writing, or production of the companion files, accompanying algorithms, code, or computer programs (“the software”), and any accompanying Web site or software of the Work, cannot and do not warrant the performance or results that might be obtained by using the contents of the Work. The author, developers, and the Publisher have used their best efforts to insure the accuracy and functionality of the textual material and/or programs contained in this package; we, however, make no warranty of any kind, express or implied, regarding the performance of these contents or programs. The Work is sold “as is” without warranty (except for defective materials used in manufacturing the book or due to faulty workmanship). The sole remedy in the event of a claim of any kind is expressly limited to replacement of the book and/or companion files, and only at the discretion of the Publisher. The use of “implied warranty” and certain “exclusions” vary from state to state and might not apply to the purchaser of this product. The companion files are available for downloading by writing to the publisher at [email protected]
CGP_C++_CH00-FM_2E_2pp.indd 2
11/13/2020 4:25:32 PM
Computer Graphics Programming in OpenGL with C++ Second Edition
V. Scott Gordon, Ph.D. California State University, Sacramento
John Clevenger, Ph.D. California State University, Sacramento
Mercury Learning and Information Dulles, Virginia Boston, Massachusetts New Delhi
CGP_C++_CH00-FM_2E_2pp.indd 3
11/13/2020 4:25:32 PM
Copyright ©2021 by Mercury Learning and Information LLC. All rights reserved. This publication, portions of it, or any accompanying software may not be reproduced in any way, stored in a retrieval system of any type, or transmitted by any means, media, electronic display or mechanical display, including, but not limited to, photocopy, recording, Internet postings, or scan ning, without prior permission in writing from the publisher. Publisher: David Pallai Mercury Learning and Information 22841 Quicksilver Drive Dulles, VA 20166 [email protected] (800) 232-0223 Computer Graphics Programming in OpenGL with C++, Second Edition. V. Scott Gordon & John Clevenger. ISBN: 978-1-68392-672-6 The publisher recognizes and respects all marks used by companies, manufacturers, and developers as a means to distinguish their products. All brand names and product names mentioned in this book are trademarks or service marks of their respective companies. Any omission or misuse (of any kind) of service marks or trademarks, etc. is not an attempt to infringe on the property of others. Library of Congress Control Number: 2020946880 202122321_C++_CH00-FM_2E_2pp.indd 4
11/13/2020 4:25:32 PM
Contents Preface What’s New in this Edition Intended Audience How to Use This Book Acknowledgments About the Authors
xi xiii xiv xv xvii xix
Chapter 1 Getting Started
1
1 2 2 3 4 4 5 5 5
1.1 Languages and Libraries 1.1.1 C++ 1.1.2 OpenGL / GLSL 1.1.3 Window Management 1.1.4 Extension Library 1.1.5 Math Library 1.1.6 Texture Management 1.1.7 Optional Libraries 1.2 Installation and Configuration
Chapter 2 The OpenGL Graphics Pipeline
2.1 The OpenGL Pipeline 2.1.1 C++/OpenGL Application 2.1.2 Vertex and Fragment Shaders 2.1.3 Tessellation 2.1.4 Geometry Shader
CGP_C++_CH00-FM_2E_2pp.indd 5
7 8 9 12 17 18
11/13/2020 4:25:32 PM
vi ■ C o n ten ts
2.2 2.3 2.4 2.5 2.6
2.1.5 Rasterization 2.1.6 Fragment Shader 2.1.7 Pixel Operations Detecting OpenGL and GLSL Errors Reading GLSL Source Code from Files Building Objects from Vertices Animating a Scene Organizing the C++ Code Files
19 20 21 22 26 27 28 31
Chapter 3 Mathematical Foundations
35
36 36 37 39 40 41 42 43 45 46 47 48 51 51 53 54 56
3.1 3.2 3.3 3.4
3.5
3.6 3.7 3.8
3.9 3.10
3D Coordinate Systems Points Matrices Transformation Matrices 3.4.1 Translation 3.4.2 Scaling 3.4.3 Rotation Vectors 3.5.1 Uses for Dot Product 3.5.2 Uses for Cross Product Local and World Space Eye Space and the Synthetic Camera Projection Matrices 3.8.1 The Perspective Projection Matrix 3.8.2 The Orthographic Projection Matrix Look-At Matrix GLSL Functions for Building Matrix Transforms
Chapter 4 Managing 3D Graphics Data
61
62 65 66 67 68 78 79 82 85
4.1 4.2 4.3 4.4 4.5 4.6
Buffers and Vertex Attributes Uniform Variables Interpolation of Vertex Attributes Model-View and Perspective Matrices Our First 3D Program – a 3D Cube Rendering Multiple Copies of an Object 4.6.1 Instancing 4.7 Rendering Multiple Different Models in a Scene 4.8 Matrix Stacks
CGP_C++_CH00-FM_2E_2pp.indd 6
11/13/2020 4:25:32 PM
C onte nts ■ vii
4.9 Combating “Z-Fighting” Artifacts 4.10 Other Options for Primitives 4.11 Coding for Performance 4.11.1 Minimizing Dynamic Memory Allocation 4.11.2 Pre-Computing the Perspective Matrix 4.11.3 Back-Face Culling
92 93 95 95 97 98
Chapter 5 Texture Mapping
103
104 106 108 109 110 Textures – Additional OpenGL Details
Chapter 6 3D Models
6.1 Procedural Models – Building a Sphere 6.2 OpenGL Indexing – Building a Torus 6.2.1 The Torus 6.2.2 Indexing in OpenGL 6.3 Loading Externally Produced Models
Chapter 7 Lighting
7.1 7.2 7.3 7.4 7.5
Lighting Models Lights Materials ADS Lighting Computations Implementing ADS Lighting 7.5.1 Gouraud Shading 7.5.2 Phong Shading 7.6 Combining Lighting and Textures
CGP_C++_CH00-FM_2E_2pp.indd 7
111 112 114 119 120 122 124
129 129 138 138 140 145
159 159 161 164 166 169 170 178 183
11/13/2020 4:25:32 PM
viii ■ Co n te n ts
Chapter 8 Shadows
189
189 190 191 192
8.1 8.2 8.3 8.4
The Importance of Shadows Projective Shadows Shadow Volumes 199 205 208 208 209 213
Chapter 9 Sky and Backgrounds
219
219 222 224 224 227 231
9.1 Skyboxes 9.2 Skydomes 9.3 Implementing a Skybox 9.3.1 Building a Skybox from Scratch 9.3.2 Using OpenGL Cube Maps 9.4 Environment Mapping
Chapter 10 Enhancing Surface Detail
241
241 243 252
10.1 Bump Mapping 10.2 Normal Mapping 10.3 Height Mapping
Chapter 11 Parametric Surfaces
259
259 261 264 266
11.1 11.2 11.3 11.4
CGP_C++_CH00-FM_2E_2pp.indd 8
Quadratic Bézier Curves Cubic Bézier Curves Quadratic Bézier Surfaces Cubic Bézier Surfaces
11/13/2020 4:25:32 PM
C onte nts ■ ix
Chapter 12 Tessellation
271
271 277 284 291
12.1 12.2 12.3 12.4
Tessellation in OpenGL Tessellation for Bézier Surfaces Tessellation for Terrain / Height Maps Controlling Level of Detail (LOD)
Chapter 13 Geometry Shaders
297
297 299 303 304 307
13.1 13.2 13.3 13.4 13.5
Per-Primitive Processing in OpenGL Altering Primitives Deleting Primitives Adding Primitives Changing Primitive Types
Chapter 14 Other Techniques
311
311 314 320 322 328 333 337 342 347
14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 14.9
Fog Compositing / Blending / Transparency User-Defined Clipping Planes 3D Textures Noise Noise Application – Marble Noise Application – Wood Noise Application – Clouds Noise Application – Special Effects
Chapter 15 Simulating Water
15.1 15.2 15.3 15.4 15.5 15.6
Pool Surface and Floor Geometry Setup Adding Surface Reflection and Refraction Adding Surface Waves Additional Corrections Animating the Water Movement Underwater Caustics
Chapter 16 Ray Tracing and Compute Shaders
16.1 Compute Shaders 16.1.1 Compiling and Using Compute Shaders 16.1.2 Parallel Computing in Compute Shaders 16.1.3 Work Groups
CGP_C++_CH00-FM_2E_2pp.indd 9
353 353 358 369 372 376 378
383 385 385 386 390
11/13/2020 4:25:32 PM
x ■ C o n te n ts
16.1.4 Work Group Details 16.1.5 Work Group Limitations 16.2 Ray Casting 16.2.1 Defining the 2D Texture Image 16.2.2 Building and Displaying the Ray Cast Image 16.2.3 Ray-Sphere Intersection 16.2.4 Axis-Aligned Ray-Box Intersection
391 393 394 394 395 403 404 405 406 408 410 413 420 424 424 428 431 432 439 443 448
Chapter 17 Stereoscopy for 3D Glasses and VR Headsets
461
View and Projection Matrices for Two Eyes Anaglyph Rendering Side-by-Side Rendering Correcting Lens Distortion in Headsets A Simple Testing Hardware Configuration
463 465 468 469 477
Appendix A Installation and Setup for PC (Windows) Appendix B Installation and Setup for Macintosh Appendix C Using the Nsight Graphics Debugger
481 489 497
Index
503
17.1 17.2 17.3 17.4 17.5
CGP_C++_CH00-FM_2E_2pp.indd 10
11/13/2020 4:25:32 PM
Preface This book is designed primarily as a textbook for a typical computer science undergraduate course in that the reader may run them all as presented. One of the things that we hope is unique about this book is that we have strived to make it accessible C++. There are several advantages to learning graphics programming in C++: •
OpenGL’s native language is C, so a C++ program can make direct OpenGL function calls.
•
OpenGL applications written in C++ typically exhibit very high performance.
•
C++ offers modern programming constructs (classes, polymorphism, etc.) not available in C.
•
C++ is a popular language choice for using OpenGL, and a large number of instructional resources for OpenGL are available in C++.
CGP_C++_CH00-FM_2E_2pp.indd 11
11/13/2020 4:25:32 PM
xii ■ Pr e fa c e
It is worth mentioning that there do exist other language bindings for OpenGL. Popular alternatives exist for Java, C#, Python, and many others. This textbook focuses only on C++. Another thing that makes this book unique is that it has a “sister” textbook: Computer Graphics Programming in OpenGL with Java 2/E. The two books are organized in lockstep, with the same chapter and section numbers and topics, figures, exercises, and theoretical descriptions. Wherever possible, the code is organized similarly. Of course, the use of C++ versus Java. This book focuses on standard OpenGL. 1 JavaScript is a scripting language that can be used to embed code in webpages. It has strong
similarities to Java, but also many important differences.
CGP_C++_CH00-FM_2E_2pp.indd 12
11/13/2020 4:25:32 PM
Pre fa c e ■ xiii
the outputs of the example programs, and since this is an introductory text, the resulting scenes are unlikely to impress an expert. However, the techniques presented do constitute the foundational elements for producing today’s stunning 3D effects. We also haven’t tried to create an OpenGL “reference.” Our coverage of OpenGL represents only a tiny fraction of its capabilities. Rather, our aim is to use OpenGL as a vehicle for teaching the fundamentals of modern shader-based 3D graphics programming and provide the reader with a sufficiently deep understanding for further study.
What’s New in this Edition We have added three new chapters in this 2nd edition of Computer Graphics Programming in OpenGL using C++:. The new chapter on stereoscopy is motivated by the increased popularity of virtual reality. However, it is also applicable to the development of animation for “3D movies”, and we have tried to provide introductory coverage of both uses equally. As a result of these additions, this 2nd edition is larger than the previous edition.
CGP_C++_CH00-FM_2E_2pp.indd 13
11/13/2020 4:25:32 PM
xiv ■ P refa c e
Besides the new material, there are important revisions throughout the book. For example, we fixed bugs in our Torus class in Chapter 6 and made significant improvements to our noise map functions in Chapter 14. We expanded our Utils.cpp utility class to handle the loading of compute shaders. We also helped identify a bug in SOIL2 (now fixed) that affected Macintosh users attempting to load cubemaps. really have tried hard. that we don’t cover, because we assume the reader already has sufficient background. In particular, these are: • • • • • • •
C++ and its most commonly used libraries, such as the Standard Template Library; familiarity with using an Integrated Development Environment (IDE), such as Visual Studio; basic data structures and algorithms, such as linked lists, stacks, and queues, etc. recursion; event-driven programming concepts; basic matrix algebra and trigonometry; and awareness of color models, such as RGB, RGBA, etc.
It is hoped that the potential audience for this new book is further bolstered by the existence of its “sister” textbook, Computer Graphics Programming in OpenGL with Java. In particular, we envision a learning environment where students are free to utilize either C++ or Java in the same classroom, selecting one or the other book. The two texts cover the material sufficiently in lockstep that we have been able to conduct a graphics programming course successfully in this manner.
CGP_C++_CH00-FM_2E_2pp.indd 14
11/13/2020 4:25:32 PM
Pre fa c e ■ xv often involve long lists of parameters. While writing this book, the authors debated whether or not to, in each case, describe all of the parameters. We decided that in the early chapters we would describe every detail. Further into the book, OpenGL and the various libraries being used. For this, there are a number of excellent online resources that we recommend using in conjunction with this book. The documentation for OpenGL is absolutely essential; details on the various commands are available either by simply using Google to search for the command in question, or by visiting: Our examples utilize a mathematics library called GLM. After installing GLM (described in the appendices), the reader should locate the accompanying online documentation and bookmark it. At press time, the current link is:
CGP_C++_CH00-FM_2E_2pp.indd 15
11/13/2020 4:25:32 PM
xvi ■ P refa c e
Another library used throughout the book for which the reader may wish to periodically consult its documentation is SOIL2, which is used for loading and processing texture image files. SOIL2, and the image loading library stb on which it is based, doesn’t have a central documentation resource, but several examples are available via Google, and on their respective repositories:14]
•
(Luna) Introduction to 3D Game Programming with DirectX 12 [LU16]
Companion Files This book is accompanied by a companion disc that contains the following items: •
All of the C++]
Instructor Ancillaries Instructors in a college or university setting are encouraged to obtain the instructor ancillary package that is available for this book, which contains the following additional items:
CGP_C++_CH00-FM_2E_2pp.indd 16
11/13/2020 4:25:32 PM
Pre fa c e ■ xvii A lot of the content in this book is built from our first book Computer Graphics Programming in OpenGL with Java, which we drafted in 2016 for the CSc-155 (Advanced Computer Graphics Programming) course at CSU Sacramento. Many CSc-155 students actively contributed suggestions and bug fixes to an early draft first edition of the Java book while teaching CSc-155 for her first time, and kept a running log of questions and corrections for each chapter, which led to many improvements both for the second edition of the Java book, and the first edition of the C++ book. In Spring 2020 we tested our idea of allowing students (in our CSc-155 course) to select either C++ or Java, using the respective edition of this textbook. It was a sort of acid test of our “sister” textbook idea, and we were pleased with how things went. Again, students caught typos – Paul McHugh in particular caught and fixed an important memory leak in our 3D texture code._C++_CH00-FM_2E_2pp.indd 17
11/13/2020 4:25:32 PM
xviii ■ P re fa c e
Dr. Alan Mills, over the course of several months starting in early 2020, sent us over two hundred suggestions and corrections from his notes as he worked through our Java edition. About half of his notes were also applicable to the C++ edition. Among his many finds was a significant correction to the texture coordinates in the torus model. Alan’s attention to detail is amazing and we greatly appreciate the positive impact of his efforts on both books.. Martín Lucas Golini, who developed and maintains the SOIL2 texture image handling library, has been very supportive and enthusiastic about our book, and has been quickly responsive whenever any problems have arisen. His availability to us has been much appreciated. We wish to thank a few other artists and researchers who were gracious enough to allow us to utilize their models and textures. James Hastings-Trew of Planet Pixel Emporium provided many of the planetary surface textures..
CGP_C++_CH00-FM_2E_2pp.indd 18
11/13/2020 4:25:32 PM
Pre fa c e ■ xix
Errata If you find any errors in our book, please let us know! Despite our best efforts, this book.
References [AS14] E. Angel and D. Shreiner, Interactive Computer Graphics: A Top-Down Approach with WebGL, 7th ed. (Pearson, 2014). [KS16] J. Kessenich, G. Sellers, and D. Shreiner, OpenGL Programming Guide: The Official Guide to Learning OpenGL, Version 4.5 with SPIR-V, 9th ed. (AddisonWesley, 2016).
CGP_C++_CH00-FM_2E_2pp.indd 19
11/13/2020 4:25:33 PM
xx ■ Prefa c e
[LU16] F. Luna, Introduction to 3D Game Programming with DirectX 12, 2nd ed. (Mercury Learning, 2016). _C++_CH00-FM_2E_2pp.indd 20
11/13/2020 4:25:335 ■
■
■■■
Graphics programming has a reputation for being among the most challenging computer science topics to learn. These days, graphics programming is shader based—that is, some of the program is written in a standard language such as C++ or Java C++.
CGP_C++_CH01_2E_2pp.indd 1
05-Oct-20 3:57:22 PM
2 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
Using OpenGL with C++ requires configuring several libraries. At this time there is a dizzying array of options, depending on one’s individual needs. In this section, we describe which libraries are needed, some common options for each, and the option(s) that we will use throughout the book. Details on how to install and configure these libraries for use on your specific platform can be found in the Appendices. In summary, you will need languages and libraries for the following functions: • • • • •
C++ development environment OpenGL / GLSL window management extension library math library
•
texture management
It is likely that the reader will need to do several preparatory steps to ensure that each of these are installed and properly accessible on his/her system. In the following subsections we briefly describe each of them; see the Appendices for details on how to install and/or configure them for use.
1.1.1 C++ C++ is a general-purpose programming language that first appeared in the mid-1980s. Its design, and the fact that it is generally compiled to native machine code, make it an excellent choice for systems that require high performance, such as 3D graphics computing. Another advantage of C++ is that the OpenGL call library is C based. Many C++ development environments are available. In this textbook we recommend using Microsoft Visual Studio [VS20] if using a PC, and Xcode [XC18] if using a Macintosh. Descriptions for installing and configuring each of them, depending on your platform, are given in the Appendices.
1.1.2 OpenGL / GLSL Version 1.0 of OpenGL appeared in 1992 as an “open” alternative to vendorspecific Application Programming Interfaces (APIs) for computer graphics.
CGP_C++_CH01_2E_2pp.indd 2
05-Oct-20 3:57:22 PM
C ha pte r 1 · G e tting Sta rte d ■ 3vr” [GV20]. 1.1.3
Window Management
OpenGL doesn’t actually draw to a computer screen. Rather, it renders to a frame buffer, and it is the job of the individual machine to then draw the contents of the frame buffer onto a window on the screen. There are various libraries that support doing this. One option is to use the windowing capabilities provided by the operating system, such as the Microsoft Windows API. This is generally impractical and requires a lot of low-level coding. GLUT is a historically popular option; however, it is deprecated. A modernized extension is freeglut. Other related options are CPW, GLOW, and GLUI. One of the most popular options, and the one used in this book, is GLFW, which has built-in support for Windows, Macintosh, Linux, and other systems [GF20]. It can be downloaded from, and it must be compiled on the machine where it is to be used (we describe those steps in the Appendices).
1
Despite this, many graphics card manufacturers (notably NVIDIA) continue to support deprecated functionality.
CGP_C++_CH01_2E_2pp.indd 3
05-Oct-20 3:57:22 PM
4 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
1.1.4
Extension Library
OpenGL is organized around a set of base functions and an extension mechanism used to support new functionality as technologies advance. Modern versions of OpenGL, such as those found in version 4+ as we use in this book, require identifying the extensions available on the GPU. There are commands built into core OpenGL for doing this, but they involve several rather convoluted lines of code that would need to be performed for each modern command used—and in this book we use such commands constantly. Therefore, it has become standard practice to use an extension library to take care of these details, and to make modern OpenGL commands available to the programmer directly. Examples are Glee, GLLoader, GLEW, and more recently GL3W and GLAD. A commonly used library among those listed is GLEW, which stands for OpenGL Extension Wrangler. It is available for a variety of operating systems including Windows, Macintosh, and Linux [GE20]. GLEW is not a perfect choice; for example, it requires an additional DLL. Recently, many developers are choosing GL3W or GLAD. They have the advantage of being automatically updated, but they also require that Python be installed. For these reasons, in this book we have opted to use GLEW. It can be downloaded at glew.sourceforge.net. Complete instructions for installing and configuring GLEW are given in the appendices. 1.1.5
Math Library
3D graphics programming makes heavy use of vector and matrix algebra. For this reason, use of OpenGL is greatly facilitated by accompanying it with a function library or class package to support common mathematical tasks. Two such libraries that are frequently used with OpenGL are Eigen and vmath, the latter being used in the popular OpenGL SuperBible [SW15]. Arguably the most popular, and the one used in this book, is OpenGL Mathematics, usually called GLM. It is a header-only C++ library compatible with Windows, Macintosh, and Linux [GM20]. GLM commands conveniently use the same naming conventions as those in GLSL, making it easy to go back and forth when reading C++ and GLSL code used in a particular application. GLM is available for download at glm.g-truc.net. GLM provides classes and basic math functions related to graphics concepts, such as vector, matrix, and quaternion. It also contains a variety of utility classes for creating and using common 3D graphics structures, such as perspective and
CGP_C++_CH01_2E_2pp.indd 4
05-Oct-20 3:57:22 PM
C ha pte r 1 · G e tting Sta rte d ■ 5
look-at matrices. It was first released in 2005, and it is maintained by Christophe Riccio [GM20]. Instructions for installing GLM are given in the appendices. 1.1.6
Texture Management
Starting with Chapter 5, we will use image files to add “texture” to the objects in our graphics scenes. This means that we will frequently need to load such image files into our C++/OpenGL code. It is possible to code a texture image loader from scratch; however, given the wide variety of image file formats, it is generally preferable to use a texture loading library. Some examples are FreeImage, DevIL, OpenGL Image (GLI), and Glraw. Probably the most commonly used OpenGL image loading library is Simple OpenGL Image Loader (SOIL), although it has become somewhat outdated. The texture image loading library used in this book is SOIL2, an updated fork of SOIL. Like the previous libraries we have chosen, SOIL2 is compatible with a wide variety of platforms [SO20], and detailed installation and configuration instructions are given in the appendices. 1.1.7
Optional Libraries
There are many other helpful libraries that the reader may wish to utilize. For example, in this book we show how to implement a simple “OBJ” model loader from scratch. However, as we will see, it doesn’t handle many of the options available in the OBJ standard. Some examples of more sophisticated OBJ importers are Assimp and tinyobjloader. For our examples, we will just use our simple model loader described and implemented in this book.
1.2
INSTALLATION AND CONFIGURATION
While developing the C++ edition of this book, we wrestled with the best approach for including the platform-specific configuration information necessary to run the example programs. Configuring a system for using OpenGL with C++ is considerably more complicated than the equivalent configuration using Java, which can be described in just a few short paragraphs (as can be seen in the Java edition of the book [GC18]). Ultimately, we opted to separate installation and configuration information into individual platform-specific Appendices. We hope
CGP_C++_CH01_2E_2pp.indd 5
05-Oct-20 3:57:22 PM
6 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
that this will provide each reader with a single relevant place to look for information regarding his/her specific system, while at the same time avoiding bogging down the rest of the text with platform-specific details which may not be relevant to every reader. In this edition, we provide detailed configuration instructions for Microsoft Windows in Appendix A, and for the Apple Macintosh in Appendix B. Continually updated library installation instructions will be maintained on this textbook’s website, available at:
References [GC18] V. Gordon and J. Clevenger, Computer Graphics Programming in OpenGL with Java, 2nd ed. (Mercury Learning, 2018). [GE20] OpenGL Extension Wrangler (GLEW), accessed July 2020, .sourceforge.net/ [GF20] Graphics Library Framework (GLFW), accessed July 2020, .glfw.org/ [GM20] OpenGL Mathematics (GLM), accessed July 2020, .net/0.9.8/index.html [GV20] GLView, realtech-vr, accessed July 2020, home/glview [SO20] Simple OpenGL Image Library 2 (SOIL2), SpartanJ, accessed July 2020, [SW15] G. Sellers, R. Wright Jr., and N. Haemel, OpenGL SuperBible: Comprehensive Tutorial and Reference, 7th ed. (Addison-Wesley, 2015). [VS20] Microsoft Visual Studio downloads, accessed July 2020, .visualstudio.com/downloads [XC18] Apple Developer site for Xcode, accessed January 2018, .apple.com/xcode
CGP_C++_CH01_2E_2pp.indd 6
05-Oct-20 3:57:22 PM
Chapter
2
The OpenGL Graphics Pipeline 2.1 The OpenGL Pipeline����������������������������������������������������������������������������������������������������8 2.2 Detecting OpenGL and GLSL Errors������������������������������������������������������������������������23 C++ Code Files����������������������������������������������������������������������������������31
Supplemental Notes������������������������������������������������������������������������������������������������������32 ■
■
■■■
OpenGL (Open Graphics Library) is a multi-platform 2D and 3D graphics API that incorporates both hardware and software. Using OpenGL requires a graphics card (GPU) that supports a sufficiently up-to-date version of OpenGL (as described in Chapter 1). On the hardware side, OpenGL provides a multi-stage graphics pipeline that is partially programmable using a language called GLSL (OpenGL Shading Language). On the software side, OpenGL’s API is written in C, and thus the calls are directly compatible with C and C++. Stable language bindings (or “wrappers”) are available for more than a dozen other popular languages (Java, Perl, Python, Visual Basic, Delphi, Haskell, Lisp, Ruby, etc.) with virtually equivalent performance. This textbook uses C++, probably the most popular language choice. When using C++, the programmer writes code that runs on the CPU (compiled, of course) and includes OpenGL calls. We will refer to a C++ program that contains OpenGL calls as a C++/OpenGL application. One important task of a C++/OpenGL application is to install the programmer’s GLSL code onto the GPU. An overview of a C++-based graphics application is shown in Figure 2.1, with the software components highlighted in pink.
CGP_C++_CH02_2E_2pp.indd 7
05-Oct-20 4:00:57 PM
8 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
Some of the code we will write will be in C++, with OpenGL calls, and some will be written in GLSL. Our C++/OpenGL application will work together with our GLSL modules, and the hardware, to create our 3D graphics output. Once our application is complete, the end user will interact with the C++ application. GLSL is an example of a shader language. Shader languages are Figure 2.1 intended to run on a GPU, in the Overview of a C++-based graphics application. C++/OpenGL application code. For the rest of this chapter, we will take a brief “tour” of the OpenGL pipeline. The reader is not expected to understand every detail thoroughly but should just C++/OpenGL application sends graphics data into the vertex shader—processing proceeds through the pipeline, and pixels emerge for display on the monitor. The stages shaded in blue (vertex, tessellation, geometry, and fragment) are programmable in GLSL. It is one of the responsibilities of the C++/OpenGL application to load GLSL programs into these shader stages, as follows:
1. It uses C++ to obtain the GLSL shader code, either from text files or hardcoded as strings.
CGP_C++_CH02_2E_2pp.indd 8
05-Oct-20 4:00:57 PM
C h a pte r 2 · The O pe nG L G ra phic s Pipe line ■ 9.
C++/OpenGL Application
The bulk of our graphics application is written in C++. Depending on the purpose of the program, it may interact with the end user using standard C++ libraries. For tasks related to 3D rendering, it uses OpenGL calls. As described in the previous chapter, we will be using several additional libraries: GLEW (OpenGL Extension Wrangler), GLM (OpenGL Mathematics), SOIL2 (Simple OpenGL Image Loader), and GLFW (Graphics Library Framework). The GLFW library includes a class called GLFWwindow on which we can draw 3D scenes. As already mentioned, OpenGL also gives us commands for installing GLSL programs onto the programmable shader stages and compiling them. Finally, OpenGL uses buffers for sending 3D models and other related graphics data down the pipeline. Before we try writing shaders, let’s write a simple C++/OpenGL application that instantiates a GLFWwindow and sets its background color. Doing that won’t require any shaders at all! The code is shown in Program 2.1. The main() function shown in Program 2.1 is the same one that we will use throughout this textbook. Among the significant operations in main() are: (a) initializes the GLFW library, (b) instantiates a GLFWwindow, (c) initializes the GLEW library, (d) calls the function “init()” once, and (e) calls the function “display()” repeatedly. The “init()” function is where we will place application-specific initialization tasks. The display() method is where we place code that draws to the GLFWwindow.
CGP_C++_CH02_2E_2pp.indd 9
05-Oct-20 4:00:58 PM
10 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
In this example, the glClearColor() command specifies the color value to be applied when clearing the background—in this case (1,0,0,1), corresponding to the RGB values of the color red (plus a “1” for the opacity component). We then use the OpenGL call glClear(GL_COLOR_BUFFER_BIT) to actually fill the color buffer with that color.
Program 2.1 First C++/OpenGL Application #include #include #include using namespace std; void init(GLFWwindow* window) { } void display(GLFWwindow* window, double currentTime) { glClearColor(1.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); } int main(void) { if (!glfwInit()) { exit(EXIT_FAILURE); } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter2 - program1", NULL, NULL); glfwMakeContextCurrent(window); if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); } glfwSwapInterval(1); init(window); while (!glfwWindowShouldClose(window)) { display(window, glfwGetTime()); glfwSwapBuffers(window); glfwPollEvents(); } glfwDestroyWindow(window); glfwTerminate(); exit(EXIT_SUCCESS); }
The output of Program 2.1 is shown in Figure 2.3.
CGP_C++_CH02_2E_2pp.indd 10
05-Oct-20 4:00:58 PM
C h a p t e r 2 · The O pe nG L G ra phic s Pipe line ■ 11
The mechanism by which these functions are deployed is as follows: the GLFW and GLEW libraries are initialized using the commands glfwInit() and glewInit() respectively. The GLFW window and an associated OpenGL context1 are created with the glfwCreateWindow() command, with options set by any preFigure 2.3 ceding window hints. Our window hints Output of Program 2.1. specify that the machine must be compatible with OpenGL version 4.3 (“major”=4. and “minor”=3). The parameters on the glfwCreateWindow() command specify the width and height of the window (in pixels) and the title placed at the top of the window. (The additional two parameters which are set to NULL, and which we aren’t using, allow for full screen mode and resource sharing.) Vertical synchronization (VSync) is enabled by using the glfwSwapInterval() and glfwSwapBuffers() commands—GLFW windows are by default double-buffered.2 Note that creating the GLFW window doesn’t automatically make the associated OpenGL context current—for that reason we also call glfwMakeContextCurrent(). Our main() includes a very simple rendering loop that calls our display() function repeatedly. It also calls glfwSwapBuffers(), which paints the screen, and glfwPollEvents(), which handles other window-related events (such as a key being pressed). The loop terminates when GLFW detects an event that should close the window (such as the user clicking the “X” in the upper right corner). Note that we have included a reference to the GLFW window object on the init() and display() calls; those functions may in certain circumstances need access to it. We have also included the current time on the call to display(), which will be useful for ensuring that our animations run at the same speed regardless of the computer being used. For this purpose, we use glfwGetTime(), which returns the elapsed time since GLFW was initialized.
1
The term “context” refers to an OpenGL instance and its state information, which includes items such as the color buffer. 2 “Double buffering” means that there are two color buffers—one that is displayed, and one that is being rendered to. After an entire frame is rendered, the buffers are swapped. Double buffering is used to reduce undesirable visual artifacts.
CGP_C++_CH02_2E_2pp.indd 11
05-Oct-20 4:00:58 PM
12 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
Now is an appropriate time to take a closer look at the OpenGL calls in Program 2.1. Consider this one: glClear(GL_COLOR_BUFFER_BIT);
In this case, the OpenGL function being called, as described in the OpenGL reference documentation (available on the web at), is: void glClear(GLbitfield mask);
The parameter references a “GLbitfield” called “GL_COLOR_BUFFER_BIT”. OpenGL has many predefined constants (some of them are called enums); a call to glClearColor(). This allows us to specify the value placed in the elements of a color buffer when it is cleared. Here we have specified (1,0,0,1), which corresponds to the RGBA color red. Finally, our render loop exits when the user attempts to close the GLFW indow. At that time, our main() asks GLFW to destroy the window and terminate, w via calls to glfwDestroyWindow() and glfwTerminate() respectively. 2.1.2
Vertex and Fragment Shaders
Our first OpenGL C++/OpenGL application, or they can be hardcoded in the C++ code or even in the GLSL code.
CGP_C++_CH02_2E_2pp.indd 12
05-Oct-20 4:00:58 PM
C h a p t e r 2 · The O pe nG L G ra phic s Pipe line ■ 13
Before any of this can happen, the C++/OpenGL application must compile and link appropriate GLSL vertex and fragment shader programs, and then load them into the pipeline. We will see the commands for doing this shortly. The C++/OpenGL application also is responsible for telling OpenGL to construct triangles. We do this by using (…..#includes are the same as before ) #define numVAOs 1 GLuint renderingProgram; GLuint vao[numVAOs];
new declarations
GLuint createShaderProgram() { const char *vshaderSource = "#version 430 \n" "void main(void) \n" "{ gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }"; const char *fshaderSource = "#version 430 \n" "out vec4 color; \n"
CGP_C++_CH02_2E_2pp.indd 13
05-Oct-20 4:00:58 PM
14 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
"void main(void) \n" "{ color = vec4(0.0, 0.0, 1.0, 1.0); }"; GLuint vShader = glCreateShader(GL_VERTEX_SHADER); GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vShader, 1, &vshaderSource, NULL); glShaderSource(fShader, 1, &fshaderSource, NULL); glCompileShader(vShader); glCompileShader(fShader); GLuint vfProgram = glCreateProgram(); glAttachShader(vfProgram, vShader); glAttachShader(vfProgram, fShader); glLinkProgram(vfProgram); return vfProgram; } void init(GLFWwindow* window) { renderingProgram = createShaderProgram(); glGenVertexArrays(numVAOs, vao); glBindVertexArray(vao[0]); } void display(GLFWwindow* window, double currentTime) { glUseProgram(renderingProgram); glDrawArrays(GL_POINTS, 0, 1); } . . . main() same as before
The program appears to have output a blank window (see Figure 2.4). But close examination reveals a tiny blue dot in the center of the window (assuming that this printed page is of sufficient resolution). The default size of a point in OpenGL is one pixel.
Figure 2.4 Output of Program 2.2.
CGP_C++_CH02_2E_2pp.indd 14
There are many important details in Program 2.2 (color-coded in the program, for convenience) for us to discuss. First, note the frequent use of “GLuint”—this is a platform-independent shorthand for “unsigned int”, provided by OpenGL (many OpenGL constructs have integer references). Next, note that init() is no longer empty—it now calls another function
05-Oct-20 4:00:58 PM
C h a p t e r 2 · The O pe nG L G ra phic s Pipe line ■ 15
named “createShaderProgram()” (that we wrote). This function starts by declaring two shaders as character strings called vshaderSource and fshaderSource. It then calls glCreateShader() twice, which generates the two shaders of types GL_VERTEX_ SHADER and GL_FRAGMENT_SHADER. OpenGL creates each shader object ( initially empty), and returns an integer ID for each that is an index for referencing it later— our code stores this ID in the variables vShader and fShader. It then calls glShaderSource(), which loads the GLSL code from the strings into the empty shader objects. The shaders are then each compiled using glCompileShader(). glShaderSource() has four parameters: (a) the shader object in which to store the shader, (b) the number of strings in the shader source code, (c) an array of pointers to strings containing the source code, and (d) an additional parameter we aren’t using (it will be explained later, in the supplementary chapter notes). Note that the two calls specify the number of lines of code in each shader as being “1”—this too is explained in the supplementary notes. The application then creates a program object named vfProgram, and saves the integer ID that points to it. An OpenGL “program” object contains a series of compiled shaders, and here we see the following commands: glCreateProgram() to create the program object, glAttachShader() to attach each of the shaders to it, and then glLinkProgram() to request that the GLSL compiler ensure that they are compatible. As we saw earlier, after init() finishes, display() is called. One of the first things C++/OpenGL program in the explanations that follow). As we saw, they have been declared in the C++/OpenGL program as arrays of strings. This is a clumsy way to code, but it is sufficient in this very simple case. The vertex shader is: #version 430 void main(void) { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }
CGP_C++_CH02_2E_2pp.indd 15
05-Oct-20 4:00:58 PM
16 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
The first line indicates the OpenGL version, in this case 4.3. There follows a “main” function (as we will see, GLSL is somewhat C++—this is that at least one VAO be created whenever shaders are being used, even if the application isn’t using any buffers. So the two lines create the required VAO..
CGP_C++_CH02_2E_2pp.indd 16
05-Oct-20 4:00:58 PM
C h a p t e r 2 · The O pe nG L G ra phic s Pipe line ■ 17
Let’s add the following command in display(), right before the glDrawArrays() call: glPointSize(30.0f);
Now, when the rasterizer receives the vertex from the vertex shader, it will set pixel color values that form a point having a size of 30 pixels. The resulting output is shown in Figure 2.5.
Figure 2.5 Changing glPointSize.
Figure 2.6 Grid produced by tessellator.
CGP_C++_CH02_2E_2pp.indd 17
05-Oct-20 4:00:58 PM
18 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
much more efficient to have the tessellator in the GPU generate the triangle mesh in hardware, rather than doing it in C++. C++/OpenGL application side. C++ through a buffer.
CGP_C++_CH02_2E_2pp.indd 18
05-Oct-20 4:00:58 PM
C h a p t e r 2 · The O pe nG L G ra phic s Pipe line ■ 19
Figure 2.7 Torus model.
2.1.5
Figure 2.8 Torus modified in geometry shader.. If rasterization were to stop here, the resulting image would appear as wireframe. This is an option in OpenGL, by adding the following command in the display() function, before the call to glDrawArrays(): glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
If the torus shown previously in Section 2.1.4 is rendered with the addition of this line of code, it appears as shown in Figure 2.10.
CGP_C++_CH02_2E_2pp.indd 19
Figure 2.9 Rasterization (step 1).
05-Oct-20 4:00:59 PM
20 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E
Figure 2.10 Torus with wireframe rendering.
Figure 2.11 Fully rasterized triangle. and curvature of the torus is, achieve realistic lighting, and many more effects.
CGP_C++_CH02_2E_2pp.indd 20
05-Oct-20 4:00:59 PM
C h a p t e r 2 · The O pe nG L G ra phic s Pipe line ■ 21
Figure 2.12 Torus with fully rasterized primitives (left), and with wireframe grid superimposed (right).. 2.1.7
Pixel Operations
As objects in our scene are drawn in the display() function using the glDrawArrays() command, we usually expect objects in front to block our view of objects behind them. This
CGP_C++_CH02_2E_2pp.indd 21
Figure 2.13 Fragment shader color variation.
05-Oct-20 4:00:59 PM
22 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2 fragment
Before a scene is rendered, the depth buffer is filled with values representing maximum depth. As a pixel color is output by the fragment shader, its distance from the viewer is calculated.
• •
If the computed distance is less than the distance stored in the depth buffer (for that pixel), then: (a) the pixel color replaces the color in the color buffer, and (b) the distance replaces the value in the depth buffer. Otherwise, the pixel is discarded.
This procedure is called the Z-buffer algorithm, as expressed in Figure 2.14.
2.2
DETECTING OPENGL AND GLSL ERRORS
The workflow for compiling and running GLSL code differs from standard coding, in that GLSL compilation happens at C++ runtime. Another complication is that GLSL code doesn’t run on the CPU (it runs on the GPU), so the operating
CGP_C++_CH02_2E_2pp.indd 22
05-Oct-20 4:01:00 PM
C h a p t e r 2 · The O pe nG L G ra phic s Pipe line ■ 23
Figure 2.14 Z-buffer algorithm.
system cannot always catch OpenGL runtime errors. This makes debugging difficult, because it is often hard to detect if a shader failed, and why. Program 2.3 (which follows) presents some modules for catching and displaying GLSL errors. They make use of the OpenGL functions g lGetShaderiv() and glGetProgramiv(), which are used to provide information about compiled GLSL shaders and programs. Accompanying them is the createShaderProgram() function from the previous Program 2.2, but with the error-detecting calls added. Program 2.3 contains the following three utilities: • • •
checkOpenGLError – checks the OpenGL error flag for the occurrence of an OpenGL error printShaderLog – displays the contents of OpenGL’s log when GLSL compilation failed printProgramLog – displays the contents of OpenGL’s log when GLSL linking failed
The first, checkOpenGLError(), is useful for detecting both GLSL compilation errors and OpenGL runtime errors, so it is highly recommended to use it throughout a C++/OpenGL application during development. For example, in the prior example (Program 2.2), the calls to glCompileShader() and glLinkProgram() could easily be augmented with the code shown in Program 2.3 to ensure that any
CGP_C++_CH02_2E_2pp.indd 23
05-Oct-20 4:01:00 PM
24 ■ C o mp u te r G ra p h ic s P ro g ramming in O pe nG L w ith C ++, 2/E C++ program to stop. So unless the programmer takes steps to catch errors at the point that they happen, debugging will be very difficult.
Program 2.3 Modules to Catch GLSL Errors void printShaderLog(GLuint shader) { int len = 0; int chWrittn = 0; char *log; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); if (len > 0) { log = (char *)malloc(len); glGetShaderInfoLog(shader, len, &chWrittn, log); cout