Files
UnrealEngine/Engine/Source/Developer/NaniteUtilities/Public/Rasterizer.h
2025-05-18 13:04:45 +08:00

434 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "VectorUtil.h"
namespace Rasterizer
{
constexpr int32 SubpixelBits = 8;
constexpr int32 SubpixelSamples = 1 << SubpixelBits;
FORCEINLINE int32 EdgeC( const FIntVector2& Edge, const FIntVector2& Vert, int32 SubpixelDilate = 0 )
{
int64 ex = Edge.X;
int64 ey = Edge.Y;
int64 vx = Vert.X;
int64 vy = Vert.Y;
// Half-edge constants
// 24.16 fixed point
int64 C = ey * vx - ex * vy;
// Correct for fill convention
// Top left rule for CCW
C -= ( Edge.Y < 0 || ( Edge.Y == 0 && Edge.X > 0 ) ) ? 0 : 1;
// Dilate edges
C += ( FMath::Abs( Edge.X ) + FMath::Abs( Edge.Y ) ) * SubpixelDilate;
// Step in pixel increments
// Low bits would always be the same and thus don't matter when testing sign.
// 24.8 fixed point
return int32( C >> SubpixelBits );
};
// Fixed point rasterization
struct FTriangle
{
FIntVector2 Vert0;
FIntVector2 Vert1;
FIntVector2 Vert2;
FIntVector2 MinPixel;
FIntVector2 MaxPixel;
FIntVector2 Edge01;
FIntVector2 Edge12;
FIntVector2 Edge20;
int32 C0;
int32 C1;
int32 C2;
bool bBackFace;
FTriangle( const FVector3f Verts[3], FIntVector2 ScissorMin, FIntVector2 ScissorMax, int32 SubpixelDilate = 0 )
{
// 24.8 fixed point
Vert0 = RoundToInt( FVector2f( Verts[0] ) * SubpixelSamples );
Vert1 = RoundToInt( FVector2f( Verts[1] ) * SubpixelSamples );
Vert2 = RoundToInt( FVector2f( Verts[2] ) * SubpixelSamples );
// Bounding rect
FIntVector2 MinSubpixel = Min3( Vert0, Vert1, Vert2 );
FIntVector2 MaxSubpixel = Max3( Vert0, Vert1, Vert2 );
MinSubpixel -= SubpixelDilate;
MaxSubpixel += SubpixelDilate;
// Round to nearest pixel
MinPixel = ( ( MinSubpixel + (SubpixelSamples / 2) - 1 ) ) / SubpixelSamples;
MaxPixel = ( ( MaxSubpixel + (SubpixelSamples / 2) - 1 ) ) / SubpixelSamples;
// Scissor
MinPixel = Max( MinPixel, ScissorMin );
MaxPixel = Min( MaxPixel, ScissorMax );
// Rebase off MinPixel with half pixel offset
// 12.8 fixed point
// Max triangle size = 2047x2047 pixels
const FIntVector2 BaseSubpixel = MinPixel * SubpixelSamples + (SubpixelSamples / 2);
Vert0 -= BaseSubpixel;
Vert1 -= BaseSubpixel;
Vert2 -= BaseSubpixel;
// 12.8 fixed point
Edge01 = Vert0 - Vert1;
Edge12 = Vert1 - Vert2;
Edge20 = Vert2 - Vert0;
// 24.16 fixed point
int64 DetXY = Edge01.Y * Edge20.X - Edge01.X * Edge20.Y;
bBackFace = DetXY >= 0;
if( bBackFace )
{
// Swap winding order
Edge01 *= -1;
Edge12 *= -1;
Edge20 *= -1;
}
C0 = EdgeC( Edge12, Vert1, SubpixelDilate );
C1 = EdgeC( Edge20, Vert2, SubpixelDilate );
C2 = EdgeC( Edge01, Vert0, SubpixelDilate );
}
bool IsCovered( int32 x, int32 y ) const
{
x -= MinPixel.X;
y -= MinPixel.Y;
int32 CX0 = C0 - x * Edge12.Y + y * Edge12.X;
int32 CX1 = C1 - x * Edge20.Y + y * Edge20.X;
int32 CX2 = C2 - x * Edge01.Y + y * Edge01.X;
return ( CX0 | CX1 | CX2 ) >= 0;
}
template< typename FFunc >
void ForAllCovered( FFunc&& Func ) const
{
int32 CY0 = C0;
int32 CY1 = C1;
int32 CY2 = C2;
for( int32 y = MinPixel.Y; y < MaxPixel.Y; y++ )
{
int32 CX0 = CY0;
int32 CX1 = CY1;
int32 CX2 = CY2;
for( int32 x = MinPixel.X; x < MaxPixel.X; x++ )
{
if( ( CX0 | CX1 | CX2 ) >= 0 )
Func( x, y );
CX0 -= Edge12.Y;
CX1 -= Edge20.Y;
CX2 -= Edge01.Y;
}
CY0 += Edge12.X;
CY1 += Edge20.X;
CY2 += Edge01.X;
}
}
FVector3f GetBarycentrics( int32 x, int32 y ) const
{
FIntVector2 p = ( FIntVector2(x,y) - MinPixel ) * SubpixelSamples;
FVector2f p0( Vert0 - p );
FVector2f p1( Vert1 - p );
FVector2f p2( Vert2 - p );
// Not perspective correct
FVector3f Barycentrics(
(float)Edge12.Y * p1.X - (float)Edge12.X * p1.Y,
(float)Edge20.Y * p2.X - (float)Edge20.X * p2.Y,
(float)Edge01.Y * p0.X - (float)Edge01.X * p0.Y );
Barycentrics /= Barycentrics[0] + Barycentrics[1] + Barycentrics[2];
return Barycentrics;
}
};
FORCEINLINE bool IsCovered( const FVector2f& Edge, const FVector2f& Vert, const FVector2f& Center, const FVector2f& Extent )
{
FVector2f Point = Vert - Center;
float Barycentric = Edge.Y * Point.X - Edge.X * Point.Y;
Barycentric += FMath::Abs( Edge.Y ) * Extent.X;
Barycentric += FMath::Abs( Edge.X ) * Extent.Y;
// Correct for fill convention
// Top left rule for CCW
bool bTopLeft = Edge.Y < 0.0f || ( Edge.Y == 0.0f && Edge.X > 0.0f );
bool bIsInside = Barycentric > 0.0f || ( Barycentric == 0.0f && bTopLeft );
return bIsInside;
}
// Floating point rasterization
struct FTriangle3f
{
FVector3f Vert0;
FVector3f Vert1;
FVector3f Vert2;
FVector3f Min;
FVector3f Max;
FVector3f Edge01;
FVector3f Edge12;
FVector3f Edge20;
FVector4f Plane;
FTriangle3f() {}
FTriangle3f( const FVector3f Verts[3] )
{
Vert0 = Verts[0];
Vert1 = Verts[1];
Vert2 = Verts[2];
Min = Min3( Vert0, Vert1, Vert2 );
Max = Max3( Vert0, Vert1, Vert2 );
Edge01 = Vert0 - Vert1;
Edge12 = Vert1 - Vert2;
Edge20 = Vert2 - Vert0;
FVector3f Normal = Edge01 ^ Edge20;
Plane = FVector4f( Normal, -( Normal | Vert0 ) );
}
FVector3f GetBarycentrics( float x, float y ) const
{
FVector2f p( x, y );
FVector2f p0 = FVector2f( Vert0 ) - p;
FVector2f p1 = FVector2f( Vert1 ) - p;
FVector2f p2 = FVector2f( Vert2 ) - p;
// Not perspective correct
FVector3f Barycentrics(
Edge12.Y * p1.X - Edge12.X * p1.Y,
Edge20.Y * p2.X - Edge20.X * p2.Y,
Edge01.Y * p0.X - Edge01.X * p0.Y );
Barycentrics /= Barycentrics[0] + Barycentrics[1] + Barycentrics[2];
return Barycentrics;
}
bool IsCovered( const FVector2f& Center, const FVector2f& Extent ) const
{
float Sign = Plane.Z >= 0.0f ? 1.0f : -1.0f;
return
Rasterizer::IsCovered( Sign * FVector2f( Edge12 ), FVector2f( Vert1 ), Center, Extent ) &&
Rasterizer::IsCovered( Sign * FVector2f( Edge20 ), FVector2f( Vert2 ), Center, Extent ) &&
Rasterizer::IsCovered( Sign * FVector2f( Edge01 ), FVector2f( Vert0 ), Center, Extent );
}
bool IsCovered( int32 x, int32 y, float PixelExtent = 0.0f ) const
{
return IsCovered( FVector2f( (float)x + 0.5f, (float)y + 0.5f ), FVector2f( PixelExtent ) );
}
bool IsCovered( FVector3f Origin, FVector3f Direction, FVector2f Time )
{
// Muller-Trumbore ray triangle intersect
FVector3f Origin0 = Origin - Vert0;
FVector3f Dirx20 = Direction ^ Edge20;
float Det = -( Edge01 | Dirx20 );
if( FMath::Abs( Det ) < 1e-8f )
return false;
float InvDet = 1.0f / Det;
float V = InvDet * ( Origin0 | Dirx20 );
float W = InvDet * ( Direction | ( Edge01 ^ Origin0 ) );
float t = InvDet * ( Edge20 | ( Edge01 ^ Origin0 ) );
if( V < 0.0f || V > 1.0f )
return false;
if( W < 0.0f || V + W > 1.0f )
return false;
if( t < Time[0] || t > Time[1] )
return false;
return true;
}
FVector3f GetDepthPlane() const
{
/*
Solve for v.z
n | (v - p) = 0;
(n|v) - (n|p) = 0;
(n.xy|v.xy) + n.z*z - (n|p) = 0;
-(n.xy|v.xy)/n.z + (n|p)/n.z = v.z;
*/
return FVector3f( -Plane.X, -Plane.Y, -Plane.W ) / Plane.Z;
}
FTriangle3f Swizzle( int32 X, int32 Y, int32 Z )
{
FTriangle3f TriXYZ;
TriXYZ.Vert0 = FVector3f( Vert0[X], Vert0[Y], Vert0[Z] );
TriXYZ.Vert1 = FVector3f( Vert1[X], Vert1[Y], Vert1[Z] );
TriXYZ.Vert2 = FVector3f( Vert2[X], Vert2[Y], Vert2[Z] );
TriXYZ.Min = FVector3f( Min[X], Min[Y], Min[Z] );
TriXYZ.Max = FVector3f( Max[X], Max[Y], Max[Z] );
TriXYZ.Edge01 = FVector3f( Edge01[X], Edge01[Y], Edge01[Z] );
TriXYZ.Edge12 = FVector3f( Edge12[X], Edge12[Y], Edge12[Z] );
TriXYZ.Edge20 = FVector3f( Edge20[X], Edge20[Y], Edge20[Z] );
TriXYZ.Plane = FVector4f( Plane[X], Plane[Y], Plane[Z], Plane.W );
return TriXYZ;
}
};
} // namespace Rasterizer
template< typename FWritePixel >
void RasterizeTri( const FVector3f Verts[3], const FIntRect& ScissorRect, uint32 SubpixelDilate, bool bBackFaceCull, FWritePixel&& WritePixel )
{
Rasterizer::FTriangle Tri( Verts, ScissorRect.Min, ScissorRect.Max, SubpixelDilate );
// Cull when no pixels covered
if( Tri.MinPixel.X >= Tri.MaxPixel.X ||
Tri.MinPixel.Y >= Tri.MaxPixel.Y )
return;
if( Tri.bBackFace && bBackFaceCull )
return;
Tri.ForAllCovered(
[&]( int32 x, int32 y )
{
FVector3f Barycentrics = Tri.GetBarycentrics( x, y );
float Depth =
Verts[0].Z * Barycentrics[0] +
Verts[1].Z * Barycentrics[1] +
Verts[2].Z * Barycentrics[2];
WritePixel( x, y, Depth, Barycentrics );
} );
}
// 6-separating voxelization
template< typename FWriteVoxel >
void VoxelizeTri6( const FVector3f Verts[3], FWriteVoxel&& WriteVoxel )
{
Rasterizer::FTriangle3f Tri( Verts );
for( int32 SwizzleZ = 0; SwizzleZ < 3; SwizzleZ++ )
{
const int32 SwizzleX = ( 1 << SwizzleZ ) & 3;
const int32 SwizzleY = ( 1 << SwizzleX ) & 3;
Rasterizer::FTriangle3f TriZ = Tri.Swizzle( SwizzleX, SwizzleY, SwizzleZ );
FVector3f DepthPlane = TriZ.GetDepthPlane();
FIntVector3 MinVoxel = RoundToInt( TriZ.Min );
FIntVector3 MaxVoxel = RoundToInt( TriZ.Max ); //exclusive
for( int32 y = MinVoxel.Y; y < MaxVoxel.Y; y++ )
{
for( int32 x = MinVoxel.X; x < MaxVoxel.X; x++ )
{
FVector3f Barycentrics = TriZ.GetBarycentrics( (float)x + 0.5f, (float)y + 0.5f );
if( !TriZ.IsCovered( x, y ) )
continue;
float CenterZ = DepthPlane | FVector3f( (float)x + 0.5f, (float)y + 0.5f, 1.0f );
int32 z = FMath::FloorToInt( CenterZ );
FIntVector3 Unswizzle;
Unswizzle[ SwizzleX ] = x;
Unswizzle[ SwizzleY ] = y;
Unswizzle[ SwizzleZ ] = z;
WriteVoxel( Unswizzle, Barycentrics );
}
}
}
}
// 26-separating voxelization, conservative
template< typename FWriteVoxel >
void VoxelizeTri26( const FVector3f Verts[3], FWriteVoxel&& WriteVoxel )
{
Rasterizer::FTriangle3f Tri( Verts );
// Dominant direction
const int32 SwizzleZ = FMath::Max3Index(
FMath::Abs( Tri.Plane.X ),
FMath::Abs( Tri.Plane.Y ),
FMath::Abs( Tri.Plane.Z ) );
const int32 SwizzleX = ( 1 << SwizzleZ ) & 3;
const int32 SwizzleY = ( 1 << SwizzleX ) & 3;
Rasterizer::FTriangle3f TriX = Tri.Swizzle( SwizzleY, SwizzleZ, SwizzleX ); // YZX
Rasterizer::FTriangle3f TriY = Tri.Swizzle( SwizzleZ, SwizzleX, SwizzleY ); // ZXY
Rasterizer::FTriangle3f TriZ = Tri.Swizzle( SwizzleX, SwizzleY, SwizzleZ ); // XYZ
FVector3f DepthPlane = TriZ.GetDepthPlane();
const float PixelExtent = 0.5f;
float ExtentZ = PixelExtent * ( FMath::Abs( DepthPlane.X ) + FMath::Abs( DepthPlane.Y ) );
FIntVector3 MinVoxel = RoundToInt( TriZ.Min - PixelExtent );
FIntVector3 MaxVoxel = RoundToInt( TriZ.Max + PixelExtent ); //exclusive
for( int32 y = MinVoxel.Y; y < MaxVoxel.Y; y++ )
{
for( int32 x = MinVoxel.X; x < MaxVoxel.X; x++ )
{
FVector3f Barycentrics = TriZ.GetBarycentrics( (float)x + 0.5f, (float)y + 0.5f );
if( !TriZ.IsCovered( x, y, PixelExtent ) )
continue;
float CenterZ = DepthPlane | FVector3f( (float)x + 0.5f, (float)y + 0.5f, 1.0f );
int32 MinZ = FMath::FloorToInt( CenterZ - ExtentZ );
int32 MaxZ = FMath::FloorToInt( CenterZ + ExtentZ );
MinZ = FMath::Max( MinZ, MinVoxel.Z );
MaxZ = FMath::Min( MaxZ, MaxVoxel.Z - 1 );
for( int32 z = MinZ; z <= MaxZ; z++ )
{
if( !TriX.IsCovered( y, z, PixelExtent ) )
continue;
if( !TriY.IsCovered( z, x, PixelExtent ) )
continue;
FIntVector3 Unswizzle;
Unswizzle[ SwizzleX ] = x;
Unswizzle[ SwizzleY ] = y;
Unswizzle[ SwizzleZ ] = z;
WriteVoxel( Unswizzle, Barycentrics );
}
}
}
}