Opengl Redbook Pdf Download Free
OpenGL Programming Guide: The Official Guide To Learning OpenGL, Version 4.5 With SPIR V Open GL 9e
OpenGL%20Programming%20Guide%209th%20Edition%20B01ITNCBU6
User Manual: Pdf
Open the PDF directly: View PDF
.
Page Count: 1486
Download from finelybook www.finelybook.com About This E-Book EPUB is an open, industry-standard format for e-books. However, support for EPUB and its many features varies across reading devices and applications. Use your device or app settings to customize the presentation to your liking. Settings that you can customize often include font, font size, single or double column, landscape or portrait mode, and figures that you can click or tap to enlarge. For additional information about the settings and features on your reading device or app, visit the device manufacturer's Web site. Many titles include programming code or configuration examples. To optimize the presentation of these elements, view the e-book in single-column, landscape mode and adjust the font size to the smallest setting. In addition to presenting code and configurations in the reflowable text format, we have included images of the code that mimic the presentation found in the print book; therefore, where the reflowable format may compromise the presentation of the code listing, you will see a "Click here to view code image" link. Click the link to view the print-fidelity code image. To return to the previous page viewed, click the Back button on your device or app. 2 Download from finelybook www.finelybook.com OpenGL® Programming Guide Ninth Edition The Official Guide to Learning OpenGL®, Version 4.5 with SPIR-V John Kessenich Graham Sellers Dave Shreiner Boston • Columbus • Indianapolis • New York • San Francisco • Amsterdam • Cape Town Dubai • London • Madrid • Milan • Munich • Paris • Montreal • Toronto • Delhi • Mexico City São Paulo • Sydney • Hong Kong • Seoul • Singapore • Taipei • Tokyo 3 Download from finelybook www.finelybook.com Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals. The authors and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein. For information about buying this title in bulk quantities, or for special sales opportunities (which may include electronic versions; custom cover designs; and content particular to your business, training goals, marketing focus, or branding interests), please contact our corporate sales department at corpsales@pearsoned.com or (800) 382-3419. For government sales inquiries, please contact governmentsales@pearsoned.com. For questions about sales outside the U.S., please contact intlcs@pearson.com. Visit us on the Web: informit.com/aw Library of Congress Control Number: 2016939338 Copyright © 2017 Pearson Education, Inc. All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, request forms and the appropriate contacts within the Pearson Education Global Rights & Permissions Department, please visit www.pearsoned.com/permissions/. ISBN-13: 978-0-13-449549-1 ISBN-10: 0-13-449549-7 Text printed in the United States on recycled paper at RR Donnelley in Crawfordsville, Indiana. 4 Download from finelybook www.finelybook.com First printing, July 2016 5 Download from finelybook www.finelybook.com Praise for previous editions of OpenGL® Programming Guide "Wow! This book is basically one-stop shopping for OpenGL information. It is the kind of book that I will be reaching for a lot. Thanks to Dave, Graham, John, and Bill for an amazing effort." —Mike Bailey, professor, Oregon State University "The most recent Red Book parallels the grand tradition of OpenGL; continuous evolution towards ever-greater power and efficiency. The eighth edition contains up-to-the minute information about the latest standard and new features, along with a solid grounding in modern OpenGL techniques that will work anywhere. The Red Book continues to be an essential reference for all new employees at my simulation company. What else can be said about this essential guide? I laughed, I cried, it was much better than Cats—I'll read it again and again." —Bob Kuehne, president, Blue Newt Software "OpenGL has undergone enormous changes since its inception twenty years ago. This new edition is your practical guide to using the OpenGL of today. Modern OpenGL is centered on the use of shaders, and this edition of the Programming Guide jumps right in, with shaders covered in depth in Chapter 2. It continues in later chapters with even more specifics on everything from texturing to compute shaders. No matter how well you know it or how long you've been doing it, if you are going to write an OpenGL program, you want to have a copy of the OpenGL® Programming Guide handy." —Marc Olano, associate professor, UMBC "If you are looking for the definitive guide to programming with the very latest version of OpenGL, look no further. The authors of this book have been deeply involved in the creation of OpenGL 4.3, and everything you need to know about the cutting edge of this industryleading API is laid out here in a clear, logical, and insightful manner." —Neil Trevett, president, Khronos Group 6 Download from finelybook www.finelybook.com 7 Download from finelybook www.finelybook.com To Brenda, Alison, and Noname —JMK To Chris, J., and Emily —GJAS To my family—Vicki, Bonnie, Bob, Cookie, Goatee, Phantom, Squiggles, Tuxedo, and Toby —DRS 8 Download from finelybook www.finelybook.com Contents Figures Tables Examples About This Guide What This Guide Contains What's New in This Edition What You Should Know Before Reading This Guide How to Obtain the Sample Code Errata Style Conventions About the OpenGL Series Acknowledgments 1. Introduction to OpenGL What Is OpenGL? Your First Look at an OpenGL Program OpenGL Syntax OpenGL's Rendering Pipeline Preparing to Send Data to OpenGL Sending Data to OpenGL Vertex Shading Tessellation Shading Geometry Shading Primitive Assembly Clipping Rasterization 9 Download from finelybook www.finelybook.com Fragment Shading Per-Fragment Operations Our First Program: A Detailed Discussion Entering main() OpenGL Initialization Our First OpenGL Drawing 2. Shader Fundamentals Shaders and OpenGL OpenGL's Programmable Pipeline An Overview of the OpenGL Shading Language Creating Shaders with GLSL Storage Qualifiers Statements Computational Invariance Shader Preprocessor Compiler Control Global Shader-Compilation Option Interface Blocks Uniform Blocks Specifying Uniform Blocks in Shaders Accessing Uniform Blocks from Your Application Buffer Blocks In/Out Blocks, Locations, and Components Compiling Shaders Shader Subroutines GLSL Subroutine Setup Selecting Shader Subroutines Separate Shader Objects SPIR-V 10 Download from finelybook www.finelybook.com Reasons to Choose SPIR-V Using SPIR-V with OpenGL Using GLSL to Generate SPIR-V for OpenGL Glslang What's Inside SPIR-V? 3. Drawing with OpenGL OpenGL Graphics Primitives Points Lines, Strips, and Loops Triangles, Strips, and Fans Data in OpenGL Buffers Creating and Allocating Buffers Getting Data into and out of Buffers Accessing the Content of Buffers Discarding Buffer Data Vertex Specification VertexAttribPointer in Depth Static Vertex-Attribute Specification OpenGL Drawing Commands Restarting Primitives Instanced Rendering 4. Color, Pixels, and Fragments Basic Color Theory Buffers and Their Uses Clearing Buffers Masking Buffers Color and OpenGL Color Representation and OpenGL Smoothly Interpolating Data 11 Download from finelybook www.finelybook.com Testing and Operating on Fragments Scissor Test Multisample Fragment Operations Stencil Test Stencil Examples Depth Test Blending Logical Operations Occlusion Query Conditional Rendering Multisampling Sample Shading Per-Primitive Antialiasing Antialiasing Lines Antialiasing Polygons Reading and Copying Pixel Data Copying Pixel Rectangles 5. Viewing Transformations, Culling, Clipping, and Feedback Viewing Viewing Model Camera Model Orthographic Viewing Model User Transformations Matrix Multiply Refresher Homogeneous Coordinates Linear Transformations and Matrices Transforming Normals OpenGL Matrices OpenGL Transformations Advanced: User Culling and Clipping 12 Download from finelybook www.finelybook.com Controlling OpenGL Transformations Transform Feedback Transform Feedback Objects Transform Feedback Buffers Configuring Transform Feedback Varyings Starting and Stopping Transform Feedback Transform Feedback Example—Particle System 6. Textures and Framebuffers Introduction to Texturing Basic Texture Types Creating and Initializing Textures Proxy Textures Specifying Texture Data Explicitly Setting Texture Data Loading Textures from Buffers Loading Images from Files Retrieving Texture Data Texture Data Layout Texture Formats Internal Formats External Formats Compressed Textures Sampler Objects Sampler Parameters Using Textures Texture Coordinates Arranging Texture Data Using Multiple Textures Complex Texture Types 13 Download from finelybook www.finelybook.com 3D Textures Array Textures Cube-Map Textures Shadow Samplers Depth-Stencil Textures Buffer Textures Texture Views Filtering Linear Filtering Using and Generating Mipmaps Calculating the Mipmap Level Mipmap Level-of-Detail Control Advanced Texture Lookup Functions Explicit Level of Detail Explicit Gradient Specification Texture Fetch with Offsets Projective Texturing Texture Queries in Shaders Gathering Texels Combining Special Functions Bindless Textures Texture Handles Texture Residency Sampling Bindless Textures Sparse Textures Sparse Texture Commitment Sparse Texture Pages Point Sprites Textured Point Sprites Controlling the Appearance of Points 14 Download from finelybook www.finelybook.com Framebuffer Objects Rendering to Texture Maps Discarding Rendered Data Renderbuffers Creating Renderbuffer Storage Framebuffer Attachments Framebuffer Completeness Invalidating Framebuffers Writing to Multiple Renderbuffers Simultaneously Selecting Color Buffers for Writing and Reading Dual-Source Blending Chapter Summary Texture Redux Texture Best Practices 7. Light and Shadow Lighting Introduction Classic Lighting Model Fragment Shaders for Different Light Styles Moving Calculations to the Vertex Shader Multiple Lights and Materials Lighting Coordinate Systems Limitations of the Classic Lighting Model Advanced Lighting Models Hemisphere Lighting Image-Based Lighting Lighting with Spherical Harmonics Shadow Mapping Creating a Shadow Map Using a Shadow Map 15 Download from finelybook www.finelybook.com 8. Procedural Texturing Procedural Texturing Regular Patterns Toy Ball Lattice Procedural Shading Summary Bump Mapping Application Setup Vertex Shader Fragment Shader Normal Maps Antialiasing Procedural Textures Sources of Aliasing Avoiding Aliasing Increasing Resolution Antialiasing High Frequencies Frequency Clamping Procedural Antialiasing Summary Noise Definition of Noise Noise Textures Trade-Offs A Simple Noise Shader Turbulence Marble Granite Wood Noise Summary Further Information 9. Tessellation Shaders 16 Download from finelybook www.finelybook.com Tessellation Shaders Tessellation Patches Tessellation Control Shaders Generating Output-Patch Vertices Tessellation Control Shader Variables Controlling Tessellation Tessellation Evaluation Shaders Specifying the Primitive Generation Domain Specifying the Face Winding for Generated Primitives Specifying the Spacing of Tessellation Coordinates Additional Tessellation Evaluation Shader layout Options Specifying a Vertex's Position Tessellation Evaluation Shader Variables A Tessellation Example: The Teapot Processing Patch Input Vertices Evaluating Tessellation Coordinates for the Teapot Additional Tessellation Techniques View-Dependent Tessellation Shared Tessellated Edges and Cracking Displacement Mapping 10. Geometry Shaders Creating a Geometry Shader Geometry Shader Inputs and Outputs Geometry Shader Inputs Special Geometry Shader Primitives Geometry Shader Outputs Producing Primitives Culling Geometry Geometry Amplification 17 Download from finelybook www.finelybook.com Advanced Transform Feedback Multiple Output Streams Primitive Queries Using Transform Feedback Results Geometry Shader Instancing Multiple Viewports and Layered Rendering Viewport Index Layered Rendering Chapter Summary Geometry Shader Redux Geometry Shader Best Practices 11. Memory Using Textures for Generic Data Storage Binding Textures to Image Units Reading and Writing to Images Shader Storage Buffer Objects Writing Structured Data Atomic Operations and Synchronization Atomic Operations on Images Atomic Operations on Buffers Sync Objects Image Qualifiers and Barriers High-Performance Atomic Counters Example: Order-Independent Transparency Principles of Operation Initialization Rendering Sorting and Blending Results 18 Download from finelybook www.finelybook.com 12. Compute Shaders Overview Workgroups and Dispatch Knowing Where You Are Communication and Synchronization Communication Synchronization Examples Physical Simulation Image Processing Chapter Summary Compute Shader Redux Compute Shader Best Practices A. Support Libraries Basics of GLFW: The OpenGL Utility Framework Initializing and Creating a Window Handling User Input Controlling the Window Shutting Down Cleanly GL3W: OpenGL Glue B. OpenGL ES and WebGL OpenGL ES WebGL Setting Up WebGL Within an HTML5 Page Initializing Shaders in WebGL Initializing Vertex Data in WebGL Using Texture Maps in WebGL C. Built-in GLSL Variables and Functions 19 Download from finelybook www.finelybook.com Built-in Variables Built-in Variable Declarations Built-in Variable Descriptions Built-in Constants Built-in Functions Angle and Trigonometry Functions Exponential Functions Common Functions Floating-Point Pack and Unpack Functions Geometric Functions Matrix Functions Vector Relational Functions Integer Functions Texture Functions Atomic-Counter Functions Atomic Memory Functions Image Functions Fragment Processing Functions Geometry Shader Functions Shader Invocation Control Functions Shader Memory Control Functions D. State Variables The Query Commands OpenGL State Variables Current Values and Associated Data Vertex Array Object State Vertex Array Data Buffer Object State Transformation State 20 Download from finelybook www.finelybook.com Coloring State Rasterization State Multisampling Textures Pixel Operations Framebuffer Controls Framebuffer State Renderbuffer State Pixel State Shader Object State Shader Program Pipeline Object State Shader Program Object State Program Interface State Program Object Resource State Vertex and Geometry Shader State Query Object State Image State Transform Feedback State Atomic Counter State Shader Storage Buffer State Sync Object State Hints Compute Dispatch State Implementation-Dependent Values Tessellation Shader Implementation-Dependent Limits Geometry Shader Implementation-Dependent Limits Fragment Shader Implementation-Dependent Limits Implementation-Dependent Compute Shader Limits Implementation-Dependent Shader Limits Implementation-Dependent Debug Output State 21 Download from finelybook www.finelybook.com Implementation-Dependent Values Internal Format-Dependent Values Implementation-Dependent Transform Feedback Limits Framebuffer-Dependent Values Miscellaneous E. Homogeneous Coordinates and Transformation Matrices Homogeneous Coordinates Transforming Vertices Transforming Normals Transformation Matrices Translation Scaling Rotation Perspective Projection Orthographic Projection F. Floating-Point Formats for Textures, Framebuffers, and Renderbuffers Reduced-Precision Floating-Point Values 16-Bit Floating-Point Values 10- and 11-Bit Unsigned Floating-Point Values G. Debugging and Profiling OpenGL Creating a Debug Context Debug Output Debug Messages Filtering Messages Application-Generated Messages Debug Groups Naming Objects 22 Download from finelybook www.finelybook.com Profiling Profiling Tools In-Application Profiling H. Buffer Object Layouts Using Standard Layout Qualifiers The std140 Layout Rules The std430 Layout Rules Glossary Index 23 Download from finelybook www.finelybook.com Figures Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure 1.1 Image from our first OpenGL program: triangles.cpp 1.2 OpenGL pipeline 2.1 Shader-compilation command sequence 3.1 Vertex layout for a triangle strip 3.2 Vertex layout for a triangle fan 3.3 Packing of elements in a BGRA-packed vertex attribute 3.4 Packing of elements in a RGBA-packed vertex attribute 3.5 Simple example of drawing commands 3.6 Using primitive restart to break a triangle strip 3.7 Two triangle strips forming a cube 3.8 Result of rendering with instanced vertex attributes 3.9 Result of instanced rendering using gl_InstanceID 4.1 Region occupied by a pixel 4.2 Polygons and their depth slopes 4.3 Aliased and antialiased lines 5.1 Steps to configure and position the viewing frustum 5.2 Coordinate systems required by OpenGL 5.3 User coordinate systems unseen by OpenGL 5.4 A view frustum 5.5 Pipeline subset for user/shader part of transforming coordinates 5.6 One-dimensional homogeneous space 5.7 Translating by skewing 5.8 Translating an object 2.5 in the x direction 5.9 Scaling an object to three times its size 5.10 Scaling an object in place 5.11 Rotation 24 Download from finelybook www.finelybook.com Figure 5.12 Rotating in place Figure 5.13 Frustum projection Figure 5.14 Orthographic projection Figure 5.15 z precision Figure 5.16 Transform feedback varyings packed in a single buffer Figure 5.17 Transform feedback varyings packed in separate buffers Figure 5.18 Transform feedback varyings packed into multiple buffers Figure 5.19 Schematic of the particle system simulator Figure 5.20 Result of the particle system simulator Figure 6.1 Byte-swap effect on byte, short, and integer data Figure 6.2 Subimage identified by *SKIP_ROWS, *SKIP_PIXELS, and *ROW_LENGTH parameters Figure 6.3 *IMAGE_HEIGHT pixel storage mode Figure 6.4 *SKIP_IMAGES pixel storage mode Figure 6.5 Output of the simple textured quad example Figure 6.6 Effect of different texture wrapping modes Figure 6.7 Two textures used in the multitexture example Figure 6.8 Output of the simple multitexture example Figure 6.9 Output of the volume texture example Figure 6.10 A sky box, shown as seen from the outside, from close up, and from the center Figure 6.11 A golden environment mapped torus Figure 6.12 A visible seam in a cube map Figure 6.13 The effect of seamless cube-map filtering Figure 6.14 Effect of texture minification and magnification Figure 6.15 Resampling of a signal in one dimension Figure 6.16 Bilinear resampling Figure 6.17 A prefiltered mipmap pyramid Figure 6.18 Effects of minification mipmap filters Figure 6.19 Illustration of mipmaps using unrelated colors 25 Download from finelybook www.finelybook.com Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure 6.20 Result of the simple textured point sprite example 6.21 Analytically calculated point sprites 6.22 Smooth edges of circular point sprites 6.23 Close-up of RGB color elements in an LCD panel 7.1 Elements of the classic lighting model 7.2 A sphere illuminated using the hemisphere lighting model 7.3 Analytic hemisphere lighting function 7.4 Lighting model comparison 7.5 Light probe image 7.6 Lat-long map 7.7 Cube map 7.8 Effects of diffuse and specular environment maps 7.9 Spherical harmonics lighting 7.10 Depth rendering 7.11 Final rendering of shadow map 8.1 Procedurally striped torus 8.2 Stripes close-up 8.3 Brick patterns 8.4 Visualizing the results of the half-space distance calculations 8.5 Intermediate results from "in" or "out" computation 8.6 Intermediate results from the toy ball shader 8.7 The lattice shader applied to the cow model 8.8 Inconsistently defined tangents leading to large lighting errors 8.9 Simple box and torus with procedural bump mapping 8.10 Normal mapping 8.11 Aliasing artifacts caused by point sampling 8.12 Supersampling 8.13 Using the s texture coordinate to create stripes on a sphere 8.14 Antialiasing the stripe pattern 26 Download from finelybook www.finelybook.com Figure 8.15 Visualizing the gradient Figure 8.16 Effect of adaptive analytical antialiasing on striped teapots Figure 8.17 The periodic step function Figure 8.18 Periodic step function (pulse train) and its integral Figure 8.19 Brick shader with and without antialiasing Figure 8.20 Checkerboard pattern Figure 8.21 A discrete 1D noise function Figure 8.22 A continuous 1D noise function Figure 8.23 Varying the frequency and the amplitude of the noise function Figure 8.24 Summing noise functions: the result of summing noise functions of different amplitude and frequency Figure 8.25 Basic 2D noise, at frequencies 4, 8, 16, and 32 (contrast enhanced) Figure 8.26 Summed noise, at 1, 2, 3, and 4 octaves (contrast enhanced) Figure 8.27 Teapots rendered with noise shaders Figure 8.28 Absolute-value noise or "turbulence" Figure 8.29 A bust of Beethoven rendered with the wood shader Figure 9.1 Quad tessellation Figure 9.2 Isoline tessellation Figure 9.3 Triangle tessellation Figure 9.4 Even and odd tessellation Figure 9.5 The tessellated patches of the teapot Figure 9.6 Tessellation cracking Figure 10.1 Lines adjacency sequence Figure 10.2 Line-strip adjacency sequence Figure 10.3 Triangles adjacency sequence Figure 10.4 Triangle-strip adjacency layout Figure 10.5 Triangle-strip adjacency sequence Figure 10.6 Texture used to represent hairs in the fur rendering example Figure 10.7 The output of the fur rendering example 27 Download from finelybook www.finelybook.com Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure 10.8 Schematic of geometry shader sorting example 10.9 Final output of geometry shader sorting example 10.10 Output of the viewport-array example 11.1 Output of the simple load-store shader 11.2 Timeline exhibited by the naïve overdraw counter shader 11.3 Output of the naïve overdraw counter shader 11.4 Output of the atomic overdraw counter shader 11.5 Cache hierarchy of a fictitious GPU 11.6 Data structures used for order-independent transparency 11.7 Inserting an item into the per-pixel linked lists 11.8 Result of order-independent transparency 12.1 Schematic of a compute workload 12.2 Relationship of global and local invocation ID 12.3 Output of the physical simulation program as simple points 12.4 Output of the physical simulation program 12.5 Image processing 12.6 Image processing artifacts B.1 WebGL demo G.1 AMD's GPUPerfStudio2 profiling Unigine Heaven 3.0 G.2 Screen Shot of Unigine Heaven 3.0 28 Download from finelybook www.finelybook.com Tables Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table 1.1 Command Suffixes and Argument Data Types 1.2 Example of Determining Parameters for glVertexAttribPointer() 2.1 Basic Data Types in GLSL 2.2 Implicit Conversions in GLSL 2.3 GLSL Vector and Matrix Types 2.4 Vector Component Accessors 2.5 GLSL Type Modifiers 2.6 GLSL Operators and Their Precedence 2.7 GLSL Control-Flow Statements 2.8 GLSL Function Parameter Access Modifiers 2.9 GLSL Preprocessor Directives 2.10 GLSL Preprocessor Predefined Macros 2.11 GLSL Extension Directive Modifiers 2.12 Layout Qualifiers for Uniform 3.1 OpenGL Primitive Mode Tokens 3.2 Buffer Binding Targets 3.3 Buffer Flags 3.4 Access Modes for glMapBuffer() 3.5 Flags for Use with glMapNamedBufferRange() 3.6 Values of type for glVertexAttribPointer() 4.1 Converting Data Values to Normalized Floating-Point Values 4.2 Query Values for the Stencil Test 4.3 Source and Destination Blending Factors 4.4 Blending Equation Mathematical Operations 4.5 Sixteen Logical Operations 4.6 Values for Use with glHint() 29 Download from finelybook www.finelybook.com Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Table Modes. Table Table Table Table Table Table Table Table Table Table 4.7 glReadPixels() Data Formats 4.8 Data Types for glReadPixels() 5.1 Drawing Modes Allowed During Transform Feedback 6.1 Texture Targets and Corresponding Sampler Types 6.2 Texture Targets and Corresponding Proxy Targets 6.3 Sized Internal Formats 6.4 External Texture Formats 6.5 Example Component Layouts for Packed Pixel Formats 6.6 Target Compatibility for Texture Views 6.7 Internal Format Compatibility for Texture Views 6.8 Framebuffer Attachments 6.9 Errors Returned by glCheckFramebufferStatus() 7.1 Spherical Harmonic Coefficients for Light-Probe Images 9.1 Tessellation Control Shader Input Variables 9.2 Evaluation Shader Primitive Types 9.3 Options for Controlling Tessellation Level Effects 9.4 Tessellation Control Shader Input Variables 10.1 Geometry Shader Primitive Types and Accepted Drawing 10.2 Geometry Shader Primitives and the Vertex Count for Each 10.3 Provoking Vertex Selection by Primitive Mode 10.4 Ordering of Cube-Map Face Indices 11.1 Generic Image Types in GLSL 11.2 Image Format Qualifiers B.1 Type Strings for WebGL Shaders B.2 WebGL Typed Arrays C.1 Cube-Map Face Targets C.2 Notation for Argument or Return Type D.1 Current Values and Associated Data 30 Download from finelybook www.finelybook.com Table D.2 State Variables for Vertex Array Objects Table D.3 State Variables for Vertex Array Data (Not Stored in a Vertex Array Object) Table D.4 State Variables for Buffer Objects Table D.5 Transformation State Variables Table D.6 State Variables for Controlling Coloring Table D.7 State Variables for Controlling Rasterization Table D.8 State Variables for Multisampling Table D.9 State Variables for Texture Units Table D.10 State Variables for Texture Objects Table D.11 State Variables for Texture Images Table D.12 State Variables Per Texture Sampler Object Table D.13 State Variables for Pixel Operations Table D.14 State Variables Controlling Framebuffer Access and Values Table D.15 State Variables for Framebuffers Per Target Table D.16 State Variables for Framebuffer Objects Table D.17 State Variables for Framebuffer Attachments Table D.18 Renderbuffer State Table D.19 State Variables Per Renderbuffer Object Table D.20 State Variables Controlling Pixel Transfers Table D.21 State Variables for Shader Objects Table D.22 State Variables for Program Pipeline Object State Table D.23 State Variables for Shader Program Objects Table D.24 State Variables for Program Interfaces Table D.25 State Variables for Program Object Resources Table D.26 State Variables for Vertex and Geometry Shader State Table D.27 State Variables for Query Objects Table D.28 State Variables Per Image Unit Table D.29 State Variables for Transform Feedback 31 Download from finelybook www.finelybook.com Table D.30 State Variables for Atomic Counters Table D.31 State Variables for Shader Storage Buffers Table D.32 State Variables for Sync Objects Table D.33 Hints Table D.34 State Variables for Compute Shader Dispatch Table D.35 State Variables Based on Implementation-Dependent Values Table D.36 State Variables for Implementation-Dependent Tessellation Shader Values Table D.37 State Variables for Implementation-Dependent Geometry Shader Values Table D.38 State Variables for Implementation-Dependent Fragment Shader Values Table D.39 State Variables for Implementation-Dependent Compute Shader Limits Table D.40 State Variables for Implementation-Dependent Shader Limits Table D.41 State Variables for Debug Output State Table D.42 Implementation-Dependent Values Table D.43 Internal Format-Dependent Values Table D.44 Implementation-Dependent Transform Feedback Limits Table D.45 Framebuffer-Dependent Values Table D.46 Miscellaneous State Values Table F.1 Reduced-Precision Floating-Point Formats Table H.1 std140 Layout Rules Table H.2 std430 Layout Rules 32 Download from finelybook www.finelybook.com Examples Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Restart Example Example Example Example Example Example Example Example Example 1.1 triangles.cpp: Our First OpenGL Program 1.2 Vertex Shader for triangles.cpp: triangles.vert 1.3 Fragment Shader for triangles.cpp: triangles.frag 2.1 A Simple Vertex Shader 2.2 Obtaining a Uniform Variable's Index and Assigning Values 2.3 Declaring a Uniform Block 2.4 Initializing Uniform Variables in a Named Uniform Block 2.5 Static Shader Control Flow 2.6 Declaring a Set of Subroutines 3.1 Initializing a Buffer Object with glNamedBufferStorage() 3.2 Initializing a Buffer Object with glMapBuffer() 3.3 Declaration of the DrawArraysIndirectCommand Structure 3.4 Declaration of the DrawElementsIndirectCommand Structure 3.5 Setting Up for the Drawing Command Example 3.6 Drawing Commands Example 3.7 Intializing Data for a Cube Made of Two Triangle Strips 3.8 Drawing a Cube Made of Two Triangle Strips Using Primitive 3.9 Vertex Shader Attributes for the Instancing Example 3.10 Example Setup for Instanced Vertex Attributes 3.11 Instanced Attributes Example Vertex Shader 3.12 Instancing Example Drawing Code 3.13 gl_VertexID Example Vertex Shader 3.14 Example Setup for Instanced Vertex Attributes 4.1 Specifying Vertex Color and Position Data: gouraud.cpp 4.2 A Simple Vertex Shader for Gouraud Shading 4.3 A Simple Fragment Shader for Gouraud Shading 33 Download from finelybook www.finelybook.com Example 4.4 Using the Stencil Test: stencil.c Example 4.5 Rendering Geometry with Occlusion Query: occquery.c Example 4.6 Retrieving the Results of an Occlusion Query Example 4.7 Rendering Using Conditional Rendering Example 4.8 A Multisample-Aware Fragment Shader Example 4.9 Setting Up Blending for Antialiasing Lines: antilines.cpp Example 5.1 Multiplying Multiple Matrices in a Vertex Shader Example 5.2 Simple Use of gl_ClipDistance Example 5.3 Example Initialization of a Transform Feedback Buffer Example 5.4 Application Specification of Transform Feedback Varyings Example 5.5 Leaving Gaps in a Transform Feedback Buffer Example 5.6 Assigning Transform Feedback Outputs to Different Buffers Example 5.7 Assigning Transform Feedback Outputs to Different Buffers Example 5.8 Shader Declaration of Transform Feedback in a Single Buffer Example 5.9 Shader Declaration of Transform Feedback in Multiple Buffers Example 5.10 Shader Declaration of Transform Feedback Varyings in Multiple Buffers Example 5.11 Vertex Shader Used in Geometry Pass of Particle System Simulator Example 5.12 Configuring the Geometry Pass of the Particle System Simulator Example 5.13 Vertex Shader Used in Simulation Pass of Particle System Simulator Example 5.14 Configuring the Simulation Pass of the Particle System Simulator Example 5.15 Main Rendering Loop of the Particle System Simulator Example 6.1 Direct Specification of Image Data in C Example 6.2 Loading Static Data into Texture Objects Example 6.3 Loading Data into a Texture Using a Buffer Object 34 Download from finelybook www.finelybook.com Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Shader Example Example Example Example Example Example Example Example Example 6.4 Definition of the vglImageData Structure 6.5 Simple Image Loading Example 6.6 Loading a Texture Using loadImage 6.7 Simple Texture Lookup Example (Fragment Shader) 6.8 Simple Texture Lookup Example (Vertex Shader) 6.9 Simple Texturing Example 6.10 Setting the Border Color of a Sampler 6.11 Texture Swizzle Example 6.12 Simple Multitexture Example (Vertex Shader) 6.13 Simple Multitexture Example (Fragment Shader) 6.14 Simple Multitexture Example 6.15 Simple Volume Texture Vertex Shader 6.16 Simple Volume Texture Fragment Shader 6.17 Initializing a Cube-Map Texture 6.18 Initializing a Cube-Map Array Texture 6.19 Simple Sky Box Example—Vertex Shader 6.20 Simple Sky Box Example—Fragment Shader 6.21 Cube-Map Environment Mapping Example—Vertex Shader 6.22 Cube-Map Environment Mapping Example—Fragment 6.23 Creating and Initializing a Buffer Texture 6.24 Texel Lookups from a Buffer Texture 6.25 Creating a Texture View with a New Format 6.26 Creating a Texture View with a New Target 6.27 Using Bindless Texture Handles in a Shader 6.28 Allocating a Large Sparse Texture 6.29 Simple Point Sprite Vertex Shader 6.30 Simple Point Sprite Fragment Shader 6.31 Analytic Shape Fragment Shader 35 Download from finelybook www.finelybook.com Example Example Example Example Example Outputs Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example 6.32 Attaching a Texture Level as a Framebuffer Attachment 6.33 Creating a 256 × 256 RGBA Color Renderbuffer 6.34 Attaching a Renderbuffer for Rendering 6.35 Specifying layout Qualifiers for MRT Rendering 6.36 Layout Qualifiers Specifying the Index of Fragment Shader 7.1 Setting Final Color Values with No Lighting 7.2 Ambient Lighting 7.3 Directional Light Source Lighting 7.4 Point-Light Source Lighting 7.5 Spotlight Lighting 7.6 Point-Light Source Lighting in the Vertex Shader 7.7 Structure for Holding Light Properties 7.8 Multiple Mixed Light Sources 7.9 Structure to Hold Material Properties 7.10 Code Snippets for Using an Array of Material Properties 7.11 Front and Back Material Properties 7.12 Vertex Shader for Hemisphere Lighting 7.13 Shaders for Image-Based Lighting 7.14 Shaders for Spherical Harmonics Lighting 7.15 Creating a Framebuffer Object with a Depth Attachment 7.16 Setting up the Matrices for Shadow-Map Generation 7.17 Simple Shader for Shadow-Map Generation 7.18 Rendering the Scene from the Light's Point of View 7.19 Matrix Calculations for Shadow-Map Rendering 7.20 Vertex Shader for Rendering from Shadow Maps 7.21 Fragment Shader for Rendering from Shadow Maps 8.1 Vertex Shader for Drawing Stripes 8.2 Fragment Shader for Drawing Stripes 36 Download from finelybook www.finelybook.com Example 8.3 Vertex Shader for Drawing Bricks Example 8.4 Fragment Shader for Drawing Bricks Example 8.5 Values for Uniform Variables Used by the Toy Ball Shader Example 8.6 Vertex Shader for Drawing a Toy Ball Example 8.7 Fragment Shader for Drawing a Toy Ball Example 8.8 Fragment Shader for Procedurally Discarding Part of an Object Example 8.9 Vertex Shader for Doing Procedural Bump Mapping Example 8.10 Fragment Shader for Procedural Bump Mapping Example 8.11 Fragment Shader for Adaptive Analytic Antialiasing Example 8.12 Source Code for an Antialiased Brick Fragment Shader Example 8.13 Source Code for an Antialiased Checkerboard Fragment Shader Example 8.14 C Function to Generate a 3D Noise Texture Example 8.15 A Function for Activating the 3D Noise Texture Example 8.16 Cloud Vertex Shader Example 8.17 Fragment Shader for Cloudy-Sky Effect Example 8.18 Sun Surface Fragment Shader Example 8.19 Fragment Shader for Marble Example 8.20 Granite Fragment Shader Example 8.21 Fragment Shader for Wood Example 9.1 Specifying Tessellation Patches Example 9.2 Passing Through Tessellation Control Shader Patch Vertices Example 9.3 Tessellation Levels for Quad Domain Tessellation Illustrated in Figure 9.1 Example 9.4 Tessellation Levels for an Isoline Domain Tessellation Shown in Figure 9.2 Example 9.5 Tessellation Levels for a Triangular Domain Tessellation Shown in Figure 9.3. Example 9.6 A Sample Tessellation Evaluation Shader 37 Download from finelybook www.finelybook.com Example 9.7 gl_in Parameters for Tessellation Evaluation Shaders Example 9.8 Tessellation Control Shader for Teapot Example Example 9.9 The main Routine of the Teapot Tessellation Evaluation Shader Example 9.10 Definition of B(i, u) for the Teapot Tessellation Evaluation Shader Example 9.11 Computing Tessellation Levels Based on View-Dependent Parameters Example 9.12 Specifying Tessellation Level Factors Using Perimeter Edge Centers Example 9.13 Displacement Mapping in main Routine of the Teapot Tessellation Evaluation Shader Example 10.1 A Simple Pass-Through Geometry Shader Example 10.2 Geometry Shader Layout Qualifiers Example 10.3 Implicit Declaration of gl_in[] Example 10.4 Implicit Declaration of Geometry Shader Outputs Example 10.5 A Geometry Shader That Drops Everything Example 10.6 Geometry Shader Passing Only Odd-Numbered Primitives Example 10.7 Fur Rendering Geometry Shader Example 10.8 Fur Rendering Fragment Shader Example 10.9 Global Layout Qualifiers Used to Specify a Stream Map Example 10.10 Example 10.9 Rewritten to Use Interface Blocks Example 10.11 Incorrect Emission of Vertices into Multiple Streams Example 10.12 Corrected Emission of Vertices into Multiple Streams Example 10.13 Assigning Transform Feedback Outputs to Buffers Example 10.14 Simple Vertex Shader for Geometry Sorting Example 10.15 Geometry Shader for Geometry Sorting Example 10.16 Configuring Transform Feedback for Geometry Sorting Example 10.17 Pass-Through Vertex Shader Used for Geometry Shader Sorting 38 Download from finelybook www.finelybook.com Example 10.18 OpenGL Setup Code for Geometry Shader Sorting Example 10.19 Rendering Loop for Geometry Shader Sorting Example 10.20 Geometry Amplification Using Nested Instancing Example 10.21 Directing Geometry to Different Viewports with a Geometry Shader Example 10.22 Creation of Matrices for Viewport Array Example Example 10.23 Specifying Four Viewports Example 10.24 Example Code to Create an FBO with an Array Texture Attachment Example 10.25 Geometry Shader for Rendering into an Array Texture Example 11.1 Examples of Image Format Layout Qualifiers Example 11.2 Creating, Allocating, and Binding a Texture to an Image Unit Example 11.3 Creating and Binding a Buffer Texture to an Image Unit Example 11.4 Simple Shader Demonstrating Loading and Storing into Images Example 11.5 Simple Declaration of a Buffer Block Example 11.6 Creating a Buffer and Using It for Shader Storage Example 11.7 Declaration of Structured Data Example 11.8 Naïvely Counting Overdraw in a Scene Example 11.9 Counting Overdraw with Atomic Operations Example 11.10 Possible Definitions for IMAGE_PARAMS Example 11.11 Equivalent Code for imageAtomicAdd Example 11.12 Equivalent Code for imageAtomicExchange and imageAtomicComp Example 11.13 Simple Per-Pixel Mutex Using imageAtomicCompSwap Example 11.14 Example Use of a Sync Object Example 11.15 Basic Spin-Loop Waiting on Memory Example 11.16 Result of Loop Hoisting on Spin Loop Example 11.17 Examples of Using the volatile Keyword Example 11.18 Examples of Using the coherent Keyword 39 Download from finelybook www.finelybook.com Example Example Example Example Example Example Example Example Example Shader Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Context Example Example Example 11.19 Example of Using the memoryBarrier() Function 11.20 Using the early_fragment_tests Layout Qualifier 11.21 Counting Red and Green Fragments Using General Atomics 11.22 Counting Red and Green Fragments Using Atomic Counters 11.23 Initializing an Atomic Counter Buffer 11.24 Initializing for Order-Independent Transparency 11.25 Per-Frame Reset for Order-Independent Transparency 11.26 Appending Fragments to Linked List for Later Sorting 11.27 Main Body of Final Order-Independent Sorting Fragment 11.28 Traversing Linked-Lists in a Fragment Shader 11.29 Sorting Fragments into Depth Order for OIT 11.30 Blending Sorted Fragments for OIT 12.1 Simple Local Workgroup Declaration 12.2 Creating, Compiling, and Linking a Compute Shader 12.3 Dispatching Compute Workloads 12.4 Declaration of Compute Shader Built-In Variables 12.5 Operating on Data 12.6 Example of Shared Variable Declarations 12.7 Particle Simulation Compute Shader 12.8 Initializing Buffers for Particle Simulation 12.9 Particle Simulation Fragment Shader 12.10 Particle Simulation Rendering Loop 12.11 Central Difference Edge-Detection Compute Shader 12.12 Dispatching the Image Processing Compute Shader B.1 An Example of Creating an OpenGL ES Version 2.0 Rendering B.2 Creating an HTML5 Canvas Element B.3 Creating an HTML5 Canvas Element That Supports WebGL B.4 Our WebGL Application's Main HTML Page 40 Download from finelybook www.finelybook.com Example Example Example Example Example Example Example Example Example Example B.5 Our WebGL Shader Loader: InitShaders.js B.6 Loading WebGL Shaders Using InitShaders() B.7 Initializing Vertex Buffers in WebGL B.8 demo.js WebGL Application G.1 Creating a Debug Context Using WGL G.2 Creating a Debug Context Using GLX G.3 Prototype for the Debug Message Callback Function G.4 Creating Debug Message Filters G.5 Sending Application-Generated Debug Messages G.6 Using an Elapsed Time Query 41 Download from finelybook www.finelybook.com About This Guide The OpenGL graphics system is a software interface to graphics hardware. (The GL stands for Graphics Library.) It allows you to create interactive programs that produce color images of moving three-dimensional objects. With OpenGL, you can control computer-graphics technology to produce realistic pictures, or ones that depart from reality in imaginative ways. This guide explains how to program with the OpenGL graphics system to deliver the visual effect you want. What This Guide Contains This guide contains the following chapters: • Chapter 1, "Introduction to OpenGL," provides a glimpse into what OpenGL can do. It also presents a simple OpenGL program and explains the essential programming details you need to know for the subsequent chapters. • Chapter 2, "Shader Fundamentals," discusses the major features of the OpenGL Shading Language and SPIR-V, demonstrating how to initialize and use them within an application. • Chapter 3, "Drawing with OpenGL," describes the various methods for rendering geometry using OpenGL, as well as some optimization techniques for making rendering more efficient. • Chapter 4, "Color, Pixels, and Fragments," explains OpenGL's processing of color, including how pixels are processed, how buffers are managed, and rendering techniques focused on pixel processing. • Chapter 5, "Viewing Transformations, Culling, Clipping, and Feedback," details the operations for presenting a three-dimensional scene on a two-dimensional computer screen, including the mathematics and shader operations for the various types of geometric projection. • Chapter 6, "Textures and Framebuffers," discusses combining geometric models and imagery for creating realistic, highly detailed threedimensional models. • Chapter 7, "Light and Shadow," describes simulating illumination 42 Download from finelybook www.finelybook.com effects for computer graphics, focusing on implementing those techniques in programmable shaders. • Chapter 8, "Procedural Texturing," details the generation of textures and other surface effects using programmable shaders for increased realism and other rendering effects. • Chapter 9, "Tessellation Shaders," explains OpenGL's shader facility for managing and tessellating geometric surfaces. • Chapter 10, "Geometry Shaders," describe an additional technique for modifying geometric primitives within the OpenGL rendering pipeline using shaders. • Chapter 11, "Memory," demonstrates techniques using OpenGL's framebuffer and buffer memories for advanced rendering techniques and nongraphical uses. • Chapter 12, "Compute Shaders," introduces the newest shader stage, which integrates general computation into the OpenGL rendering pipeline. Additionally, a number of appendices are available for reference: • Appendix A, "Support Libraries," discusses the supporting libraries used by this book's example applications. GLFW is portable, and it makes code examples shorter and more comprehensible; GL3W handles binding your application to OpenGL. • Appendix B, "OpenGL ES and WebGL," details the other APIs in the OpenGL family, including OpenGL ES for embedded and mobile systems and WebGL for interactive 3D applications within Web browsers. • Appendix C, "Built-in GLSL Variables and Functions," provides a detailed reference to OpenGL Shading Language. • Appendix D, "State Variables," lists the state variables that OpenGL maintains and describes how to obtain their values. • Appendix E, "Homogeneous Coordinates and Transformation Matrices," explains some of the mathematics behind matrix transformations. • Appendix F, "Floating-Point Formats for Textures, Framebuffers, and Renderbuffers," provides an overview of the floating-point formats used within OpenGL. • Appendix G, "Debugging and Profiling OpenGL," discusses the latest 43 Download from finelybook www.finelybook.com debug features available within OpenGL. • Appendix H, "Buffer Object Layouts," provides a reference for use with uniform buffers using the standard memory layouts defined in OpenGL. What's New in This Edition This edition of OpenGL Programming Guide has been revised and updated to cover the most recent version of OpenGL: Version 4.5. Unlike the previous edition, which was a complete rewrite of the editions before it, this update has provided us the opportunity to address feedback, fix issues, and rework parts of the book that we and our readers felt could have been better. On the application side of things, the biggest update in OpenGL Version 4.5 is the introduction of direct state access, which is an overhaul of the OpenGL programming model and the way that applications access objects. Also, continuing on the trend of moving more and more functionality into the graphics processor, more of this book is devoted to shader functionality and GPU processing in general. What You Should Know Before Reading This Guide This guide assumes only that you know how to program in the C++ language (yes, we use C++, but nothing you won't be able to figure out if you're familiar with good-old C) and that you have some background in mathematics (geometry, trigonometry, linear algebra, calculus, and differential geometry). Even if you have little or no experience with computer-graphics technology, you should be able to follow most of the discussions in this book. Of course, computer graphics is an ever-expanding subject, so you may want to enrich your learning experience with supplemental reading: • Computer Graphics: Principles and Practice, Third Edition by John F. Hughes, Andries van Dam, Morgan McGuire, David F. Sklar, James D. Foley, Steven K. Feiner, and Kurt Akeley (Addison-Wesley, 2013)— This book is an encyclopedic treatment of the subject of computer graphics. It includes a wealth of information but is probably best read after you have some experience with the subject. • OpenGL SuperBible: Comprehensive Tutorial and Reference, Seventh Edition by Graham Sellers, Richard S. Wright Jr. and Nicolas Haemel (Addison-Wesley, 2015)—This book, written in a tutorial style, begins 44 Download from finelybook www.finelybook.com assuming you know almost nothing about computer graphics and gently guides you through the process of learning OpenGL. • OpenGL Insights by Patrick Cozzi and Christophe Riccio (eds.) (A K Peters, 2012)—This is a contributed collection of articles on advanced OpenGL topics, including war stories from professional developers, researchers, and tinkerers from the real world. Each article concentrates on a specific technique and is a great source of inspiration for your next project. Another great place for all sorts of general information is the Official OpenGL Web site. This Web site contains software, sample programs, documentation, FAQs, discussion boards, and news. It is always a good place to start any search for answers to your OpenGL questions: http://www.opengl.org/ Additionally, full documentation of all the procedures and shading language syntax that compose the latest OpenGL version are documented and available at the Official OpenGL Web site. These Web pages replace the OpenGL Reference Manual that was published by the OpenGL Architecture Review Board and Addison-Wesley. OpenGL is really a hardware-independent specification of a programming interface, and you use a particular implementation of it on a particular kind of hardware. This guide explains how to program with any OpenGL implementation. However, because implementations may vary slightly—in performance and in providing additional, optional features, for example—you might want to investigate whether supplementary documentation is available for the particular implementation you're using. In addition, the provider of your particular implementation might have OpenGL-related utilities, toolkits, programming and debugging support, widgets, sample programs, and demos available at its Web site. How to Obtain the Sample Code This guide contains many sample programs to illustrate the use of particular OpenGL programming techniques. As the audience for this guide has a wide range of experience, from novice to seasoned veteran, with both computer graphics and OpenGL, the examples published in these pages usually present the simplest approach to a particular rendering situation, demonstrated using the OpenGL Version 4.5 interface. This is done mainly to make the presentation 45 Download from finelybook www.finelybook.com straightforward and obtainable to those readers just starting with OpenGL. For those of you with extensive experience looking for implementations using the latest features of the API, we first thank you for your patience with those following in your footsteps and ask that you please visit our Web site: http://www.opengl-redbook.com/ There, you will find the source code for all examples in this text, implementations using the latest features, and additional discussion describing the modifications required in moving from one version of OpenGL to another. All of the programs contained within this book use the GLFW utility library, originally authored by Marcus Geelnard and now maintained by Camilla Berglund. GLFW is open-source and under continuous improvement. You can find the GLFW project page at the following address: http://www.glfw.org/ You can obtain code and binaries of their implementation at this site. The section "Our First Program: A Detailed Discussion" in Chapter 1 and Appendix A give more information about using GLFW. Additional resources to help accelerate your learning and programming of OpenGL and GLFW can be found at the OpenGL Web site's resource pages: http://www.opengl.org/resources/ Implementations of OpenGL might also include the code samples as part of the system. This source code is probably the best source for your implementation because it might have been optimized for your system. Read your machinespecific OpenGL documentation to see where those code samples can be found. Errata OpenGL is updated during the publication of this guide: Errors are corrected, clarifications are made to the specification, and new specifications are released. We keep a list of bugs and updates at our Web site, http://www.opengl-redbook.com/, where we also offer facilities for reporting any new bugs you might find. If you find an error, accept our apologies and our thanks in advance for reporting it. Style Conventions 46 Download from finelybook www.finelybook.com These style conventions are used in this guide: • Bold—API commands and enumerants • Italics—Variables, arguments, parameter names, spatial dimensions, matrix components, and first occurrences of key terms • Monospace—GLSL built-in functions and variables, as well as all example code Command summaries are shaded with gray boxes. In a command summary, we sometimes use braces to identify options among data types. In the following example, glCommand() has four possible suffixes: s, i, f, and d, which stand for the data types GLshort, GLint, GLfloat, and GLdouble. In the function prototype for glCommand(), TYPE is a wild card that represents the data type indicated by the suffix. void glCommand{sifd}(TYPE x1, TYPE y1, TYPE x2, TYPE y2); We use this form when the number of permutations of the function become unruly. Register your copy of OpenGL® Programming Guide, Ninth Edition, at informit.com for convenient access to downloads, updates, and corrections as they become available. To start the registration process, go to informit.com/register and log in or create an account. Enter the product ISBN (9780134495491) and click Submit. Once the process is complete, you will find any available bonus content under "Registered Products." About the OpenGL Series The OpenGL Series from Addison-Wesley comprises tutorial and reference books that help programmers gain a practical understanding of OpenGL standards, along with the insight needed to unlock OpenGL's full potential. Visit informit.com/opengl for a complete list of available products. 47 Download from finelybook www.finelybook.com Acknowledgments John Kessenich Thanks to Graham for doing so much of the writing. Thanks to Alison for her flexibility with lost weekends, taking interest in this project, and helping me put parts of it together. Thanks also to Google for their flexibility and support in scheduling my time. Finally, I'm thankful that Khronos continues to be an excellent shepherd of OpenGL advancement, which is ultimately the responsibility of dedicated individuals like Neil Trevett and Barthold Lichtenbelt. Graham Sellers Thanks to my wife, Chris; my kids; and the rest of my family for putting all the early mornings, late nights, weekends and vacations that I've spent holed up typing. To my colleagues at AMD and to my peers at Khronos who help to continually drive OpenGL forward, I am indebted. To you, the reader, thanks for paying attention. Here's to OpenGL. Dave Shreiner First and foremost, thanks to John and Graham, both for being great coauthors and for doing a great job with this edition. I'm always indebted to Vicki and Cookie for their support and patience while I do projects when I should have been spending the time with them. Likewise, to my parents, Bonnie and Bob, who wax lyrical over my efforts; no son could be luckier or prouder. And as with every edition, my sincerest appreciation to the readers of this guide and the practitioners of OpenGL worldwide; may you have great success with OpenGL, and happy rendering! 48 Download from finelybook www.finelybook.com Chapter 1. Introduction to OpenGL Chapter Objectives After reading this chapter, you'll be able to do the following: • Describe the purpose of OpenGL, and what it can and cannot do in creating computer-generated images. • Identify the common structure of an OpenGL application. • Enumerate the shading stages that compose the OpenGL rendering pipeline. This chapter introduces OpenGL. It has the following major sections: • "What Is OpenGL?" explains what OpenGL is, what it does and doesn't do, and how it works. • "Your First Look at an OpenGL Program" provides a first look at what an OpenGL program looks like. • "OpenGL Syntax" describes the format of the command names that OpenGL uses. • "OpenGL's Rendering Pipeline" discusses the processing pipeline that OpenGL uses in creating images. • "Our First Program: A Detailed Discussion" dissects the first program presented and provides more detail on the activities of each section of the program. What Is OpenGL? OpenGL is an application programming interface, API for short, which is merely a software library for accessing features in graphics hardware. Version 4.5 of the OpenGL library (which this text covers) contains over 500 distinct commands that you use to specify the objects, images, and operations needed to produce interactive three-dimensional computer-graphics applications. OpenGL is designed as a streamlined, hardware-independent interface that can be implemented on many different types of graphics hardware systems, or entirely in software (if no graphics hardware is present in the system), independent of a computer's operating or windowing system. As such, OpenGL 49 Download from finelybook www.finelybook.com doesn't include functions for performing windowing tasks or processing user input; instead, your application will need to use the facilities provided by the windowing system where the application will execute. Similarly, OpenGL doesn't provide any functionality for describing models of three-dimensional objects, or operations for reading image files (JPEG files, for example). Instead, you must construct your three-dimensional objects from a small set of geometric primitives: points, lines, triangles, and patches. Since OpenGL has been around a while—it was first developed at Silicon Graphics Computer Systems, with Version 1.0 released in July of 1994—there are many versions of OpenGL, as well as many software libraries built on OpenGL for simplifying application development, whether you're writing a video game, creating a visualization for scientific or medical purposes, or just showing images. However, the more modern version of OpenGL differs from the original in significant ways. In this book, we describe how to use the most recent versions of OpenGL to create those applications. The following list briefly describes the major operations that an OpenGL application would perform to render an image. (See "OpenGL's Rendering Pipeline" on page 10 for detailed information on these operations.) • Specify the data for constructing shapes from OpenGL's geometric primitives. • Execute various shaders to perform calculations on the input primitives to determine their position, color, and other rendering attributes. • Convert the mathematical description of the input primitives into their fragments associated with locations on the screen. This process is called rasterization. (A fragment in OpenGL is what becomes a pixel, if it makes it all the way to the final rendered image.) • Finally, execute a fragment shader for each of the fragments generated by rasterization, which will determine the fragment's final color and position. • Possibly perform additional per-fragment operations, such as determining if the object that the fragment was generated from is visible, or blending the fragment's color with the current color in that screen location. OpenGL is implemented as a client-server system, with the application you write being considered the client and the OpenGL implementation provided by 50 Download from finelybook www.finelybook.com the manufacturer of your computer graphics hardware being the server. In some implementations of OpenGL (such as those associated with the X Window System), the client and server might execute on different machines that are connected by a network. In such cases, the client will issue the OpenGL commands, which will be converted into a window-system specific protocol that is transmitted to the server via their shared network, where they are executed to produce the final image. In most modern implementations, a hardware graphics accelerator is used to implement most of OpenGL and is either built into (but still separate form) the computer's central processor, or it is mounted on a separate circuit board and plugged into the computer's motherboard. In either case, it is reasonable to think of the client as your application and the server as the graphics accelerator. Your First Look at an OpenGL Program Because you can do so many things with OpenGL, an OpenGL program can potentially be large and complicated. However, the basic structure of all OpenGL applications is usually similar to the following: 1. Initialize the state associated with how objects should be rendered. 2. Specify those objects to be rendered. Before you look at any code, let's introduce some commonly used graphics terms. Rendering, which we've already used without defining, is the process by which a computer creates an image from models. OpenGL is just one example of a rendering system; there are many others. OpenGL is a rasterization-based system, but there are other methods for generating images as well, such as ray tracing, whose techniques are outside the scope of this book. However, even a system that uses ray tracing may employ OpenGL to display an image or compute information to be used in creating an image. Further, the flexibility available in recent versions of OpenGL has become so great that algorithms such as ray tracing, photon mapping, path tracing, and image-based rendering have become relatively easy to implement on programmable graphics hardware. Our models, or objects—we'll use the terms interchangeably—are constructed from geometric primitives: points, lines, and triangles, which are specified by their vertices. 51 Download from finelybook www.finelybook.com Another concept that is essential to using OpenGL is shaders, which are special functions that the graphics hardware executes. The best way to think of shaders is as little programs that are specifically compiled for your graphics processing unit (GPU). OpenGL includes all the compiler tools internally to take the source code of your shader and create the code that the GPU needs to execute. In OpenGL, there are six shader stages that you can use. The most common are vertex shaders, which process vertex data, and fragment shaders, which operate on the fragments generated by the rasterizer. The final generated image consists of pixels drawn on the screen; a pixel is the smallest visible element on your display. The pixels in your system are stored in a framebuffer, which is a chunk of memory that the graphics hardware manages and feeds to your display device. Figure 1.1 shows the output of a simple OpenGL program, which renders two blue triangles into a window. The source code for the entire example is provided in Example 1.1. 52 Download from finelybook www.finelybook.com Figure 1.1 Image from our first OpenGL program: triangles.cpp Example 1.1 triangles.cpp: Our First OpenGL Program Click here to view code image ////////////////////////////////////////////////////////////////////// // // triangles.cpp // 53 Download from finelybook www.finelybook.com ////////////////////////////////////////////////////////////////////// #include using namespace std; #include "vgl.h" #include "LoadShaders.h" enum VAO_IDs { Triangles, NumVAOs }; enum Buffer_IDs { ArrayBuffer, NumBuffers }; enum Attrib_IDs { vPosition = 0 }; GLuint GLuint VAOs[NumVAOs]; Buffers[NumBuffers]; const GLuint NumVertices = 6; //------------------------------------------------------------------// // init // void init(void) { static const { { -0.90, { 0.85, { -0.90, { 0.90, { 0.90, { -0.85, }; GLfloat vertices[NumVertices][2] = -0.90 -0.90 0.85 -0.85 0.90 0.90 }, }, }, }, }, } // Triangle 1 // Triangle 2 glCreateBuffers(NumBuffers, Buffers); glNamedBufferStorage(Buffers[ArrayBuffer], sizeof(vertices), vertices, 0); ShaderInfo shaders[] = { { GL_VERTEX_SHADER, "triangles.vert" }, 54 Download from finelybook www.finelybook.com { GL_FRAGMENT_SHADER, "triangles.frag" }, { GL_NONE, NULL } }; GLuint program = LoadShaders(shaders); glUseProgram(program); glGenVertexArrays(NumVAOs, VAOs); glBindVertexArray(VAOs[Triangles]); glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); glEnableVertexAttribArray(vPosition); } //------------------------------------------------------------------// // display // void display(void) { static const float black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; glClearBufferfv(GL_COLOR, 0, black); glBindVertexArray(VAOs[Triangles]); glDrawArrays(GL_TRIANGLES, 0, NumVertices); } //------------------------------------------------------------------// // main // int main(int argc, char** argv) { glfwInit(); 55 Download from finelybook www.finelybook.com GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL); glfwMakeContextCurrent(window); gl3wInit(); init(); while (!glfwWindowShouldClose(window)) { display(); glfwSwapBuffers(window); glfwPollEvents(); } glfwDestroyWindow(window); glfwTerminate(); } While that may be more code than you were expecting, you'll find that this program will be the basis of just about every OpenGL application you write. We use some additional software libraries that aren't officially part of OpenGL to simplify things like creating a window, or receiving mouse or keyboard input—those things that OpenGL doesn't include. We've also created some helper functions and small C++ classes to simplify our examples. While OpenGL is a C-language library, all of our examples are in C++, but very simple C++. In fact, most of the C++ we use is to implement the mathematical constructs vectors and matrices. In a nutshell, here's what Example 1.1 does. We'll explain all of these concepts in complete detail later, so don't worry. • In the preamble of the program, we include the appropriate header files and declare global variables1 and other useful programming constructs. 1. Yes, in general we eschew global variables in large applications, but for the purposes of demonstration, we use them here. • The init() routine is used to set up data for use later in the program. This may be vertex information for later use when rendering primitives or 56 Download from finelybook www.finelybook.com image data for use in a technique called texture mapping, which we describe in Chapter 6. In this version of init(), we first specify the position information for the two triangles that we render. After that, we specify shaders we're going to use in our program. In this case, we only use the required vertex and fragment shaders. The LoadShaders() routine is one that we've written to simplify the process of preparing shaders for a GPU. In Chapter 2 we'll discuss everything it does. The final part of init() is doing what we like to call shader plumbing, where you associate the data in your application with variables in shader programs. This is also described in detail in Chapter 2. • The display() routine is what really does the rendering. That is, it calls the OpenGL functions that request something be rendered. Almost all display() routines will do the same three steps as in our simple example here. 1. Clear the window by calling glClearBufferfv(). 2. Issue the OpenGL calls required to render your object. 3. Request that the image is presented to the screen. • Finally, main() does the heavy lifting of creating a window, calling init(), and finally entering into the event loop. Here, you also see functions that begin with "gl" but look different than the other functions in the application. Those, which we'll describe momentarily, are from the libraries we use to make it simple to write OpenGL programs across the different operating and window systems: GLFW, and GL3W. Before we dive in to describe the routines in detail, let us explain OpenGL labels functions, constants, and other useful programming constructs. OpenGL Syntax As you likely picked up on, all the functions in the OpenGL library begin with the letters gl, immediately followed by one or more capitalized words to name the function (glBindVertexArray(), for example). All functions in OpenGL are like that. In the program you also saw the functions that began with glfw, which are from GLFW, which is a library that abstracts window management and other system tasks. Similarly, you see a single function, gl3wInit(), which 57 Download from finelybook www.finelybook.com comes from GL3W. We describe the GLFW library in more detail in Appendix A. Similar to OpenGL's function-naming convention, constants like GL_COLOR, which you saw in display(), are defined for the OpenGL library. All constant tokens begin with GL and use underscores to separate words. Their definitions are merely #defines found in the OpenGL header files: glcorearb.h and glext.h. To aid in moving OpenGL applications between operating systems, OpenGL also defines various types of data for its functions, such as GLfloat, which is the floating-point value type we used to declare vertices in Example 1.1. OpenGL defines typedefs for all of the data types accepted by its functions, which are listed in Table 1.1. Additionally, because OpenGL is a C-language library, it doesn't have function overloading to deal with the different types of data; it uses a function-naming convention to organize the multitude of functions that result from that situation. For example, we'll encounter a function named glUniform*() in Chapter 2, "Shader Fundamentals," which comes in numerous forms, such as glUniform2f() and glUniform3fv(). The suffixes at the end of the "core" part of the function name provide information about the arguments passed to the function. For example, the 2 in glUniform2f() represents that two data values will be passed into the function. (There are other parameters as well, but they are the same across all 24 versions of the glUniform*() function. In this book, we'll use glUniform*() to represent the collection of all glUniform*() functions.) Also note the f following the 2. This indicates that those two parameters are of type GLfloat. Finally, some versions of the functions' names end with a v, which is short for vector, meaning that the two floating-point values (in the case of glUniform2fv()) are passed as a onedimensional array of GLfloats, instead of two separate parameters. 58 Download from finelybook www.finelybook.com Table 1.1 Command Suffixes and Argument Data Types To decode all of those combinations, the letters used as suffixes are described in Table 1.1, along with their types. Note Implementations of OpenGL have leeway in selecting which C data types to use to represent OpenGL data types. If you resolutely use the OpenGL-defined data types throughout your application, you will avoid mismatched types when porting your code between different implementations. OpenGL's Rendering Pipeline OpenGL implements what's commonly called a rendering pipeline, which is a sequence of processing stages for converting the data your application provides to OpenGL into a final rendered image. Figure 1.2 shows the OpenGL pipeline associated with Version 4.5. The OpenGL pipeline has evolved considerably since its introduction. 59 Download from finelybook www.finelybook.com Figure 1.2 OpenGL pipeline OpenGL begins with the geometric data you provide (vertices and geometric primitives) and first processes it through a sequence of shader stages—vertex shading, tessellation shading (which itself can use two shaders), and finally geometry shading—before it's passed to the rasterizer. The rasterizer will generate fragments for any primitive that's inside the clipping region and execute a fragment shader for each of the generated fragments. As you can see, shaders play an essential role in creating OpenGL applications. You have complete control of which shader stages are used and what each of them do. Not all stages are required; in fact, only vertex shaders and fragment shaders must be included. Tessellation and geometry shaders are optional. Now we dive deeper into each stage to provide you a bit more background. We understand that this may be a somewhat overwhelming at this point, but bear with us. It will turn out that understanding just a few concepts will get you very far along with OpenGL. Preparing to Send Data to OpenGL OpenGL requires that all data be stored in buffer objects, which are just 60 Download from finelybook www.finelybook.com chunks of memory managed by OpenGL. Populating these buffers with data can occur in numerous ways, but one of the most common is to specify the data at the same time as you specify the buffer's size using the glNamedBufferStorage() command like in Example 1.1. There is some additional setup required with buffers, which we'll cover in Chapter 3. Sending Data to OpenGL After we've initialized our buffers, we can request geometric primitives be rendered by calling one of OpenGL's drawing commands, such as glDrawArrays(), as we did in Example 1.1. Drawing in OpenGL usually means transferring vertex data to the OpenGL server. Think of a vertex as a bundle of data values that are processed together. While the data in the bundle can be anything you'd like it to be (i.e., you define all the data that makes up a vertex), it almost always includes positional data. Any other data will be values you'll need to determine the pixel's final color. Drawing commands are covered in detail in Chapter 3, "Drawing with OpenGL." Vertex Shading For each vertex that is issued by a drawing command, a vertex shader will be called to process the data associated with that vertex. Depending on whether any other pre-rasterization shaders are active, vertex shaders may be very simple, perhaps just copying data to pass it through this shading stage, what we call a pass-through shader, to a very complex shader that's performing many computations to potentially compute the vertex's screen position (usually using transformation matrices, described in Chapter 5), determining the vertex's color using lighting computations described in Chapter 7, or any multitude of other techniques. Typically, an application of any complexity will have multiple vertex shaders, but only one can be active at any one time. Tessellation Shading After the vertex shader has processed each vertex's associated data, the tessellation shader stage will continue processing that data, if it's been activated. As we'll see in Chapter 9, tessellation uses patches to describe an 61 Download from finelybook www.finelybook.com object's shape and allows relatively simple collections of patch geometry to be tessellated to increase the number of geometric primitives, providing better-looking models. The tessellation shading stage can potentially use two shaders to manipulate the patch data and generate the final shape. Geometry Shading The next shader stage, geometry shading, allows additional processing of individual geometric primitives, including creating new ones, before rasterization. This shading stage is optional but powerful, as we'll see in Chapter 10. Primitive Assembly The previous shading stages all operate on vertices, with the information about how those vertices are organized into geometric primitives being carried along internal to OpenGL. The primitive assembly stage organizes the vertices into their associated geometric primitives in preparation for clipping and rasterization. Clipping Occasionally, vertices will be outside of the viewport—the region of the window where you're permitted to draw—and cause the primitive associated with that vertex to be modified so none of its pixels are outside of the viewport. This operation is called clipping and is handled automatically by OpenGL. Rasterization Immediately after clipping, the updated primitives are sent to the rasterizer for fragment generation. The job of the rasterizer is to determine which screen locations are covered by a particular piece of geometry (point, line, or triangle). Knowing those locations, along with the input vertex data, the rasterizer linearly interpolates the data values for each varying variable in the fragment shader and sends those values as inputs into your fragment shader. Consider a fragment a "candidate pixel," in that pixels have a home in the framebuffer, while a fragment still can be rejected and never update its associated pixel location. Processing of fragments occurs in the next two 62 Download from finelybook www.finelybook.com stages: fragment shading and per-fragment operations. Note How an OpenGL implementation rasterizes and interpolates values is platform-dependent; you should not expect that different platforms will interpolate values identically. While rasterization starts a fragment's life, and the computations done in the fragment shader are essential in computing the fragment's final color, it's by no means all the processing that can be applied to a fragment. Fragment Shading The final stage where you have programmable control over the color of a screen location is fragment shading. In this shader stage, you use a shader to determine the fragment's final color (although the next stage, per-fragment operations, can modify the color one last time) and potentially its depth value. Fragment shaders are very powerful, as they often employ texture mapping to augment the colors provided by the vertex processing stages. A fragment shader may also terminate processing a fragment if it determines the fragment shouldn't be drawn; this process is called fragment discard. A helpful way of thinking about the difference between shaders that deal with vertices and fragment shaders is this: vertex shading (including tessellation and geometry shading) determines where on the screen a primitive is, while fragment shading uses that information to determine what color that fragment will be. Per-Fragment Operations Additional fragment processing, outside of what you can currently do in a fragment shader, is the final processing of individual fragments. During this stage, a fragment's visibility is determined using depth testing (also commonly known as z-buffering) and stencil testing. If a fragment successfully makes it through all of the enabled tests, it may be written directly to the framebuffer, updating the color (and possibly depth value) of its pixel, or if blending is enabled, the fragment's color will be combined with the pixel's current color to generate a new color that is written 63 Download from finelybook www.finelybook.com into the framebuffer. As you saw in Figure 1.2, there's also a path for pixel data. Generally, pixel data comes from an image file, although it may also be created by rending using OpenGL. Pixel data is usually stored in a texture map for use with texture mapping, which allows any texture stage to look up data values from one or more texture maps. Texture mapping is covered in depth in Chapter 6. With that brief introduction to the OpenGL pipeline, we'll dissect Example 1.1 and map the operations back to the rendering pipeline. Our First Program: A Detailed Discussion Let's have a more detailed look at our first program. Entering main() Starting at the beginning of our program's execution, we first look at what's going on in main(). The first six lines use GLFW to configure and open a window for us. While the details of each of these routines is covered in Appendix A, we discuss the flow of the commands here. Click here to view code image int main(int argc, char** argv) { glfwInit(); GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL); glfwMakeContextCurrent(window); gl3wInit(); init(); while (!glfwWindowShouldClose(window)) { display(); glfwSwapBuffers(window); glfwPollEvents(); 64 Download from finelybook www.finelybook.com } glfwDestroyWindow(window); glfwTerminate(); } The first function, glfwInit(), initializes the GLFW library. It processes the command-line arguments provided to the program and removes any that control how GLFW might operate (such as specifying the size of a window). glfwInit() needs to be the first GLFW function that your application calls, as it sets up data structures required by subsequent GLFW routines. glfwCreateWindow() configures the type of window we want to use with our application and the size of the window, as you might expect. While we don't do it here, you can also query the size of the display device to dynamically size the window relative to your computer screen. glfwCreateWindow() also creates an OpenGL context that is associated with that window. To begin using the context, we must make it current, which means that OpenGL commands are directed toward that context. A single application can use multiple contexts and multiple windows, AND the current context2 is the one that processes the commands you make. 2. There is actually a current context for each thread in your application. Continuing on, the call to gl3wInit() initializes another helper library we use: GL3W. GL3W simplifies dealing with accessing functions and other interesting programming phenomena introduced by the various operating systems with OpenGL. Without GL3W, a considerable amount of additional work is required to get an application going. At this point, we're truly set up to do interesting things with OpenGL. The init() routine, which we'll discuss momentarily, initializes all of our relevant OpenGL data so we can use it for rendering later. The final function in main() is a loop that works with the window and operating systems to process user input and other operations like that. It's this loop that determines whether a window needs to be closed or not (by calling glfwWindowShouldClose()), redraws its contents and presents them to the user (by calling glfwSwapBuffers()), and checks for any incoming messages from the operating system (by calling glfwPollEvents()). 65 Download from finelybook www.finelybook.com If we determine that our window has been closed and that our application should exit, we clean up the window by calling glfwDestroyWindow() and then shut down the GLFW library by calling glfwTerminate(). OpenGL Initialization The next routine that we need to discuss is init() from Example 1.1. Once again, here's the code to refresh your memory. Click here to view code image void init(void) { static const { { -0.90, { 0.85, { -0.90, { 0.90, { 0.90, { -0.85, }; GLfloat vertices[NumVertices][2] = -0.90 -0.90 0.85 -0.85 0.90 0.90 }, }, }, }, }, } // Triangle 1 // Triangle 2 glCreateVertexArrays(NumVAOs, VAOs); glCreateBuffers(NumBuffers, Buffers); glNamedBufferStorage(Buffers[ArrayBuffer], sizeof(vertices), vertices, 0); ShaderInfo shaders[] = { { GL_VERTEX_SHADER, "triangles.vert" }, { GL_FRAGMENT_SHADER, "triangles.frag" }, { GL_NONE, NULL } }; GLuint program = LoadShaders(shaders); glUseProgram(program); glBindVertexArray(VAOs[Triangles]); glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); glVertexAttribPointer(vPosition, 2, GL_FLOAT, 66 Download from finelybook www.finelybook.com GL_FALSE, 0, BUFFER_OFFSET(0)); glEnableVertexAttribArray(vPosition); } Initializing Our Vertex-Array Objects There's a lot going on in the functions and data of init(). Starting at the top, we begin by allocating a vertex-array object by calling glCreateVertexArrays(). This causes OpenGL to allocate some number of vertex array object names for our use—in our case, NumVAOs, which we specified in the global variable section of the code. glCreateVertexArrays() returns that number of names to us in the array provided, VAOs in this case. Here's a complete description of glCreateVertexArrays(): void glCreateVertexArrays(GLsizei n, GLuint *arrays); Returns n currently unused names for use as vertex-array objects in the array arrays. The names returned are initialized with values representing the default state of the collection of uninitialized vertex arrays. Throws a GL_INVALID_VALUE if n is negative. We'll see numerous OpenGL commands of the form glCreate*, for allocating names to the various types of OpenGL objects. A name is a little like a pointertype variable in C, in that you can allocate an object in memory and have the name reference it. Once you have the object, you can bind it to the OpenGL context in order to use it. For our example, we bind a vertex-array object using glBindVertexArray(). void glBindVertexArray(GLuint array); glBindVertexArray() does two things. When using the value array that is other than zero and was returned from glCreateVertexArrays(), that vertex array object becomes active, which additionally affects the vertex array state stored in the object. When binding to an array value of zero, OpenGL stops using the previously bound vertex array. A GL_INVALID_OPERATION error is generated if array is 67 Download from finelybook www.finelybook.com not a value previously returned from glCreateVertexArrays(), or if it is a value that has been released by glDeleteVertexArrays(). In our example, after we create a vertex-array object, we bind it with our call to glBindVertexArray(). Object binding like this is a very common operation in OpenGL, but it may not be immediately intuitive how or why it works. When you bind an object (e.g., when glBind*() is called for a particular object name), OpenGL will make that object current, which means that any operations relevant to the bound object, like the vertex-array object we're working with, will affect its state from that point on in the program's execution. After the first call to any glCreate*() function, the newly created object will be initialized to its default state and will usually require some additional initialization to make it useful. Think of binding an object like setting a track switch in a railroad yard. Once a track switch has been set, all trains go down that set of tracks. When the switch is set to another track, all trains will then travel that new track. It is the same for OpenGL objects. Generally speaking, you will bind an object in two situations: initially, when you create and initialize the data it will hold; then every time you want to use it, and it's not currently bound. We'll see this situation when we discuss the display() routine, where glBindVertexArray() is called the second time in the program. Because our example is as minimal as possible, we don't do some operations that you might in larger programs. For example, once you're finished with a vertex-array object, you can delete it by calling glDeleteVertexArrays(). void glDeleteVertexArrays(GLsizei n, const GLuint *arrays); Deletes the n vertex-arrays objects specified in arrays, enabling the names for reuse as vertex arrays later. If a bound vertex array is deleted, the bindings for that vertex array become zero (as if you had called glBindVertexArray() with a value of zero) and there is no longer a current vertex array. Unused names in arrays are released, but no changes to the current vertex array state are made. Finally, for completeness, you can also determine whether a name has already 68 Download from finelybook www.finelybook.com been created as a vertex-array object by calling glIsVertexArray(). GLboolean glIsVertexArray(GLuint array); Returns GL_TRUE if array is the name of a vertex-array object that was previously created with glCreateVertexArrays() but not subsequently deleted. Returns GL_FALSE if array is zero or a nonzero value that is not the name of a vertex-array object. You'll find many similar routines of the form glDelete* and glIs* for all the different types of objects in OpenGL. Allocating Buffer Objects A vertex-array object holds various data related to a collection of vertices. Those data are stored in buffer objects and managed by the currently bound vertex-array object. While there is only a single type of vertex-array object, there are many types of objects, but not all of them specifically deal with vertex data. As mentioned previously, a buffer object is memory that the OpenGL server allocates and owns, and almost all data passed into OpenGL is done by storing the data in a buffer object. The sequence of initializing a buffer object is similar in flow to that of creating a vertex-array object, with an added step to actually populate the buffer with data. To begin, you need to create some names for your vertex-buffer objects. As you might expect, you'll call a function of the form glCreate*—in this case, glCreateBuffers(). In our example, we allocate NumVBOs (short for vertexbuffer objects—a term used to mean a buffer object used to store vertex data) to our array buffers. Here is the full description of glCreateBuffers(): void glCreateBuffers(GLsizei n, GLuint *buffers); Returns n currently unused names for buffer objects in the array buffers. The names returned in buffers do not have to be a contiguous set of integers. Throws a GL_INVALID_VALUE if n is negative. 69 Download from finelybook www.finelybook.com The names returned represent newly created buffer objects with default valid state. Zero is a reserved buffer object name and is never returned as a buffer object by glCreateBuffers(). Once you have created your buffers, you can bind them to the OpenGL context by calling glBindBuffer(). Because there are many different places where buffer objects can be in OpenGL, when we bind a buffer, we need to specify which what we'd like to use it for. In our example, because we're storing vertex data into the buffer, we use GL_ARRAY_BUFFER. The place where the buffer is bound is known as the binding target. There are many buffer binding targets, which are each used for various features in OpenGL. We will discuss each target's operation in the relevant sections later in the book. Here is the full detail for glBindBuffer(): void glBindBuffer(GLenum target, GLuint buffer); Specifies the current active buffer object. target must be set to one of GL_ARRAY_BUFFER, GL_ATOMIC_COUNTER_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_SHADER_STORAGE_BUFFER, GL_QUERY_RESULT_BUFFER, GL_DRAW_INDIRECT_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER, or GL_UNIFORM_BUFFER. buffer specifies the buffer object to be bound to. glBindBuffer() does two things: First, when binding to a previously created buffer object, that buffer object becomes the active buffer object for the specified target; and second, when binding to a buffer value of zero, OpenGL stops using any buffer object previously bound to that target. 70 Download from finelybook www.finelybook.com As with other objects, you can delete buffer objects with glDeleteBuffers(). void glDeleteBuffers(GLsizei n, const GLuint *buffers); Deletes n buffer objects, named by elements in the array buffers. The freed buffer objects may now be reused (for example, by glCreateBuffers()). If a buffer object is deleted while bound, all bindings to that object are reset to the default buffer object, as if glBindBuffer() had been called with zero as the specified buffer object. Attempts to delete nonexistent buffer objects or the buffer object named zero are ignored without generating an error. You can query whether an integer value is a buffer-object name with glIsBuffer(). GLboolean glIsBuffer(GLuint buffer); Returns GL_TRUE if buffer is the name of a buffer object that has been created but not subsequently deleted. Returns GL_FALSE if buffer is zero or if buffer is a nonzero value that is not the name of a buffer object. Loading Data into a Buffer Object After initializing our vertex-buffer object, we need to ask OpenGL to allocate space for the buffer object and transfer the vertex data into the buffer object. This is done by the glNamedBufferStorage() routine. This performs dual duty: allocating storage for holding the vertex data and optionally copying the data from arrays in the application to the OpenGL server's memory. glNamedBufferStorage() allocates storage for a buffer, the name of which you supply (and which doesn't need to be bound). As glNamedBufferStorage() will be used many times in many different scenarios, it's worth discussing them in more detail here, although we will revisit its use many times in this book. To begin, here's the full description of glNamedBufferStorage(). 71 Download from finelybook www.finelybook.com void glNamedBufferStorage(GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); Allocates size storage units (usually bytes) of OpenGL server memory for storing data or indices. glNamedBufferStorage() affects the buffer named in buffer. This command does not require a target parameter. size is the amount of storage required for storing the respective data. This value is generally the number of elements in the data multiplied by their respective storage size. data is either a pointer to a client memory that is used to initialize the buffer object or NULL. If a valid pointer is passed, size units of storage are copied from the client to the server. If NULL is passed, size units of storage are reserved for use but are left uninitialized. flags provides information about how the buffer's data store will be used. It is a logical combination of a selection of the following bits: GL_DYNAMIC_STORAGE_BIT, GL_MAP_READ_BIT, GL_MAP_WRITE_BIT, GL_MAP_PERSISTENT_BIT, GL_MAP_COHERENT_BIT, and GL_CLIENT_STORAGE_BIT. Each is explained in detail later in the book. glNamedBufferStorage() will generate a GL_OUT_OF_MEMORY error if the requested size exceeds what the server is able to allocate. It will generate a GL_INVALID_VALUE error if flags contains an unrecognized bit. We know that was a lot to see at one time, but you will use these functions so much that it's good to make them easy to find at the beginning of the book. For our example, our call to glNamedBufferStorage() is straightforward. Our vertex data is stored in the array vertices. While we've statically allocated it in our example, you might read these values from a file containing a model or generate the values algorithmically. Because our data is vertex-attribute data, 72 Download from finelybook www.finelybook.com we bind this buffer to the GL_ARRAY_BUFFER target and specify that value as the first parameter. We also need to specify the size of memory to be allocated (in bytes), so we merely compute sizeof(vertices), which does all the heavy lifting. Finally, we need to specify how the data will be used by OpenGL. We can simply set the flags field to zero. The usage of the other defined bits that can be set in flags is discussed in more detail later in the book. If you look at the values in the vertices array, you'll note they are all in the range [–1, 1] in both x and y. In reality, OpenGL only knows how to draw geometric primitives into this coordinate space. In fact, that range of coordinates is known as normalized-device coordinates (commonly called NDCs). While that may sound like a limitation, it's really none at all. Chapter 5 will discuss all the mathematics required to take the most complex objects in a three-dimensional space and map them into normalized-device coordinates. We used NDCs here to simplify the example, but in reality, you will almost always use more complex coordinate spaces. At this point, we've successfully created a vertex-array object and populated its buffer objects. Next, we need to set up the shaders that our application will use. Initializing Our Vertex and Fragment Shaders Every OpenGL program that needs to draw something must provide at least two shaders: a vertex shader and a fragment shader. In our example, we do that by using our helper function LoadShaders(), which takes an array of ShaderInfo structures (all of the details for this structure are included in the LoadShaders.h header file). For an OpenGL programmer (at this point), a shader is a small program written in the OpenGL Shading Language (OpenGL Shading Language (GLSL)), a special language very similar to C++ for constructing OpenGL shaders. We use GLSL for all shaders in OpenGL, although not every feature in GLSL is usable in every OpenGL shader stage. You provide your GLSL shader to OpenGL as a string of characters. To simplify our examples, and to make it easier for you to experiment with shaders, we store our shader strings in files, and use LoadShaders() to take care of reading the files and creating our OpenGL shader programs. The gory details of working with OpenGL shaders are 73 Download from finelybook www.finelybook.com discussed in detail in Chapter 2. To gain an appreciation of shaders, we need to show you some without going into full detail of every nuance. GLSL details will come in subsequent chapters, so right now, it suffices to show our vertex shader in Example 1.2. Example 1.2 Vertex Shader for triangles.cpp: triangles.vert Click here to view code image #version 450 core layout (location = 0) in vec4 vPosition; void main() { gl_Position = vPosition; } Yes; that's all there is. In fact, this is an example of a pass-through shader we eluded to earlier. It only copies input data to output data. That said, there is a lot to discuss here. The first line, #version 450 core, specifies which version of the OpenGL Shading Language we want to use. The 450 here indicates that we want to use the version of GLSL associated with OpenGL Version 4.5. The naming scheme of GLSL versions based on OpenGL versions works back to Version 3.3. In versions of OpenGL before that, the version numbers incremented differently (the details are in Chapter 2). The core relates to wanting to use OpenGL's core profile, which is the profile we use for new application. Every shader must have a #version line at its start; otherwise, version 110 is assumed, which is incompatible with OpenGL's core profile versions. We're going to stick to shaders declaring version 330 or above, depending on what features the shaders use; you may achieve a bit more portability by not using the most recent version number unless you need the most recent features. Next, we allocate a shader variable. Shader variables are a shader's connections to the outside world. That is, a shader doesn't know where its data comes from; it merely sees its input variables populated with data every time it executes. It's our responsibility to connect the shader plumbing (this is our 74 Download from finelybook www.finelybook.com term, but you'll see why it makes sense) so that data in your application can flow into and between the various OpenGL shader stages. In our simple example, we have one input variable named vPosition, which you can determine by the "in" on its declaration line. In fact, there's a lot going on in this one line. Click here to view code image layout (location = 0) in vec4 vPosition; It's easier to parse the line from right to left. • vPosition is, of course, the name of the variable. We use the convention of prefixing a vertex attribute with the letter "v." So in this case, this variable will hold a vertex's positional information. • Next, you see vec4, which is vPosition's type. In this case, it's a GLSL 4-component vector of floating-point values. There are many data types in GLSL, as we'll discuss in Chapter 2. You may have noticed that when we specified the data for each vertex in Example 1.1, we specified only two coordinates, but in our vertex shader, we use a vec4. Where do the other two coordinates come from? OpenGL will automatically fill in any missing coordinates with default values. The default value for a vec4 is (0.0, 0.0, 0.0, 1.0), so if we specify only the x- and y-coordinates, the other values (z and w), are assigned 0 and 1, respectively. • Preceding the type is the in we mentioned before, which specifies which direction data flows into the shader. If you're wondering if there might be an out, yes, you're right. We don't show that here but will soon. • Finally, the layout (location = 0) part is called a layout qualifier and provides meta-data for our variable declaration. There are many options that can be set with a layout qualifier, some of which are shader-stage specific. In this case, we just set vPosition attribute location to zero. We'll use that information in conjunction with the last two routines in init(). Finally, the core of the shader is defined in its main() routine. Every shader in OpenGL, regardless of which shader stage its used for, will have a main() routine. For this shader, all it does is copy the input vertex position to the 75 Download from finelybook www.finelybook.com special vertex-shader output gl_Position. You'll soon see there are several shader variables provided by OpenGL that you'll use, and they all begin with the gl_ prefix. Similarly, we need a fragment shader to accompany our vertex shader. Here's the one for our example, shown in Example 1.3. Example 1.3 Fragment Shader for triangles.cpp: triangles.frag Click here to view code image #version 450 core layout (location = 0) out vec4 fColor; void main() { fColor = vec4(0.5, 0.4, 0.8, 1.0); } We hope that much of this looks familiar, even if it's an entirely different type of shader. We have the version string, a variable declaration, and our main() routine. There are a few differences, but as you'll find, almost all shaders will have this structure. The highlights of our fragment shader are as follows: • The variable declaration for fColor. If you guessed that there was an out qualifier, you were right! In this case, the shader will output values through fColor, which is the fragment's color (hence the choice of "f" as a prefix). • Similarly to our input to the vertex shader, preceding the fColor output declaration with a layout (location = 0) qualifier. A fragment shader can have multiple outputs, and which output a particular variable corresponds to is referred to as its location. Although we're using only a single output in this shader, it's a good habit to get into specifying locations for all your inputs and outputs. • Assigning the fragment's color. In this case, each fragment is assigned this vector of four values. In OpenGL, colors are represented in what's called the RGB color space, with each color component (R for red, G for green, and B for blue) ranging from [0, 1]. The observant reader is 76 Download from finelybook www.finelybook.com probably asking "Um, but there are four numbers there." Indeed, OpenGL really uses an RGBA color space, with the fourth color not really being a color at all. It's for a value called alpha, which is really a measure of translucency. We'll discuss it in detail in Chapter 4, but for now, we set it to 1.0, which indicates the color is fully opaque. Fragment shaders are immensely powerful, and there will be many techniques that we can do with them. We're almost done with our initialization routine. The final two routines in init() deal specifically with associating variables in a vertex shader with data that we've stored in a buffer object. This is exactly what we mean by shader plumbing, in that you need to connect conduits between the application and a shader, and, as we'll see, between various shader stages. To associate data going into our vertex shader, which is the entrance all vertex data take to get processed by OpenGL, we need to connect our shader in variables to a vertex-attribute array, and we do that with the glVertexAttribPointer() routine. void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); Specifies where the data values for index (shader attribute location) can be accessed. pointer is the offset from the start of the buffer object (assuming zero-based addressing) in basicmachine units (i.e., bytes) for the first set of values in the array. size represents the number of components to be updated per vertex and can be 1, 2, 3, 4, or GL_BGRA. type specifies the data type (GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FIXED, GL_HALF_FLOAT, GL_FLOAT, or GL_DOUBLE) of each element in the array. normalized indicates that the vertex data should be normalized before being stored (in the same manner as glVertexAttribFourN*()). stride is the byte offset between consecutive elements in the array. If stride 77 Download from finelybook www.finelybook.com is zero, the data is assumed to be tightly packed. While that may seem like a lot of things to figure out, it's because glVertexAttribPointer() is a very flexible command. As long as your data is regularly organized in memory (i.e., it's in a contiguous array and not in some other node-based container, like a linked list), you can use glVertexAttribPointer() to tell OpenGL how to retrieve data from that memory. In our case, vertices has all the information we need. Table 1.2 works through glVertexAttribPointer()'s parameters. Table 1.2 Example of Determining Parameters for glVertexAttribPointer() We hope that explanation of how we arrived at the parameters will help you determine the necessary values for your own data structures. We will have 78 Download from finelybook www.finelybook.com plenty more examples of using glVertexAttribPointer(). One additional technique we use is using our BUFFER_OFFSET macro in glVertexAttribPointer() to specify the offset. There's nothing special about our macro; here's its definition. Click here to view code image #define BUFFER_OFFSET(offset) ((void *)(offset)) While there's a long history of OpenGL lore about why one might do this,3 we use this macro to make the point that we're specifying an offset into a buffer object, rather than a pointer to a block of memory as glVertexAttribPointer()'s prototype would suggest. 3. In versions before 3.1, vertex-attribute data was permitted to be stored in application memory instead of GPU buffer objects, so pointers made sense in that respect. At this point, we have one task left to do in init(), which is to enable our vertex-attribute array. We do this by calling glEnableVertexAttribArray() and passing the index of the attribute array pointer we initialized by calling glVertexAttribPointer(). Here are the full details for glEnableVertexAttribArray(): void glEnableVertexAttribArray(GLuint index); void glDisableVertexAttribArray(GLuint index); Specifies that the vertex array associated with variable index be enabled or disabled. index must be a value between zero and GL_MAX_VERTEX_ATTRIBS – 1. It's important to note that the state we've just specified by calling glVertexAttribPointer() and glEnableVertexAttribArray() is stored in the vertex array object we bound at the start of the function. The modifications to the state in this object are implied through this binding. If we wanted to set up a vertex array object without binding it to the context, we could instead call glEnableVertexArrayAttrib(), glVertexArrayAttribFormat(), and glVertexArrayVertexBuffers(), which are the direct state access versions of these functions. Now all that's left is to draw something. 79 Download from finelybook www.finelybook.com Our First OpenGL Drawing With all that setup and data initialization, rendering (for the moment) will be simple. While our display() routine is only four lines long, its sequence of operations is virtually the same in all OpenGL applications. Here it is once again. Click here to view code image void display(void) { static const float black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; glClearBufferfv(GL_COLOR, 0, black); glBindVertexArray(VAOs[Triangles]); glDrawArrays(GL_TRIANGLES, 0, NumVertices); } We begin rendering by clearing our framebuffer. This is done by calling glClearBufferfv(). void glClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat *value); Clears the specified buffer within the current draw framebuffer to the specified clear values. The buffer argument is specifies the buffer to clear and may be GL_COLOR, GL_DEPTH, or GL_STENCIL. The drawbuffer parameter is the index of the buffer to clear. When the default framebuffer is bound, or when buffer is GL_DEPTH or GL_STENCIL, drawbuffer must be zero. Otherwise, it is the index of the color attachment to be cleared. value is a pointer to an array of one or four floating-point values specifying the color to which to clear the buffer. When buffer is GL_COLOR, value must point to an array of at least four values containing the clear color. When buffer is GL_DEPTH or GL_STENCIL, value is a pointer to a single floating-point value 80 Download from finelybook www.finelybook.com that will be used to clear the depth or stencil buffer, respectively. We discuss depth and stencil buffering, as well as an expanded discussion of color, in Chapter 4, "Color, Pixels, and Fragments." In this example, we clear the color buffer to black. Let's say you always want to clear the background of the viewport to white. You would call glClearBufferfv() and pass value as a pointer to an array of four floatingpoint 1.0 values. Try This Change the values in the black variables in triangles.cpp to see the effect of changing the clear color. Drawing with OpenGL Our next two calls select the collection of vertices we want to draw and requests that they be rendered. We first call glBindVertexArray() to select the vertex array that we want to use as vertex data. As mentioned before, you would do this to switch between different collections of vertex data. Next, we call glDrawArrays(), which actually sends vertex data to the OpenGL pipeline. void glDrawArrays(GLenum mode, GLint first, GLsizei count); Constructs a sequence of geometric primitives using the elements from the currently bound vertex array starting at first and ending at first + count – 1. mode specifies what kinds of primitives are constructed and is one of GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, and GL_PATCHES. The glDrawArrays() function can be thought of as a shortcut to the much more complex glDrawArraysInstancedBaseInstance() function which contains several more parameters. These will be explained in "Instanced Rendering" in Chapter 3. 81 Download from finelybook www.finelybook.com In our example, we request that individual triangles be rendered by setting the rendering mode to GL_TRIANGLES, starting at offset zero with respect to the buffer offset we set with glVertexAttribPointer(), and continuing for NumVertices (in our case, 6) vertices. We describe all of the rendering shapes in detail in Chapter 3. Try This Modify triangles.cpp to render a different type of geometric primitive, like GL_POINTS or GL_LINES. Any of the listed primitives can be used, but some of the results may not be what you expect, and for GL_PATCHES, you won't see anything as it requires use of tessellation shaders, which we discuss in Chapter 9. That's it! Now we've drawn something. The framework code will take care of showing the results to your user. Enabling and Disabling Operations in OpenGL One important feature that we didn't need to use in our first program, but will use throughout this book, is enabling and disabling modes of operation in OpenGL. Most operational features are turned on and off by the glEnable() and glDisable() commands. void glEnable(GLenum capability); void glDisable(GLenum capability); glEnable() turns on a capability, and glDisable() turns it off. There are numerous enumerated values that can be passed as parameters to glEnable() or glDisable(). Examples include GL_DEPTH_TEST for turning on and off depth testing, GL_BLEND to control blending, and GL_RASTERIZER_DISCARD for advanced rendering control while doing transform feedback. You may often find, particularly if you have to write libraries that use OpenGL that will be used by other programmers, that you need to determine a feature's state before changing for your own needs. glIsEnabled() will return if a 82 Download from finelybook www.finelybook.com particular capability is currently enabled. GLboolean glIsEnabled(GLenum capability); Returns GL_TRUE or GL_FALSE, depending on whether the queried capability is currently activated. 83 Download from finelybook www.finelybook.com Chapter 2. Shader Fundamentals Chapter Objectives After reading this chapter, you'll be able to do the following: • Identify the various types of shaders that OpenGL uses to create images. • Construct and compile shaders using the OpenGL Shading Language. • Pass data into shaders using a variety of mechanisms available in OpenGL. • Employ advanced GLSL shading capabilities to make shaders more reusable. This chapter introduces shaders and explains how to use them with OpenGL. Along the way, we describe the OpenGL Shading Language (commonly called GLSL), and detail how shaders will influence your OpenGL applications. This chapter contains the following major sections: • "Shaders and OpenGL" discusses programmable graphics shaders in the context of OpenGL applications. • "OpenGL's Programmable Pipeline" details each stage of the OpenGL programmable pipeline. • "An Overview of the OpenGL Shading Language" introduces the OpenGL Shading Language. • "Interface Blocks" shows how to organize shader variables shared with the application or between stages. • "Compiling Shaders" describes the process of converting GLSL shaders into programmable shader programs usable in your OpenGL application. • "Shader Subroutines" discusses a method to increase the usability of shaders by allowing them to select execution routines without recompiling shaders. • "Separate Shader Objects" details how to composite elements from multiple shaders into a single, configurable graphics pipeline. • "SPIR-V" discusses how to set shaders compiled to the SPIR-V binary intermediate language. 84 Download from finelybook www.finelybook.com Shaders and OpenGL The modern OpenGL rendering pipeline relies very heavily on using shaders to process the data you pass to it. About the only rendering you can do with OpenGL without shaders is clearing a window, which should give you a feel for how important shaders are when using OpenGL. Shaders, whether for OpenGL or any other graphics API, are usually written in a specialized programming language. For OpenGL, we use GLSL, the OpenGL Shading Language, which has been around since OpenGL Version 2.0 (and before as extensions). It has evolved along with OpenGL, usually being updated with each new version of OpenGL. While GLSL is a programming language specially designed for graphics, you'll find it's very similar to the C language, with a little C++ mixed in. Shaders are so fundamental to the operation of OpenGL, it's important to introduce them early and get you comfortable with writing them. Any OpenGL program will essentially be divided into two main parts; the part that's running on the CPU, written in a language such as C++, and the part that's running on the GPU, which is written in GLSL. In this chapter, we describe how to write shaders, gradually introducing GLSL along the way, discuss compiling and integrating shaders into your application, and show how data in your application passes between the various shaders. OpenGL's Programmable Pipeline While Chapter 1 provided a brief introduction to OpenGL's rendering pipeline, we glossed over the mechanics of the shaders themselves and didn't even show you what the simple shaders used by the first example contained. Here, we describe in greater detail the various stages and what operations they carry out. Version 4.5's graphical pipeline contains four processing stages, plus a compute stage, each of which you control by providing a shader. 1. The Vertex shading stage receives the vertex data that you specified in your vertex-buffer objects, processing each vertex separately. This is the only mandatory stage, and all OpenGL programs must have a shader bound to it when drawing. We describe vertex shading operation in Chapter 3, "Drawing with OpenGL." 2. The Tessellation shading stage is an optional stage that generates additional geometry within the OpenGL pipeline, as compared to having 85 Download from finelybook www.finelybook.com the application specify each geometric primitive explicitly. This stage, if activated, receives the output of the vertex shading stage and does further processing of the received vertices. The tessellation stage is actually divided into two shaders known as the tessellation control shader and the tessellation evaluation shader. These will be explained in more detail in Chapter 9, "Tessellation Shaders." We use the term tessellation shader to mean either or both of these shading stages, and will sometimes use the terms control shader and evaluation shader as shorthand for its two parts. 3. The Geometry shading stage is another optional stage that can modify entire geometric primitives within the OpenGL pipeline. This stage operates on individual geometric primitives allowing each to be modified. In this stage, you might generate more geometry from the input primitive, change the type of geometric primitive (e.g., converting triangles to lines), or discard the geometry altogether. If activated, a geometry shader receives its input either after vertex shading has completed processing the vertices of a geometric primitive or from the primitives generated from the tessellation shading stage, if it's been enabled. The geometry shading stage is described in Chapter 10, "Geometry Shaders." 4. Finally, the last part of the OpenGL shading pipeline is the Fragment shading stage. This stage processes the individual fragments (or samples, if sample-shading mode is enabled) generated by OpenGL's rasterizer and must have a shader bound to it. In this stage, a fragment's color and depth values are computed and then sent for further processing in the fragment-testing and blending parts of the pipeline. Fragment shading operation is discussed in many sections of the text. 5. The Compute shading stage is not part of the graphical pipeline like the stages above, but stands on its own as the only stage in a program. A compute shader processes generic work items, driven by an applicationchosen range, rather than by graphical inputs like vertices and fragments. Compute shaders can process buffers created and consumed by other shader programs in your application. This includes framebuffer postprocessing effects or really anything you want. Compute shaders are described in Chapter 12, "Compute Shaders." An important concept to understand in general is how data flows between the 86 Download from finelybook www.finelybook.com shading stages. Shaders, as you saw in Chapter 1, are like a function call: Data is passed in, processed, and passed back out. In C, for example, this can either be done using global variables or arguments to the function. GLSL is a little different. Each shader looks a complete C program, in that its entry point is a function named main(). Unlike C, GLSL's main() doesn't take any arguments; rather all data going into and out of a shader stage is passed using special global variables in the shader. (Please don't confuse them with global variables in your application; shader variables are entirely separate from the variables you've declared in your application code.) For example, take a look at Example 2.1. Example 2.1 A Simple Vertex Shader Click here to view code image #version 450 core in vec4 in vec4 out vec4 vPosition; vColor; color; uniform mat4 ModelViewProjectionMatrix; void main() { color = vColor; gl_Position = ModelViewProjectionMatrix * vPosition; } Even though that's a very short shader, there are a lot of things to take note of. Regardless of which shading stage you're programming for, shaders will generally have the same structure as this one. This includes starting with a declaration of the version using #version. First, notice the global variables. Those are the inputs and outputs OpenGL uses to pass data through the shader. Aside from each variable having a type (e.g., vec4, which we'll get into more momentarily), data is copied into the shader from OpenGL through the in variables and likewise copied out of the shader through the out variables. The values in those variables are updated 87 Download from finelybook www.finelybook.com every time OpenGL executes the shader (e.g., if OpenGL is processing vertices, then new values are passed through those variables for each vertex; when processing fragments, then for each fragment). The other category of variable that's available to receive data from an OpenGL application are uniform variables. Uniform values don't change per vertex or fragment, but have the same value across geometric primitives until the application updates them. An Overview of the OpenGL Shading Language This section provides an overview of the shading language used within OpenGL. GLSL shares many traits with C++ and Java, and is used for authoring shaders for all the stages supported in OpenGL, although certain features are available only for particular types of shaders. We will first describe GLSL's requirements, types, and other language constructs that are shared between the various shader stages, and then discuss the features unique to each type of shader. Creating Shaders with GLSL Here, we describe how to create a complete shader. The Starting Point A shader program, just like a C program, starts execution in main(). Every GLSL shader program begins life as follows: #version 330 core void main() { // Your code goes here } The // construct is a comment and terminates at the end of the current line, just like in C. Additionally, C-type, multi-line comments—the /* and */ type— are also supported. However, unlike ANSI C, main() does not return an integer value; it is declared void. Also, as with C and its derivative languages, statements are terminated with a semicolon. While this is a perfectly legal GLSL program that compiles and even runs, its functionality leaves something 88 Download from finelybook www.finelybook.com to be desired. To add a little more excitement to our shaders, we'll continue by describing variables and their operation. Declaring Variables GLSL is a typed language; every variable must be declared and have an associated type. Variable names conform to the same rules as those for C: You can use letters, numbers, and the underscore character (_) to compose variable names. However, a digit cannot be the first character in a variable name. Similarly, variable names cannot contain consecutive underscores; those names are reserved in GLSL. Table 2.1 shows the basic types available in GLSL. Table 2.1 Basic Data Types in GLSL These types (and later, aggregate types composed of these) are all transparent. That is, their internal form is exposed and the shader code gets to assume what they look like internally. An additional set of types, the opaque types, do not have their internal form exposed. These include sampler types, image types, and atomic counter types. They declare variables used as opaque handles for accessing texture maps, images, and atomic counters, as described in Chapter 4, "Color, Pixels, and Fragments." The various types of samplers and their uses are discussed in Chapter 6, "Textures and Framebuffers." Variable Scoping While all variables must be declared, they may be declared any time before their use (just as in C++). The scoping rules of GLSL, which closely parallel those of C++, are as follows: • Variables declared outside of any function definition have global scope and are visible to all subsequent functions within the shader program. 89 Download from finelybook www.finelybook.com • Variables declared within a set of curly braces (e.g., function definition, block following a loop or "if" statement, etc.) exist within the scope of those braces only. • Loop iteration variables, such as i in the loop Click here to view code image for (int i = 0; i < 10; ++i) { // loop body } are scoped only for the body of the loop. Variable Initialization Variables may also be initialized when declared. For example: Click here to view code image int float bool double i, numParticles = 1500; force, g = -9.8; falling = true; pi = 3.1415926535897932384626LF; Integer literal constants may be expressed as octal, decimal, or hexadecimal values. An optional minus sign before a numeric value negates the constant, and a trailing 'u' or 'U' denotes an unsigned integer value. Floating-point literals must include a decimal point, unless described in scientific format, as in 3E-7. (However, there are many situations where an integer literal will be implicitly converted to a floating-point value.) Additionally, they may optionally include an 'f' or 'F' suffix as in C on a float literal. You must include a suffix of 'lF' or 'LF' to make a literal have the precision of a double. Boolean values are either true or false and can be initialized to either of those values or as the result of an operation that resolves to a Boolean expression. Constructors As mentioned, GLSL is more type safe than C++, having fewer implicit conversion between values. For example, int f = false; 90 Download from finelybook www.finelybook.com will result in a compilation error due to assigning a Boolean value to an integer variable. Types will be implicitly converted as shown in Table 2.2. Table 2.2 Implicit Conversions in GLSL These type conversions work for scalars, vectors, and matrices of these types. Conversions will never change whether something is a vector or a matrix, or how many components it has. Conversions also don't apply to arrays or structures. Any other conversion of values requires explicit conversion using a conversion constructor. A constructor, as in other languages like C++, is a function with the same name as a type, which returns a value of that type. For example, float f = 10.0; int ten = int(f); uses an int conversion constructor to do the conversion. Likewise, the other types also have conversion constructors: float, double, uint, bool, and vectors and matrices of these types. Each accepts multiple other types to explicitly convert from. These functions also illustrate another feature of GLSL: function overloading, whereby each function takes various input types, but all use the same base function name. We will discuss more on functions in a bit. Aggregate Types GLSL's basic types can be combined to better match core OpenGL's data values and to ease computational operations. First, GLSL supports vectors of two, three, or four components for each of the basic types of bool, int, uint, float, and double. Also, matrices of float and double are available. Table 2.3 lists the valid vector and matrix types. 91 Download from finelybook www.finelybook.com Table 2.3 GLSL Vector and Matrix Types Matrix types that list both dimensions, such as mat4x3, use the first value to specify the number of columns, the second the number of rows. Variables declared with these types can be initialized similar to their scalar counterparts: Click here to view code image vec3 velocity = vec3(0.0, 2.0, 3.0); Converting between types is equally accessible: Click here to view code image ivec3 steps = ivec3(velocity); Vector constructors can also be used to truncate or lengthen a vector. If a longer vector is passed into the constructor of a smaller vector, the vector is truncated to the appropriate length. Click here to view code image vec4 color; vec3 RGB = vec3(color); // now RGB only has three elements Scalar values can be promoted to vectors, but that's the only way a vector constructor takes fewer components than its size indicates: Click here to view code image 92 Download from finelybook www.finelybook.com vec3 white = vec3(1.0); // white = (1.0, 1.0, 1.0) vec4 translucent = vec4(white, 0.5); Matrices are constructed in the same manner and can be initialized to either a diagonal matrix or a fully populated matrix. In the case of diagonal matrices, a single value is passed into the constructor, and the diagonal elements of the matrix are set to that value, with all others being set to zero, as in Matrices can also be created by specifying the value of every element in the matrix in the constructor. Values can be specified by combinations of scalars and vectors as long as enough values are provided and each column is specified in the same manner. Additionally, matrices are specified in columnmajor order, meaning the values are used to populate columns before rows (which is the opposite of how C initializes two-dimensional arrays). For example, we could initialize a 3 × 3 matrix in any of the following ways: Click here to view code image mat3 M = mat3(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0); vec3 column1 = vec3(1.0, 2.0, 3.0); vec3 column2 = vec3(4.0, 5.0, 6.0); vec3 column3 = vec3(7.0, 8.0, 9.0); mat3 M = mat3(column1, column2, column3); or even Click here to view code image vec2 column1 = vec2(1.0, 2.0); vec2 column2 = vec2(4.0, 5.0); vec2 column3 = vec2(7.0, 8.0); mat3 M = mat3(column1, 3.0, column2, 6.0, column3, 9.0); 93 Download from finelybook www.finelybook.com all yielding the same matrix, Accessing Elements in Vectors and Matrices The individual elements of vectors and matrices can be accessed and assigned. Vectors support two types of element access: a named-component method and an array-like method. Matrices use a two-dimensional array-like method. Components of a vector can be accessed by name, as in float red = color.r; float v_y = velocity.y; or by using a zero-based index scheme. The following yield identical results to the previous listing: float red = color[0]; float v_y = velocity[1]; In fact, as shown in Table 2.4, there are three sets of component names, all of which do the same thing. The multiple sets are useful for clarifying the operations that you're doing. Table 2.4 Vector Component Accessors A common use for component-wise access to vectors is for swizzling components, as you might do with colors, perhaps for color space conversion. For example, you could do the following to specify a luminance value based on the red component of an input color: vec3 luminance = color.rrr; Likewise, if you needed to move components around in a vector, you might do Click here to view code image 94 Download from finelybook www.finelybook.com color = color.abgr; // reverse the components of a color The only restriction is that only one set of components can be used with a variable in one statement. That is, you can't do Click here to view code image vec4 color = otherColor.rgz; // Error: 'z' is from a different group Also, a compile-time error will be raised if you attempt to access an element that's outside the range of the type. For example, Click here to view code image vec2 pos; float zPos = pos.z; // Error: no 'z' component in 2D vectors Matrix elements can be accessed using the array notation. Either a single scalar value or an array of elements can be accessed from a matrix: Click here to view code image mat4 m = mat4(2.0); vec4 zVec = m[2]; // get column 2 of the matrix float yScale = m[1][1]; // or m[1].y works as well Structures You can also logically group collections of different types into a structure. Structures are convenient for passing groups of associated data into functions. When a structure is defined, it automatically creates a new type and implicitly defines a constructor function that takes the types of the elements of the structure as parameters. Click here to view code image struct Particle { float lifetime; vec3 position; vec3 velocity; }; Particle p = Particle(10.0, pos, vel); // pos, vel are vec3s 95 Download from finelybook www.finelybook.com Likewise, to reference elements of a structure, use the familiar "dot" notation as you would in C. Arrays GLSL also supports arrays of any type, including structures. As with C, arrays are indexed using brackets ([ ]). The range of elements in an array of size n is 0 ... n – 1. Unlike in C, however, neither negative array indices nor positive indices out of range are permitted. As of GLSL 4.3, arrays can be made out of arrays, providing a way to handle multidimensional data. However, GLSL 4.2 and earlier versions do not allow arrays of arrays to be created (that is, you cannot create a multidimensional array). Arrays can be declared sized or unsized. You might use an unsized array as a forward declaration of an array variable and later redeclare it to the appropriate size. Array declarations use the bracket notation, as in Click here to view code image float float[3] int size coeff[3]; // an array of 3 floats coeff; // same thing indices[]; // unsized. Redeclare later with a Arrays are first-class types in GLSL, meaning they have constructors and can be used as function parameters and return types. To statically initialize an array of values, you would use a constructor in the following manner: Click here to view code image float coeff[3] = float[3](2.38, 3.14, 42.0); The dimension value on the constructor is optional. Additionally, similar to Java, GLSL arrays have an implicit method for reporting their number of elements: the length() method. If you would like to operate on all the values in an array, here is an example using the length() method: Click here to view code image for (int i = 0; i < coeff.length(); ++i) { coeff[i] *= 2.0; } 96 Download from finelybook www.finelybook.com The length() method also works on vectors and matrices. A vector's length is the number of components it contains, while a matrix's length is the number of columns it contains. This is exactly what you need when using array syntax for indexing vectors and matrices. (m[2] is the third column of a matrix m.) Click here to view code image mat3x4 m; int c = m.length(); // number of columns in m: 3 int r = m[0].length(); // number of components in column vector 0: 4 When the length is known at compile time, the length() method will return a compile-time constant that can be used where compile-time constants are required. For example: Click here to view code image mat4 m; float diagonal[m.length()]; matrix size float x[gl_in.length()]; number of // array of size matching the // array of size matching the // geometry shader input vertices For all vectors and matrices, and most arrays, length() is known at compile time. However, for some arrays, length() is not known until link time. This happens when relying on the linker to deduce the size from multiple shaders in the same stage. For shader storage buffer objects (declared with buffer, as described shortly), length() might not be known until render time. If you want a compile-time constant returned from length(), just make sure you establish the array size in your shader before using the length() method. Multidimensional arrays are really arrays made from arrays and have a syntax similar to C: Click here to view code image float coeff[3][5]; of size 5 coeff[2][1] *= 2.0; outer is 2 // an array of size 3 of arrays // inner-dimension index is 1, 97 Download from finelybook www.finelybook.com coeff.length(); coeff[2]; size 5 coeff[2].length(); // this returns the constant 3 // a one-dimensional array of // this returns the constant 5 Multidimensional arrays can be formed in this way for virtually any type and resource. When shared with the application, the innermost (rightmost) dimension changes the fastest in the memory layout. Storage Qualifiers Types can also have modifiers that affect their behavior. There are several modifiers defined in GLSL, as shown in Table 2.5, with the behaviors they exhibit when used at global scope. Table 2.5 GLSL Type Modifiers const Storage Qualifier Just as with C, const type modifier indicates that the variable is read-only. For example, the statement const float Pi = 3.141529; sets the variable Pi to an approximation of π. With the addition of the const modifier, it becomes an error to write to a variable after its declaration, so const variables must be initialized when declared. 98 Download from finelybook www.finelybook.com in Storage Qualifier The in modifier is used to qualify inputs into a shader stage. Those inputs may be vertex attributes (for vertex shaders) or output variables from the preceding shader stage. Fragment shaders can further qualify their input values using some additional keywords that we discuss in Chapter 4, "Color, Pixels, and Fragments." out Storage Qualifier The out modifier is used to qualify outputs from a shader stage. For example, the transformed homogeneous coordinates from a vertex shader or the final fragment color from a fragment shader. uniform Storage Qualifier The uniform modifier specifies that a variable's value will be specified by the application before the shader's execution and does not change across the primitive being processed. Uniform variables are shared among all the shader stages enabled in a program and must be declared as global variables. Any type of variable, including structures and arrays, can be specified as uniform. A shader cannot write to a uniform variable and change its value. For example, you might want to use a color for shading a primitive. You might declare a uniform variable to pass that information into your shaders. In the shaders, you would make the declaration uniform vec4 BaseColor; Within your shaders, you can reference BaseColor by name, but to set its value in your application, you need to do a little extra work. The GLSL compiler creates a table of all uniform variables when it links your shader program. To set BaseColor's value from your application, you need to obtain the index of BaseColor in the table, which is done using the glGetUniformLocation() routine. GLint glGetUniformLocation(GLuint program, const char* name); Returns the index of the uniform variable name associated with the shader program. name is a null-terminated character string 99 Download from finelybook www.finelybook.com with no spaces. A value of minus one (–1) is returned if name does not correspond to a uniform variable in the active shader program or if a reserved shader variable name (those starting with gl_ prefix) is specified. name can be a single variable name, an element of an array (by including the appropriate index in brackets with the name), or a field of a structure (by specifying name, then "." followed by the field name, as you would in the shader program). For arrays of uniform variables, the index of the first element of the array may be queried either by specifying only the array name (for example, arrayName) or by specifying the index to the first element of the array (as in arrayName[0]). The returned value will not change unless the shader program is relinked (see glLinkProgram()). Once you have the associated index for the uniform variable, you can set the value of the uniform variable using the glUniform*() or glUniformMatrix*() routine. Example 2.2 demonstrates obtaining a uniform variable's index and assigning values. Example 2.2 Obtaining a Uniform Variable's Index and Assigning Values Click here to view code image GLint timeLoc; /* Uniform index for variable "time" in shader */ GLfloat timeValue; /* Application time */ timeLoc = glGetUniformLocation(program, "time"); glUniform1f(timeLoc, timeValue); void glUniform{1234}{fdi ui}(GLint location, TYPE value); void glUniform{1234}{fdi ui}v(GLint location, GLsizei count, const TYPE * values); void glUniformMatrix{234}{fd}v(GLint location, GLsizei count, GLboolean transpose, 100 Download from finelybook www.finelybook.com const GLfloat * values); void glUniformMatrix{2x3,2x4,3x2,3x4,4x2,4x3}{fd}v( GLint location, GLsizei count, GLboolean transpose, const GLfloat * values); Sets the value for the uniform variable associated with the index location. The vector form loads count sets of values (from one to four values, depending upon which glUniform*() call is used) into the uniform variable's starting location. If location is the start of an array, count sequential elements of the array are loaded. The GLfloat forms can be used to load the single-precision types of float, a vector of floats, an array of floats, or an array of vectors of floats. Similarly, the GLdouble forms can be used for loading double-precision scalars, vectors, and arrays. The GLfloat forms can also load Boolean types. The GLint forms can be used to update a single signed integer, a signed integer vector, an array of signed integers, or an array of signed integer vectors. Additionally, individual and arrays of texture samplers and Boolean scalars, vectors, and arrays can be loaded. Similarly, the GLuint forms can be used for loading unsigned scalars, vectors, and arrays. For glUniformMatrix{234}*(), count sets of 2 × 2, 3 × 3, or 4 × 4 matrices are loaded from values. For glUniformMatrix{2x3,2x4,3x2,3x4,4x2,4x3}*(), count sets of like-dimensioned matrices are loaded from values. If transpose is GL_TRUE, values are specified in row-major order (like arrays in C); or if GL_FALSE is specified, values are taken to be in column-major order. buffer Storage Qualifier The recommended way to share a large buffer with the application is through use of a buffer variable. Buffer variables are much like uniform variables, except that they can be modified by the shader. Typically, you'd use 101 Download from finelybook www.finelybook.com buffer variables in a buffer block, and blocks in general are described later in this chapter. The buffer modifier specifies that the subsequent block is a memory buffer shared between the shader and the application. This buffer is both readable and writable by the shader. The size of the buffer can be established after shader compilation and program linking. shared Storage Qualifier The shared modifier is used only in compute shaders to establish memory shared within a local work group. This is discussed in more detail in Chapter 12, "Compute Shaders." Statements The real work in a shader is done by computing values and making decisions. In the same manner as C++, GLSL has a rich set of operators for constructing arithmetic operations for computing values and a standard set of logical constructs for controlling shader execution. Arithmetic Operations No text describing a language is complete without the mandatory table of operator precedence (see Table 2.6). The operators are ordered in decreasing precedence. In general, the types being operated on must be the same, and for vector and matrices, the operands must be of the same dimension. In the table, integer types include int and uint and vectors of them; floating-point types include float and double types and vectors and matrices of them; arithmetic types include all integer and floating-point types; and any additionally includes structures and arrays. 102 Download from finelybook www.finelybook.com Table 2.6 GLSL Operators and Their Precedence Overloaded Operators Most operators in GLSL are overloaded, meaning that they operate on a varied set of types. Specifically, arithmetic operations (including pre- and postincrement and -decrement) for vectors and matrices are well defined in GLSL. For example, to multiply a vector and a matrix (recalling that the order of operands is important; matrix multiplication is noncommutative, for all you math heads), use the following operation: 103 Download from finelybook www.finelybook.com vec3 v; mat3 m; vec3 result = v * m; The normal restrictions apply, that the dimensionality of the matrix and the vector must match. Additionally, scalar multiplication with a vector or matrix will produce the expected result. One notable exception is that the multiplication of two vectors will result in component-wise multiplication of components; however, multiplying two matrices will result in normal matrix multiplication. Click here to view code image vec2 a, mat2 m, c = a * m = u * b, u, b; v; c; v; // // // c = (a.x*b.x, a.y*b.y) m = (u00*v00+u01*v10 u00*v01+u01*v11 u01*v00+u11*v10 u10*v01+u11*v11) Additional common vector operations (e.g., dot and cross products) are supported by function calls, as well as various per-component operations on vectors and matrices. Control Flow GLSL's logical control structures are the popular if-else and switch statements. As with the C language, the else clause is optional, and multiple statements require a block. if (truth) { // true clause } else { // false clause } Similar to the situation in C, switch statements are available (starting with GLSL 1.30) in their familiar form: switch (int_value) { case n: // statements break; 104 Download from finelybook www.finelybook.com case m: // statements break; default: // statements break; } GLSL switch statements also support "fall-through" cases—case statements that do not end with break statements. Each case does require some statement to execute before the end of the switch (before the closing brace). Also, unlike in C++, no statements are allowed before the first case. If no case matches the switch and a default label is present, then it is executed. Looping Constructs GLSL supports the familiar C form of for, while, and do ... while loops. The for loop permits the declaration of the loop iteration variable in the initialization clause of the for loop. The scope of iteration variables declared in this manner is only for the lifetime of the loop. Click here to view code image for (int i = 0; i < 10; ++i) { ... } while (n < 10) { ... } do { ... } while (n < 10); Control-Flow Statements Additional control statements beyond conditionals and loops are available in GLSL. Table 2.7 describes available control-flow statements. 105 Download from finelybook www.finelybook.com Table 2.7 GLSL Control-Flow Statements The discard statement is available only in fragment programs. The execution of the fragment shader may be terminated at the execution of the discard statement, but this is implementation-dependent. Functions Functions permit you to replace occurrences of common code with a function call. This, of course, allows for smaller code and fewer chances for errors. GLSL defines a number of built-in functions, which are listed in Appendix C, as well as support for user-defined functions. User-defined functions can be defined in a single shader object and reused in multiple shader programs. Declarations Function declaration syntax is very similar to C, with the exception of the access modifiers on variables: Click here to view code image returnType functionName([accessModifier] type1 variable1, [accessModifier] type2 variable2, ...) { // function body return returnValue; // unless returnType is void } Function names can be any combination of letters, numbers, and the underscore character, with the exception that it can neither begin with a digit nor with gl_ 106 Download from finelybook www.finelybook.com nor contain consecutive underscores. Return types can be any built-in GLSL type or user-defined structure or array type. Arrays as return values must explicitly specify their size. If a function doesn't return a value, its return type is void. Parameters to functions can also be of any type, including arrays (which must specify their size). Functions must be either declared with a prototype or defined with a body before they are called. Just as in C++, the compiler must have seen the function's declaration before its use, or an error will be raised. If a function is used in a shader object other than the one where it's defined, a prototype must be declared. A prototype is merely the function's signature without its accompanying body. Here's a simple example: Click here to view code image float HornerEvalPolynomial(float coeff[10], float x); Parameter Qualifiers While functions in GLSL are able to modify and return values after their execution, there's no concept of a pointer or reference, as in C or C++. Rather, parameters of functions have associated parameter qualifiers indicating whether the value should be copied into, or out of, a function after execution. Table 2.8 describes the available parameter qualifiers in GLSL. Table 2.8 GLSL Function Parameter Access Modifiers The in keyword is optional. If a variable does not include an access modifier, an in modifier is implicitly added to the parameter's declaration. However, if the variable's value needs to be copied out of a function, it must either be tagged with an out (for copy out-only variables) or an inout (for a variable both copied in and copied out) modifier. Writing to a variable not tagged with one of these modifiers will generate a compile-time error. Additionally, to verify at compile time that a function doesn't modify an input107 Download from finelybook www.finelybook.com only variable, adding a const in modifier will cause the compiler to check that the variable is not written to in the function. If you don't do this and do write to an input-only variable, the write only modifies the local copy in the function. Computational Invariance GLSL does not guarantee that two identical computations in different shaders will result in exactly the same value. The situation is no different than for computational applications executing on the CPU, where the choice of optimizations may result in tiny differences in results. These tiny errors may be an issue for multipass algorithms that expect positions to be computed exactly the same for each shader pass. GLSL has two methods for enforcing this type of invariance between shaders, using the invariant or precise keywords. Both of these methods will cause computations done by the graphics device to create reproducibility (invariance) in results of the same expression. However, they do not help reproduce the same results between the host and the graphics device. Compile-time constant expressions are computed on the compiler's host, and there is no guarantee that the host computes in exactly the same way as the graphics device. For example: Click here to view code image uniform float ten; // application sets this to 10.0 const float f = sin(10.0); // computed on compiler host float g = sin(ten); // computed on graphics device void main() { if (f == g) ; } // f and g might be not equal In this example, it would not matter if invariant or precise was used on any of the variables involved, as they affect only two computations done on the graphics device. The invariant Qualifier The invariant qualifier may be applied to any shader output variable. It will 108 Download from finelybook www.finelybook.com guarantee that if two shader invocations each set the output variable with the same expression and the same values for the variables in that expression, both will compute the same value. The output variable declared as invariant may be a built-in variable or a userdefined one. For example: Click here to view code image invariant gl_Position; invariant centroid out vec3 Color; As you may recall, output variables are used to pass data from one stage to the next. The invariant keyword may be applied at any time before use of the variable in the shader and may be used to modify built-in variables. This is done by declaring the variable only with invariant, as was shown earlier for gl_Position. For debugging, it may be useful to impose invariance on all varying variables in shader. This can be accomplished by using the vertex shader preprocessor pragma. #pragma STDGL invariant(all) Global invariance in this manner is useful for debugging; however, it may likely have an impact on the shader's performance. Guaranteeing invariance usually disables optimizations that may have been performed by the GLSL compiler. The precise Qualifier The precise qualifier may be applied to any computed variable or function return value. Despite its name, its purpose is not to increase precision, but to increase reproducibility of a computation. It is mostly used in tessellation shaders to avoid forming cracks in your geometry. Tessellation shading in general is described in Chapter 9, "Tessellation Shaders," and there is additional discussion in that chapter about a use case for precise qualification. Generally, you use precise instead of invariant when you need to get the same result from an expression, even if values feeding the expression are permuted in a way that should not mathematically affect the result. For example, the following expression should get the same result if the values for a 109 Download from finelybook www.finelybook.com and b are exchanged. It should also get the same result if the values for c and d and exchanged, or if both a and c are exchanged and b and d are exchanged, and so on. Location = a * b + c * d; The precise qualifier may be applied to a built-in variable, user variable, or function return value. Click here to view code image precise gl_Position; precise out vec3 Location; precise vec3 subdivide(vec3 P1, vec3 P2) { ... } The precise keyword may be applied at any time before use of the variable in the shader and may be used to modify previously declared variables. One practical impact in a compiler of using precise is an expression like the one above cannot be evaluated using two different methods of multiplication for the two multiply operations—for example, a multiply instruction for the first multiply and a fused multiply-and-add instruction for the second multiply. This is because these two instructions will get slightly different results for the same values. Because that was disallowed by precise, the compiler is prevented from doing this. Because use of fused multiply-and-add instructions is important to performance, it would be unfortunate to completely disallow them. So there is a built-in function in GLSL, fma(), that you can use to explicitly say this is okay. precise out float result; ... float f = c * d; float result = fma(a, b, f); Of course, you do that only if you weren't going to have the values of a and c permuted, as you would be defeating the purpose of using precise. Shader Preprocessor The first step in compilation of a GLSL shader is parsing by the preprocessor. Similar to the C preprocessor, there are a number of directives for creating conditional compilation blocks and defining values. However, unlike in the C preprocessor, there is no file inclusion (#include). 110 Download from finelybook www.finelybook.com Preprocessor Directives Table 2.9 lists the preprocessor directives accepted by the GLSL preprocessor and their functions. Table 2.9 GLSL Preprocessor Directives Macro Definition The GLSL preprocessor allows macro definition in much the same manner as the C preprocessor, with the exception of the string substitution and concatenation facilities. Macros might define a single value, as in #define NUM_ELEMENTS 10 or with parameters like Click here to view code image #define LPos(n) gl_LightSource[(n)].position Additionally, there are several predefined macros for aiding in diagnostic messages (that you might issue with the #error directive, for example), as shown in Table 2.10. 111 Download from finelybook www.finelybook.com Table 2.10 GLSL Preprocessor Predefined Macros Likewise, macros (excluding those defined by GLSL) may be undefined by using the #undef directive. For example, #undef LPos Preprocessor Conditionals Identical to the processing by the C preprocessor, the GLSL preprocessor provides conditional code inclusion based on macro definition and integer constant evaluation. Macro definition may be determined in two ways. Use the #ifdef directive: #ifdef NUM_ELEMENTS ...
Komentar
Posting Komentar