Last active
October 3, 2020 10:30
-
-
Save chaidhat/4c934711b3de8ad8cebe1e377e1eb23d to your computer and use it in GitHub Desktop.
Does CPU-based raytracing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment