/* A port of Dmitry Sokolov's tiny raytracer to C and to FemtoRV32 */ /* Displays on the small OLED display and/or HDMI */ /* Bruno Levy, 2020 */ /* Original tinyraytracer: https://github.com/ssloy/tinyraytracer */ #include #include #include #include "perf.h" #include "io.h" /*******************************************************************/ typedef int BOOL; static inline float max(float x, float y) { return x>y?x:y; } static inline float min(float x, float y) { return x to the UART, to exit the simulation in Verilator, // it is captured by special code in RTL/DEVICES/uart.v static inline void graphics_terminate() { printf("\033[48;5;16m" // set background color black "\033[38;5;15m" // set foreground color white ); } // Replace with your own code. void graphics_set_pixel(int x, int y, float r, float g, float b) { r = max(0.0f, min(1.0f, r)); g = max(0.0f, min(1.0f, g)); b = max(0.0f, min(1.0f, b)); uint8_t R = (uint8_t)(255.0f * r); uint8_t G = (uint8_t)(255.0f * g); uint8_t B = (uint8_t)(255.0f * b); // graphics output deactivated for bench run if(bench_run) { if(y & 1) { if(x == graphics_width-1) { printf("%d",y/2); } } return; } #ifdef graphics_double_lines static uint8_t prev_R=0; static uint8_t prev_G=0; static uint8_t prev_B=0; if(y&1) { if((R == prev_R) && (G == prev_G) && (B == prev_B)) { printf("\033[48;2;%d;%d;%dm ",(int)R,(int)G,(int)B); } else { printf("\033[48;2;%d;%d;%dm",(int)prev_R,(int)prev_G,(int)prev_B); printf("\033[38;2;%d;%d;%dm",(int)R,(int)G,(int)B); // https://www.w3.org/TR/xml-entity-names/025.html // https://onlineunicodetools.com/convert-unicode-to-utf8 printf("\xE2\x96\x83"); } if(x == graphics_width-1) { printf("\033[38;2;0;0;0m"); printf("\033[48;2;0;0;0m\n"); } } else { prev_R = R; prev_G = G; prev_B = B; } #else printf("\033[48;2;%d;%d;%dm ",(int)R,(int)G,(int)B); if(x == graphics_width-1) { printf("\033[48;2;0;0;0m\n"); } #endif } // Begins statistics collection for current pixel // Leave emtpy if not needed. // There are these two levels because on some // femtorv32 cores (quark, tachyon), the clock tick counter does not // have sufficient bits and will wrap during the time taken by // rendering a frame (up to several minutes). static inline stats_begin_pixel() { } // Ends statistics collection for current pixel // Leave emtpy if not needed. static inline stats_end_pixel() { } // Print "fixed point" number (integer/1000) static void printk(uint64_t kx) { int intpart = (int)(kx / 1000); int fracpart = (int)(kx % 1000); printf("%d.",intpart); if(fracpart<100) { printf("0"); } if(fracpart<10) { printf("0"); } printf("%d",fracpart); } static uint64_t instret_start; static uint64_t cycles_start; // Begins statistics collection for current frame. // Leave emtpy if not needed. static inline stats_begin_frame() { instret_start = rdinstret(); cycles_start = rdcycle(); } // Ends statistics collection for current frame // and displays result. // Leave emtpy if not needed. static inline stats_end_frame() { graphics_terminate(); uint64_t instret = rdinstret() - instret_start; uint64_t cycles = rdcycle() - cycles_start ; uint64_t kCPI = cycles*1000/instret; uint64_t pixels = graphics_width * graphics_height; uint64_t kRAYSTONES = (pixels*1000000000)/cycles; printf( "\n%dx%d %s ", graphics_width,graphics_height, bench_run ? "no gfx output (measurement is accurate)" : "gfx output (measurement is NOT accurate)" ); printf("CPI="); printk(kCPI); printf(" "); printf("RAYSTONES="); printk(kRAYSTONES); printf("\n"); } // Normally you will not need to modify anything beyond that point. /*******************************************************************/ typedef struct { float x,y,z; } vec3; typedef struct { float x,y,z,w; } vec4; static inline vec3 make_vec3(float x, float y, float z) { vec3 V; V.x = x; V.y = y; V.z = z; return V; } static inline vec4 make_vec4(float x, float y, float z, float w) { vec4 V; V.x = x; V.y = y; V.z = z; V.w = w; return V; } static inline vec3 vec3_neg(vec3 V) { return make_vec3(-V.x, -V.y, -V.z); } static inline vec3 vec3_add(vec3 U, vec3 V) { return make_vec3(U.x+V.x, U.y+V.y, U.z+V.z); } static inline vec3 vec3_sub(vec3 U, vec3 V) { return make_vec3(U.x-V.x, U.y-V.y, U.z-V.z); } static inline float vec3_dot(vec3 U, vec3 V) { return U.x*V.x+U.y*V.y+U.z*V.z; } static inline vec3 vec3_scale(float s, vec3 U) { return make_vec3(s*U.x, s*U.y, s*U.z); } static inline float vec3_length(vec3 U) { return sqrtf(U.x*U.x+U.y*U.y+U.z*U.z); } static inline vec3 vec3_normalize(vec3 U) { return vec3_scale(1.0f/vec3_length(U),U); } /*************************************************************************/ typedef struct Light { vec3 position; float intensity; } Light; Light make_Light(vec3 position, float intensity) { Light L; L.position = position; L.intensity = intensity; return L; } /*************************************************************************/ typedef struct { float refractive_index; vec4 albedo; vec3 diffuse_color; float specular_exponent; } Material; Material make_Material(float r, vec4 a, vec3 color, float spec) { Material M; M.refractive_index = r; M.albedo = a; M.diffuse_color = color; M.specular_exponent = spec; return M; } Material make_Material_default() { Material M; M.refractive_index = 1; M.albedo = make_vec4(1,0,0,0); M.diffuse_color = make_vec3(0,0,0); M.specular_exponent = 0; return M; } /*************************************************************************/ typedef struct { vec3 center; float radius; Material material; } Sphere; Sphere make_Sphere(vec3 c, float r, Material M) { Sphere S; S.center = c; S.radius = r; S.material = M; return S; } BOOL Sphere_ray_intersect(Sphere* S, vec3 orig, vec3 dir, float* t0) { vec3 L = vec3_sub(S->center, orig); float tca = vec3_dot(L,dir); float d2 = vec3_dot(L,L) - tca*tca; float r2 = S->radius*S->radius; if (d2 > r2) return 0; float thc = sqrtf(r2 - d2); *t0 = tca - thc; float t1 = tca + thc; if (*t0 < 0) *t0 = t1; if (*t0 < 0) return 0; return 1; } vec3 reflect(vec3 I, vec3 N) { return vec3_sub(I, vec3_scale(2.f*vec3_dot(I,N),N)); } vec3 refract(vec3 I, vec3 N, float eta_t, float eta_i /* =1.f */) { // Snell's law float cosi = -max(-1.f, min(1.f, vec3_dot(I,N))); // if the ray comes from the inside the object, swap the air and the media if (cosi<0) return refract(I, vec3_neg(N), eta_i, eta_t); float eta = eta_i / eta_t; float k = 1 - eta*eta*(1 - cosi*cosi); // k<0 = total reflection, no ray to refract. // I refract it anyways, this has no physical meaning return k<0 ? make_vec3(1,0,0) : vec3_add(vec3_scale(eta,I),vec3_scale((eta*cosi - sqrtf(k)),N)); } BOOL scene_intersect( vec3 orig, vec3 dir, Sphere* spheres, int nb_spheres, vec3* hit, vec3* N, Material* material ) { float spheres_dist = 1e30; for(int i=0; i1e-3) { float d = -(orig.y+4)/dir.y; // the checkerboard plane has equation y = -4 vec3 pt = vec3_add(orig, vec3_scale(d,dir)); if (d>0 && fabs(pt.x)<10 && pt.z<-10 && pt.z>-30 && ddiffuse_color = (((int)(.5*hit->x+1000) + (int)(.5*hit->z)) & 1) ? make_vec3(.3, .3, .3) : make_vec3(.3, .2, .1); } } return min(spheres_dist, checkerboard_dist)<1000; } vec3 cast_ray( vec3 orig, vec3 dir, Sphere* spheres, int nb_spheres, Light* lights, int nb_lights, int depth /* =0 */ ) { vec3 point,N; Material material = make_Material_default(); if ( depth>2 || !scene_intersect(orig, dir, spheres, nb_spheres, &point, &N, &material) ) { float s = 0.5*(dir.y + 1.0); return vec3_add( vec3_scale(s,make_vec3(0.2, 0.7, 0.8)), vec3_scale(s,make_vec3(0.0, 0.0, 0.5)) ); } vec3 reflect_dir=vec3_normalize(reflect(dir, N)); vec3 refract_dir=vec3_normalize(refract(dir,N,material.refractive_index,1)); // offset the original point to avoid occlusion by the object itself vec3 reflect_orig = vec3_dot(reflect_dir,N) < 0 ? vec3_sub(point,vec3_scale(1e-3,N)) : vec3_add(point,vec3_scale(1e-3,N)); vec3 refract_orig = vec3_dot(refract_dir,N) < 0 ? vec3_sub(point,vec3_scale(1e-3,N)) : vec3_add(point,vec3_scale(1e-3,N)); vec3 reflect_color = cast_ray( reflect_orig, reflect_dir, spheres, nb_spheres, lights, nb_lights, depth + 1 ); vec3 refract_color = cast_ray( refract_orig, refract_dir, spheres, nb_spheres, lights, nb_lights, depth + 1 ); float diffuse_light_intensity = 0, specular_light_intensity = 0; for (int i=0; i 0.0f && def > 0.0f) { specular_light_intensity += powf(abc,def)*lights[i].intensity; } } vec3 result = vec3_scale( diffuse_light_intensity * material.albedo.x, material.diffuse_color ); result = vec3_add( result, vec3_scale(specular_light_intensity * material.albedo.y, make_vec3(1,1,1)) ); result = vec3_add(result, vec3_scale(material.albedo.z, reflect_color)); result = vec3_add(result, vec3_scale(material.albedo.w, refract_color)); return result; } static inline void render_pixel( int i, int j, Sphere* spheres, int nb_spheres, Light* lights, int nb_lights ) { const float fov = M_PI/3.; stats_begin_pixel(); float dir_x = (i + 0.5) - graphics_width/2.; float dir_y = -(j + 0.5) + graphics_height/2.; // this flips the image. float dir_z = -graphics_height/(2.*tan(fov/2.)); vec3 C = cast_ray( make_vec3(0,0,0), vec3_normalize(make_vec3(dir_x, dir_y, dir_z)), spheres, nb_spheres, lights, nb_lights, 0 ); graphics_set_pixel(i,j,C.x,C.y,C.z); stats_end_pixel(); } void render(Sphere* spheres, int nb_spheres, Light* lights, int nb_lights) { stats_begin_frame(); #ifdef graphics_double_lines for (int j = 0; j