Inside the companion
The Shader Workshop
Open a native OpenGL window, draw a triangle, then move a camera around three exhibits.
Project files 8
Predict the transformed point
GPU-independent camera, projection, geometry, and size-boundary checks.
#include "contracts.hpp"
#include <iostream>
#include <stdexcept>
int main() {
std::size_t checks=0;
const auto require=[&](bool condition,const char* message){++checks;if(!condition)throw std::runtime_error(message);};
try {
using namespace observatory;
require(!unit({0,0,0}),"zero direction rejected");
const auto normal=unit({0,3,4});
require(normal&&std::abs(normal->y-0.6F)<1e-6F&&std::abs(normal->z-0.8F)<1e-6F,"known normalization");
require(!unit({std::numeric_limits<float>::infinity(),0,0}),"nonfinite direction rejected");
const auto rotated=multiply(identity(),rotate_z(std::numbers::pi_v<float>/2));
require(std::abs(rotated.data[0])<1e-6F&&std::abs(rotated.data[1]-1)<1e-6F,"quarter rotation and identity multiply");
require(!perspective(45,0,0.1F,100),"zero aspect rejected");
require(!perspective(45,1,1,0.1F),"reversed depth rejected");
const auto projection=perspective(60,1,0.1F,10);
require(projection.has_value(),"valid perspective accepted");
const auto ndc=[&](float z){return (projection->data[10]*z+projection->data[14])/(-z);};
require(std::abs(ndc(-0.1F)+1)<1e-5F&&std::abs(ndc(-10)-1)<1e-5F,"OpenGL near/far NDC convention");
const auto close=[](float a,float b){return std::abs(a-b)<1e-5F;};
const auto view=look_at({0,0,4},{0,0,0},{0,1,0});
require(view.has_value(),"axis camera accepted");
const auto origin=transform(*view,{0,0,0,1});
const auto eye=transform(*view,{0,0,4,1});
require(close(origin[2],-4)&&close(origin[3],1),"target is four units along view negative Z");
require(close(eye[0],0)&&close(eye[1],0)&&close(eye[2],0),"eye maps to view origin");
const auto side_view=look_at({4,0,0},{0,0,0},{0,1,0});
require(side_view.has_value(),"side camera accepted");
const auto side_target=transform(*side_view,{0,0,0,1});
const auto side_right=transform(*side_view,{0,0,-1,1});
require(close(side_target[2],-4)&&close(side_right[0],1),"side camera basis and handedness");
require(!look_at({0,0,0},{0,0,0},{0,1,0}),"coincident eye and target rejected");
require(!look_at({0,0,4},{0,0,0},{0,0,1}),"parallel up rejected");
require(!look_at({0,0,4},{0,0,0},{0,0,0}),"zero up rejected");
require(!look_at({0,0,std::numeric_limits<float>::quiet_NaN()},{0,0,0},{0,1,0}),"nonfinite view rejected");
const auto model=translation({1,2,3});
const auto moved=transform(model,{0,0,0,1});
const auto direction=transform(model,{0,1,0,0});
require(close(moved[0],1)&&close(moved[1],2)&&close(moved[2],3),"point translation");
require(close(direction[0],0)&&close(direction[1],1)&&close(direction[2],0),"direction is not translated");
const auto composed=transform(multiply(*projection,multiply(*view,model)),{0,0,0,1});
const auto staged=transform(*projection,transform(*view,transform(model,{0,0,0,1})));
require(composed==staged&&close(composed[3],1),"projection view model agrees with staged transform");
const auto wide=perspective(60,2,0.1F,10);
require(wide&&close(wide->data[0]*2,projection->data[0])&&close(wide->data[5],projection->data[5]),"resize aspect changes horizontal scale only");
const auto edge=transform(*projection,{std::tan(std::numbers::pi_v<float>/6),0,-1,1});
require(close(edge[0]/edge[3],1),"known frustum right edge maps to NDC one");
require(!rgb_bytes(0,10)&&!rgb_bytes(-1,10),"invalid capture extents rejected");
require(rgb_bytes(4096,4096)==16U*1024U*1024U*3U,"exact capture budget");
require(!rgb_bytes(4096,4097),"one row beyond budget rejected");
std::cout<<checks<<" CPU contracts passed. No OpenGL context was exercised.\n";
return 0;
} catch(const std::exception& error){std::cerr<<error.what()<<'\n';return 1;}
}
This is the file included with the course. Copy and download give you its complete source.
Taking your work offline? Download all course companions
