CS 455 Lecture 19 – Skyboxes
Agenda
- what ?s
- skyboxes
- up next: environment mapping
TODO
As a lab exercise:
- Sync with the class repo to get some tweaks to the
Texture
andObjUtilities
classes, a skybox starter project, a box model, and some skybox textures. - Build and run
skybox
to make sure it works. You should see a heightmap with a solid blue background. - In
OnInitialize
, load the skybox geometry in themodels
directory intoattributes[1]
. I’ve provided a convenience methodObjUtilities::Read(path)
that returns a pointer to aVertexAttributes
instance. Just feed itMODELS_DIR "/skybox.obj"
. - Create the skybox shader in
shader_programs[1]
using the vanilla shaders in the project directory:shader_programs[1] = ShaderProgram::FromFiles(SHADERS_DIR "/skybox.v.glsl", SHADERS_DIR "/skybox.f.glsl");
- Marry
attributes[1]
andshader_programs[1]
intoskybox
, aVertexArray
instance variable. - In
OnSizeChanged
, uncomment the code to upload the projection matrix toshader_programs[1]
. - In
OnDraw
, change the#if 0
to#if 1
to start drawing the skybox geometry. Run and locate the white cube in the scene. - The next thing we want to do is make that cube follow the camera around wherever it goes. To do this, we must translate it from its model space position around (0, 0, 0) to a world space position around the camera. In particular, we want to translate it to the camera’s from position. Alter
OnDraw
to upload an appropriatemodelview
transformation to the shader. - Now we texture the cube. We’ll need some texture coordinates. The cubemap texturing hardware expects a three-dimensional texture coordinates. The strongest component selects the face, and the other two select the pixel within the face. For example if the y-coordinate is 0.9, then the color will be pulled from the top texture. The xz-coordinates will identify the pixel within this top texture.
So, where do we get these texture coordinates? Since the cube is centered around the origin in model space, the vertex positions double as vectors pointing at the cube. These positions are our texture coordinates. To get them in the fragment shader, create a
varying
and simply assign it the modelspace vertex position. The position will be interpolated across the faces and become input to the fragment shader. In the fragment shader, setgl_FragColor
to this texture coordinate. We should see red on the right, green above, and blue behind us. - Let’s add a texture. We start by loading in six images. You are encouraged to locate your own cubemap skyboxes (check out http://www.custommapmakers.org/skyboxes.php), but for now let’s use
models/hell_*.ppm
. All six must be squares and have the same resolution. Load them inOnInitialize
with:#define SKYBOX "hell" Image *images[] = { new Image(MODELS_DIR "/" SKYBOX "_left.ppm"), new Image(MODELS_DIR "/" SKYBOX "_right.ppm"), new Image(MODELS_DIR "/" SKYBOX "_bottom.ppm"), new Image(MODELS_DIR "/" SKYBOX "_top.ppm"), new Image(MODELS_DIR "/" SKYBOX "_back.ppm"), new Image(MODELS_DIR "/" SKYBOX "_front.ppm") };
- Create a new texture object in the
skybox_texture
instance variable. Call itsUpload
method and pass the texture resolution and pixel data. - Add a
samplerCube
uniform to the fragment shader. InOnDraw
, bind the skybox texture to this uniform. - Perform a texture lookup in the fragment shader using your uniform and texture coordinate. The
textureCube
function will invoke the cube mapping lookup hardware:vec3 rgb = textureCube(skybox_texture, ftexcoord).rgb;
- Run your program. How’s it look? First off, you might notice some seams. We can reduce their visibility with the following two commands:
skybox_texture->Wrap(Texture::CLAMP_TO_EDGE); glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
- In the vertex shader, I temporarily multiplied the cube’s model coordinates by 50 so that we could locate it in the scene. Remove this. Run it again. What happens?
- We want the cube to appear behind everything else that’s been drawn. We could find a multiplier that’s as big as our scene, but there’s a simpler trick that requires less calculation. Given the perspective divide that is built in to the hardware, we know that vertex’s normalized device coordinate will be (
x/w
,y/w
,z/w
). Ifz/w
is 1, it will have the greatest depth possible. So, let’s make sure thatz/w
is 1 by hacking z in the vertex shader:gl_Position.z = gl_Position.w;
- Now how’s it look?