Skip to content

Instantly share code, notes, and snippets.

@bwedding
Last active January 24, 2020 17:07
Show Gist options
  • Save bwedding/749603db22453ef232738a7686e573d2 to your computer and use it in GitHub Desktop.
Save bwedding/749603db22453ef232738a7686e573d2 to your computer and use it in GitHub Desktop.
Raytrace a sphere
// Raytrace.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
// Viewer here: https://www.kylepaulsen.com/stuff/NetpbmViewer/
#include <iostream>
#include <fstream>
#include <vector>
struct vec3
{
float x;
float y;
float z;
vec3 operator+(const vec3& other) const
{
return { x + other.x, y + other.y, z + other.z };
}
vec3 operator-(const vec3& other) const
{
return { x - other.x, y - other.y, z - other.z };
}
vec3 operator-() const
{
return { -x, -y, -z };
}
vec3 operator*(const float& scalar) const
{
return { x * scalar, y * scalar, z * scalar };
}
vec3 operator/(const float& scalar) const
{
return { x / scalar, y / scalar, z / scalar };
}
float dot(const vec3& other) const
{
return x * other.x + y * other.y + z * other.z;
}
float len() const
{
return sqrtf(this->dot(*this));
}
void normalize()
{
float len = this->len();
x /= len;
y /= len;
z /= len;
}
};
float intersectSphere(vec3 origin, vec3 dir, vec3 center, float radius)
{
vec3 oc = origin - center;
float a = dir.dot(dir);
float b = 2.0f * oc.dot(dir);
float c = oc.dot(oc) - radius * radius;
float discriminant = b * b - 4 * a * c;
if (discriminant < 0.0f)
{
return -1.0f;
}
return (-b - sqrtf(discriminant)) / (2.0f * a);
}
void FillColors(std::vector<std::string>& colors)
{
for (int i = 0; i < 256; i++)
{
char buffer[64];
sprintf_s(buffer, sizeof(buffer), "%d ", i);
colors.push_back(buffer);
}
};
int main()
{
std::ofstream outfile;
outfile.open("c:/temp/raytrace.txt");
// Write the header
// P2 is grayscale next is image size in pixels, finally 63 represents brightest
// color (white)
outfile << "P2" << std::endl << "1000 1000" << std::endl << "255" << std::endl;
// Image width and height.
const int width = 1000;
const int height = 1000;
// For square pixels, e.g. PPM file, set to 1.0f
const float pixelAspect = 1.0f;
// Sphere position and radius.
// Adjust the 3rd parameter to move the camera back. Too close
// and you will have a black hole in the sphere. If you have a black
// hole, just increase the value. It should be close, just under, the radius
const vec3 center{ 0.0f, 0.0f, 360.0f };
// Adjust this to make the spere bigger
const float radius = 382.0f;
// Light shines from top left
vec3 light{ 1.0f, 1.0f, 2.0f };
light.normalize();
// Darkest to lightest
std::vector<std::string> colors;
FillColors(colors);
const size_t numColors = colors.size();
// Ray direction - all parallel, down Z axis.
const vec3 dir{ 0.0f, 0.0f, 1.0f };
// For each pixel...
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float xWorld = x - (width / 2) + 0.5f;
float yWorld = (y - (height / 2) + 0.5f) * pixelAspect;
vec3 origin = vec3{ xWorld, yWorld, -30.0f };
float t = intersectSphere(origin, dir, center, radius);
if (t > 0.0f)
{
// Ray hit.
vec3 intersection = origin + (dir * t);
vec3 normal = intersection - center;
normal.normalize();
float directional = normal.dot(-light) * 0.95f;
if (directional < 0.0f)
{
directional = 0.0f;
}
float ambient = 0.05f;
float lum = directional + ambient;
if (lum < 0)
{
// Give some brightness even if pointing
// completely away from the light.
// With 64 shades, 1 isn't enough
outfile << colors[3];
}
else
{
// Pick character based on light value.
outfile << colors[(int)(numColors * lum)];
}
}
else
{
// Ray miss. Make it solid black
outfile << colors[0];
}
}
outfile << std::endl;
}
outfile.close();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment