As the title is saying I'm trying to make a simple skybox to learn how it works. Using, of course OpenGL and SDL.
I have tried read some sites, here are them: link 1 link 2 link 3.
None of them were of good use, since they are poorly written or simply too much complex. For example one of them use glfw instead of SDL, while another use another way that I didn't understand and wouldn't use anyways, because I don't want to rewrite all my code JUST so that I can try it.
Or they also use a camera implementation (all of the 3 of this links, If I'm not mistaken), but I don't know if I necessarily need a camera, since my objective isn't moving or rotating around, it's just to draw the skybox.
Also they use shaders. Which I also don't know if is necessary, but I tried to do implement them anyway. All of my attempts did compile and didn't show any errors but it also didn't draw anything, just a grey screen. Probably because of "glClearColor(0.1f, 0.1f, 0.1f, 1.0f)" as some people said (I searched and read about similar problems), they also said that maybe it was a geometry problem, I don't know if it's and how to fix it, since no error is shown.
Also, don't ask me the code of each attempt because I haven't, they are all around, some commented, some I deleted and don't remember anymore, I simply can't put it all together anymore. But from reading the links and "to prove" (I guess) I got some understanding of it, so instead I will share my understanding.
To draw something or in this case the skybox I would need:
1° Thing, create the VAO and VBO:
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
2° Thing, is generating a texture ID and binding a texture to it and some parameters:
unsigned int ID;
unsigned char *data = //load image here
int width, height;
glGenTextures(1, &ID);
glBindTexture(GL_TEXTURE_CUBE_MAP, ID);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
*image here is refering to the loaded image, one of the links use stb_image and other SDL_LoadBMP for example. I tried all of them, I don't know if it's relevant to use the one that the tutorial is using, while I tried them, I also put in error checks that the tutorials gave and no error was give whatsoever. The book that I'm reading uses SOIL_load_image though.
SOIL_load_image("path", &width, &height, 0, SOIL_LOAD_AUTO);
//So:
data = SOIL_load_image("path", &width, &height, 0, SOIL_LOAD_AUTO);
*Also, a couple of observations:
In the case of link1, they use gluBuild2DMipmaps, with GL_TEXTURE_2D.
In the case of link2, they use the above glTexImage2D, with GL_TEXTURE_CUBE_MAP_POSITIVE_X + i. i goes from 0 to 5, each face of a cube.
In the case of link3, they use glTexImage2D, with GL_TEXTURE_2D, but individually for each face.
3° Thing, is then drawing in the main loop:
//glEnable(GL_DEPTH_TEST); One of the tutorials says that I should use this before drawing.
//Loop
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glBindVertexArray(VAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
SDL_GL_SwapWindow(mWindow);
//
*One of the tutorials just draw a cube right away, I also tried this, no success:
//repeate for each face, here the tutorial obviously generates each face texture ID individually.
glBindTexture(GL_TEXTURE_2D, face_textureID);
glBegin(GL_QUADS);
glTexCoord2f(0,0);
glVertex3f(size/2,size/2,size/2);
glTexCoord2f(1,0);
glVertex3f(-size/2,size/2,size/2);
glTexCoord2f(1,1);
glVertex3f(-size/2,-size/2,size/2);
glTexCoord2f(0,1);
glVertex3f(size/2,-size/2,size/2);
glEnd();
//
*Here a lot happens on the tutorials I read that I don't understand or don't know the purpose of.
//Some tutorials use those functions
glDepthFunc(GL_LESS);
glDepthFunc(GL_LEQUAL);
glLoadIdentity();
glLightfv(GL_LIGHT0,GL_POSITION,pos); //float pos[]={-1.0,1.0,-2.0,1.0};
glEnable(GL_LIGHTING);
//and
glDisable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
//and
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
*In addition. Shaders.
unsigned int vertex, fragment;
unsigned int shaderID;
//Example of a shader of a tutorial
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"out vec4 vertexColor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n"
" vertexColor = vec4(0.9, 0.5, 0.0, 1.0);\n"
"}\0";
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vertexShaderSource, NULL);
glCompileShader(vertex);
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fragmentShaderSource, NULL);
glCompileShader(fragment);
shaderID = glCreateProgram();
glAttachShader(Cubemap_Shader_ID, vertex);
glAttachShader(Cubemap_Shader_ID, fragment);
glLinkProgram(Cubemap_Shader_ID);
glDeleteShader(vertex);
glDeleteShader(fragment);
glUseProgram(shaderID);
//Draw. Bind VAO, activate texture, bind texture, draw arrays and bind arrays
SDL_GL_SwapWindow(mWindow);
I will even give the entire code I used, to those out there that cry about a "minimal reproducible code". The code used here is from "Game Programming in C++: Creating 3D Games (Game Design) 1st Edition by Sanjay Madhav" if you're wondering or if you want to see more of it.
//game.h
#pragma once
#include "../Header Files/SDL/SDL_types.h"
#include <unordered_map>
#include <string>
#include <vector>
class Game
{
public:
Game();
bool Initialize();
void RunLoop();
void Shutdown();
void ProcessInput();
private:
bool mIsRunning;
class Renderer* mRenderer;
};
//renderer.h
#pragma once
#include "../Header Files/SDL/SDL.h"
#include <string>
#include <vector>
#include <unordered_map>
class Renderer
{
public:
Renderer(class Game* game);
~Renderer();
bool Initialize(float screenW, float screenH);
void Shutdown();
void Init_Things();
void Draw();
private:
class Game* mGame;
SDL_Window* mWindow;
SDL_GLContext mContext;
float mScreenW;
float mScreenH;
//Here you should create VAO, VBO, shader and the textureID so that you can use initialize them and use in the drawing function. For example:
//unsigned int VAO;
//unsigned int VBO;
//unsigned int shader;
//unsigned int textureID
}
//main.cpp
#include "../Header Files/Game.h"
int main(int argc, char *argv[])
{
Game game;
bool success = game.Initialize();
if (success)
{
game.RunLoop();
}
game.Shutdown();
return 0;
};
//game.cpp
#include "../Header Files/Game.h"
#include "../Header Files/Renderer.h"
#include "../Header Files/SDL/SDL.h"
#include <algorithm>
Game::Game() :mRenderer(nullptr), mIsRunning(true) {}
bool Game::Initialize()
{
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0)
{
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return false;
}
mRenderer = new Renderer(this);
if (!mRenderer->Initialize(1024.0f, 768.0f))
{
SDL_Log("Failed to initialize renderer");
delete mRenderer;
mRenderer = nullptr;
return false;
}
mRenderer->Init_Things();
//I put steps 1° and 2° here.
return true;
}
void Game::RunLoop()
{
while (mIsRunning)
{
ProcessInput();
mRenderer->Draw();
//Here things are drawn.
}
}
void Game::Shutdown()
{
if (mRenderer)
{
mRenderer->Shutdown();
}
SDL_Quit();
}
void Game::ProcessInput()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
mIsRunning = false;
break;
}
}
const Uint8* state = SDL_GetKeyboardState(NULL);
if (state[SDL_SCANCODE_ESCAPE])
{
mIsRunning = false;
}
}
#include "../Header Files/Game.h"
#include "../Header Files/Renderer.h"
#include <algorithm>
#include "../Header Files/GL/glew.h"
#include "../Header Files/SOIL/SOIL.h"
Renderer::Renderer(Game* game) :mGame(game) {}
Renderer::~Renderer() {}
bool Renderer::Initialize(float screenW, float screenH)
{
mScreenW = screenW;
mScreenH = screenH;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
mWindow = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, static_cast<int>(mScreenW), static_cast<int>(mScreenH), SDL_WINDOW_OPENGL);
if (!mWindow)
{
SDL_Log("Failed to create window: %s", SDL_GetError());
return false;
}
mContext = SDL_GL_CreateContext(mWindow);
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
SDL_Log("Failed to initialize GLEW.");
return false;
}
glGetError();
return true;
}
void Renderer::Shutdown()
{
//Here you can delete the VAO, VBO and shaders. Using:
//glDeleteVertexArrays(1, &VAO);
//glDeleteBuffers(1, &VBO);
//glDeleteProgram(shader);
SDL_GL_DeleteContext(mContext);
SDL_DestroyWindow(mWindow);
}
void Renderer::Init_Things()
{
//Here I would put steps 1° and 2°.
//One of the tutorials say that I should activate the shader(s) in the initialization (before drawing), like for example:
//glUseProgram(CubemapShaderID);
//glUseProgram(SkyboxShaderID);
}
void Renderer::Draw()
{
//glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//Here I would put step 3° (Drawing).
//SDL_GL_SwapWindow(mWindow);
}
TL;DR and as clear as possible. How to make a SIMPLE Skybox. Without a camera and shader(s), only if necessary.
And if necessary, please 😭 just a simple shader (without Reflection, Refraction, etc.), and a simple camera implementation (without translation, rotation, etc.)...