// 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 ); } } } }