Do I need to use a try..catch block, and explicit rollback in a SQL Server procedure?

The answer to your question depends on the SET XACT_ABORT setting:

Specifies whether SQL Server automatically rolls back the current transaction when a Transact-SQL statement raises a run-time error.

When SET XACT_ABORT is ON, if a Transact-SQL statement raises a run-time error, the entire transaction is terminated and rolled back.

When SET XACT_ABORT is OFF, in some cases only the Transact-SQL statement that raised the error is rolled back and the transaction continues processing. Depending upon the severity of the error, the entire transaction may be rolled back even when SET XACT_ABORT is OFF. OFF is the default setting.

Compile errors, such as syntax errors, are not affected by SET XACT_ABORT.

For example, try the following code. The first division by 0 raises an error but continues execution. The second division by zero raises an error and halts execution:

begin transaction

set xact_abort off
    
select 1 / 0 -- causes divide by zero error, but continues
select @@trancount -- returns 1

set xact_abort on

select 1 / 0 -- causes divide by zero error and terminates execution
select @@trancount -- we never get here

rollback

If XACT_ABORT is ON, then errors will abort the transaction, and you don't need a TRY / CATCH.

If XACT_ABORT is OFF, you will need to check the status of each statement to see if an error occurred:

begin transaction

delete from...
if @@error <> 0
begin
    if @@trancount > 0
        rollback
    return
end

insert into...
if @@error <> 0
begin
    if @@trancount > 0
        rollback
    return
end

commit

However, if you ever find a case where you need to TRY / CATCH, you may need to do something special when the error occurs. If so, don't forget to TRY / CATCH the exception handling:

begin transaction

set xact_abort on

begin try
    select 1 / 0 -- causes divide by zero error and terminates execution
    select @@trancount -- we never get here
    commit
end try
begin catch
    select xact_state() -- this will be -1 indicating you MUST rollback before doing any other operations
    select @@trancount -- this will probably be one, because we haven't ended the transaction yet
    if xact_state() <> 0
    begin try
        select 'rollback'
        rollback
        
        -- do something to handle or record the error before leaving the current scope
        select 'exception processing here'
        --insert into...
    end try
    begin catch
        -- ignore rollback errors
    end catch
    
end catch

rollbacks will occur automatically if there is an error IN MOST CASES BUT NOT ALL

if you want to guarantee a rollback for all errors precede the begin transaction with SET XACT_ABORT ON

Best practice is to explicity catch errors with a try-catch block and take action there, including perhaps a rollback and reporting/logging the error.