In UniswapV3, the fee accumulation is measured on a fee per liquidity base. On a high-level overview, this is very trivial if one goes not into detail, it is as simple as:
> (feeGrowthNew - feeGrowthLast) * liquidity
This is a very simple representation of a user’s fee accumulation. The NonfungiblePositionManager handles the management of the fee exactly that way. This is illustrated during the position mint as follows:
> (feeGrowthNew - feeGrowthLast) * liquidity
This is a very simple representation of a user’s fee accumulation. The NonfungiblePositionManager handles the management of the fee exactly that way. This is illustrated during the position mint as follows:

As one can see, during the minting, the updated fee feeGrowthLast is passed to the _positions struct. If the position now accumulates fee, this will increase feeGrowthInsideLast and can be claimed as follows:

which is a simple multiplication of liquidity with the fee delta.
When applying this methodology, it is important to ensure you cannot manipulate your own liquidity share without adjusting feeGrowthLast, as this would essentially result in a theft of fees from the system.
Within the underlying pool, the feeGrowth calculation is a bit more complicated. For that, feeGrowthGlobal0 and feeGrowthGlobal! variables are used which serve as global accumulators for the fee. The goal for a position [pa; pb]; liquidity, is then to calculate how much feeGrowth this position has experienced.
To simplify the explanation, we refer to feeGrowthInside and feeGrowthOutside, whereas:
feeGrowthInside: fee accumulated b/w a position
feeGrowthOutside: fees that accumulate below tickLower and above tickUpper
Continuing with the simplified explanation, we can now derive the actual feeGrowthInside by subtracting the outside feeGrowthOutside from feeGrowthInside which then in fact returns how much has been accumulated by the tick range. The actual implementation is within the getFeeGrowthInside calculation which calculates the correct fee based on the currentTick and the range ticks:

That if for example the position range is around the currentTick, this will yield the following calculation
feeGrowthInside = feeGrowthGlobal - lower.feeGrowthOutSide - upper.feeGrowthOutside
There are furthermore a few caveats when initializing the ticks for a position or when crossing a tick, which are:
feeGrowthOutside is only set to feeGrowthGlobal if the tick to be initialized is equal/below the currentTick
Whenever a tick is crossed, feeGrowthOutside is set to feeGrowthGlobal - tick.feeGrowthOutside to ensure the feeGrowthOutside value in fact corresponds to what has been accumulated outside of the range.
If we have thus created a new position with all ticks below the currentTick and feeGrowthGlobal = 100, the outer boundaries for this position become both 100. If we now swap downwards and the upper tick is crossed, upper.outerGrowth will be set to feeGrowthGlobal - upper.feeGrowthOuter which is zero. If the position accumulates fees, feeGrowthGlobal will increase. If we now apply the above calculation on the following state:
upper.feeGrowth = 0 (due to cross)
lower.feeGrowth = 100 (due to init state)
feeGrowthGlobal = 200 (due to accumulation within range)
feeGrowthInside = 200 - 100 - 0
feeGrowthInside = 100
If we now continue swapping down (without any fee for demonstration purposes) while crossing the lowerTick, lower.FeeGrowth will be set to feeGrowthGlobal - lower.FeeGrowthOuter this will yield the following state:
upper.feeGrowth = 0 (due to cross)
lower.feeGrowth = 100 (due to cross)
feeGrowthGlobal = 200 (due to accumulation within range)
The formula if the currentTick is below the range is as follows:
> feeGrowthGlobal - (feeGrowthGlobal - lower.feeGrowthOutside) - upper.feeGrowthOutside
> 200 - (200 - 100) - 0
> 100