Culling improvements + GRAPH_RENDER_INVISIBLE check change (#590)

* Culling improvements + earlier GRAPH_RENDER_INVISIBLE check

GRAPH_RENDER_INVISIBLE is now checked during geo_process_object before any uncessesary transformation is applied to
the object, translation is still calculated for sound even if the object is invisible.
Half fov is now computed during geo_process_perspective.
Vertical culling has been added (only when bellow the screen to prevent shadow´s being culled).
Emulators have basically infinite culling aspect ratio to prevent early culling with widescreen viewport hacks.
Default culling radius is now a define.
This was written by both me and Kaze Emanuar, he provided the suggestion to use absf and informed me of
integer division being remarkably slow (although it´s only used once during geo_process_perspective).

* Badly placed new line (major fix)

* integer

Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com>

* parenthesis

doesn´t affect order of operation, just for code quality

Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com>

* uncessary whitespace

Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com>

---------

Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com>
This commit is contained in:
Lilaa3
2023-06-23 16:06:24 +01:00
committed by GitHub
parent 73fad6195d
commit 16e55ad952
5 changed files with 99 additions and 54 deletions

View File

@@ -133,3 +133,20 @@
* NOTE: When this is enabled, The 49th hardcoded rectangle shadow will act as a regular circular shadow, due to Mario's shadow ID being 99 in vanilla.
*/
#define LEGACY_SHADOW_IDS
/**
* Limits the horizontal fov on emulator like on console. May break viewport widescreen hacks.
*/
//#define HORIZONTAL_CULLING_ON_EMULATOR
/**
* Makes objects bellow the screen be culled.
*/
#define VERTICAL_CULLING
/**
* If the first command of an object´s geolayout is not GEO_CULLING_RADIUS, DEFAULT_CULLING_RADIUS
* will be used instead.
*/
#define DEFAULT_CULLING_RADIUS 300

View File

@@ -261,7 +261,7 @@ void geo_layout_cmd_node_perspective(void) {
gGeoLayoutCommand += 4 << CMD_SIZE_SHIFT;
}
graphNode = init_graph_node_perspective(gGraphNodePool, NULL, (f32) fov, near, far, frustumFunc, 0);
graphNode = init_graph_node_perspective(gGraphNodePool, NULL, (f32) fov, near, far, frustumFunc);
register_scene_graph_node(&graphNode->fnNode.node);

View File

@@ -71,7 +71,7 @@ init_graph_node_ortho_projection(struct AllocOnlyPool *pool, struct GraphNodeOrt
struct GraphNodePerspective *init_graph_node_perspective(struct AllocOnlyPool *pool,
struct GraphNodePerspective *graphNode,
f32 fov, u16 near, u16 far,
GraphNodeFunc nodeFunc, s32 unused) {
GraphNodeFunc nodeFunc) {
if (pool != NULL) {
graphNode = alloc_only_pool_alloc(pool, sizeof(struct GraphNodePerspective));
}
@@ -83,7 +83,6 @@ struct GraphNodePerspective *init_graph_node_perspective(struct AllocOnlyPool *p
graphNode->near = near;
graphNode->far = far;
graphNode->fnNode.func = nodeFunc;
graphNode->unused = unused;
if (nodeFunc != NULL) {
nodeFunc(GEO_CONTEXT_CREATE, &graphNode->fnNode.node, pool);

View File

@@ -131,10 +131,13 @@ struct GraphNodeOrthoProjection {
*/
struct GraphNodePerspective {
/*0x00*/ struct FnGraphNode fnNode;
/*0x18*/ s32 unused;
/*0x1C*/ f32 fov; // horizontal field of view in degrees
/*0x20*/ u16 near; // near clipping plane
/*0x22*/ u16 far; // far clipping plane
/*0x18*/ f32 fov; // horizontal field of view in degrees
/*0x1C*/ u16 near; // near clipping plane
/*0x1E*/ u16 far; // far clipping plane
/*0x20*/ f32 halfFovHorizontal;
#ifdef VERTICAL_CULLING
/*0x24*/ f32 halfFovVertical;
#endif
};
/** An entry in the master list. It is a linked list of display lists
@@ -369,7 +372,7 @@ void init_scene_graph_node_links(struct GraphNode *graphNode, s32 type);
struct GraphNodeRoot *init_graph_node_root (struct AllocOnlyPool *pool, struct GraphNodeRoot *graphNode, s16 areaIndex, s16 x, s16 y, s16 width, s16 height);
struct GraphNodeOrthoProjection *init_graph_node_ortho_projection (struct AllocOnlyPool *pool, struct GraphNodeOrthoProjection *graphNode, f32 scale);
struct GraphNodePerspective *init_graph_node_perspective (struct AllocOnlyPool *pool, struct GraphNodePerspective *graphNode, f32 fov, u16 near, u16 far, GraphNodeFunc nodeFunc, s32 unused);
struct GraphNodePerspective *init_graph_node_perspective (struct AllocOnlyPool *pool, struct GraphNodePerspective *graphNode, f32 fov, u16 near, u16 far, GraphNodeFunc nodeFunc);
struct GraphNodeStart *init_graph_node_start (struct AllocOnlyPool *pool, struct GraphNodeStart *graphNode);
struct GraphNodeMasterList *init_graph_node_master_list (struct AllocOnlyPool *pool, struct GraphNodeMasterList *graphNode, s16 on);
struct GraphNodeLevelOfDetail *init_graph_node_render_range (struct AllocOnlyPool *pool, struct GraphNodeLevelOfDetail *graphNode, s16 minDistance, s16 maxDistance);

View File

@@ -566,6 +566,28 @@ void geo_process_perspective(struct GraphNodePerspective *node) {
sAspectRatio = 4.0f / 3.0f; // 1.33333f
#endif
// The reason this is not divided as an integer is to prevent an integer division.
f32 vHalfFov = ( (f32) ((node->fov * 4096) + 8192) ) / 45.f;
// We need to account for aspect ratio changes by multiplying by the widescreen horizontal stretch
// (normally 1.775).
f32 hHalfFov = vHalfFov * sAspectRatio;
node->halfFovHorizontal = tans(hHalfFov);
#ifdef VERTICAL_CULLING
node->halfFovVertical = tans(vHalfFov);
#endif
#ifndef HORIZONTAL_CULLING_ON_EMULATOR
// If an emulator is detected, use a large value for the half fov
// horizontal value to account for viewport widescreen hacks.
if(!gIsConsole){
node->halfFovHorizontal = 9999.0f;
}
#endif
guPerspective(mtx, &perspNorm, node->fov, sAspectRatio, node->near / WORLD_SCALE, node->far / WORLD_SCALE, 1.0f);
gSPPerspNormalize(gDisplayListHead++, perspNorm);
@@ -1015,23 +1037,24 @@ void geo_process_shadow(struct GraphNodeShadow *node) {
}
/**
* Check whether an object is in view to determine whether it should be drawn.
* Check whether an object is in view to determine whether if it should be drawn.
* This is known as frustum culling.
* It checks whether the object is far away, very close / behind the camera,
* or horizontally out of view. It does not check whether it is vertically
* out of view. It assumes a sphere of 300 units around the object's position
* unless the object has a culling radius node that specifies otherwise.
*
* It checks whether the object is far away, very close or behind the camera and
* vertically or horizontally out of view.
* The radius used is specified in DEFAULT_CULLING_RADIUS unless the object
* has a culling radius node that specifies another value.
*
* The matrix parameter should be the top of the matrix stack, which is the
* object's transformation matrix times the camera 'look-at' matrix. The math
* is counter-intuitive, but it checks column 3 (translation vector) of this
* matrix to determine where the origin (0,0,0) in object space will be once
* transformed to camera space (x+ = right, y+ = up, z = 'coming out the screen').
*
* In 3D graphics, you typically model the world as being moved in front of a
* static camera instead of a moving camera through a static world, which in
* this case simplifies calculations. Note that the perspective matrix is not
* on the matrix stack, so there are still calculations with the fov to compute
* the slope of the lines of the frustum.
* the slope of the lines of the frustum, these are done once during geo_process_perspective.
*
* z-
*
@@ -1046,10 +1069,6 @@ void geo_process_shadow(struct GraphNodeShadow *node) {
* Since (0,0,0) is unaffected by rotation, columns 0, 1 and 2 are ignored.
*/
s32 obj_is_in_view(struct GraphNodeObject *node) {
if (node->node.flags & GRAPH_RENDER_INVISIBLE) {
return FALSE;
}
struct GraphNode *geo = node->sharedChild;
s16 cullingRadius;
@@ -1057,43 +1076,38 @@ s32 obj_is_in_view(struct GraphNodeObject *node) {
if (geo != NULL && geo->type == GRAPH_NODE_TYPE_CULLING_RADIUS) {
cullingRadius = ((struct GraphNodeCullingRadius *) geo)->cullingRadius;
} else {
cullingRadius = 300;
cullingRadius = DEFAULT_CULLING_RADIUS;
}
// Don't render if the object is close to or behind the camera
if (node->cameraToObject[2] > -100.0f + cullingRadius) {
// Check whether the object is not too far away or too close / behind the camera.
// This makes the HOLP not update when the camera is far away, and it
// makes PU travel safe when the camera is locked on the main map.
// If Mario were rendered with a depth over 65536 it would cause overflow
// when converting the transformation matrix to a fixed point matrix.
f32 cameraToObjectDepth = node->cameraToObject[2];
#define VALID_DEPTH_MIDDLE (-20100.f / 2.f)
#define VALID_DEPTH_RANGE (19900 / 2.f)
if (absf(cameraToObjectDepth - VALID_DEPTH_MIDDLE) >= VALID_DEPTH_RANGE + cullingRadius) {
return FALSE;
}
//! This makes the HOLP not update when the camera is far away, and it
// makes PU travel safe when the camera is locked on the main map.
// If Mario were rendered with a depth over 65536 it would cause overflow
// when converting the transformation matrix to a fixed point matrix.
if (node->cameraToObject[2] < -20000.0f - cullingRadius) {
#ifdef VERTICAL_CULLING
f32 vScreenEdge = -cameraToObjectDepth * gCurGraphNodeCamFrustum->halfFovVertical;
// Unlike with horizontal culling, we only check if the object is bellow the screen
// to prevent shadows from being culled.
if (node->cameraToObject[1] < -vScreenEdge - cullingRadius) {
return FALSE;
}
// half of the fov in in-game angle units instead of degrees
s16 halfFov = (((((gCurGraphNodeCamFrustum->fov * sAspectRatio) / 2.0f) + 1.0f) * 32768.0f) / 180.0f) + 0.5f;
#endif
f32 hScreenEdge = -cameraToObjectDepth * gCurGraphNodeCamFrustum->halfFovHorizontal;
f32 hScreenEdge = -node->cameraToObject[2] * tans(halfFov);
// -matrix[3][2] is the depth, which gets multiplied by tan(halfFov) to get
// the amount of units between the center of the screen and the horizontal edge
// given the distance from the object to the camera.
// This multiplication should really be performed on 4:3 as well,
// but the issue will be more apparent on widescreen.
// HackerSM64: This multiplication is done regardless of aspect ratio to fix object pop-in on the edges of the screen (which happens at 4:3 too)
// hScreenEdge *= GFX_DIMENSIONS_ASPECT_RATIO;
// Check whether the object is horizontally in view
if (node->cameraToObject[0] > hScreenEdge + cullingRadius) {
if (absf(node->cameraToObject[0]) > hScreenEdge + cullingRadius) {
return FALSE;
}
if (node->cameraToObject[0] < -hScreenEdge - cullingRadius) {
return FALSE;
}
return TRUE;
}
@@ -1132,14 +1146,25 @@ void visualise_object_hitbox(struct Object *node) {
*/
void geo_process_object(struct Object *node) {
if (node->header.gfx.areaIndex == gCurGraphNodeRoot->areaIndex) {
if (node->header.gfx.throwMatrix != NULL) {
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix, node->header.gfx.scale);
} else if (node->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD) {
mtxf_billboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex],
node->header.gfx.pos, node->header.gfx.scale, gCurGraphNodeCamera->roll);
} else {
mtxf_rotate_zxy_and_translate(gMatStack[gMatStackIndex + 1], node->header.gfx.pos, node->header.gfx.angle);
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], node->header.gfx.scale);
s32 isInvisible = (node->header.gfx.node.flags & GRAPH_RENDER_INVISIBLE);
s32 noThrowMatrix = (node->header.gfx.throwMatrix == NULL);
// If the throw matrix is null and the object is invisible, there is no need
// to update billboarding, scale, rotation, etc.
// This still updates translation since it is needed for sound.
if (isInvisible && noThrowMatrix) {
mtxf_translate(gMatStack[gMatStackIndex + 1], node->header.gfx.pos);
}
else{
if (!noThrowMatrix) {
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix, node->header.gfx.scale);
} else if (node->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD) {
mtxf_billboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex],
node->header.gfx.pos, node->header.gfx.scale, gCurGraphNodeCamera->roll);
} else {
mtxf_rotate_zxy_and_translate(gMatStack[gMatStackIndex + 1], node->header.gfx.pos, node->header.gfx.angle);
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], node->header.gfx.scale);
}
}
node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex];
@@ -1149,7 +1174,8 @@ void geo_process_object(struct Object *node) {
if (node->header.gfx.animInfo.curAnim != NULL) {
geo_set_animation_globals(&node->header.gfx.animInfo, (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0);
}
if (obj_is_in_view(&node->header.gfx)) {
if (!isInvisible && obj_is_in_view(&node->header.gfx)) {
gMatStackIndex--;
inc_mat_stack();