Update (11/5/2019)
As of writing this, the default KGL libraries for KOS are basically deprecated.
It's recommended to use Kazade's GLdc libraries over on GitHub.
To install Kazade's GLdc libraries:
1. Go to your kos/addons folder
2. type:
git clone https://github.com/Kazade/GLdc.git
3. type:
cd GLdc
4. type:
make defaultall
5. type:
make create_kos_link
Then, in your makefile, include "-lGLdc" instead of "-lGL".
Here's my makefile for reference:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: rm-elf main.elf | |
include $(KOS_BASE)/Makefile.rules | |
OBJS = main.o | |
clean: | |
-rm -f main.elf $(OBJS) | |
clean-all: | |
-rm -f main.elf $(OBJS) main.iso output.bin Program.cdi 1st_read.bin | |
dist: | |
-rm -f $(OBJS) | |
$(KOS_STRIP) main.elf | |
rm-elf: | |
-rm -f main.elf | |
main.elf: $(OBJS) | |
$(KOS_CC) $(KOS_CFLAGS) $(KOS_LDFLAGS) -o $@ $(KOS_START) $^ -lGLdc -lm $(KOS_LIBS) |
Note: Most of the functions from KGL carry over to GLdc, with one exception: glutSwapBuffers() is now glKosSwapBuffers().
In my last post, I explained how to draw a simple triangle using OpenGL. However, we STILL didn't have anything that remotely looked 3D! This time, however, we're going to go all-in! Instead of one triangle, we're going to draw 6 triangles!
...In a pyramid formation, anyway!
Relevant Example Projects
- Anything in kos/examples/dreamcast/kgl
- More specifically, anything in kos/addons/GLdc/samples
- Even MORE specifically, kos/addons/GLdc/samples/nehe02
The Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <kos.h> | |
#include <GL\gl.h> | |
#include <GL\glu.h> | |
#include <GL\glut.h> | |
KOS_INIT_FLAGS(INIT_DEFAULT); | |
bool run = true; | |
float xPos = 0.0f; | |
float yPos = 0.0f; | |
float zPos = 5.0f; | |
float sensitivity = 1000.0f; // This is so we don't go flying accross | |
// the screen every time we try and move. | |
GLfloat rot; // The rotation matrix takes in a GLfloat. | |
// GLfloat is basically just a float, but | |
// defined by OpenGL. | |
GLfloat rotSpeed = 1.0f; // Just to make sure our speed can multiply with our | |
// rot variable, we'll make this a GLfloat, too. | |
void HandleControls(maple_device_t* controller, cont_state_t* controllerState){ | |
controllerState = (cont_state_t*) maple_dev_status(controller); | |
if (controllerState->buttons & CONT_A) | |
run = false; | |
if (controllerState->buttons & CONT_DPAD_RIGHT) | |
rotSpeed += 1; | |
if (controllerState->buttons & CONT_DPAD_LEFT) | |
rotSpeed -= 1; | |
if (controllerState->buttons & CONT_DPAD_UP) | |
zPos -= 1; | |
if (controllerState->buttons & CONT_DPAD_DOWN) | |
zPos +=1 ; | |
// Go left and right based on whatever direction we hit | |
// Negative because otherwise it's inverted. | |
xPos -= (float)(controllerState->joyx / sensitivity); | |
// Go up and down based on whatever direction we hit | |
yPos += (float)(controllerState->joyy / sensitivity); | |
} | |
int main(int argc, char** argv){ | |
maple_device_t* controller = maple_enum_type(0,MAPLE_FUNC_CONTROLLER); | |
cont_state_t* controllerState; | |
vid_set_mode(DM_640x480, PM_RGB565); | |
glKosInit(); | |
glMatrixMode(GL_PROJECTION); // This sends our matrix operations to the | |
// projection matrix stack. Essentially this is | |
// manipulating our camera. | |
glLoadIdentity(); // Whenever we start to do things with matrices, | |
// it seems like most tutorials load the Identity | |
// matrix to reset everything, making sure we're | |
// working with a clean slate. | |
gluPerspective(45.0f,640.0f/480.0f, 0.1f, 100.0f); // This is our Field of View. | |
// Our FOV value is 45, then we | |
// pass in our aspect ratio, and | |
// finally we give our near-plane | |
// and far-plane | |
glMatrixMode(GL_MODELVIEW); // This sends our matrix operations to the | |
// modelview matrix stack. | |
// We want to enable depth, otherwise our triangles will look really weird. | |
// The PVR won't be able to tell what's supposed to be in front, and what | |
// should be in the back. | |
glEnable(GL_DEPTH_TEST); | |
glDepthFunc(GL_LEQUAL); | |
while(run){ | |
if (controller) | |
HandleControls(controller, controllerState); | |
//glMatrixMode(GL_MODELVIEW); | |
// Now we need to clear both the color buffer AND the depth buffer when we | |
// go to draw. | |
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); | |
glLoadIdentity(); // Load a blank matrix to work with | |
gluLookAt(xPos,yPos,zPos, // This is essentially our camera | |
0,0,0, | |
0,1,0); | |
// Our transformations for our pyramid | |
glTranslatef(0.0f,0.0f,-1.0f); // Move the object back by 1 | |
glRotatef(rot*rotSpeed, 0.0f,1.0f,0.0f); // Setup the rotation | |
// We're telling OpenGL that we want to render triangles. | |
glBegin(GL_TRIANGLES); | |
// Each of the pyramid's faces will have 3 vertices. | |
// We'll start drawing at the top, then go down to the bottom left, | |
// then to the right. | |
// When we start our next triangle, we're going to be going back to | |
// the top-middle. Imagine drawing a pyramid without ever lifting your | |
// pen up. | |
// New Triangle - Front | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
// New Triangle - Right | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(1.0f,-1.0f,-1.0f); | |
// New Triangle - Back | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,-1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); | |
// New Triangle - left | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(-1.0f,-1.0f,1.0f); | |
// New Triangle - Bottom 1 | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); | |
// New Triangle - Bottom 2 | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); // Note: we're starting from the last point | |
// of the previous triangle. | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,-1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
glEnd(); | |
rot += 0.1f; // Increase our rotation a bit every pass | |
glKosSwapBuffers(); | |
} | |
return 0; | |
} |
I could have comprised drawing the triangles into a separate function, but I wanted it to all be in one loop for clarity's sake! So, let's get into it.
Some Housekeeping Items
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
float xPos = 0.0f; | |
float yPos = 0.0f; | |
float zPos = 5.0f; | |
float sensitivity = 1000.0f; // This is so we don't go flying accross | |
// the screen every time we try and move. | |
GLfloat rot; // The rotation matrix takes in a GLfloat. | |
// GLfloat is basically just a float, but | |
// defined by OpenGL. | |
GLfloat rotSpeed = 1.0f; // Just to make sure our speed can multiply with our | |
// rot variable, we'll make this a GLfloat, too. |
The Setup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
vid_set_mode(DM_640x480, PM_RGB565); | |
glKosInit(); | |
glMatrixMode(GL_PROJECTION); // This sends our matrix operations to the | |
// projection matrix stack. Essentially this is | |
// manipulating our camera. | |
glLoadIdentity(); // Whenever we start to do things with matrices, | |
// it seems like most tutorials load the Identity | |
// matrix to reset everything, making sure we're | |
// working with a clean slate. | |
gluPerspective(45.0f,640.0f/480.0f, 0.1f, 100.0f); // This is our Field of View. | |
// Our FOV value is 45, then we | |
// pass in our aspect ratio, and | |
// finally we give our near-plane | |
// and far-plane | |
glMatrixMode(GL_MODELVIEW); // This sends our matrix operations to the | |
// modelview matrix stack. | |
// We want to enable depth, otherwise our triangles will look really weird. | |
// The PVR won't be able to tell what's supposed to be in front, and what | |
// should be in the back. | |
glEnable(GL_DEPTH_TEST); | |
glDepthFunc(GL_LEQUAL); |
An identity matrix. Credit: KhanAcademy |
However, this time we're going to go into some scary territory: Matrices! Truthfully, matrices are my Achilles heel. However, after taking a 3D Graphics course last school year, I'm a bit more comfortable with the concepts!
First off, we're going to start by loading in an identity matrix. An identity matrix is essentially a blank slate. It's a matrix that will do absolutely nothing when it's multiplied, added, divided, or subtracted from any other matrix.
We use this to work as a starting point to mish-mash a bunch of other matrices together to do our rotations, translations, and scaling. We'll multiply this one, big, messy matrix we've put together by our vertices' positions to tell it where to go, and how to angle itself. If it's a little confusing to you, and you'd like to know more on how matrix math works, I'd recommend looking up some tutorials and videos online to help better explain it!
In the case above: we're loading an identity matrix so that we can set up our perspective! After that, we switch our mode to "modelview" so that we know we're going to be affecting the position, rotation, and scale of objects as opposed to our perspective.
Lastly, we enable our depth test and give it a function to work from so that the GPU knows to stop drawing our triangles if they're behind another triangle.
The Loop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
while(run){ | |
if (controller) | |
HandleControls(controller, controllerState); | |
//glMatrixMode(GL_MODELVIEW); | |
// Now we need to clear both the color buffer AND the depth buffer when we | |
// go to draw. | |
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); | |
glLoadIdentity(); // Load a blank matrix to work with | |
gluLookAt(xPos,yPos,zPos, // This is essentially our camera | |
0,0,0, | |
0,1,0); | |
// Our transformations for our pyramid | |
glTranslatef(0.0f,0.0f,-1.0f); // Move the object back by 1 | |
glRotatef(rot*rotSpeed, 0.0f,1.0f,0.0f); // Setup the rotation | |
// We're telling OpenGL that we want to render triangles. | |
glBegin(GL_TRIANGLES); | |
// Each of the pyramid's faces will have 3 vertices. | |
// We'll start drawing at the top, then go down to the bottom left, | |
// then to the right. | |
// When we start our next triangle, we're going to be going back to | |
// the top-middle. Imagine drawing a pyramid without ever lifting your | |
// pen up. | |
// New Triangle - Front | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
// New Triangle - Right | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(1.0f,-1.0f,-1.0f); | |
// New Triangle - Back | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,-1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); | |
// New Triangle - left | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(-1.0f,-1.0f,1.0f); | |
// New Triangle - Bottom 1 | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); | |
// New Triangle - Bottom 2 | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,-1.0f); // Note: we're starting from the last point | |
// of the previous triangle. | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(1.0f,-1.0f,-1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); | |
glEnd(); | |
rot += 0.1f; // Increase our rotation a bit every pass | |
glKosSwapBuffers(); | |
} |
We use gluLookAt() to act as our camera. This function takes our position, filled in by xPos, yPos, and zPos, and then takes 3 other values to be where we're looking at. Lastly, we need to tell it what direction is up, which in this case is our y-axis.
We then tell the matrix we want to translate everything we're about to draw back by 1, and tell it we're going to want to rotate everything by rot*rotSpeed, using the y-axis so it spins around clockwise/counter-clockwise instead of on it's side or something!
Once we have our big mess of a matrix put together, we can start drawing! Every vertex after will be affected by this matrix we just put together.
The Vertices
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// New Triangle - Front | |
glColor3f(1.0f,0.0f,0.0f); | |
glVertex3f(0.0f,1.0f,0.0f); | |
glColor3f(0.0f,1.0f,0.0f); | |
glVertex3f(-1.0f,-1.0f,1.0f); | |
glColor3f(0.0f,0.0f,1.0f); | |
glVertex3f(1.0f,-1.0f,1.0f); |

Sometimes it's really difficult to wrap your head around 3D coordinates, and how to connect them together, so I usually draw it out!
Some people are probably asking, "Tofu, why did you draw the bottom parts at -1 instead of 0?" And the answer to that question is that I'm a dumb-dumb who didn't think ahead of time!
As you can see, the bottom square is actually made up of 2 triangles, instead of just one! This is why we need 6, as opposed to 4 triangles to make up our pyramid. We could leave the bottom hollowed out, but I wanted to make a complete, whole pyramid.

The Controls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void HandleControls(maple_device_t* controller, cont_state_t* controllerState){ | |
controllerState = (cont_state_t*) maple_dev_status(controller); | |
if (controllerState->buttons & CONT_A) | |
run = false; | |
if (controllerState->buttons & CONT_DPAD_RIGHT) | |
rotSpeed += 1; | |
if (controllerState->buttons & CONT_DPAD_LEFT) | |
rotSpeed -= 1; | |
if (controllerState->buttons & CONT_DPAD_UP) | |
zPos -= 1; | |
if (controllerState->buttons & CONT_DPAD_DOWN) | |
zPos +=1 ; | |
// Go left and right based on whatever direction we hit | |
// Negative because otherwise it's inverted. | |
xPos -= (float)(controllerState->joyx / sensitivity); | |
// Go up and down based on whatever direction we hit | |
yPos += (float)(controllerState->joyy / sensitivity); | |
} |
Here, you can see that the controls are setup so that A quits, Dpad right and left control the rotation speed, Dpad up and down control the zoom, and the joystick controls the X and Y position of the camera. You'll notice, upon testing this out for yourself, that the camera kinda rotates a bit while moving. This is because of that previously noted gluLookAt() function, which is actually trying to look at a specific point in the world. You can replace that with a glTransformf(), passing in xPos, yPos, and zPos to get rid of that effect, if you'd like.
And that's about it! One notable issue I had is that NullDC has crashed twice on me: once with this code and once with another similar project. I don't know the reason, since NullDC doesn't generate logs, but if I had to guess I'd wager it might have to do with rot going out of bounds or something
Further Learning
Learning is all about venturing into uncharted territories! The best way to learn is to experiment on your own, look at examples, read documentation, and google things! If you'd like to try to expand off this tutorial, I'd recommend trying:
- Adding a second pyramid to the scene with it's own rotation and translation
- Try changing the rotation to spin on a different axis
- Enable back-face culling (You'll find I have a little mistake in how I rendered the triangles if you do this!)
Next time we'll look at a better, more optimized way of rendering out objects with OpenGL!
Hi Guy... I've tried to compile your code, but I'm getting this error:
ReplyDelete/../kos-ports/include/GL/glut.h:20:21: error: expected initializer before ‘glutSwapBuffers’
/../kos-ports/include/GL/glut.h:25:21: error: expected initializer before ‘glutCopyBufferToTexture’
I'm using KOS 2.1 under windows (cygwin) and I have included -lGL in my Makefile, so I don't know if I'm doing something wrong or if this is a some problem with the OpenGL Library... Can you help me please? ^^
Hello!
DeleteI'm sorry for the super late reply on this! I didn't see the notification that you posted!
Funny enough, I can't seem to get my own version of this working for completely different reasons (The emulators seem to just crash).
In the time since I posted this, I found out that the KGL libraries are actually incomplete, and have a lot of bugs in them. It's recommended to use Kazade's GLdc libraries instead!
You can find out a bit more about that over here:
https://dcemulation.org/phpBB/viewtopic.php?t=104595
Essentially, you should be able to:
1. Go to your "kos/addons" folder
2. type "git clone https://github.com/Kazade/GLdc.git" (without the quotation marks)
3. type "cd GLdc" (Again, without quotes)
4. type "make defaultall" (...again, without quotes)
5. type "make create_kos_link" (You guessed it!)
Then, in your makefile for your project, instead of "-lGL" you'd want "-lGLdc".
The only real difference is that instead of "glutSwapBuffers" it's "glKosSwapBuffers". Pretty much every other function should be the same as the KGL library!
Hope this helps! I'll update this post with that information. :)