Skip to content

Instantly share code, notes, and snippets.

@chaidhat
Last active October 3, 2020 10:30
Show Gist options
  • Save chaidhat/4c934711b3de8ad8cebe1e377e1eb23d to your computer and use it in GitHub Desktop.
Save chaidhat/4c934711b3de8ad8cebe1e377e1eb23d to your computer and use it in GitHub Desktop.
Does CPU-based raytracing
// C Raytracer by Chaidhat Chaimongkol
// 20/12/2019
// Does very simple CPU-based raytracing and outputs it as ASCII art
//
// Inspired by Sebastian Lague
// https://www.youtube.com/watch?v=9RHGLZLUuwc&list=LL2T54R7-vJmcMBzcaTL_oBw&index=1707
// References:
// http://blog.three-eyed-games.com/2018/05/03/gpu-ray-tracing-in-unity-part-1/
// http://mewbies.com/geek_fun_files/ascii/ascii_art_light_scale_and_gray_scale_chart.htm
// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection
// http://chanhaeng.blogspot.com/2018/12/how-to-use-stbimagewrite.html
// https://www.youtube.com/watch?v=HFPlKQGChpE
// gcc -o /Users/chaidhatchaimongkol/Documents/GitHub/craytracer/craytracer /Users/chaidhatchaimongkol/Documents/GitHub/craytracer/craytracer.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "craytracer.h"
Vector2 V2(float x, float y)
{
Vector2 vec;
vec.x = x;
vec.y = y;
return vec;
}
Vector3 V3(float x, float y, float z)
{
Vector3 vec;
vec.x = x;
vec.y = y;
vec.z = z;
return vec;
}
Vector3 V3Add(Vector3 vectora, Vector3 vectorb)
{
Vector3 vec;
vec.x = vectora.x + vectorb.x;
vec.y = vectora.y + vectorb.y;
vec.z = vectora.z + vectorb.z;
return vec;
}
Vector3 V3Sub(Vector3 vectora, Vector3 vectorb)
{
Vector3 vec;
vec.x = vectora.x - vectorb.x;
vec.y = vectora.y - vectorb.y;
vec.z = vectora.z - vectorb.z;
return vec;
}
float V3Dot(Vector3 vectora, Vector3 vectorb)
{
float vec;
vec = vectora.x * vectorb.x + vectora.y * vectorb.y + vectora.z * vectorb.z;
return vec;
}
Vector3 V3Mul(Vector3 vector, float mul)
{
Vector3 vec;
vec.x = vector.x * mul;
vec.y = vector.y * mul;
vec.z = vector.z * mul;
return vec;
}
float V3Mag(Vector3 vector)
{
return sqrt((vector.x*vector.x) + (vector.y*vector.y) + (vector.z*vector.z));
}
Vector3 V3Add(Vector3 vectora, Vector3 vectorb);
float V3Dot(Vector3 vectora, Vector3 floatb);
Vector3 V3Mul(Vector3 vectora, float floatb);
float V3Mag(Vector3 vector);
Resolution* CreateResolution(int x, int y)
{
Resolution* res = malloc(sizeof(Resolution));
res->x = x;
res->y = y;
return res;
}
Image* CreateImage(Resolution* resolution)
{
Image* image = malloc(sizeof(Image));
image->data = malloc(sizeof(Pixel) * resolution->x * resolution->y);
int x = 0;
image->resolution = resolution;
return image;
}
void DestroyImage(Image* image)
{
free(image->resolution);
free(image->data);
free(image);
}
Pixel getPixel(Image* image, int x, int y)
{
return image->data[image->resolution->x * y + x];
}
void setPixel(Image* image, int x, int y, Pixel value)
{
image->data[image->resolution->x * y + x] = value;
}
char getPixelChar(Pixel value)
{
return colourScale[colourScaleLength - (int)round(value * (colourScaleLength))];
}
void DrawImage(Image* image)
{
Resolution* res = image->resolution;
for (int iy = 0; iy < res->y; iy += 10)
{
for (int ix = 0; ix < res->x; ix += 10)
{
printf("%c", getPixelChar(getPixel(image, ix, iy)));
}
printf("\n");
}
}
void RenderImage(Image* image, Vector2 fov, Vector3 position)
{
float maxx = tan((fov.x/2) / DEG_TO_RAD);
float minx = tan(-(fov.x/2) / DEG_TO_RAD);
float maxy = tan((fov.y/2) / DEG_TO_RAD);
float miny = tan(-(fov.y/2) / DEG_TO_RAD);
float stepx = (maxx-minx) / image->resolution->x;
float stepy = (maxy-miny) / image->resolution->y;
for (int iy = 0; iy < image->resolution->y; iy++) {
for (int ix = 0; ix < image->resolution->x; ix++)
{
float dirx = maxx - stepx * ix;
float diry = maxy - stepy * iy;
static Ray ray;
ray.origin = position;
ray.direction = V3(dirx, diry, 1);
RayHit* rayHit = TraceRay(ray);
if (rayHit->isHit == 1)
{
setPixel(image, ix, iy, rayHit->colour);
}
else
{
setPixel(image, ix, iy, 0);
}
free(rayHit);
}
}
}
RayHit* TraceRay(Ray ray)
{
RayHit* rayHit = malloc(sizeof(RayHit));
rayHit->isHit = 0;
Sphere sphere;
sphere.position = V3(0, 1, -4);
sphere.radius = 2;
IntersectGround(rayHit, ray);
IntersectSphere(rayHit, ray, sphere);
/*
distance to colour
int threshold = 10;
if (rayHit->isHit == 1)
{
rayHit->colour = rayHit->distance / threshold;
if (rayHit->colour > 1)
rayHit->colour = 1;
}
*/
return rayHit;
}
void IntersectGround(RayHit* rayHit, Ray ray)
{
Vector3 origin = ray.origin;
Vector3 direction = ray.direction;
float tz = -origin.y / direction.y;
rayHit->distance = sqrt(pow(tz, 2) + pow(origin.y, 2));
float tx = fabsf(tz * direction.x);
rayHit->distance = sqrt(pow(tx, 2) + pow(rayHit->distance, 2));
int threshold = 70;
if (tz > 0 && rayHit->distance < threshold)
{
rayHit->isHit = 1;
int stripex = round(fmod(tx, 1));
int stripey = round(fmod(tz / 2, 1));
rayHit->colour = stripex + stripey == 2 ? 0 : stripex + stripey;
}
else if (rayHit->distance >= threshold)
{
rayHit->isHit = 1;
rayHit->colour = 1;
}
}
void IntersectSphere(RayHit* rayHit, Ray ray, Sphere sphere)
{
// https://www.youtube.com/watch?v=HFPlKQGChpE
float t = V3Dot(V3Sub(sphere.position, ray.origin), ray.direction);
Vector3 p = V3Add(ray.origin, V3Mul(ray.direction, t));
float y = V3Mag(V3Sub(sphere.position, p));
if (y < sphere.radius)
{
float x = sqrt(sphere.radius*sphere.radius - y*y);
float t1 = t-x;
float t2 = t+x;
// has it already hit a surface closer than the sphere?
if (rayHit->isHit == 1)
{
if (rayHit->distance < t1)
return;
}
rayHit->isHit = 1;
rayHit->distance = t1;
rayHit->colour = (t1 - (sphere.position.z)) / -sphere.radius;
}
}
int main (int argc, char* argv[])
{
Image* image = CreateImage(CreateResolution(960, 480));
RenderImage(image, V2(80,80), V3(0, 1, 0));
DrawImage(image);
#ifdef DEBUG_MODE
/*** NOTICE!! You have to use uint8_t array to pass in stb function ***/
// Because the size of color is normally 255, 8bit.
// If you don't use this one, you will get a weird imge.
const int width = image->resolution->x;
const int height = image->resolution->y * 2;
pixels = malloc(sizeof(uint8_t) * width * height * CHANNEL_NUM);
int index = 0;
for (int j = 0; j < height; j += 2)
{
for (int i = 0; i < width; i++)
{
uint8_t pix = (int)(getPixel(image, i, j / 2) * 255);
pixels[(j * width + i) * 3] = pix;
pixels[(j * width + i) * 3 + 1] = pix;
pixels[(j * width + i) * 3 + 2] = pix;
pixels[((j + 1) * width + i) * 3] = pix;
pixels[((j + 1) * width + i) * 3 + 1] = pix;
pixels[((j + 1) * width + i) * 3 + 2] = pix;
}
}
// if CHANNEL_NUM is 4, you can use alpha channel in png
stbi_write_png("desktop/stbpng.png", width, height, CHANNEL_NUM, pixels, width * CHANNEL_NUM);
#endif
DestroyImage(image);
return 0;
}
// C Raytracer by Chaidhat Chaimongkol
// 20/12/2019
// Does very simple CPU-based raytracing and outputs it as ASCII art
// thanks http://mewbies.com/geek_fun_files/ascii/ascii_art_light_scale_and_gray_scale_chart.htm
#define DEG_TO_RAD 57.3
//#define DEBUG_MODE
#ifdef DEBUG_MODE
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#define CHANNEL_NUM 3
uint8_t* pixels;
#endif
const char* colourScale = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`\'. ";
const int colourScaleLength = 69;
typedef struct
{
float x, y;
} Vector2;
Vector2 V2(float x, float y);
typedef struct
{
float x, y, z;
} Vector3;
Vector3 V3(float x, float y, float z);
Vector3 V3Add(Vector3 vectora, Vector3 vectorb);
Vector3 V3Sub(Vector3 vectora, Vector3 vectorb);
float V3Dot(Vector3 vectora, Vector3 vectorb);
Vector3 V3Mul(Vector3 vector, float mul);
float V3Mag(Vector3 vector);
typedef struct
{
int x, y;
} Resolution;
Resolution* CreateResolution(int x, int y);
typedef float Pixel;
typedef struct
{
Pixel* data;
Resolution* resolution;
} Image;
Image* CreateImage(Resolution* resolution);
void DestroyImage(Image* image);
Pixel getPixel(Image* image, int x, int y);
void setPixel(Image* image, int x, int y, Pixel value);
char getPixelChar(Pixel value);
void DrawImage(Image* image);
void RenderImage(Image* image, Vector2 fov, Vector3 position);
typedef struct
{
Vector3 origin;
Vector3 direction;
} Ray;
typedef struct
{
char isHit;
float distance;
float colour;
} RayHit;
RayHit* TraceRay(Ray ray);
void IntersectGround(RayHit* rayHit, Ray ray);
typedef struct
{
Vector3 position;
float radius;
} Sphere;
void IntersectSphere(RayHit* rayHit, Ray ray, Sphere sphere);
@chaidhat
Copy link
Author

chaidhat commented Oct 3, 2020

image

@chaidhat
Copy link
Author

chaidhat commented Oct 3, 2020

Without sphere:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment