From 7670658db62679c0228501cc5dd40aaebfaa6d99 Mon Sep 17 00:00:00 2001 From: Miloslav Ciz Date: Sat, 16 Apr 2022 22:29:16 +0200 Subject: [PATCH] Add new near cross strategy --- README.md | 3 +- programs/city.c | 2 +- programs/level.c | 2 +- small3dlib.h | 142 ++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 125 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1905a41..71c6124 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,7 @@ And advantages at the same time :) - There is **no far plane**. - There is **no subpixel accuracy** (PS1 style graphics). - There is **no antialiasing**, but you can still achieve it by supersampling (render in higher resolution and downscale) or filters like FXAA. -- There is a near plane but a **proper culling by it (subdividing triangles) is missing**. You can either cull whole triangles completely or "push" them by the near plane. These options are okay when drawing a model not very close to the camera, but e.g. 3D environments may suffer from artifacts. -- Due to the limitations of 32bit integer arithmetics, some types of movement (particularly camera) **may look jerky, and artifact may appear** in specific situations. +- Due to the limitations of 32bit integer arithmetics some types of movement (particularly camera) **may look jerky, and artifact may appear** in specific situations. ## how to use diff --git a/programs/city.c b/programs/city.c index c414d40..448e607 100644 --- a/programs/city.c +++ b/programs/city.c @@ -10,7 +10,7 @@ #include #define S3L_FLAT 0 -#define S3L_NEAR_CROSS_STRATEGY 1 +#define S3L_NEAR_CROSS_STRATEGY 3 #define S3L_PERSPECTIVE_CORRECTION 2 #define S3L_SORT 0 #define S3L_STENCIL_BUFFER 0 diff --git a/programs/level.c b/programs/level.c index 104a22a..4dcd35d 100644 --- a/programs/level.c +++ b/programs/level.c @@ -11,7 +11,7 @@ #include #include -#define S3L_NEAR_CROSS_STRATEGY 2 +#define S3L_NEAR_CROSS_STRATEGY 3 #if TEXTURES #define S3L_PERSPECTIVE_CORRECTION 2 diff --git a/small3dlib.h b/small3dlib.h index 6058013..84bc0e8 100644 --- a/small3dlib.h +++ b/small3dlib.h @@ -10,7 +10,7 @@ license: CC0 1.0 (public domain) found at https://creativecommons.org/publicdomain/zero/1.0/ + additional waiver of all IP - version: 0.860d + version: 0.900d Before including the library, define S3L_PIXEL_FUNCTION to the name of the function you'll be using to draw single pixels (this function will be called @@ -203,8 +203,7 @@ typedef uint16_t S3L_Index; expensive, but the results will be geometrically correct, even though barycentric correction is not performed so texturing artifacts will appear. Can be ideal with S3L_FLAT. - 3: NOT IMPLEMENTED YET - Perform both geometrical and barycentric correction of triangle crossing + 3: Perform both geometrical and barycentric correction of triangle crossing the near plane. This is significantly more expensive but results in correct rendering. */ @@ -249,7 +248,7 @@ typedef uint16_t S3L_Index; #endif #if S3L_PERSPECTIVE_CORRECTION -#define S3L_COMPUTE_DEPTH 1 // PC inevitably computes depth, so enable it + #define S3L_COMPUTE_DEPTH 1 // PC inevitably computes depth, so enable it #endif #ifndef S3L_COMPUTE_DEPTH @@ -556,7 +555,7 @@ typedef struct S3L_Unit barycentric[3]; /**< Barycentric coords correspond to the three vertices. These serve to locate the pixel on a - triangle and interpolate values between it's + triangle and interpolate values between its three points. Each one goes from 0 to S3L_FRACTIONS_PER_UNIT (including), but due to rounding error may fall outside this range (you @@ -1853,6 +1852,16 @@ void S3L_newFrame(void) S3L_stencilBufferClear(); } +/* + the following serves to communicate info about if the triangle has been split + and how the barycentrics should be remapped. +*/ +uint8_t _S3L_projectedTriangleState = 0; // 0 = normal, 1 = cut, 2 = split + +#if S3L_NEAR_CROSS_STRATEGY == 3 +S3L_Vec4 _S3L_triangleRemapBarycentrics[6]; +#endif + void S3L_drawTriangle( S3L_Vec4 point0, S3L_Vec4 point1, @@ -2337,6 +2346,37 @@ void S3L_drawTriangle( *barycentric2 = S3L_FRACTIONS_PER_UNIT - *barycentric0 - *barycentric1; #endif + +#if S3L_NEAR_CROSS_STRATEGY == 3 + +if (_S3L_projectedTriangleState != 0) +{ + S3L_Unit newBarycentric[3]; + + newBarycentric[0] = S3L_interpolateBarycentric( + _S3L_triangleRemapBarycentrics[0].x, + _S3L_triangleRemapBarycentrics[1].x, + _S3L_triangleRemapBarycentrics[2].x, + p.barycentric); + + newBarycentric[1] = S3L_interpolateBarycentric( + _S3L_triangleRemapBarycentrics[0].y, + _S3L_triangleRemapBarycentrics[1].y, + _S3L_triangleRemapBarycentrics[2].y, + p.barycentric); + + newBarycentric[2] = S3L_interpolateBarycentric( + _S3L_triangleRemapBarycentrics[0].z, + _S3L_triangleRemapBarycentrics[1].z, + _S3L_triangleRemapBarycentrics[2].z, + p.barycentric); + + p.barycentric[0] = newBarycentric[0]; + p.barycentric[1] = newBarycentric[1]; + p.barycentric[2] = newBarycentric[2]; +} +#endif + S3L_PIXEL_FUNCTION(&p); } // tests passed @@ -2569,9 +2609,10 @@ void _S3L_mapProjectedVertexToScreen(S3L_Vec4 *vertex, S3L_Unit focalLength) /** Projects a triangle to the screen. If enabled, a triangle can be potentially subdivided into two if it crosses the near plane, in which case two projected - triangles are returned (return value will be 1). + triangles are returned (the info about splitting or cutting the triangle is + passed in global variables, see above). */ -uint8_t _S3L_projectTriangle( +void _S3L_projectTriangle( const S3L_Model3D *model, S3L_Index triangleIndex, S3L_Mat4 matrix, @@ -2582,9 +2623,9 @@ uint8_t _S3L_projectTriangle( _S3L_projectVertex(model,triangleIndex,1,matrix,&(transformed[1])); _S3L_projectVertex(model,triangleIndex,2,matrix,&(transformed[2])); - uint8_t result = 0; + _S3L_projectedTriangleState = 0; -#if S3L_NEAR_CROSS_STRATEGY == 2 +#if S3L_NEAR_CROSS_STRATEGY == 2 || S3L_NEAR_CROSS_STRATEGY == 3 uint8_t infront = 0; uint8_t behind = 0; uint8_t infrontI[3]; @@ -2602,6 +2643,15 @@ uint8_t _S3L_projectTriangle( behind++; } +#if S3L_NEAR_CROSS_STRATEGY == 3 + for (int i = 0; i < 3; ++i) + S3L_vec4Init(&(_S3L_triangleRemapBarycentrics[i])); + + _S3L_triangleRemapBarycentrics[0].x = S3L_FRACTIONS_PER_UNIT; + _S3L_triangleRemapBarycentrics[1].y = S3L_FRACTIONS_PER_UNIT; + _S3L_triangleRemapBarycentrics[2].z = S3L_FRACTIONS_PER_UNIT; +#endif + #define interpolateVertex \ S3L_Unit ratio =\ ((transformed[be].z - S3L_NEAR) * S3L_FRACTIONS_PER_UNIT) /\ @@ -2612,7 +2662,15 @@ uint8_t _S3L_projectTriangle( transformed[in].y = transformed[be].y -\ ((transformed[be].y - transformed[in].y) * ratio) /\ S3L_FRACTIONS_PER_UNIT;\ - transformed[in].z = S3L_NEAR; + transformed[in].z = S3L_NEAR;\ + if (beI != 0) {\ + beI->x = (beI->x * ratio) / S3L_FRACTIONS_PER_UNIT;\ + beI->y = (beI->y * ratio) / S3L_FRACTIONS_PER_UNIT;\ + beI->z = (beI->z * ratio) / S3L_FRACTIONS_PER_UNIT;\ + ratio = S3L_FRACTIONS_PER_UNIT - ratio;\ + beI->x += (beB->x * ratio) / S3L_FRACTIONS_PER_UNIT;\ + beI->y += (beB->y * ratio) / S3L_FRACTIONS_PER_UNIT;\ + beI->z += (beB->z * ratio) / S3L_FRACTIONS_PER_UNIT; } if (infront == 2) { @@ -2620,8 +2678,17 @@ uint8_t _S3L_projectTriangle( for (uint8_t i = 0; i < 2; ++i) { uint8_t be = behindI[0], in = infrontI[i]; - + +#if S3L_NEAR_CROSS_STRATEGY == 3 + S3L_Vec4 *beI = &(_S3L_triangleRemapBarycentrics[in]), + *beB = &(_S3L_triangleRemapBarycentrics[be]); +#else + S3L_Vec4 *beI = 0, *beB = 0; +#endif + interpolateVertex + + _S3L_projectedTriangleState = 1; } } else if (infront == 1) @@ -2631,20 +2698,41 @@ uint8_t _S3L_projectTriangle( transformed[4] = transformed[infrontI[0]]; transformed[5] = transformed[infrontI[0]]; +#if S3L_NEAR_CROSS_STRATEGY == 3 + _S3L_triangleRemapBarycentrics[3] = + _S3L_triangleRemapBarycentrics[behindI[1]]; + _S3L_triangleRemapBarycentrics[4] = + _S3L_triangleRemapBarycentrics[infrontI[0]]; + _S3L_triangleRemapBarycentrics[5] = + _S3L_triangleRemapBarycentrics[infrontI[0]]; +#endif + for (uint8_t i = 0; i < 2; ++i) { uint8_t be = behindI[i], in = i + 4; +#if S3L_NEAR_CROSS_STRATEGY == 3 + S3L_Vec4 *beI = &(_S3L_triangleRemapBarycentrics[in]), + *beB = &(_S3L_triangleRemapBarycentrics[be]); +#else + S3L_Vec4 *beI = 0, *beB = 0; +#endif + interpolateVertex } +#if S3L_NEAR_CROSS_STRATEGY == 3 + _S3L_triangleRemapBarycentrics[infrontI[0]] = + _S3L_triangleRemapBarycentrics[4]; +#endif + transformed[infrontI[0]] = transformed[4]; _S3L_mapProjectedVertexToScreen(&transformed[3],focalLength); _S3L_mapProjectedVertexToScreen(&transformed[4],focalLength); _S3L_mapProjectedVertexToScreen(&transformed[5],focalLength); - result = 1; + _S3L_projectedTriangleState = 2; } #undef interpolateVertex @@ -2653,8 +2741,6 @@ uint8_t _S3L_projectTriangle( _S3L_mapProjectedVertexToScreen(&transformed[0],focalLength); _S3L_mapProjectedVertexToScreen(&transformed[1],focalLength); _S3L_mapProjectedVertexToScreen(&transformed[2],focalLength); - - return result; } void S3L_drawScene(S3L_Scene scene) @@ -2709,7 +2795,7 @@ void S3L_drawScene(S3L_Scene scene) already projected vertices, but after some testing this was abandoned, no gain was seen. */ - uint8_t split = _S3L_projectTriangle(model,triangleIndex,matFinal, + _S3L_projectTriangle(model,triangleIndex,matFinal, scene.camera.focalLength,transformed); if (S3L_triangleIsVisible(transformed[0],transformed[1],transformed[2], @@ -2720,11 +2806,18 @@ void S3L_drawScene(S3L_Scene scene) S3L_drawTriangle(transformed[0],transformed[1],transformed[2],modelIndex, triangleIndex); - if (split) // draw potential subtriangle + if (_S3L_projectedTriangleState == 2) // draw potential subtriangle + { +#if S3L_NEAR_CROSS_STRATEGY == 3 + _S3L_triangleRemapBarycentrics[0] = _S3L_triangleRemapBarycentrics[3]; + _S3L_triangleRemapBarycentrics[1] = _S3L_triangleRemapBarycentrics[4]; + _S3L_triangleRemapBarycentrics[2] = _S3L_triangleRemapBarycentrics[5]; +#endif + S3L_drawTriangle(transformed[3],transformed[4],transformed[5], modelIndex, triangleIndex); + } #else - S3L_UNUSED(split); if (S3L_sortArrayLength >= S3L_MAX_TRIANGES_DRAWN) break; @@ -2799,15 +2892,24 @@ void S3L_drawScene(S3L_Scene scene) require a lot of memory, which for small resolutions could be even worse than z-bufer. So this seems to be the best way memory-wise. */ - uint8_t split = _S3L_projectTriangle(model,triangleIndex,matFinal, - scene.camera.focalLength,transformed); + _S3L_projectTriangle(model,triangleIndex,matFinal,scene.camera.focalLength, + transformed); S3L_drawTriangle(transformed[0],transformed[1],transformed[2],modelIndex, triangleIndex); - if (split) + if (_S3L_projectedTriangleState == 2) + { +#if S3L_NEAR_CROSS_STRATEGY == 3 + _S3L_triangleRemapBarycentrics[0] = _S3L_triangleRemapBarycentrics[3]; + _S3L_triangleRemapBarycentrics[1] = _S3L_triangleRemapBarycentrics[4]; + _S3L_triangleRemapBarycentrics[2] = _S3L_triangleRemapBarycentrics[5]; +#endif + S3L_drawTriangle(transformed[3],transformed[4],transformed[5], modelIndex, triangleIndex); + } + } #endif }