Inside the companion
The Shader Workshop
Open a native OpenGL window, draw a triangle, then move a camera around three exhibits.
Project files 8
Make the camera math inspectable
Checked vector and matrix operations, projections, and capture-size arithmetic.
#pragma once
#include <array>
#include <cmath>
#include <cstddef>
#include <expected>
#include <limits>
#include <numbers>
#include <string_view>
namespace observatory {
struct Vec3 { float x{}, y{}, z{}; };
inline Vec3 subtract(Vec3 a, Vec3 b) { return {a.x-b.x,a.y-b.y,a.z-b.z}; }
inline float dot(Vec3 a, Vec3 b) { return a.x*b.x+a.y*b.y+a.z*b.z; }
inline Vec3 cross(Vec3 a, Vec3 b) { return {a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x}; }
inline std::expected<Vec3,std::string_view> unit(Vec3 value) {
const float squared=dot(value,value);
if (!std::isfinite(squared)||squared<1e-12F)
return std::unexpected("Direction must have a supported finite nonzero length.");
const float scale=1.0F/std::sqrt(squared);
return Vec3{value.x*scale,value.y*scale,value.z*scale};
}
// Column-major storage, column vectors, right-handed view looking along -Z.
struct Mat4 { std::array<float,16> data{}; };
inline Mat4 identity() { return {{{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}}}; }
inline std::array<float,4> transform(const Mat4& matrix,const std::array<float,4>& point) {
std::array<float,4> result{};
for(std::size_t row=0;row<4;++row)
for(std::size_t column=0;column<4;++column)
result[row]+=matrix.data[column*4+row]*point[column];
return result;
}
inline Mat4 translation(Vec3 offset) {
auto result=identity();
result.data[12]=offset.x;result.data[13]=offset.y;result.data[14]=offset.z;
return result;
}
inline std::expected<Mat4,std::string_view> look_at(Vec3 eye,Vec3 target,Vec3 up) {
const auto bounded=[](Vec3 value){return std::isfinite(value.x)&&std::isfinite(value.y)&&
std::isfinite(value.z)&&std::abs(value.x)<=1000&&std::abs(value.y)<=1000&&std::abs(value.z)<=1000;};
if(!bounded(eye)||!bounded(target)||!bounded(up))return std::unexpected("Unsupported view inputs.");
const auto forward=unit(subtract(target,eye));
const auto up_unit=unit(up);
if(!forward||!up_unit)return std::unexpected("Eye and target must differ; up must be nonzero.");
const auto right=unit(cross(*forward,*up_unit));
if(!right)return std::unexpected("Up must not be parallel to the viewing direction.");
const auto camera_up=cross(*right,*forward);
return Mat4{{{right->x,camera_up.x,-forward->x,0,
right->y,camera_up.y,-forward->y,0,
right->z,camera_up.z,-forward->z,0,
-dot(*right,eye),-dot(camera_up,eye),dot(*forward,eye),1}}};
}
inline Mat4 multiply(const Mat4& a,const Mat4& b) {
Mat4 result;
for(std::size_t column=0;column<4;++column)
for(std::size_t row=0;row<4;++row)
for(std::size_t k=0;k<4;++k)
result.data[column*4+row]+=a.data[k*4+row]*b.data[column*4+k];
return result;
}
inline Mat4 rotate_z(float radians) {
auto result=identity();
const float c=std::cos(radians),s=std::sin(radians);
result.data[0]=c;result.data[1]=s;result.data[4]=-s;result.data[5]=c;
return result;
}
inline std::expected<Mat4,std::string_view> perspective(float vertical_degrees,float aspect,float near_z,float far_z) {
if(!std::isfinite(vertical_degrees)||vertical_degrees<10||vertical_degrees>120||
!std::isfinite(aspect)||aspect<0.05F||aspect>20||
!std::isfinite(near_z)||!std::isfinite(far_z)||near_z<0.001F||far_z>10000||far_z<=near_z)
return std::unexpected("Unsupported perspective parameters.");
const float scale=1/std::tan(vertical_degrees*std::numbers::pi_v<float>/360);
Mat4 result;
result.data[0]=scale/aspect;result.data[5]=scale;
result.data[10]=(far_z+near_z)/(near_z-far_z);
result.data[11]=-1;
result.data[14]=(2*far_z*near_z)/(near_z-far_z);
return result;
}
inline std::expected<std::size_t,std::string_view> rgb_bytes(int width,int height) {
if(width<=0||height<=0||width>8192||height>8192)
return std::unexpected("Framebuffer dimensions are unsupported.");
const auto w=static_cast<std::size_t>(width),h=static_cast<std::size_t>(height);
constexpr std::size_t max_pixels=16*1024*1024;
if(h>max_pixels/w) return std::unexpected("Framebuffer exceeds the capture pixel budget.");
return w*h*3;
}
} // namespace observatory
This is the file included with the course. Copy and download give you its complete source.
Taking your work offline? Download all course companions
