Hey folks!
Ever wanted to get into 3D programming but didn't know where to start?
This tutorial will contain a series of examples which will help you to learn OpenGL programming using the ObjAsm32 rapid application development environment (OOPASM).
You will need MASM or JWASM installed, and you will also need to install ObjAsm32.

Our first mission will be to create a simple demo which creates an application window, initializes opengl, and uses opengl to render black nothingness. We will implement code to toggle between windowed and fullscreen mode, and we'll watch the ESCAPE key for quitting the application.

Attached is the democode, in the next post I'll explain what's going on in that sourcecode, in detail.
All future demos in this thread will be based apon this simple application framework.


ObjAsm users, be aware of the magical ONLINE UPDATER tool (see Projects folder). You can use this to keep up to date with the latest OA32 updates, without having to reinstall the entire package.

Biterider, feel free to go ahead and add these to ObjAsm32's examples folder as I roll them out.

Posted on 2010-03-12 22:08:15 by Homer
Ooh, I missed one file - uploaded.
I have only bothered to correct the prototypes of functions I actually used so far.

So, let's have a look whats happening in this sourcecode, starting at the Program Entrypoint:

;Program EntryPoint
start:
SysInit
mov    CommandLine,$inv(GetCommandLine)
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
SysDone
invoke ExitProcess,eax



At the program entrypoint, the first line we see says "SysInit".
This is typically the first line in any ObjAsm sourcecode, it initializes the OOP environment, I won't elaborate since this is not a tutorial on the internals of ObjAsm !! Suffice to say, it is partnered with a line 'SysDone', which is typically immediately followed by a call to ExitProcess.... what interests us must occur between these bookends.

So, ignoring those, our first 'real' line is a call to GetCommandLine.
I'm not using commandline stuff in this program, but you never know, its nice to have.
Then we see a call to WinMain, ahhh here we go !!
We can see that when this call returns, the program will terminate, so all the goodness must be inside WinMain :)



;Our main program loop.. windows message pump !!
;Create our App Window for OpenGL Rendering..
;Then start pumping WM's until its time to die.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL msg:MSG



; Ask The User Which Screen Mode They Prefer
@invoke MessageBox,NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNOCANCEL or MB_ICONQUESTION
.if eax==IDNO
mov fullscreen,FALSE
.elseif eax==IDCANCEL
return FALSE
.endif


First thing WinMain does is ask the user what screenmode to start with.
The user can change it by pressing F1, so its not too important, but its nice to ask.


; Create Our OpenGL Window
@invoke CreateGLWindow,"OpenGL Demo #1",640,480,16,fullscreen
.if !eax
DbgWarning "Window Create failed"
return FALSE
.endif

We see a call to a function named 'CreateGLWindow' - its parameters are: Window Width, Window Height, ColorBitsPerPixel, Boolean WantFullScreen?
This function returns TRUE or FALSE to indicate success or failure.
We look at the returned value, and if we failed we'll quit now.
Assuming we did not fail, the next code is:

	
.repeat
.if $invoke(PeekMessage,addr msg,NULL,0,0,PM_REMOVE)
.if msg.message==WM_QUIT
.break
.else
invoke TranslateMessage,addr msg
invoke DispatchMessage,addr msg
.endif
.else
; Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
.if (active && !$invoke(DrawGLScene)) || keys
.break
.else
invoke SwapBuffers,hDC
.endif

.if keys
mov keys,FALSE
;Destroy the Window
invoke KillGLWindow
;Toggle fullscreen mode flag
xor fullscreen,TRUE
;Recreate the Window with new screen mode
@invoke CreateGLWindow,"OpenGL Demo #1",640,480,16,fullscreen
.if !eax
DbgWarning "CreateGLWindow failed"
.break
.endif
.endif
.endif
.until 0

Note that this code is bookended by '.repeat .... .until 0'
This is an INFINITE LOOP - we'll have to break out of this loop if we want to quit now.
What we are looking at is called the 'Window MessagePump' - its the main program loop.
I assume you're familiar with this thing.
Pseudocode for my loop is:

-if theres a Window Message (WM) available
-- dispatch it
-else
--if the escape key isnt pressed
---call DrawGLScene
--else
---break the main loop
--endif
--if F1 key is pressed
---call KillGLWindow
---toggle screen mode
---call CreateGLWindow
--endif
-endif


; Shutdown
invoke KillGLWindow
return msg.wParam
WinMain endp


Should we break from the infinite loop, we'll kill the window and return from WinMain and terminate the program.

So, we just discovered two new functions we'll wanna look at - CreateGLWindow and KillGLWindow.
But there's one more that should interest us - WndProc. Let's continue with CreateGLWindow, where WndProc is first mentioned...

Everyone keeping up so far? This is all pretty simple stuff for people who have already written Windows apps, but it might benefit someone, who knows.

Next post will document at least the three functions mentioned, and I might even manage to squeeze in the whole remaining program description, maybe.

Let me know if any of you have problems building the demo!

Posted on 2010-03-13 18:57:27 by Homer

CreateGLWindow proc psztitle, dwidth, dheight,  dbits,  fullscreenflag
LOCAL PixelFormat
local wc:WNDCLASS
local dwStyle,dwExStyle
LOCAL rc:RECT
LOCAL dmScreenSettings:DEVMODE
LOCAL pfd:PIXELFORMATDESCRIPTOR
mov rc.left,0
m2m rc.right,dwidth
mov rc.top,0
m2m rc.bottom,dheight
m2m fullscreen,fullscreenflag

invoke RtlZeroMemory,addr wc,sizeof wc
mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC; // Redraw On Size, And Own DC For Window.
mov wc.lpfnWndProc ,offset WndProc
mov wc.cbClsExtra ,0
mov wc.cbWndExtra ,0
m2m wc.hInstance ,hInstance
mov wc.hIcon ,$inv(LoadIcon,NULL, IDI_WINLOGO); // Load The Default Icon
mov wc.hCursor ,$inv(LoadCursor,NULL, IDC_ARROW); // Load The Arrow Pointer
mov wc.hbrBackground,0
mov wc.lpszMenuName ,0
mov wc.lpszClassName,$OfsCStr("OpenGL")

.if !$inv(RegisterClass,addr wc)
@invoke MessageBox,NULL,"Failed To Register The Window Class.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE; // Return FALSE
.endif

We are setting up a RECT to contain our desired window dimensions, and then registering a Window Class whose messagehandler is called 'WndProc' - we'll look at that function shortly.



.if fullscreen
DbgText "yes"
invoke RtlZeroMemory,addr dmScreenSettings,sizeof dmScreenSettings
mov dmScreenSettings.dmSize,sizeof dmScreenSettings
m2m dmScreenSettings.dmPelsWidth,dwidth
m2m dmScreenSettings.dmPelsHeight,dheight
m2m dmScreenSettings.dmBitsPerPel,dbits
mov dmScreenSettings.dmFields,(DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT)
; Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
.if $inv(ChangeDisplaySettings,addr dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL
; If The Mode Fails, Offer Two Options.  Quit Or Use Windowed Mode.
@invoke MessageBox,NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO or MB_ICONEXCLAMATION
.if eax==IDYES
mov fullscreen,FALSE
.else
; Pop Up A Message Box Letting User Know The Program Is Closing.
@invoke MessageBox,NULL,"Program Will Now Close.","ERROR",MB_OK or MB_ICONSTOP
return FALSE
.endif
.endif
.endif

If the caller has requested fullscreen mode, we'll fill out a description of our desired fullscreen settings, and call a function named 'ChangeDisplaySettings' - if that failed, we can fall back on windowed mode, but ask the user first.


	
.if (fullscreen) ; Are We Still In Fullscreen Mode?
mov dwExStyle,WS_EX_APPWINDOW; // Window Extended Style
mov dwStyle,WS_POPUP; // Windows Style
invoke ShowCursor,FALSE; Hide Mouse Pointer
.else
mov dwExStyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE; // Window Extended Style
mov dwStyle,WS_OVERLAPPEDWINDOW; // Windows Style
.endif

invoke AdjustWindowRectEx,addr rc, dwStyle, FALSE, dwExStyle; // Adjust Window To True Requested Size

; Create The Window

mov edx,dwStyle
or edx,WS_CLIPSIBLINGS or WS_CLIPSIBLINGS
invoke CreateWindowEx, dwExStyle,$OfsCStr("OpenGL"),psztitle,edx,
0, 0,
dwidth,;rc.right-rc.left, ;// Calculate Window Width
dheight,;rc.bottom-rc.top, ;// Calculate Window Height
0,0,hInstance,0
mov hWnd,eax
.if !eax
invoke KillGLWindow
@invoke MessageBox,NULL,"Window Creation Error.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE
.endif

Now we'll create the application window, using the Window Class we registered earlier.
If fullscreen, we'll use a POPUP window style, which has no buttons or titlebar at the top.
Otherwise it'll be a normal overlapped window style.



invoke RtlZeroMemory,addr pfd,sizeof pfd
mov pfd.nSize,sizeof pfd
mov pfd.nVersion,1
mov pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
mov pfd.iPixelType,PFD_TYPE_RGBA
mov edx,dbits
mov pfd.cColorBits,dl
mov pfd.cDepthBits,16    ;should be dbits
mov pfd.iLayerType,PFD_MAIN_PLANE

mov hDC,$inv(GetDC,hWnd)
.if !eax
invoke KillGLWindow
int 3
@invoke MessageBox,NULL,"Can't Create A GL Device Context.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE
.endif

mov PixelFormat,$inv(ChoosePixelFormat,hDC,addr pfd)
.if !eax
invoke KillGLWindow
@invoke MessageBox,NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE
.endif

We'll ask OpenGL to choose a pixel format that is compatible with our window.


.if !$inv(SetPixelFormat,hDC,PixelFormat,addr pfd)
invoke KillGLWindow
@invoke MessageBox,NULL,"Can't Set The PixelFormat.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE
.endif

We'll tell OpenGL to use the pixelformat it suggested.


mov hRC,$inv(wglCreateContext,hDC)
.if eax==0
invoke KillGLWindow
@invoke MessageBox,NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE
.endif

Try to create an OpenGL Rendering Context.


.if $inv(wglMakeCurrent,hDC,hRC)==0
invoke KillGLWindow
@invoke MessageBox,NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE
.endif

Tell OpenGL to use this Render Context


invoke ShowWindow,hWnd,SW_SHOW
invoke SetForegroundWindow,hWnd
invoke SetFocus,hWnd
invoke ReSizeGLScene,dwidth, dheight

Make our window topmost, give it keyboard focus, and call a function named 'ResizeGLScene' ... we'll need to call this anytime the size of the window changed.


.if !$inv(InitGL)
invoke KillGLWindow
@invoke MessageBox,NULL,"Initialization Failed.","ERROR",MB_OK or MB_ICONEXCLAMATION
return FALSE
.endif

return TRUE
CreateGLWindow endp


Call a function named 'InitGL' - it simply selects some minimal rendering settings.
Then we return to the caller TRUE (yay) or FALSE (noooo)
That's it for CreateGLWindow.
Now we can look quickly at KillGLWindow, then start to look at the new functions whose names we have discovered.

Posted on 2010-03-13 19:24:36 by Homer
;Release our RenderDC, WindowDC, Window and Class
KillGLWindow proc
.if fullscreen
invoke ChangeDisplaySettings,NULL,0
invoke ShowCursor,TRUE
.endif

If we're in fullscreen mode, switch back to windowed mode, and unhide the mouse cursor


.if hRC
.if !$invoke (wglMakeCurrent,NULL,NULL)
@invoke MessageBox,NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK or MB_ICONINFORMATION
.endif

Unselect the Render Context


.if !$invoke(wglDeleteContext,hRC)
@invoke MessageBox,NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK or MB_ICONINFORMATION
.endif
mov hRC,NULL
.endif

Destroy our Render Context


.if hDC && ($invoke(ReleaseDC,hWnd,hDC)==0)
@invoke MessageBox,NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK or MB_ICONINFORMATION
mov hDC,0
.endif

Release our Device Context


.if hWnd && ($invoke(DestroyWindow,hWnd)==0)
@invoke MessageBox,NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK or MB_ICONINFORMATION
mov hWnd,0
.else
DbgWarning "Window Closed"
.endif

Destroy application window


@invoke UnregisterClass,"OpenGL",hInstance
.if !eax
@invoke MessageBox,NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK or MB_ICONINFORMATION
mov hInstance,0
.endif
ret
KillGLWindow endp

Unregister our Window Class and return to caller.

Well that covers the window create and destroy functions, now we'll look at WndProc, and then the last couple of remaining functions :)
Almost there !!
Posted on 2010-03-13 19:34:42 by Homer

;Application Window's Message Handler
;No suprises here, we watch for some keypresses and make sure nothing turns the screen off
WndProc proc hWin:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.switch uMsg
.case WM_ACTIVATE
mov eax,wParam
shr eax,16
.if !ax
mov active,TRUE
.else
mov active,FALSE
.endif
return 0

If the application becomes inactive or is reactivated, we'll take note.


.case WM_SYSCOMMAND
mov eax,wParam
.if eax==SC_SCREENSAVE || eax==SC_MONITORPOWER
return 0;Prevent From Happening
.endif

If a screensaver or powersaving feature tries to turn the screen off, we'll force it to stay awake


.case WM_CLOSE
invoke PostQuitMessage,0
return 0

If the user closes the application, we'll post a message to quit.


.case WM_KEYDOWN
mov edx,wParam
mov keys , TRUE
return 0

.case WM_KEYUP
mov edx,wParam
mov keys,FALSE
return 0

We will keep an array of 256 bytes, representing all possible keypresses.
If a key is pressed or released, we will set a byte representing that key to true or false.


.case WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0FFFFh
invoke ReSizeGLScene,eax,edx
return 0
.endsw

If the user stretches the window, we will call ResuzeGLScene with the new window dimensions.


; Pass All Unhandled Messages To DefWindowProc
invoke DefWindowProc,hWin,uMsg,wParam,lParam
ret
WndProc endp

Just what it says - any WM's we did not handle ourselves are passed to the default handler.

OK, That's it for WndProc.
We just have a handful of functions left to look at :)
Posted on 2010-03-13 19:47:02 by Homer

;Initialize our most basic render settings
InitGL proc
invoke glEnable,GL_TEXTURE_2D; Enable Texture Mapping
invoke glShadeModel,GL_SMOOTH; Enable Smooth Shading
@invoke glClearColor,0,0,0, r4_half; Black Background
invoke glClearDepth,r8_1; Depth Buffer Setup
invoke glEnable,GL_DEPTH_TEST; Enables Depth Testing
invoke glDepthFunc,GL_LEQUAL; The Type Of Depth Testing To Do
invoke glHint,GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST; Really Nice Perspective Calculations
return TRUE; Initialization Went OK
InitGL endp


This just sets up a bunch of typical render settings - we can change these any time.


;Render the Scene (one frame)
DrawGLScene proc
invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
return TRUE
DrawGLScene endp

Our render function just clears the screen to blackness, and clears the 'depth buffer' (more on that later).
If our app is working, we will see a black window :)

This is the end of Tutorial #1.
If you have questions, please feel free to ask them here !!
Posted on 2010-03-13 19:52:36 by Homer
For those who managed to build the first tutorial sourcecode, congratulations, you made your first OpenGL app :)
But gee Homer, its a bit boring.
It would be nice to be able to see something other than just blackness!!
Today we're going to draw a Triangle, and a Square.
Not super exciting, but hey, we're just getting started :)
Anyway it will be nice to be able to see 'something', right?


;Render the Scene (one frame)
;We are going to draw a Triangle, and a Rectangle ('quad').
DrawGLScene proc
.data
 ;Let's define a 3D Vector as a set of three floats:
 Vec3 struct
  x real4 ?
  y real4 ?
  z real4 ?
 Vec3 ends
 ;Data for our Triangle and Quad  
 Translate1 Vec3 <-1.5f,0.0f,-6.0f>   ;Translation from (0,0,0) to origin of Triangle
 Translate2 Vec3 <3.0f,0.0f,0.0f>     ;Translation from origin of Triangle to origin of Square
 TriPoint1  Vec3 <0.0f, 1.0f, 0.0f> ;Three points make a Triangle
 TriPoint2  Vec3 <-1.0f,-1.0f, 0.0f>
 TriPoint3  Vec3 <1.0f,-1.0f, 0.0f>
 QuadPoint1 Vec3 <-1.0f, 1.0f, 0.0f>   ;Four points make a Quad
 QuadPoint2 Vec3 <1.0f, 1.0f, 0.0f>
 QuadPoint3 Vec3 <1.0f,-1.0f, 0.0f>
 QuadPoint4 Vec3 <-1.0f,-1.0f, 0.0f>
 .code

Define some data we'll use to draw a triangle and a square.


invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT

Clear the window to blackness, also clear the z buffer


invoke glLoadIdentity ; Reset the current modelview matrix

Set our magical 3D pen to the world origin (0,0,0)


invoke glTranslatef,Translate1.x,Translate1.y,Translate1.z ; Move left 1.5 units and into the screen 6.0

Move our magical 3D pen to where we want our triangle to be drawn


invoke glBegin,GL_TRIANGLES ; Drawing using triangles

Tell OpenGL that we want to draw triangles


invoke glVertex3f,TriPoint1.x,TriPoint1.y,TriPoint1.z ; Top
invoke glVertex3f,TriPoint2.x,TriPoint2.y,TriPoint2.z ; Bottom left
invoke glVertex3f,TriPoint3.x,TriPoint3.y,TriPoint3.z ; Bottom right

Draw the triangle by declaring its three points


invoke glEnd ; Finished Drawing The Triangle

Tell OpenGL that we're finished drawing (triangles)


invoke glTranslatef,Translate2.x,Translate2.y,Translate2.z ; Move right 3 units

Move our magical 3D pen over to the right - notice this is relative to the previous position, since we have not called 'glLoadIdentity' to reset the magical 3D pen to the world origin again.


invoke glBegin,GL_QUADS     ; Draw a quad

Tell OpenGL we want to draw using 'quads' (rectangles)


invoke glVertex3f,QuadPoint1.x,QuadPoint1.y,QuadPoint1.z; Top left
invoke glVertex3f,QuadPoint2.x,QuadPoint2.y,QuadPoint2.z; Top right
invoke glVertex3f,QuadPoint3.x,QuadPoint3.y,QuadPoint3.z; Bottom right
invoke glVertex3f,QuadPoint4.x,QuadPoint4.y,QuadPoint4.z; Bottom left

Draw a square, that means 4 points are needed.


invoke glEnd ; Finished Drawing The Quad

Tell OpenGL we're done drawing (quads)


return TRUE
DrawGLScene endp

Return to caller...

You can copy/paste that replacement function, or if you're lazy like me, download the attached update :)
Of course, feel free to ask questions or just comment - this is not a formal classroom, this is a friendly learning environment :)

Attachments:
Posted on 2010-03-13 21:18:50 by Homer
Well, white shapes on a black background isnt much more exciting, is it?
Lets add some color :)


;Render the Scene (one frame)
;We are going to draw a Triangle, and a Rectangle ('quad').
DrawGLScene proc
.data
 ;Let's define a 3D Vector as a set of three floats:
 Vec3 struct
  x real4 ?
  y real4 ?
  z real4 ?
 Vec3 ends
 ;Lets define a Color with 3 components (RGB)
 RGB struct
  red   real4 ?
  green real4 ?
  blue  real4 ?
 RGB ends
 ;Data for our Triangle and Quad  
 Translate1 Vec3 <-1.5f,0.0f,-6.0f>   ;Translation from (0,0,0) to origin of Triangle
 Translate2 Vec3 <3.0f,0.0f,0.0f>     ;Translation from origin of Triangle to origin of Square
 TriPoint1  Vec3 <0.0f, 1.0f, 0.0f> ;Three points make a Triangle
 TriPoint2  Vec3 <-1.0f,-1.0f, 0.0f>
 TriPoint3  Vec3 <1.0f,-1.0f, 0.0f>
 QuadPoint1 Vec3 <-1.0f, 1.0f, 0.0f>   ;Four points make a Quad
 QuadPoint2 Vec3 <1.0f, 1.0f, 0.0f>
 QuadPoint3 Vec3 <1.0f,-1.0f, 0.0f>
 QuadPoint4 Vec3 <-1.0f,-1.0f, 0.0f>
 ;Some colors
 Color1 RGB <1.0f,0.0f,0.0f>
 Color2 RGB <0.0f,1.0f,0.0f>
 Color3 RGB <0.0f,0.0f,1.0f>
 .code
invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
invoke glLoadIdentity ; Reset the current modelview matrix
invoke glTranslatef,Translate1.x,Translate1.y,Translate1.z ; Move left 1.5 units and into the screen 6.0
invoke glBegin,GL_TRIANGLES ; Drawing using triangles
invoke glColor3f,Color1.red,Color1.green,Color1.blue
invoke glVertex3f,TriPoint1.x,TriPoint1.y,TriPoint1.z ; Top
invoke glColor3f,Color2.red,Color2.green,Color2.blue
invoke glVertex3f,TriPoint2.x,TriPoint2.y,TriPoint2.z ; Bottom left
invoke glColor3f,Color3.red,Color3.green,Color3.blue
invoke glVertex3f,TriPoint3.x,TriPoint3.y,TriPoint3.z ; Bottom right
invoke glEnd ; Finished Drawing The Triangle
invoke glTranslatef,Translate2.x,Translate2.y,Translate2.z ; Move right 3 units
invoke glBegin,GL_QUADS     ; Draw a quad
invoke glColor3f,Color1.red,Color2.green,Color3.blue
invoke glVertex3f,QuadPoint1.x,QuadPoint1.y,QuadPoint1.z; Top left
invoke glColor3f,Color2.red,Color2.green,Color3.blue
invoke glVertex3f,QuadPoint2.x,QuadPoint2.y,QuadPoint2.z; Top right
invoke glColor3f,Color1.red,Color1.green,Color3.blue
invoke glVertex3f,QuadPoint3.x,QuadPoint3.y,QuadPoint3.z; Bottom right
invoke glColor3f,Color1.red,Color2.green,Color2.blue
invoke glVertex3f,QuadPoint4.x,QuadPoint4.y,QuadPoint4.z; Bottom left
invoke glEnd ; Finished Drawing The Quad
return TRUE
DrawGLScene endp

Just before we declare each Point in our geometry (triangle and square), we will specify a COLOR for that point, using the function 'glColor3f' (the 3f indicates that theres 3x real4 floating point parameters).

Colors can be any mixture of the three components Red, Green and Blue, which are each represented as a floating-point value in the range 0.0 to 1.0  ... so you can invent your own colors very easily :)

In fact, we don't have to set the color for EVERY point - if we wanted several points to have the same color, we could set the color once, then emit several points.
In OpenGL, most settings will remain active unless you change them.

Lazy people can download the update :)
Attachments:
Posted on 2010-03-13 21:54:18 by Homer

Tutorial #4 :)
So far, we have been using 'relative translation' to move our imaginary 3D pen from one place to the next.
It's ok if we know all the positions of our different 'entities' (triangle and square).
But what if their positions are changing? Would we like to deal with them completely separately?

This time, we're going to see how to use 'absolute translation'.
Before we draw each entity, we will call glLoadIdentity to reset the imaginary 3D pen to the world origin (0,0,0).
Our translation values for positioning each entity will now ALL be relative to the world origin.
Thus we can specify exactly the 3D worldspace position of each entity.


;Render the Scene (one frame)
;We are going to draw a Triangle, and a Rectangle ('quad').
DrawGLScene proc
.data
  ;Let's define a 3D Vector as a set of three floats:
  Vec3 struct
  x real4 ?
  y real4 ?
  z real4 ?
  Vec3 ends
  ;Lets define a Color with 3 components (RGB)
  RGB struct
  red  real4 ?
  green real4 ?
  blue  real4 ?
  RGB ends
  ;Data for our Triangle and Quad 
  Translate1 Vec3 <-1.5f,0.0f,-6.0f>  ;Translation from (0,0,0) to origin of Triangle
  Translate2 Vec3 <+1.5f,0.0f,-6.0f>  ;Translation from (0,0,0) to origin of Square
  TriPoint1  Vec3 <0.0f, 1.0f, 0.0f> ;Three points make a Triangle
  TriPoint2  Vec3 <-1.0f,-1.0f, 0.0f>
  TriPoint3  Vec3 <1.0f,-1.0f, 0.0f>
  QuadPoint1 Vec3 <-1.0f, 1.0f, 0.0f>  ;Four points make a Quad
  QuadPoint2 Vec3 <1.0f, 1.0f, 0.0f>
  QuadPoint3 Vec3 <1.0f,-1.0f, 0.0f>
  QuadPoint4 Vec3 <-1.0f,-1.0f, 0.0f>
  ;Some colors
  Color1 RGB <1.0f,0.0f,0.0f>
  Color2 RGB <0.0f,1.0f,0.0f>
  Color3 RGB <0.0f,0.0f,1.0f>
  .code
invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT

invoke glLoadIdentity ; Reset the current modelview matrix
invoke glTranslatef,Translate1.x,Translate1.y,Translate1.z ; Move left 1.5 units and into the screen 6.0
invoke glBegin,GL_TRIANGLES ; Drawing using triangles
invoke glColor3f,Color1.red,Color1.green,Color1.blue
invoke glVertex3f,TriPoint1.x,TriPoint1.y,TriPoint1.z ; Top
invoke glColor3f,Color2.red,Color2.green,Color2.blue
invoke glVertex3f,TriPoint2.x,TriPoint2.y,TriPoint2.z ; Bottom left
invoke glColor3f,Color3.red,Color3.green,Color3.blue
invoke glVertex3f,TriPoint3.x,TriPoint3.y,TriPoint3.z ; Bottom right
invoke glEnd ; Finished Drawing The Triangle

invoke glLoadIdentity
invoke glTranslatef,Translate2.x,Translate2.y,Translate2.z ; Move right 3 units
invoke glBegin,GL_QUADS    ; Draw a quad
invoke glColor3f,Color1.red,Color2.green,Color3.blue
invoke glVertex3f,QuadPoint1.x,QuadPoint1.y,QuadPoint1.z; Top left
invoke glColor3f,Color2.red,Color2.green,Color3.blue
invoke glVertex3f,QuadPoint2.x,QuadPoint2.y,QuadPoint2.z; Top right
invoke glColor3f,Color1.red,Color1.green,Color3.blue
invoke glVertex3f,QuadPoint3.x,QuadPoint3.y,QuadPoint3.z; Bottom right
invoke glColor3f,Color1.red,Color2.green,Color2.blue
invoke glVertex3f,QuadPoint4.x,QuadPoint4.y,QuadPoint4.z; Bottom left
invoke glEnd ; Finished Drawing The Quad

return TRUE
DrawGLScene endp


You won't see any visible difference if you run this program.
But take a close look at the translation values, and notice the extra 'glLoadIdentity' call ;)
As you can see, the triangle and the square now have unique worldspace positions, and can be manipulated individually without knowledge of the other's position.
That's important - we now understand how to control individual entities in 3D.

But so far, we can't actually TELL that we are looking at a 3D scene !!!
In the next update, we'll look at how to play with the 3D orientation of our entities :)

Attachments:
Posted on 2010-03-13 22:23:52 by Homer
Tutorial #5 :)

In this example, we'll learn how to rotate our entities about an arbitrary 3D axis.
We'll rotate the Triangle around the Y axis, and we'll rotate the Square around a COMPOUND axis.
And we'll make them both spin at different speeds.


DrawGLScene proc
.data
 ;Let's define a 3D Vector as a set of three floats:
 Vec3 struct
  x real4 ?
  y real4 ?
  z real4 ?
 Vec3 ends
 ;Lets define a Color with 3 components (RGB)
 RGB struct
  red   real4 ?
  green real4 ?
  blue  real4 ?
 RGB ends
 ;Data for our Triangle and Quad  
 Translate1 Vec3 <-1.5f,0.0f,-6.0f>   ;Translation from (0,0,0) to origin of Triangle
 Translate2 Vec3 <+1.5f,0.0f,-6.0f>   ;Translation from (0,0,0) to origin of Square
 TriPoint1  Vec3 <0.0f, 1.0f, 0.0f> ;Three points make a Triangle
 TriPoint2  Vec3 <-1.0f,-1.0f, 0.0f>
 TriPoint3  Vec3 <1.0f,-1.0f, 0.0f>
 QuadPoint1 Vec3 <-1.0f, 1.0f, 0.0f>   ;Four points make a Quad
 QuadPoint2 Vec3 <1.0f, 1.0f, 0.0f>
 QuadPoint3 Vec3 <1.0f,-1.0f, 0.0f>
 QuadPoint4 Vec3 <-1.0f,-1.0f, 0.0f>
 ;Some colors
 Color1 RGB <1.0f,0.0f,0.0f>
 Color2 RGB <0.0f,1.0f,0.0f>
 Color3 RGB <0.0f,0.0f,1.0f>
 ;Rotation
 fRotation_Triangle real4 0.0f ;Angle to rotate, in degrees
 vAxis_Triangle     Vec3 <0.0f,1.0f,0.0f> ;Axis of Rotation
 fRotation_Square   real4 0.0f
 vAxis_Square       Vec3 <1.0f,0.0f,1.0f>
 fRotSpeed_Triangle real4 1.2f ;Amount to increment rotation angle per frame
 fRotSpeed_Square   real4 20.0f
 .code
invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT

invoke glLoadIdentity ; Reset the current modelview matrix
invoke glTranslatef,Translate1.x,Translate1.y,Translate1.z ; Move left 1.5 units and into the screen 6.0
invoke glRotatef,fRotation_Triangle,vAxis_Triangle.x,vAxis_Triangle.y,vAxis_Triangle.z
invoke glBegin,GL_TRIANGLES ; Drawing using triangles
invoke glColor3f,Color1.red,Color1.green,Color1.blue
invoke glVertex3f,TriPoint1.x,TriPoint1.y,TriPoint1.z ; Top
invoke glColor3f,Color2.red,Color2.green,Color2.blue
invoke glVertex3f,TriPoint2.x,TriPoint2.y,TriPoint2.z ; Bottom left
invoke glColor3f,Color3.red,Color3.green,Color3.blue
invoke glVertex3f,TriPoint3.x,TriPoint3.y,TriPoint3.z ; Bottom right
invoke glEnd ; Finished Drawing The Triangle

invoke glLoadIdentity
invoke glTranslatef,Translate2.x,Translate2.y,Translate2.z ; Move right 3 units
invoke glRotatef,fRotation_Triangle, vAxis_Square.x,vAxis_Square.y,vAxis_Square.z
invoke glBegin,GL_QUADS     ; Draw a quad
invoke glColor3f,Color1.red,Color2.green,Color3.blue
invoke glVertex3f,QuadPoint1.x,QuadPoint1.y,QuadPoint1.z; Top left
invoke glColor3f,Color2.red,Color2.green,Color3.blue
invoke glVertex3f,QuadPoint2.x,QuadPoint2.y,QuadPoint2.z; Top right
invoke glColor3f,Color1.red,Color1.green,Color3.blue
invoke glVertex3f,QuadPoint3.x,QuadPoint3.y,QuadPoint3.z; Bottom right
invoke glColor3f,Color1.red,Color2.green,Color2.blue
invoke glVertex3f,QuadPoint4.x,QuadPoint4.y,QuadPoint4.z; Bottom left
invoke glEnd ; Finished Drawing The Quad

fld fRotSpeed_Triangle
fadd fRotation_Triangle
fstp fRotation_Triangle

fld fRotSpeed_Square
fadd fRotation_Square
fstp fRotation_Square

return TRUE
DrawGLScene endp


As you can see, we use the 'glRotatef' function to rotate our imaginary 3D pen.
Note we do this after translating, and before drawing each entity.

First we reset the pen to the world origin, no rotation.
Then we translate to the position of the triangle.
Then we rotate our pen.
Then we draw the triangle.

Then we reset the pen to the world origin, no rotation.
Then we translate to the position of the square.
Then we rotate our pen.
Then we draw the square.

Finally, we increment our rotation angle counters.

See how it works?
Attachments:
Posted on 2010-03-13 22:47:50 by Homer

The next tutorial will be one of two things, I will let the readers decide.

Option A) Textures And TextureMapping
Option B) Simple 3D Polytopes (Tetrahedron and Cube)

Say which order you would like them :)
Posted on 2010-03-13 23:05:25 by Homer
I keep mentioning a 'magical 3D pen' to describe 3D rotation and translation prior to rendering an entity.
It doesn't actually exist, its just one way to describe and understand what's happening.
It's a useful tool for the purpose of communicating the concepts.
You could also say that we are rotating and translating the entire universe space around the thing that we're about to draw, but that would be less intuitive, agreed?
Posted on 2010-03-14 03:45:21 by Homer
As an aside... with OpenGL 3.3 and 4.0 announced, the way forward in OpenGL is to remove the 'naive' legacy functionality (drawing geometry with glBegin()/glEnd() etc, using fixed function transform/lighting/texturing), and instead doing everything with vertex buffer objects and GLSL shaders. (This legacy functionality had already been marked 'deprecated' in OpenGL 3.0, and some of it was already removed in 3.1).

So when you get into texturing and lighting, perhaps you should focus only on using GLSL, and ignore the legacy fixedfunction stuff?
Likewise, perhaps you shouldn't linger with glBegin()/glEnd()-based geometry too long, and show people how to use VBOs at an early stage?
Posted on 2010-03-16 05:28:38 by Scali
I second that!
I understand that these first posts were addressed to beginners but I would like to see more of the "modern" approach instead of the "legacy" approach. There are many "legacy" OpenGL tutorials and only few "modern" ones. So, Homer, please go for the modern stuff as soon you're done with the basics, pretty please ^^

And thanks for another tutorial - they are always a good read :)
Posted on 2010-03-16 11:31:35 by ti_mo_n
I will indeed be talking about vertexbuffer objects and GLSL at my first convenience.
However I will cover the basics first, from nothin to guru, as I see them.
So, NOONE has an opinion on whether to cover (basic) 3d geometry, or (5 kinds of) texturemapping first?
I will choose the geom.
I've just spent an hour calculating the vertices for an equilateral tetrahedron with a central origin ;)
Posted on 2010-03-17 01:03:26 by Homer

I will indeed be talking about vertexbuffer objects and GLSL at my first convenience.
However I will cover the basics first, from nothin to guru, as I see them.


The situation is quite similar to Direct3D 9 vs Direct3D 10/11...
You could start teaching with D3D9, but when you go to D3D10/11, you'll basically have to say "Okay, forget everything you learnt with D3D9, because none of that is available in D3D10/11 anyway".
The 'basics' just don't exist in the modern D3D and OpenGL APIs anymore (and a lot of 'optimizations' via display lists and such are no longer valid in OpenGL either, so it completely throws everything around. Things that older tutorials told people were 'the right thing').
In my opinion, you shouldn't even mention the existence of the legacy functionality. In fact, an interesting tutorial might be to implement your own glBegin()/glEnd()-style of rendering, using a VBO internally.
Then you can both explain the concepts of modern rendering AND offer a simple way of getting basic triangles on screen (you could supply the helper functions first, and explain their internal workings at a later stage).
Posted on 2010-03-17 05:57:18 by Scali

In fact, an interesting tutorial might be to implement your own glBegin()/glEnd()-style of rendering, using a VBO internally.
Then you can both explain the concepts of modern rendering AND offer a simple way of getting basic triangles on screen (you could supply the helper functions first, and explain their internal workings at a later stage).


That sounds like a very sensible suggestion, Scali.
I will do that!
But bear with me while I emit a couple more jaded examples to lay out the basic concepts for absolute beginners.
I'm just trying to introduce the concepts first, however I totally agree with your comments :)

The attached example is the first to contain 'true 3d objects'.
This time I have made the triangle into a tetrahedron (4 triangles), and the square into a cube (6 quads).

The next example will introduce texturemapping, and then we'll add normals and lighting.
After that we'll look at display lists, vertex arrays, and then vertex buffer objects.
We'll even write a VBO class that helps us to 'contruct' 3D shapes from arbitrary input data.
This will make a good solid base for file import/export demos.

Attachments:
Posted on 2010-03-18 06:29:23 by Homer
My comments in the posted examples have not been well maintained.
But they still make sense.
Deal with it, noones paying me to teach this.
I recommend the application WinMerge to help identify changes from one example to the next!
But I'm pretty sure you readers are able to keep up so far.
It's nice to see this thread is receiving some attention.
Honk if you love free stuff!

Oh, we are so gonna have to cover matrices soon.
Matrix transformations, yaknow.
That will be a major milestone.

Scali mentioned math issues in another thread.
I will provide a full set of functions for math with real4 and real8 floats, 3D vectors, 4D planes, 3x3 matrices and 4x4 matrices of both real4 and real8 precision.
And much of it is written as macros, rather than procedures, which makes for even faster execution, at the cost of (if you're crap) replicating code (aka bloat).
Posted on 2010-03-18 07:39:37 by Homer

Our next demo will be an example of simple texture-mapping.
In order to cover our surfaces with texture, we need to be able to load a texture from an image file.

Here is a very simple function to load a texture from a BMP file.
We won't worry about 'mipmaps' just yet.
Consider this a place to start.


; ---------------------------------------------------------------------------
;Helper function to create OpenGL Texture from a BMP File
;szFileName = ptr to namestring
;ptexid = ptr to dword to receive ID of tex resource
;Returns eax = 0(FALSE), or a positive integer (texture id)
LoadBMP proc szFileName
LOCAL hBMP
LOCAL bmp:BITMAP
local texid


;Make sure path exists
.if $inv(GetFileAttributes,szFileName) !=-1
                ;Generate a new, empty texture container.
                ;If all goes well, we will be returned EAX=0 and texid=ID of new texture
mov texid,0
.if $inv(glGenTextures,1, addr texid)==0
;Bind to (ie 'select') the newly-generated Texture immediately
;All subsequent texture operations apply to the currently bound texture
invoke glBindTexture,GL_TEXTURE_2D, texid

;Load the BMP image - WE SHOULD USE PIXELMAP AS A LOADER !!!
mov hBMP,$invoke (LoadImage,hInstance, szFileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION or LR_LOADFROMFILE)
.if eax==0
DbgWarning "LoadImage failed"
DbgStr szFileName
return FALSE
.endif
;Get access to the BMP's pixel data
invoke GetObject,hBMP, sizeof bmp, addr bmp

;Set up texture parameters
invoke glPixelStorei,GL_UNPACK_ALIGNMENT, 4; Pixel Storage Mode (Word Alignment / 4 Bytes)
invoke glTexParameteri,GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR; // Linear Min Filter
invoke glTexParameteri,GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR; // Linear Mag Filter
;Upload the texture to video card
invoke glTexImage2D,GL_TEXTURE_2D, 0, 3, bmp.bmWidth, bmp.bmHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, bmp.bmBits
invoke DeleteObject,hBMP
mov eax,texid
.else
@invoke MessageBox,0,"Failed glGenTextures","Error",MB_OK+MB_ICONERROR
mov eax, FALSE
.endif

.endif
ret
LoadBMP endp


We create a new texture id, we 'bind' to it, we gain access to the raw pixel data of the imagefile, we set up a few texture parameters and we call 'glTexImage2D' to upload our pixels to the videocard (ie copy them into our texture). Then we clean up the imagefile stuff since we're done.
We will return NULL for failure, or we will return the TextureID (which is never NULL).

In the near future, we'll improve this function to handle more image file formats etc
Posted on 2010-03-19 01:50:10 by Homer
As mentioned in the comments, when we 'bind' to a texture, we are infact 'selecting' it.
We will need to do this in our rendering function too.
We must bind to a texture BEFORE we call glBegin - we can't change textures inside a glBegin/glEnd block.
But soon we'll learn some new ways to draw stuff that don't require glBegin/glEnd, and it won't matter so much.
I like that this design forces the programmer to make as few texture changes as possible, which is a good thing.
It will become more apparent soon, when we start to meddle with 3D meshes, we will need to render 'per-texture' groups of faces. But before that, we have to learn a bit more about the basics - textures, lighting/normals, materials, in that order will form the foundation of everything to follow.

Attached is Demo #7, and a bitmap (bmp) file.
We will use the skeleton from demo #5, but we will replace Color with Texture at each vertex.
You will notice a problem with this demo: if we switch screen modes, we lose our texture!
In the next post, I will address this problem, and we'll also make some improvements to our image loader.
The next demo will be based on Demo #6 - we will see textured 3D objects :)

Attachments:
Posted on 2010-03-19 04:05:46 by Homer