--- /dev/null
+#ifndef BLANK_GRAPHICS_SKYBOXSHADER_HPP_
+#define BLANK_GRAPHICS_SKYBOXSHADER_HPP_
+
+#include "CubeMap.hpp"
+
+
+namespace blank {
+
+class SkyBoxShader {
+
+public:
+       SkyBoxShader();
+
+       void Activate() noexcept;
+
+       void SetTexture(CubeMap &) noexcept;
+
+       void SetM(const glm::mat4 &m) noexcept;
+       void SetProjection(const glm::mat4 &p) noexcept;
+       void SetView(const glm::mat4 &v) noexcept;
+       void SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept;
+       void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
+
+       const glm::mat4 &Projection() const noexcept { return projection; }
+       const glm::mat4 &View() const noexcept { return view; }
+       const glm::mat4 &GetVP() const noexcept { return vp; }
+
+private:
+       Program program;
+
+       glm::mat4 projection;
+       glm::mat4 view;
+       glm::mat4 vp;
+
+       GLuint m_handle;
+       GLuint mv_handle;
+       GLuint mvp_handle;
+       GLuint sampler_handle;
+
+};
+
+}
+
+#endif
 
 #include "Canvas.hpp"
 #include "DirectionalLighting.hpp"
 #include "PlainColor.hpp"
+#include "SkyBoxShader.hpp"
 
 #include <glm/glm.hpp>
 
        void VSync(bool b) noexcept;
 
        void EnableDepthTest() noexcept;
+       void EqualDepthTest() noexcept;
        void DisableDepthTest() noexcept;
 
        void EnableBackfaceCulling() noexcept;
        DirectionalLighting &HUDProgram() noexcept;
        PlainColor &WorldOutlineProgram() noexcept;
        PlainColor &HUDOutlineProgram() noexcept;
+       SkyBoxShader &SkyBoxProgram() noexcept;
        BlendedSprite &SpriteProgram() noexcept;
 
        void WorldPosition(const glm::mat4 &) noexcept;
        BlockLighting chunk_prog;
        DirectionalLighting entity_prog;
        PlainColor outline_prog;
+       SkyBoxShader sky_prog;
        BlendedSprite sprite_prog;
 
        enum {
                HUD,
                OUTLINE_WORLD,
                OUTLINE_HUD,
+               SKY_BOX,
                SPRITE,
        } active_prog;
 
 
 #include "PlainColor.hpp"
 #include "Program.hpp"
 #include "Shader.hpp"
+#include "SkyBoxShader.hpp"
 
 #include "ArrayTexture.hpp"
+#include "CubeMap.hpp"
 #include "Texture.hpp"
 #include "../app/init.hpp"
 
 }
 
 
+SkyBoxShader::SkyBoxShader()
+: program()
+, vp(1.0f)
+, m_handle(0)
+, mv_handle(0)
+, mvp_handle(0)
+, sampler_handle(0) {
+       program.LoadShader(
+               GL_VERTEX_SHADER,
+               "#version 330 core\n"
+               "layout(location = 0) in vec3 vtx_position;\n"
+               "uniform mat4 M;\n"
+               "uniform mat4 MV;\n"
+               "uniform mat4 MVP;\n"
+               "out vec3 vtx_viewspace;\n"
+               "void main() {\n"
+                       "gl_Position = MVP * vec4(vtx_position, 1);\n"
+                       "gl_Position.z = gl_Position.w;\n"
+                       "vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
+               "}\n"
+       );
+       program.LoadShader(
+               GL_FRAGMENT_SHADER,
+               "#version 330 core\n"
+               "in vec3 vtx_viewspace;\n"
+               "uniform samplerCube tex_sampler;\n"
+               "out vec3 color;\n"
+               "void main() {\n"
+                       "color = texture(tex_sampler, vtx_viewspace).rgb;\n"
+               "}\n"
+       );
+       program.Link();
+       if (!program.Linked()) {
+               program.Log(std::cerr);
+               throw std::runtime_error("link program");
+       }
+
+       m_handle = program.UniformLocation("M");
+       mv_handle = program.UniformLocation("MV");
+       mvp_handle = program.UniformLocation("MVP");
+       sampler_handle = program.UniformLocation("tex_sampler");
+}
+
+
+void SkyBoxShader::Activate() noexcept {
+       program.Use();
+}
+
+void SkyBoxShader::SetM(const glm::mat4 &M) noexcept {
+       glm::mat4 m(M);
+       m[0].w = 0.0f;
+       m[1].w = 0.0f;
+       m[2].w = 0.0f;
+       m[3] = { 0.0f, 0.0f, 0.0f, 1.0f };
+       program.Uniform(m_handle, m);
+       program.Uniform(mv_handle, view * m);
+       program.Uniform(mvp_handle, vp * m);
+}
+
+void SkyBoxShader::SetTexture(CubeMap &tex) noexcept {
+       glActiveTexture(GL_TEXTURE0);
+       tex.Bind();
+       program.Uniform(sampler_handle, GLint(0));
+}
+
+void SkyBoxShader::SetProjection(const glm::mat4 &p) noexcept {
+       projection = p;
+       vp = p * view;
+}
+
+void SkyBoxShader::SetView(const glm::mat4 &v) noexcept {
+       view = v;
+       view[0].w = 0.0f;
+       view[1].w = 0.0f;
+       view[2].w = 0.0f;
+       view[3] = { 0.0f, 0.0f, 0.0f, 1.0f };
+       vp = projection * v;
+}
+
+void SkyBoxShader::SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept {
+       projection = p;
+       SetView(v);
+}
+
+void SkyBoxShader::SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept {
+       SetVP(v, p);
+       SetM(m);
+}
+
+
 PlainColor::PlainColor()
 : program()
 , vp(1.0f)
 
 , cursor(1.0f)
 , chunk_prog()
 , entity_prog()
+, sky_prog()
 , sprite_prog()
 , active_prog(NONE) {
        glClearColor(0.0, 0.0, 0.0, 1.0);
        glDepthFunc(GL_LESS);
 }
 
+void Viewport::EqualDepthTest() noexcept {
+       glEnable(GL_DEPTH_TEST);
+       glDepthFunc(GL_LEQUAL);
+}
+
 void Viewport::DisableDepthTest() noexcept {
        glDisable(GL_DEPTH_TEST);
 }
        return outline_prog;
 }
 
+SkyBoxShader &Viewport::SkyBoxProgram() noexcept {
+       if (active_prog != SKY_BOX) {
+               sky_prog.Activate();
+               DisableBlending();
+               EqualDepthTest();
+               active_prog = SKY_BOX;
+       }
+       return sky_prog;
+}
+
 BlendedSprite &Viewport::SpriteProgram() noexcept {
        if (active_prog != SPRITE) {
                sprite_prog.Activate();
 
 #include "BlockModel.hpp"
 #include "EntityModel.hpp"
 #include "OutlineModel.hpp"
+#include "SkyBoxModel.hpp"
 
 #include <glm/glm.hpp>
 
                float tex_offset = 0.0f,
                BlockModel::Index idx_offset = 0
        ) const;
+       void Vertices(
+               SkyBoxModel::Buffer &out
+       ) const;
 
        /// the number of vertices this shape's outline has
        size_t OutlineCount() const { return out_pos.size(); }
 
--- /dev/null
+#ifndef BLANK_MODEL_SKYBOXMODEL_HPP_
+#define BLANK_MODEL_SKYBOXMODEL_HPP_
+
+#include "../graphics/VertexArray.hpp"
+
+#include <vector>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class SkyBoxModel {
+
+public:
+       using Position = glm::vec3;
+       using Index = unsigned int;
+
+       using Positions = std::vector<Position>;
+       using Indices = std::vector<Index>;
+
+       enum Attribute {
+               ATTRIB_VERTEX,
+               ATTRIB_INDEX,
+               ATTRIB_COUNT,
+       };
+
+       struct Buffer {
+
+               Positions vertices;
+               Indices indices;
+
+               void Clear() noexcept {
+                       vertices.clear();
+                       indices.clear();
+               }
+
+               void Reserve(size_t p, size_t i) {
+                       vertices.reserve(p);
+                       indices.reserve(i);
+               }
+
+       };
+
+       using VAO = VertexArray<ATTRIB_COUNT>;
+
+public:
+       void LoadUnitBox();
+       void Update(const Buffer &) noexcept;
+
+       void Draw() const noexcept;
+
+private:
+       VAO vao;
+
+};
+
+}
+
+#endif
 
 #include "BlockModel.hpp"
 #include "EntityModel.hpp"
 #include "OutlineModel.hpp"
+#include "SkyBoxModel.hpp"
 #include "SpriteModel.hpp"
 
+#include "shapes.hpp"
+
 #include <algorithm>
 #include <iostream>
 
 }
 
 
+void SkyBoxModel::LoadUnitBox() {
+       Buffer buffer;
+       CuboidShape shape({{ -1, -1, -1 }, { 1, 1, 1 }});
+       shape.Vertices(buffer);
+       Update(buffer);
+}
+
+void SkyBoxModel::Update(const Buffer &buf) noexcept {
+       vao.Bind();
+       vao.PushAttribute(ATTRIB_VERTEX, buf.vertices);
+       vao.PushIndices(ATTRIB_INDEX, buf.indices);
+}
+
+void SkyBoxModel::Draw() const noexcept {
+       vao.DrawTriangleElements();
+}
+
+
 void SpriteModel::Buffer::LoadRect(
        float w, float h,
        const glm::vec2 &pivot,
 
        }
 }
 
+void Shape::Vertices(
+       SkyBoxModel::Buffer &out
+) const {
+       for (const auto &pos : vtx_pos) {
+               out.vertices.emplace_back(pos);
+       }
+       for (auto idx : vtx_idx) {
+               out.indices.emplace_back(idx);
+       }
+}
+
 void Shape::Outline(OutlineModel::Buffer &out) const {
        out.vertices.insert(out.vertices.end(), out_pos.begin(), out_pos.end());
        out.indices.insert(out.indices.end(), out_idx.begin(), out_idx.end());