CHDIR "programs\samples\open_gl"
' This example shows how models with textures or materials can be displayed with OpenGL using QB64
'
'IMPORTANT:
' Whilst the .X file loader is optimized for speed, it is very incomplete:
' -only .X files in text file format
' -only one object, not a cluster of objects
' -if using a texture, use a single texture which will be applied to all materials
' -all the 3D models in this example were exported from Blender, a free 3D creation tool
' Blender tips: CTRL+J to amalgamate objects, select object to export first, in the UV/image-editor
' window you can export the textures built into your .blend file, apply the decimate
' modifier to reduce your polygon count to below 10000, preferably ~3000 or less
' This program is not a definitive guide to OpenGL in any way
' The GLH functions are something I threw together to stop people crashing their code by making
' calls to OpenGL with incorrectly sized memory regions. The GLH... prefixed commands are not mandatory or
' part of QB64, nor do they represent a complete library of helper commands.
' Lighting is not this example's strongest point, there's probably some work to do on light positioning
' and vertex normals
'
'Finally, I hope you enjoy this program as much as I enjoyed piecing it together,
' Galleon
'###################################### GLH SETUP #############################################
'Used to manage textures
TYPE DONT_USE_GLH_Handle_TYPE
'Used by GLH RGB/etc helper functions
REDIM SHARED DONT_USE_GLH_Handle
(1000) AS DONT_USE_GLH_Handle_TYPE
'.X Format Model Loading Data
Diffuse
AS MATERIAL_RGBAI_TYPE
'regular col Specular
AS MATERIAL_RGBAI_TYPE
'hightlight/shine col Texture_Image
AS LONG 'both an image and a texture handle are held Texture
AS LONG 'if 0, there is no texture
'##############################################################################################
'Load (default) model
GLH_Load_Model_Format_X "marty.x", "marty_tmap.png"
'draw backdrop
AllowSubGL = 1
'This is our program's main loop
PRINT "{Horizonal Movement}Spin" PRINT "{Vertical Movement}Flip" PRINT "Keyboard comands:" PRINT "Switch rendering order: {1}GL behind, {2}GL on top, {3}GL only, good for speed" PRINT "Switch/Load model: {A}Zebra, {B}Pig, {C}Car"
PRINT "Angles:"; rot1
, rot2
, rot3
AllowSubGL = 0
GLH_Load_Model_Format_X "marty.x", "marty_tmap.png"
AllowSubGL = 1
AllowSubGL = 0
GLH_Load_Model_Format_X "piggy_mini3.x", ""
AllowSubGL = 1
AllowSubGL = 0
GLH_Load_Model_Format_X "gasprin.x", "gasprin_tmap.png"
AllowSubGL = 1
IF k$
= "." THEN rot3
= rot3
+ 1 IF k$
= "," THEN rot3
= rot3
- 1
'this specially named sub "_GL" is detected by QB64 and adds support for OpenGL commands
'it is called automatically whenever the underlying software deems an update is possible
'usually/ideally, this is in sync with your monitor's refresh rate
'STATIC was used above to make all variables in this sub maintain their values between calls to this sub
'timing is everything, we don't know how fast the 3D renderer will call this sub to we use timers to smooth things out
ET# = T# - ETT#
ETT# = T#
sub_gl_called = 1 'we only need to perform the following code once
'...
'These settings affect how OpenGL will render our content
'!!! THESE SETTINGS ARE TO SHOW HOW ALPHA CAN WORK, BUT IT IS 10x FASTER WHEN ALPHA OPTIONS ARE DISABLED !!!
'*** every setting must be reset because SUB _GL cannot guarantee settings have not changed since last time ***
_gluPerspective 45, _WIDTH(0) / _HEIGHT(0), 1, 100 'QB64 internally supports this GLU command for convenience sake, but does not support GLU _glBlendFunc _GL_SRC_ALPHA
, _GL_ONE_MINUS_SRC_ALPHA
'how alpha values are interpretted _glAlphaFunc _GL_GREATER
, 0.5 'dont do anything if alpha isn't greater than 0.5 (or 128) '**************************************************************************************************************
'setup our light
_glLightfv _GL_LIGHT0
, _GL_DIFFUSE
, GLH_RGB
(.8, .8, .8) _glLightfv _GL_LIGHT0
, _GL_AMBIENT
, GLH_RGB
(0.1, 0.1, 0.1) _glLightfv _GL_LIGHT0
, _GL_SPECULAR
, GLH_RGB
(0.3, 0.3, 0.3)
light_rot = light_rot + ET#
_glLightfv _GL_LIGHT0
, _GL_POSITION
, GLH_RGBA
(SIN(light_rot
) * 20, COS(light_rot
) * 20, 20, 1)
current_m = -1
m = FACE(F).Material
IF m
<> current_m
THEN 'we don't switch materials unless we have to IF current_m
<> -1 THEN _glEnd 'stop rendering triangles so we can change some settings current_m = m
IF MATERIAL
(m
).Texture_Image
THEN
_glTexParameteri _GL_TEXTURE_2D
, _GL_TEXTURE_MAG_FILTER
, _GL_LINEAR
'seems these need to be respecified
IF MATERIAL
(m
).Texture
= 0 THEN MATERIAL(m).Texture = GLH_Image_to_Texture(MATERIAL(m).Texture_Image)
GLH_Select_Texture MATERIAL(m).Texture
'use materials, disable textures
mult = MATERIAL(m).Diffuse.Intensity 'otherwise known as "power"
r = MATERIAL(m).Diffuse.R * mult
g = MATERIAL(m).Diffuse.G * mult
b = MATERIAL(m).Diffuse.B * mult
' _glColor3f r, g, b
mult = MATERIAL(m).Specular.Intensity
r = MATERIAL(m).Specular.R * mult
g = MATERIAL(m).Specular.G * mult
b = MATERIAL(m).Specular.B * mult
IF s
= 1 THEN v
= FACE
(F
).V1.V
IF s
= 2 THEN v
= FACE
(F
).V2.V
IF s
= 3 THEN v
= FACE
(F
).V3.V
v = v + 1
'vertex
x = (VERTEX(v).X + 0) * scale
y = (VERTEX(v).Y + 0) * scale
z = (VERTEX(v).Z + 0) * scale
'normal direction from vertex
nx = VERTEX(v).NX: ny = VERTEX(v).NY: nz = VERTEX(v).NZ
'corner's texture coordinates
IF s
= 1 THEN tx
= FACE
(F
).V1.TX: ty
= FACE
(F
).V1.TY
IF s
= 2 THEN tx
= FACE
(F
).V2.TX: ty
= FACE
(F
).V2.TY
IF s
= 3 THEN tx
= FACE
(F
).V3.TX: ty
= FACE
(F
).V3.TY
'QB64 OPEN-GL HELPER MACROS (aka. GLH macros) #######################################################################
SUB GLH_Select_Texture
(texture_handle
AS LONG) 'turn an image handle into a texture handle _glBindTexture _GL_TEXTURE_2D
, DONT_USE_GLH_Handle
(texture_handle
).handle
FUNCTION GLH_Image_to_Texture
(image_handle
AS LONG) 'turn an image handle into a texture handle h = DONT_USE_GLH_New_Texture_Handle
GLH_Image_to_Texture = h
FUNCTION DONT_USE_GLH_New_Texture_Handle
handle&& = 0
DONT_USE_GLH_New_Texture_Handle = handle&&
IF DONT_USE_GLH_Handle
(h
).in_use
= 0 THEN DONT_USE_GLH_Handle(h).in_use = 1
DONT_USE_GLH_Handle(h).handle = handle&&
DONT_USE_GLH_New_Texture_Handle = h
DONT_USE_GLH_Handle(h).in_use = 1
DONT_USE_GLH_Handle(h).handle = handle&&
DONT_USE_GLH_New_Texture_Handle = h
SUB GLH_Load_Model_Format_X
(Filename$
, Optional_Texture_Filename$
)
texture_image
= _LOADIMAGE(Optional_Texture_Filename$
, 32) IF texure_image
= -1 THEN texure_image
= 0
'temporary arrays
DIM SIDE_LIST
(10000) AS LONG 'used for wrangling triangle-fans/triangle-strips
'buffer file
file_x = 1
file_data$
= UCASE$(file_data$
)
ASC_COMMA = 44
ASC_SEMICOLON = 59
ASC_LBRAC = 123
ASC_RBRAC = 125
ASC_SPACE = 32
ASC_TAB = 9
ASC_CR = 13
ASC_LF = 10
ASC_FSLASH = 47
ASC_DOT = 46
ASC_MINUS = 45
WhiteSpace(ASC_LF) = -1
WhiteSpace(ASC_CR) = -1
WhiteSpace(ASC_SPACE) = -1
WhiteSpace(ASC_TAB) = -1
FormattingCharacter(ASC_COMMA) = -1
FormattingCharacter(ASC_SEMICOLON) = -1
FormattingCharacter(ASC_LBRAC) = -1
FormattingCharacter(ASC_RBRAC) = -1
Numeric(a) = -1
Numeric(ASC_DOT) = -1
Numeric(ASC_MINUS) = -1
skip_comment:
'find start of element
x1 = -1
IF a
= ASC_FSLASH
THEN 'commend IF a
= ASC_CR
OR a
= ASC_LF
THEN file_x
= x
+ 1:
GOTO skip_comment
'//.....
'find end of element
x2 = x1
'not whitespace
x2 = x
file_x = x2 + 1
a2$
= MID$(file_data$
, x1
, x2
- x1
+ 1)
skip_until$ = ""
IF Numeric
(a
) AND a
<> ASC_DOT
THEN 'faster than VAL, value conversion v = 0
dp = 0
div = 1
IF a
= ASC_MINUS
THEN neg
= 1: x1
= 2 ELSE neg
= 0: x1
= 1 dp = 1
v = v * 10 + (a2 - 48)
v# = v
div# = div
IF neg
THEN value#
= (-v#
) / div#
ELSE value#
= v#
/ div#
IF last_a2$
= ";" THEN face_input
= 0 SLI = SLI + 1
face_input = 2
polygon = polygon + 1
SIDE_LIST(SLI) = value#
FACES = FACES + 1
FACE(FACES).V1.V = SIDE_LIST(1)
FACE(FACES).V2.V = SIDE_LIST(SLI - 1)
FACE(FACES).V3.V = SIDE_LIST(SLI)
IF POLY_FACE_INDEX_FIRST
(polygon
) = 0 THEN POLY_FACE_INDEX_FIRST
(polygon
) = FACES
POLY_FACE_INDEX_LAST(polygon) = FACES
FACE(FACES).Index = polygon
file_x = file_x + 1: a2$ = ";": a = ASC_SEMICOLON: SLI = SLI + 1
SIDES = value#
SLI = 0
face_input = 3
POLYGONS = value#
polygon = 1
face_input = 2
FACES = 0
mesh_input = 0: face_input = 1
face_input = 0 'face input is unrequired on 2nd pass
skip_until$ = "MESHMATERIALLIST"
'ignore
vertex = vertex + 1
IF plane
= 1 THEN VERTEX
(vertex
).NX
= value#
IF plane
= 2 THEN VERTEX
(vertex
).NY
= value#
IF plane
= 3 THEN VERTEX
(vertex
).NZ
= value#
IF plane
= 1 THEN VERTEX
(vertex
).X
= value#
IF plane
= 2 THEN VERTEX
(vertex
).Y
= value#
IF plane
= 3 THEN VERTEX
(vertex
).Z
= value#
plane = plane + 1
plane = 1
IF vertex
= VERTICES
THEN mesh_input
= 5
file_x = file_x + 1 'skip next character (semicolon)
IF a2$
= ";" THEN mesh_input
= 4 VERTICES = value#
REDIM VERTEX
(VERTICES
) AS VERTEX_TYPE
mesh_input = 3
IF a2$
= "{" THEN mesh_input
= 2: plane
= 1: vertex
= 1
'do nothing
matlist_input = 0
polygon = polygon + 1: m = value#
FOR f
= POLY_FACE_INDEX_FIRST
(polygon
) TO POLY_FACE_INDEX_LAST
(polygon
) FACE(f).Material = m + 1
IF matlist_input
= 5 AND a2$
= ";" THEN matlist_input
= 6: polygon
= 0: face_search_start
= 1:
GOTO done
IF matlist_input
= 4 THEN matlist_input
= 5:
GOTO done
IF matlist_input
= 3 AND a2$
= ";" THEN matlist_input
= 4:
GOTO done
IF matlist_input
= 2 THEN MATERIALS
= value#:
REDIM MATERIAL
(MATERIALS
) AS MATERIAL_TYPE: matlist_input
= 3:
GOTO done
IF matlist_input
= 1 AND a2$
= "{" THEN matlist_input
= 2:
GOTO done
'do nothing
material_input = 0
N = material_n
IF N
= 1 THEN MATERIAL
(MATERIAL
).Diffuse.R
= value#
IF N
= 2 THEN MATERIAL
(MATERIAL
).Diffuse.G
= value#
IF N
= 3 THEN MATERIAL
(MATERIAL
).Diffuse.B
= value#
IF N
= 4 THEN MATERIAL
(MATERIAL
).Diffuse.A
= value#
IF N
= 5 THEN MATERIAL
(MATERIAL
).Diffuse.Intensity
= value#
/ 100 IF N
= 6 THEN MATERIAL
(MATERIAL
).Specular.R
= value#
IF N
= 7 THEN MATERIAL
(MATERIAL
).Specular.G
= value#
IF N
= 8 THEN MATERIAL
(MATERIAL
).Specular.B
= value#
IF N
= 9 THEN MATERIAL
(MATERIAL
).Specular.A
= value#
IF N
= 10 THEN MATERIAL
(MATERIAL
).Specular.Intensity
= MATERIAL
(MATERIAL
).Diffuse.Intensity
'if texture_image
material_n = N + 1
IF material_input
= 1 AND a2$
= "{" THEN material_input
= 2: material_n
= 1:
GOTO done
texco_input = 0
plane
= plane
+ 1:
IF plane
= 3 THEN plane
= 1 vertex = vertex + 1
TEXCO_TX(vertex) = value#
TEXCO_TY(vertex) = value#
IF a2$
= ";" THEN texco_input
= 4: plane
= 1: vertex
= 1 'vertices already known
texco_input = 3
IF a2$
= "{" THEN texco_input
= 2
'mode switch?
IF a2$
= "MESHTEXTURECOORDS" THEN texco_input
= 1:
PRINT "[Texture Coordinates]";:
GOTO done
IF a2$
= "MESHNORMALS" THEN normals_input
= 1: mesh_input
= 1: face_input
= 0:
PRINT "[Normals]";:
GOTO done
IF a2$
= "MESH" THEN mesh_input
= 1:
PRINT "[Mesh Vertices & Faces]";:
GOTO done
IF a2$
= "MESHMATERIALLIST" THEN matlist_input
= 1:
PRINT "[Face Material Indexes]";:
GOTO done
material_input = 1: MATERIAL = MATERIAL + 1
MATERIAL(MATERIAL).Texture = 0: MATERIAL(MATERIAL).Texture_Image = texture_image
done:
progress
= progress
+ 1:
IF progress
> 5000 THEN PRINT ".";: progress
= 0
last_a2$ = a2$
finished:
'change texture coords (with are organised per vertex to be organised by face side
'that way one vertex can share multiple materials without duplicating the vertex
PRINT "[Attaching Texture Coordinates to Face Cornders]";
f = 1
v = FACE(f).V1.V + 1: FACE(f).V1.TX = TEXCO_TX(v): FACE(f).V1.TY = TEXCO_TY(v)
v = FACE(f).V2.V + 1: FACE(f).V2.TX = TEXCO_TX(v): FACE(f).V2.TY = TEXCO_TY(v)
v = FACE(f).V3.V + 1: FACE(f).V3.TX = TEXCO_TX(v): FACE(f).V3.TY = TEXCO_TY(v)
f = f + 1
DONT_USE_GLH_COL_RGBA(1) = r
DONT_USE_GLH_COL_RGBA(2) = g
DONT_USE_GLH_COL_RGBA(3) = b
DONT_USE_GLH_COL_RGBA(4) = 1
GLH_RGB
= _OFFSET(DONT_USE_GLH_COL_RGBA
())
DONT_USE_GLH_COL_RGBA(1) = r
DONT_USE_GLH_COL_RGBA(2) = g
DONT_USE_GLH_COL_RGBA(3) = b
DONT_USE_GLH_COL_RGBA(4) = a
GLH_RGBA
= _OFFSET(DONT_USE_GLH_COL_RGBA
())