FWIW in my experience an audit table that logs data changes BY COLUMN, rather than BY ROW, is much less useful - hard to report on etc. and the resulting Audit table is a lot more "bulky"
I have had the same experience on some legacy databases that I had to work with where the audit table was very cleverly done with table name column, changed column name, and whether it was an update or insert, and what the changed value is.
A nightmare to report on!! Now I stick with the KISS principle. Keep It Simple and Sweet.
We had a client who asked us to generate Audit "by column" for all the application tables - I guess it was about 100 tables. We mechanically generated the code (once we figured out what it needed to look like!) so it wasn't hard to do, but the resulting Trigger scripts were absolutely massive, and the size of the Audit tables was horrific (relative to the actual database's core tables).
In our row-based Audit tables, now, we don't even store the "current" value (to save space - its IN the database, after all ) we just store rows from DELETED when anything is Updated/Deleted.
CREATE TRIGGER oeordlin_sql__trg
ON dbo.oeordlin_sql
AFTER UPDATE
AS
SET NOCOUNT ON;
IF UPDATE(tax_sched)
BEGIN
INSERT INTO dbo.Audit (
Type, TableName, PrimaryKeyField, PrimaryKeyValue,
FieldName, OldValue, NewValue,
UpdateDate, UserName )
SELECT
'U', 'oeordlin_sql', '<add_pk_name_here>', CAST(<add_pk_name_here> AS varchar(1000)),
'tax_sched', d.tax_sched, i.tax_sched,
GETDATE(), ORIGINAL_LOGIN()
FROM inserted i
INNER JOIN deleted d ON
i.$IDENTITY = d.$IDENTITY --or i.pk_column = d.pk_column
WHERE
INULL(i.tax_sched, '~') <> ISNULL(d.tax_sched, '~')
END --IF
GO --end of trigger
UPDATE is a very efficient, single bit check, so it's worth checking it to avoid more involved logic if there's no possibility that tax_sched was affected.