Introduction to Computer Graphics Instructor Prof TongYee Lee

  • Slides: 77
Download presentation
Introduction to Computer Graphics Instructor: Prof. Tong-Yee Lee 李同益 Dr. Chih-Kuo Yeh 葉智國

Introduction to Computer Graphics Instructor: Prof. Tong-Yee Lee 李同益 Dr. Chih-Kuo Yeh 葉智國

Open Graphics Library (Open. GL)

Open Graphics Library (Open. GL)

Open. GL • Created in 1991 by Silicon Graphics. Now it is managed by

Open. GL • Created in 1991 by Silicon Graphics. Now it is managed by the Khronos consortium • Application programming interface (API) for rendering 2 D3 D graphics • The API interacts with the graphics processing unit (GPU) • Language-independent: has many bindings, e. g. , Web. GL. • Platform-independent: works on Win/Linux/. .

Software Organization application program Open. GL Motif widget or similar GLUT GLX, AGL or

Software Organization application program Open. GL Motif widget or similar GLUT GLX, AGL or WGL X, Win 32, Mac O/S software and/or hardware GLU GL

Open. GL – Design • A library of functions (e. g. , gl. Draw.

Open. GL – Design • A library of functions (e. g. , gl. Draw. Arrays) and constants (e. g. , GL_TRIANGLES) • Language-independent: has many bindings, e. g. , Web. GL. • Platform-independent: works on Win/Linux/. .

Open. GL – Libraries • Open. GL doesn’t support input or windowing • GLUT

Open. GL – Libraries • Open. GL doesn’t support input or windowing • GLUT (or freeglut) is used to create a context • Platform-dependent extensions can be provided by vendors. Identified by ARB, EXT, . . • GLEW wraps the extraction of functionality

Open. GL – History • Open. GL 1 (1992): Fixed pipeline • Open. GL

Open. GL – History • Open. GL 1 (1992): Fixed pipeline • Open. GL 2 (2004): Programmable shaders (GLSL) • Open. GL 3: Deprecated the fixed functionality • Open. GL 3. 1 (2009): Removed completely the fixed pipeline • . . • Open. GL 4. 5 (2014):

Open. GL 1. X Functions • Primitives • Points • Line Segments • Polygons

Open. GL 1. X Functions • Primitives • Points • Line Segments • Polygons • Attributes • Transformations • Viewing • Modeling • Control • Input (GLUT) 8

Open. GL 1. X State • Open. GL is a state machine • Open.

Open. GL 1. X State • Open. GL is a state machine • Open. GL functions are of two types • Primitive generating • Can cause output if primitive is visible • How vertices are processes and appearance of primitive are controlled by the state • State changing • Transformation functions • Attribute functions 9

Lack of Object Orientation • Open. GL is not object oriented so that there

Lack of Object Orientation • Open. GL is not object oriented so that there are multiple functions for a given logical function, e. g. gl. Vertex 3 f, gl. Vertex 2 i, gl. Vertex 3 dv, …. . • Underlying storage mode is the same • Easy to create overloaded functions in C++ but issue is efficiency 10

Open. GL function format function name gl. Vertex 3 f(x, y, z) belongs to

Open. GL function format function name gl. Vertex 3 f(x, y, z) belongs to GL library x, y, z are floats gl. Vertex 3 fv(p) p is a pointer to an array 11

Open. GL #defines • Most constants are defined in the include files gl. h,

Open. GL #defines • Most constants are defined in the include files gl. h, glu. h and glut. h • • Note #include <glut. h> should automatically include the others Examples gl. Begin(GL_POLYGON) gl. Clear(GL_COLOR_BUFFER_BIT) • include files also define Open. GL data types: GLfloat, GLdouble, …. 12

A Simple Program Generate a square on a solid background 13

A Simple Program Generate a square on a solid background 13

simple. c #include <glut. h> void mydisplay(){ gl. Clear(GL_COLOR_BUFFER_BIT); gl. Begin(GL_POLYGON); gl. Vertex 2

simple. c #include <glut. h> void mydisplay(){ gl. Clear(GL_COLOR_BUFFER_BIT); gl. Begin(GL_POLYGON); gl. Vertex 2 f(-0. 5, -0. 5); gl. Vertex 2 f(-0. 5, 0. 5); gl. Vertex 2 f(0. 5, -0. 5); gl. End(); gl. Flush(); } int main(int argc, char** argv){ glut. Init(&argc, argv); glut. Create. Window("simple"); glut. Display. Func(mydisplay); glut. Main. Loop(); } 14

Event Loop • Note that the program defines a display callback function named mydisplay

Event Loop • Note that the program defines a display callback function named mydisplay • Every glut program must have a display callback • The display callback is executed whenever Open. GL decides the display must be refreshed, for example when the window is opened • The main function ends with the program entering an event loop 15

Defaults • simple. c is too simple • Makes heavy use of state variable

Defaults • simple. c is too simple • Makes heavy use of state variable default values for • Viewing • Colors • Window parameters • Next version will make the defaults more explicit 16

Compilation on Windows • Visual C++ • • • Get glut. h, glut 32.

Compilation on Windows • Visual C++ • • • Get glut. h, glut 32. lib and glut 32. dll from web Create a console application Add path to find include files (GL/glut. h) Add opengl 32. lib, glut 32. lib to project settings (for library linking) glut 32. dll is used during the program execution. (Other DLL files are included in the device driver of the graphics accelerator. ) 17

Programming with Open. GL Part 2: Complete Programs

Programming with Open. GL Part 2: Complete Programs

Objectives • Refine the first program • Alter the default values • Introduce a

Objectives • Refine the first program • Alter the default values • Introduce a standard program structure • Simple viewing • Two-dimensional viewing as a special case of threedimensional viewing • Fundamental Open. GL primitives • Attributes 19

Program Structure • Most Open. GL programs have a similar structure that consists of

Program Structure • Most Open. GL programs have a similar structure that consists of the following functions • main(): • defines the callback functions • opens one or more windows with the required properties • enters event loop (last executable statement) • init(): sets the state variables • viewing • Attributes • callbacks • Display function • Input and window functions 20

Simple. c revisited • In this version, we will see the same output but

Simple. c revisited • In this version, we will see the same output but have defined all the relevant state values through function calls with the default values • In particular, we set • Colors • Viewing conditions • Window properties 21

main. c #include <GL/glut. h> includes gl. h int main(int argc, char** argv) {

main. c #include <GL/glut. h> includes gl. h int main(int argc, char** argv) { glut. Init(&argc, argv); glut. Init. Display. Mode(GLUT_SINGLE|GLUT_RGB); glut. Init. Window. Size(500, 500); glut. Init. Window. Position(0, 0); glut. Create. Window("simple"); glut. Display. Func(mydisplay); define window properties display callback init(); } glut. Main. Loop(); set Open. GL state enter event loop 22

GLUT functions • glut. Init allows application to get command line arguments and initializes

GLUT functions • glut. Init allows application to get command line arguments and initializes system • glu. Init. Display. Mode requests properties of the window (the rendering context) • RGB color • Single buffering • Properties logically ORed together • • • glut. Window. Size in pixels glut. Window. Position from top-left corner of display glut. Create. Window create window with title “simple” glut. Display. Func display callback glut. Main. Loop enter infinite event loop 23

init. c black clear color opaque window void init() { gl. Clear. Color (0.

init. c black clear color opaque window void init() { gl. Clear. Color (0. 0, 1. 0); gl. Color 3 f(1. 0, 1. 0); } fill with white gl. Matrix. Mode (GL_PROJECTION); gl. Load. Identity (); gl. Ortho(-1. 0, -1. 0, 1. 0); viewing volume 24

Double-Buffered Animation

Double-Buffered Animation

Double-Buffered Animation #include <GL/glut. h> int main(int argc, char** argv) { glut. Init(&argc, argv);

Double-Buffered Animation #include <GL/glut. h> int main(int argc, char** argv) { glut. Init(&argc, argv); glut. Init. Display. Mode(GLUT_DOUBLE|GLUT_RGB); glut. Init. Window. Size(500, 500); glut. Init. Window. Position(0, 0); glut. Create. Window("simple"); glut. Display. Func(mydisplay); init(); glut. Main. Loop(); }

Double-Buffered Animation void mydisplay(){ gl. Clear(GL_COLOR_BUFFER_BIT); gl. Begin(GL_POLYGON); gl. Vertex 2 f(-0. 5, -0.

Double-Buffered Animation void mydisplay(){ gl. Clear(GL_COLOR_BUFFER_BIT); gl. Begin(GL_POLYGON); gl. Vertex 2 f(-0. 5, -0. 5); gl. Vertex 2 f(-0. 5, 0. 5); gl. Vertex 2 f(0. 5, -0. 5); gl. End(); gl. Flush(); glut. Swap. Buffers(); }

Open. GL – History • Open. GL 1 • Open. GL 2 • Open.

Open. GL – History • Open. GL 1 • Open. GL 2 • Open. GL 3. 1 • . . • Open. GL 4. 5 State Machine Data flow model

No Fixed Function • Built-In matrix-functions/stacks: • gl. Matrix. Mode, gl. Mult/Load. Matrix, •

No Fixed Function • Built-In matrix-functions/stacks: • gl. Matrix. Mode, gl. Mult/Load. Matrix, • gl. Rotate/Translate/Scale, gl. Push/Pop. Matrix… • Immediate Mode: • gl. Begin/End, gl. Vertex, gl. Tex. Coords… • Material and Lighting: • gl. Light, gl. Material, … • Attribute-Stack: • gl. Push/Pop. Attrib, …

Open. GL – Coding • The API is obtained by including freeglut: #include <GL/freeglut.

Open. GL – Coding • The API is obtained by including freeglut: #include <GL/freeglut. h> • Extensions are accessed through GLEW: #include <GL/glew. h> glew. Init();

Open. GL – Coding • gl. Some. Function*();

Open. GL – Coding • gl. Some. Function*();

Open. GL – Coding • Examples: gl. Uniform 1 f(. . ); gl. Uniform

Open. GL – Coding • Examples: gl. Uniform 1 f(. . ); gl. Uniform 3 iv(. . ); • Later, we will see how to transfer matrices

Open. GL – Coding • Send the array points to the GPU • Name

Open. GL – Coding • Send the array points to the GPU • Name and create a vertex array object (VAO) GLuint abuffer; gl. Gen. Vertex. Arrays(1, &abuffer); gl. Bind. Vertex. Array(abuffer);

Open. GL – Coding • Next, we create a vertex buffer object (VBO) •

Open. GL – Coding • Next, we create a vertex buffer object (VBO) • This is how data is actually stored GLuint buffer; gl. Gen. Buffers(1, &buffer); gl. Bind. Buffer(GL_ARRAY_BUFFER, buffer); gl. Buffer. Data(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

Open. GL – Coding • Rendering our data can be done simply by gl.

Open. GL – Coding • Rendering our data can be done simply by gl. Draw. Arrays(GL_TRIANGLES, 0, sizeof(points)); • Thus, a simple display method is void display(void) { gl. Clear(GL_COLOR_BUFFER_BIT); gl. Draw. Arrays(GL_TRIANGLES, 0, sizeof(points)); gl. Flush(); }

GLSL Rendering Pipeline • Similar to our software renderer • Your program will consist

GLSL Rendering Pipeline • Similar to our software renderer • Your program will consist of shaders • No default shaders • You must at least supply vertex and fragment shaders

Open. GL – Shaders • Vertex shader: • Send a vertex location to the

Open. GL – Shaders • Vertex shader: • Send a vertex location to the rasterizer • … • Fragment shader: • Its input is a fragment inside the clipping volume • Output a color to the fragment • …

GL Shading Language (GLSL) • C-like language • Need to compile and link in

GL Shading Language (GLSL) • C-like language • Need to compile and link in runtime • Create a program object with shader objects • Connect shader’s entities to our program

Open. GL – Coding • Create a program object with shader objects GLuint program;

Open. GL – Coding • Create a program object with shader objects GLuint program; program = Init. Shader("vsource. glsl", "fsource. glsl"); • Connect shader’s entities to our program GLuint loc; loc = gl. Get. Attrib. Location(program, "v. Position"); gl. Enable. Vertex. Attrib. Array(loc); gl. Vertex. Attrib. Pointer(loc, 2, GL_FLOAT, GL_FALSE, 0, 0);

Open. GL – Example static char* read. Shader. Source(const char* shader. File) { …

Open. GL – Example static char* read. Shader. Source(const char* shader. File) { … } Gluint Init. Shader(const char* v. Shader. File, const char* f. Shader. File) { … }

Open. GL – Example void init(void) { … points = …; … GLuint program

Open. GL – Example void init(void) { … points = …; … GLuint program = Init. Shader( "vshader. glsl", "fshader. glsl" ); gl. Use. Program( program ); GLuint vao; gl. Gen. Vertex. Arrays( 1, &vao ); gl. Bind. Vertex. Array( vao );

Open. GL – Example GLuint buffer; gl. Gen. Buffers( 1, &buffer ); gl. Bind.

Open. GL – Example GLuint buffer; gl. Gen. Buffers( 1, &buffer ); gl. Bind. Buffer( GL_ARRAY_BUFFER, buffer ); gl. Buffer. Data( GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW ); GLuint loc = gl. Get. Attrib. Location( program, "v. Position" ); gl. Enable. Vertex. Attrib. Array( loc ); gl. Vertex. Attrib. Pointer(loc, 2, GL_FLOAT, GL_FALSE, 0, 0) gl. Clear. Color( 1. 0, 1. 0 ); }

Open. GL – Example void display(void) { gl. Clear( GL_COLOR_BUFFER_BIT ); gl. Draw. Arrays(

Open. GL – Example void display(void) { gl. Clear( GL_COLOR_BUFFER_BIT ); gl. Draw. Arrays( GL_POINTS, 0, sizeof(points) ); gl. Flush( void ); }

Open. GL – Example int main( int argc, char **argv ) { glut. Init(

Open. GL – Example int main( int argc, char **argv ) { glut. Init( &argc, argv ); glut. Init. Display. Mode( GLUT_RGBA ); glut. Init. Window. Size( 512, 512 ); glut. Init. Context. Version( 3, 2 ); glut. Init. Context. Profile( GLUT_CORE_PROFILE ); glut. Create. Window( “Example" ); glew. Init( void ); init( void ); glut. Display. Func( display ); glut. Main. Loop( void ); return 0; }

Open. GL – Example #version 150 in vec 4 v. Position; void main() {

Open. GL – Example #version 150 in vec 4 v. Position; void main() { gl_Position = v. Position; } #version 150 out vec 4 f. Color; void main() { f. Color = vec 4( 1. 0, 0. 0, 1. 0 ); }

A Simplified Pipeline Model Application Vertices Vertex Processing Vertex Shader Framebuffer GPU Data Flow

A Simplified Pipeline Model Application Vertices Vertex Processing Vertex Shader Framebuffer GPU Data Flow Pixels Fragments Rasterizer Fragment Processing Fragment Shader

Representing Geometric Objects • Geometric objects are represented using vertices • A vertex is

Representing Geometric Objects • Geometric objects are represented using vertices • A vertex is a collection of generic attributes • • positional coordinates colors texture coordinates any other data associated with that point in space • Position stored in 4 dimensional homogeneous coordinates • Vertex data must be stored in vertex buffer objects (VBOs) • VBOs must be stored in vertex array objects (VAOs)

Open. GL’s Geometric Primitives • All primitives are specified by vertices 3 0 1

Open. GL’s Geometric Primitives • All primitives are specified by vertices 3 0 1 1 3 3 0 2 2 2 0 GL_LINE_STRIP 5 1 3 GL_TRIANGLES 5 4 GL_LINE_LOOP 6 7 2 5 6 GL_LINES GL_POINTS 0 1 4 3 2 0 0 1 GL_TRIANGLE_STRIP 4 3 2 1 GL_TRIANGLE_FAN

1. 2. 3. 4. 5. static const glm: : vec 2 Vertex. Data[Vertex. Count]

1. 2. 3. 4. 5. static const glm: : vec 2 Vertex. Data[Vertex. Count] = { glm: : vec 2(-0. 90 f, -0. 90 f), // Triangle 1 glm: : vec 2(0. 85 f, -0. 90 f), glm: : vec 2(-0. 90 f, 0. 85 f), 6. 7. 8. 9. glm: : vec 2(0. 90 f, -0. 85 f), // Triangle 2 glm: : vec 2(0. 90 f, 0. 90 f), glm: : vec 2(-0. 85 f, 0. 90 f), };

Vertex Array Objects (VAOs) • VAOs store the data of an geometric object •

Vertex Array Objects (VAOs) • VAOs store the data of an geometric object • Steps in using a VAO • • generate VAO names by calling gl. Gen. Vertex. Arrays() bind a specific VAO for initialization by calling gl. Bind. Vertex. Array() update VBOs associated with this VAO bind VAO for use in rendering • This approach allows a single function call to specify all the data for an objects • previously, you might have needed to make many calls to make all the data current

VAOs in Code • Create a vertex array object GLuint vao; gl. Gen. Vertex.

VAOs in Code • Create a vertex array object GLuint vao; gl. Gen. Vertex. Arrays( 1, &vao ); gl. Bind. Vertex. Array( vao );

Storing Vertex Attributes • Vertex data must be stored in a VBO, and associated

Storing Vertex Attributes • Vertex data must be stored in a VBO, and associated with a VAO • The code-flow is similar to configuring a VAO • generate VBO names by calling gl. Gen. Buffers() • bind a specific VBO for initialization by calling gl. Bind. Buffer( GL_ARRAY_BUFFER, … ) • load data into VBO using gl. Buffer. Data( GL_ARRAY_BUFFER, … ) • bind VAO for use in rendering gl. Bind. Vertex. Array()

VBOs in Code • Create and initialize a buffer object GLuint buffer; gl. Gen.

VBOs in Code • Create and initialize a buffer object GLuint buffer; gl. Gen. Buffers( 1, &buffer ); gl. Bind. Buffer( GL_ARRAY_BUFFER, buffer ); gl. Buffer. Data( GL_ARRAY_BUFFER, sizeof(v. Positions) + sizeof(v. Colors), NULL, GL_STATIC_DRAW ); gl. Buffer. Sub. Data( GL_ARRAY_BUFFER, 0, sizeof(v. Positions), v. Positions ); gl. Buffer. Sub. Data( GL_ARRAY_BUFFER, sizeof(v. Positions), sizeof(v. Colors), v. Colors );

Connecting Vertex Shaders with Geometric Data • Application vertex data enters the Open. GL

Connecting Vertex Shaders with Geometric Data • Application vertex data enters the Open. GL pipeline through the vertex shader • Need to connect vertex data to shader variables • requires knowing the attribute location • Attribute location can either be queried by calling gl. Get. Vertex. Attrib. Location()

Vertex Array Code • Associate shader variables with vertex arrays • do this after

Vertex Array Code • Associate shader variables with vertex arrays • do this after shaders are loaded GLuint v. Position = gl. Get. Attrib. Location( program, “v. Position" ); gl. Enable. Vertex. Attrib. Array( v. Position ); gl. Vertex. Attrib. Pointer( v. Position, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0) ); GLuint v. Color = gl. Get. Attrib. Location( program, "v. Color" ); gl. Enable. Vertex. Attrib. Array( v. Color ); gl. Vertex. Attrib. Pointer( v. Color, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(v. Positions)) );

Drawing Geometric Primitives • For contiguous groups of vertices gl. Draw. Arrays( GL_TRIANGLES, 0,

Drawing Geometric Primitives • For contiguous groups of vertices gl. Draw. Arrays( GL_TRIANGLES, 0, Num. Vertices ); • Usually invoked in display callback • Initiates vertex shader

Shaders and GLSL

Shaders and GLSL

Shader-compilation command sequence

Shader-compilation command sequence

For each shader object: 1. Create a shader object. 2. Compile your shader source

For each shader object: 1. Create a shader object. 2. Compile your shader source into the object. 3. Verify that your shader compiled successfully

Then 1. 2. 3. 4. 5. Create a shader program. Attach the appropriate shader

Then 1. 2. 3. 4. 5. Create a shader program. Attach the appropriate shader objects to the shader program. Link the shader program. Verify that the shader link phase completed successfully. Use the shader for vertex or fragment processing.

Getting Your Shaders into Open. GL • Shaders need to be compiled and linked

Getting Your Shaders into Open. GL • Shaders need to be compiled and linked to form an executable shader program • Open. GL provides the compiler and linker • A program must contain • vertex and fragment shaders • other shaders are optional Create Program gl. Create. Program() Create Shader gl. Create. Shader() Load Shader Source gl. Shader. Source() Compile Shader gl. Compile. Shader() Attach Shader to Program gl. Attach. Shader() Link Program gl. Link. Program() Use Program gl. Use. Program() These steps need to be repeated for each type of shader in the shader program

GLSL Data Types • Scalar types: • Vector types: float, int, bool vec 2,

GLSL Data Types • Scalar types: • Vector types: float, int, bool vec 2, vec 3, vec 4 ivec 2, ivec 3, ivec 4 bvec 2, bvec 3, bvec 4 • Matrix types: mat 2, mat 3, mat 4 • Texture sampling: sampler 1 D, sampler 2 D, sampler 3 D, sampler. Cube • C++ Style Constructors vec 3 a = vec 3(1. 0, 2. 0, 3. 0);

Operators • Standard C/C++ arithmetic and logic operators • Overloaded operators for matrix and

Operators • Standard C/C++ arithmetic and logic operators • Overloaded operators for matrix and vector operations mat 4 m; vec 4 a, b, c; b = a*m; c = m*a;

Components and Swizzling • Access vector components using either: • [ ] (c-style array

Components and Swizzling • Access vector components using either: • [ ] (c-style array indexing) • xyzw, rgba or strq (named components) • For example: vec 3 v; v[1], v. y, v. g, v. t - all refer to the same element • Component swizzling: vec 3 a, b; a. xy = b. yx;

Qualifiers • in, out • Copy vertex attributes and other variable into and out

Qualifiers • in, out • Copy vertex attributes and other variable into and out of shaders in vec 2 tex. Coord; out vec 4 color; • uniform • shader-constant variable from application uniform float time; uniform vec 4 rotation;

Functions • Built in • Arithmetic: sqrt, power, abs • Trigonometric: sin, asin •

Functions • Built in • Arithmetic: sqrt, power, abs • Trigonometric: sin, asin • Graphical: length, reflect • User defined

Built-in Variables • gl_Position • (required) output position from vertex shader • gl_Frag. Coord

Built-in Variables • gl_Position • (required) output position from vertex shader • gl_Frag. Coord • input fragment position • gl_Frag. Depth • input depth value in fragment shader

Simple Vertex Shader for Cube Example #version 430 in vec 4 v. Position; in

Simple Vertex Shader for Cube Example #version 430 in vec 4 v. Position; in vec 4 v. Color; out vec 4 color; void main() { color = v. Color; gl_Position = v. Position; }

The Simplest Fragment Shader #version 430 in vec 4 color; out vec 4 f.

The Simplest Fragment Shader #version 430 in vec 4 color; out vec 4 f. Color; // fragment’s final color void main() { f. Color = color; }

Associating Shader Variables and Data • Need to associate a shader variable with an

Associating Shader Variables and Data • Need to associate a shader variable with an Open. GL data source • vertex shader attributes → app vertex attributes • shader uniforms → app provided uniform values • Open. GL relates shader variables to indices for the app to set • Two methods for determining variable/index association • specify association before program linkage • query association after program linkage

Determining Locations After Linking • Assumes you already know the variables’ names GLint loc

Determining Locations After Linking • Assumes you already know the variables’ names GLint loc = gl. Get. Attrib. Location( program, “name” ); GLint loc = gl. Get. Uniform. Location( program, “name” );

Initializing Uniform Variable Values • Uniform Variables gl. Uniform 4 f( index, x, y,

Initializing Uniform Variable Values • Uniform Variables gl. Uniform 4 f( index, x, y, z, w ); GLboolean transpose = GL_TRUE; // Since we’re C programmers GLfloat mat[3][4][4] = { … }; gl. Uniform. Matrix 4 fv( index, 3, transpose, mat );

Homework

Homework

Install • Min. GW (Optional, you can either use g++ or Visual Studio C++)

Install • Min. GW (Optional, you can either use g++ or Visual Studio C++) • cmake • GLEW (The Open. GL Extension Wrangler Library) • GLFW • GLM

Install Min. GW

Install Min. GW

Build GLFW • mkdir build • cd build • cmake. . -G "Min. GW

Build GLFW • mkdir build • cd build • cmake. . -G "Min. GW Makefiles" • mingw 32 -make

Homework • Modify Obj. Render project and write a program to generate a torus

Homework • Modify Obj. Render project and write a program to generate a torus instead of load obj file.