Override camera FOV for arms/gun in first person mode HDRP

Hello everyone!

I found video https://youtu.be/5AmI2yOx0Nc?si=1ydj3UajxiCtb8Xg&t=291 recorded with older version of Unity and HDRP, where arms and gun of first person character are rendered in separate passes with camera FOV override, which allows to have wide field of view for environment and at the same time prevent arms and gun of character from undesired projection distortion.
It Unity 6 (HDRP 17.0.3) there is Custom Pass Volume component with similar settings, but in Overrides foldout there is no ‘Override Camera’ checkbox.

Is it possible to override FOV for character arms and gun in first person mode in Unity 6 without heavy performance impact? (it is possible to use two cameras, but such approach is not very performant)

Hey, the video you linked in an old version of URP, HDRP doesn’t allow to override the camera FOV using the default custom passes.

There are some examples about how to achieve FPS foreground effect in this repository: GitHub - alelievr/HDRP-Custom-Passes: A bunch of custom passes made for HDRP

1 Like

Thanks for the link, this repository contains useful examples :slightly_smiling_face:
I found FPSForegroundV2 custom pass and wrote similar one, which just renders objects on FirstPerson layer with custom FOV:

using UnityEngine;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering;

#if UNITY_EDITOR

using UnityEditor.Rendering.HighDefinition;

[CustomPassDrawer(typeof(FirstPersonPass))]
class FirstPersonPassEditor : CustomPassDrawer
{
    protected override PassUIFlag commonPassUIFlags => PassUIFlag.Name;
}

#endif

class FirstPersonPass : CustomPass
{
    public float        FOV = 45;
    public LayerMask    FirstPersonMask;
    public Camera       FirstPersonCamera;
    protected override bool executeInSceneView => false;

    protected override void AggregateCullingParameters(ref ScriptableCullingParameters cullingParameters, HDCamera hdCamera)
        => cullingParameters.cullingMask |= (uint)FirstPersonMask.value;

    protected override void Execute(CustomPassContext ctx)
    {
        if (FirstPersonCamera == null) return;

        FirstPersonCamera.CopyFrom(ctx.hdCamera.camera);
        FirstPersonCamera.fieldOfView = FOV;
        FirstPersonCamera.cullingMask = FirstPersonMask;

        var overrideDepthState = new RenderStateBlock(RenderStateMask.Depth)
        {
            depthState = new DepthState(true, CompareFunction.LessEqual)
        };

        CoreUtils.SetRenderTarget(ctx.cmd, new RenderTargetIdentifier[] { ctx.cameraColorBuffer, ctx.cameraMotionVectorsBuffer }, ctx.cameraDepthBuffer);
        CustomPassUtils.RenderFromCamera(ctx, FirstPersonCamera, FirstPersonMask, overrideRenderState: overrideDepthState);
    }
}

But I found two issues, the first one is that materials with Subsurface Scattering (like character body and arms) have black color.

The second problem is that objects placed on FirstPerson layer don’t cast shadows. If set Culling Mask to Everything for Main Camera, then shadows will appear, but arms and gun will be rendered twice. If set Cast Shadows to Shadows Only for body SkinnedMeshRenderer and gun MeshRenderer, then shadows will appear, but arms and gun will disappear both for Main Camera and for FirstPersonPass.

Hey, for the shadows and sub-surface scattering these are limitations of the system. What the custom pass is doing is rendering a list of objects at a single point in the pipeline, it’s not being included in all the other passes like regular objects.

If you need sub-surface scattering then the only solution is to use a regular object, you can set the FoV of the object by creating a ShaderGraph that has a custom vertex code that counter the camera matrix and apply a custom one.
This will also work for shadows but it doesn’t prevent wall clipping, if this is also an issue for you then you can make the object smaller and closer to the camera so that it doesn’t clip in the walls but remains the same size on screen. However, this will not work for shadows as well as the object will just be smaller. If you can afford to duplciate the FPS objects and render only the shadows from the main camera with a regular shader and then another one with shadows off and the custom shader it might work (except self-shadowing I think).

Thank you for answer, which confirms my suggestions that there is no approach which will provide custom FOV, Subsurface Scattering support, proper shadow casting and proper arms/gun self-shadowing simultaneously :laughing:

I also tried approach with ShaderGraph with custom function node in vertex stage

#define FIRST_PERSON_FOV 60

void ApplyFirstPersonProjection_float(float3 objectPosition, out float3 projectedObjectPosition) {
    projectedObjectPosition = objectPosition;
    #if SHADERPASS==SHADERPASS_SHADOWS
    return;
	#endif
    float3 viewPosition = TransformWorldToView(TransformObjectToWorld(objectPosition));
    viewPosition.xy *= -rcp(UNITY_MATRIX_P[1][1] * tan(0.5 * FIRST_PERSON_FOV * PI / 180));
    projectedObjectPosition = TransformWorldToObject(mul(UNITY_MATRIX_I_V, float4(viewPosition, 1)).xyz);
}

Then I assigned material with FirstPerson_Projection ShaderGraph to arms and gun of character and in general it preserves individual FOV, casts proper shadows on planet surface, looks properly under water and supports FSR

But arms and gun have incorrect self-shadowing (probably because visible geometry is scaled in vertex stage to preserve first person FOV, but shadow geometry must be unscaled to look propely on environment surfaces)

So if use custom ShaderGraph to preserve first person FOV for arms with gun, then either self-shadowing will be incorrect, or shadow of player character will have incorrect proportions (if scale vertices both for shadow geometry and visible geometry).

Then I tried approach with custom pass, and changed material for character arms so it doesn’t use Subsurface Scattering and in general looks similar to original material

And duplicated body skinned mesh with gun meshes, set Default layer and Shadows Only mode for all copies

So it seems that custom pass is the best possible option, it preserves separate FOV for character arms and gun, it can draw arms and gun on top of environment if needed, it works faster than setup with two cameras, it provides proper shadow casting and arms/gun self-shadowing. The only downside is that it doesn’t support Subsurface scattering, but it is possible to create copy of character’s arms material without Subsurface scattering and switch material when player enters first person mode.