Last active
March 23, 2023 01:27
-
-
Save JettMonstersGoBoom/92e64daec13fb08a1048211318d1d6c6 to your computer and use it in GitHub Desktop.
triangle rasterizer with psx style dither and bpp per gun.
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
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <math.h> | |
#include "tigr.h" | |
#include "vec2.h" | |
// extension of this code | |
// https://github.com/gustavopezzi/triangle-rasterizer-float | |
// added dithering | |
// added bpp per color gun | |
// added flat uncolored | |
// added texturing | |
// NOTE the dithering and color conversion could have been added to put_pixel | |
// using https://github.com/erkkah/tigr | |
// removes the dep of SDL and the trickyness to get that setup. | |
// just gitclone into the folder | |
// I also used https://poloviiinkin.itch.io/textures for testing | |
Tigr* screen; | |
Tigr* texture; | |
#define SCREEN_WIDTH 320 | |
#define SCREEN_HEIGHT 192 | |
#define MIN(a, b) (((a) < (b)) ? (a) : (b)) | |
#define MAX(a, b) (((a) > (b)) ? (a) : (b)) | |
bool is_running = false; | |
vec2_t vertices[4] = { | |
{ .x = -128, .y = -128 }, | |
{ .x = 128, .y = -128 }, | |
{ .x = -128, .y = 128 }, | |
{ .x = 128, .y = 128 } | |
}; | |
typedef struct { | |
uint8_t r; | |
uint8_t g; | |
uint8_t b; | |
} color_t; | |
color_t colors[] = { | |
{ .r = 0xFF, .g = 0xff, .b = 0xff }, | |
{ .r = 0x00, .g = 0xFF, .b = 0x00 }, | |
{ .r = 0x00, .g = 0x00, .b = 0xFF }, | |
{ .r = 0xFF, .g = 0x00, .b = 0x00 }, | |
}; | |
// MGB added | |
typedef struct { | |
uint16_t u; | |
uint16_t v; | |
} uv_t; | |
uv_t uvs[4] = { | |
{ .u = 0, .v = 0 }, // values manually converted to 16.16 fixed point | |
{ .u = 512, .v = 0 }, // values manually converted to 16.16 fixed point | |
{ .u = 0, .v = 512 }, // values manually converted to 16.16 fixed point | |
{ .u = 512, .v = 512 }, // values manually converted to 16.16 fixed point | |
}; | |
int count = 0; | |
bool is_top_left(vec2_t* start, vec2_t* end) { | |
vec2_t edge = { end->x - start->x, end->y - start->y }; | |
bool is_top_edge = edge.y == 0 && edge.x > 0; | |
bool is_left_edge = edge.y < 0; | |
return is_left_edge || is_top_edge; | |
} | |
float edge_cross(vec2_t* a, vec2_t* b, vec2_t* p) { | |
vec2_t ab = { b->x - a->x, b->y - a->y }; | |
vec2_t ap = { p->x - a->x, p->y - a->y }; | |
return ab.x * ap.y - ab.y * ap.x; | |
} | |
#define MAPW 16 | |
#define MAPH 16 | |
#define TILEW 48 | |
#define TILEH 48 | |
uint8_t map[MAPW*MAPH]; | |
static float psx_dither_table[4*4] = | |
{ | |
0, 8, 2, 10, | |
12, 4, 14, 6, | |
3, 11, 1, 9, | |
15, 7, 13, 5 | |
}; | |
struct | |
{ | |
uint16_t dither:1; | |
uint16_t texture:1; | |
uint16_t gouraud:1; | |
uint16_t tiled:1; | |
uint16_t rbpp:3; | |
uint16_t gbpp:3; | |
uint16_t bbpp:3; | |
} render_flags; | |
uint8_t bpp_table[] = { | |
0b10000000, | |
0b11000000, | |
0b11100000, | |
0b11110000, | |
0b11111000, | |
0b11111100, | |
0b11111110, | |
0b11111111, | |
}; | |
// for tigr specific get and plot | |
TPixel get_pixel(int x,int y) | |
{ | |
if (render_flags.tiled==0) | |
return(tigrGet(texture,x % texture->w,y % texture->h)); | |
int map_x = (x/TILEW)%MAPW; | |
int map_y = (y/TILEH)%MAPH; | |
uint8_t tile = map[map_x+(map_y*MAPW)]; | |
int t_x = (((tile&0xf)*TILEW) + (x%TILEW)) % texture->w; | |
int t_y = (((tile/16)*TILEH) + (y%TILEH)) % texture->h; | |
return tigrGet(texture,t_x,t_y); | |
} | |
void put_pixel(int x,int y,uint8_t r,uint8_t g,uint8_t b) | |
{ | |
tigrPlot(screen,x,y,tigrRGB(r,g,b)); | |
} | |
void triangle_fill(vec2_t v0, vec2_t v1, vec2_t v2,int c0,int c1,int c2,int u0,int u1,int u2) { | |
// Finds the bounding box with all candidate pixels | |
int x_min = floor(MIN(MIN(v0.x, v1.x), v2.x)); | |
int y_min = floor(MIN(MIN(v0.y, v1.y), v2.y)); | |
int x_max = ceil(MAX(MAX(v0.x, v1.x), v2.x)); | |
int y_max = ceil(MAX(MAX(v0.y, v1.y), v2.y)); | |
// Compute the area of the entire triangle/parallelogram | |
float area = edge_cross(&v0, &v1, &v2); | |
// Compute the constant delta_s that will be used for the horizontal and vertical steps | |
float delta_w0_col = (v1.y - v2.y); | |
float delta_w1_col = (v2.y - v0.y); | |
float delta_w2_col = (v0.y - v1.y); | |
float delta_w0_row = (v2.x - v1.x); | |
float delta_w1_row = (v0.x - v2.x); | |
float delta_w2_row = (v1.x - v0.x); | |
// Rasterization fill rule, not 100% precise due to floating point innacuracy | |
float bias0 = is_top_left(&v1, &v2) ? 0 : -0.0001; | |
float bias1 = is_top_left(&v2, &v0) ? 0 : -0.0001; | |
float bias2 = is_top_left(&v0, &v1) ? 0 : -0.0001; | |
// Compute the edge functions for the fist (top-left) point | |
vec2_t p0 = { x_min + 0.5f , y_min + 0.5f }; | |
float w0_row = edge_cross(&v1, &v2, &p0) + bias0; | |
float w1_row = edge_cross(&v2, &v0, &p0) + bias1; | |
float w2_row = edge_cross(&v0, &v1, &p0) + bias2; | |
// Loop all candidate pixels inside the bounding box | |
for (int y = y_min; y <= y_max; y++) { | |
float w0 = w0_row; | |
float w1 = w1_row; | |
float w2 = w2_row; | |
for (int x = x_min; x <= x_max; x++) { | |
bool is_inside = w0 >= 0 && w1 >= 0 && w2 >= 0; | |
if (is_inside) { | |
float alpha = w0 / area; | |
float beta = w1 / area; | |
float gamma = w2 / area; | |
int r; | |
int g; | |
int b; | |
// if gouraud then apply it here | |
if (render_flags.gouraud==1) | |
{ | |
r = (alpha) * colors[c0].r + (beta) * colors[c1].r + (gamma) * colors[c2].r; | |
g = (alpha) * colors[c0].g + (beta) * colors[c1].g + (gamma) * colors[c2].g; | |
b = (alpha) * colors[c0].b + (beta) * colors[c1].b + (gamma) * colors[c2].b; | |
} | |
else | |
{ | |
r=colors[c0].r; | |
g=colors[c0].g; | |
b=colors[c0].b; | |
} | |
// if textured then sample and mix | |
if (render_flags.texture==1) | |
{ | |
TPixel tex; | |
int U = (alpha) * uvs[u0].u + (beta) * uvs[u1].u + (gamma) * uvs[u2].u; | |
int V = (alpha) * uvs[u0].v + (beta) * uvs[u1].v + (gamma) * uvs[u2].v; | |
tex = get_pixel(U,V); | |
r=(tex.r*r)>>8; | |
g=(tex.g*g)>>8; | |
b=(tex.b*b)>>8; | |
} | |
// if dithered , apply it | |
if (render_flags.dither==1) | |
{ | |
float dither = (psx_dither_table[(x & 3) + ((y & 3)<<2)])/2.0f+4.0f; | |
r+=dither; | |
g+=dither; | |
b+=dither; | |
} | |
// clamp to 0-255 range | |
r=MIN(r,255); | |
g=MIN(g,255); | |
b=MIN(b,255); | |
// convert to bits per pixel output | |
r&=bpp_table[render_flags.rbpp]; | |
g&=bpp_table[render_flags.gbpp]; | |
b&=bpp_table[render_flags.bbpp]; | |
// plot pixel | |
put_pixel(x,y,r,g,b); | |
} | |
w0 += delta_w0_col; | |
w1 += delta_w1_col; | |
w2 += delta_w2_col; | |
} | |
w0_row += delta_w0_row; | |
w1_row += delta_w1_row; | |
w2_row += delta_w2_row; | |
} | |
} | |
void render(void) { | |
static float time = 0; | |
time+=tigrTime(); | |
float angle = time * 0.4f; | |
map[rand()%sizeof(map)]=rand(); | |
vec2_t center = { SCREEN_WIDTH / 2.0f, SCREEN_HEIGHT / 2.0f }; | |
vec2_t v0 = vec2_rotate(vertices[0], center, angle); | |
vec2_t v1 = vec2_rotate(vertices[1], center, angle); | |
vec2_t v2 = vec2_rotate(vertices[2], center, angle); | |
vec2_t v3 = vec2_rotate(vertices[3], center, angle); | |
render_flags.texture=1; | |
triangle_fill(v0, v1, v2,0,0,2,0,1,2); | |
render_flags.texture=0; | |
triangle_fill(v3, v2, v1,3,2,1,0,0,0); | |
} | |
int main(int argc, char* argv[]) { | |
screen = tigrWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "texture test", 0); | |
texture = tigrLoadImage("texture2.png"); | |
while (!tigrClosed(screen) && !tigrKeyDown(screen, TK_ESCAPE)) { | |
tigrClear(screen, tigrRGB(0x80, 0x90, 0xa0)); | |
if (tigrKeyDown(screen,'D')) | |
render_flags.dither=!render_flags.dither; | |
if (tigrKeyDown(screen,'T')) | |
render_flags.tiled=!render_flags.tiled; | |
if (tigrKeyDown(screen,'S')) | |
render_flags.gouraud=!render_flags.gouraud; | |
if (tigrKeyHeld(screen,TK_SHIFT)) | |
{ | |
if (tigrKeyDown(screen,'R')) | |
render_flags.rbpp--; | |
if (tigrKeyDown(screen,'G')) | |
render_flags.gbpp--; | |
if (tigrKeyDown(screen,'B')) | |
render_flags.bbpp--; | |
} | |
else | |
{ | |
if (tigrKeyDown(screen,'R')) | |
render_flags.rbpp++; | |
if (tigrKeyDown(screen,'G')) | |
render_flags.gbpp++; | |
if (tigrKeyDown(screen,'B')) | |
render_flags.bbpp++; | |
} | |
render(); | |
tigrPrint(screen, tfont, 0, 0, tigrRGB(0xff, 0xff, 0xff), | |
"R %d G %d B %d\nDither %s\nTiled %s\nGouraud %s", | |
1+render_flags.rbpp, | |
1+render_flags.gbpp, | |
1+render_flags.bbpp, | |
render_flags.dither ? "Enabled" : "Disabled", | |
render_flags.tiled ? "Enabled" : "Disabled", | |
render_flags.gouraud ? "Enabled" : "Disabled"); | |
tigrUpdate(screen); | |
} | |
tigrFree(texture); | |
tigrFree(screen); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment