How to create "Gravity Roads" in a FPS?

For a student project, I have to figure out a way to create “gravity roads”. It would be roads to which the player would stick (kinda like cars do in F-zero or other sci-fi racers). A small subtlety is that those roads would also change the gravity accordingly.
The demo/exercise will have to be played with standard FPS controls. Jump can be disabled but keeping it would be extra.

I think i have the basic principle : read the normal of the collided polygon, and rotate the gravity and character controller accordingly.
I spent some hours to get this :

void OnControllerColliderHit(ControllerColliderHit hit)
    {
        if ( hit.controller.isGrounded ) {

            if (hit.collider.tag == "GravityRoad")
            {
                isOnGravRoad = true;
                Debug.Log(currentGravity + " " + currentGravity.magnitude);

                if (hit.normal.normalized != -currentGravity.normalized)
                {
                    currentGravity = -(hit.normal.normalized) * 9.81f;
                    Physics.gravity = currentGravity;
                }

            }
            else
            {
                isOnGravRoad = false;
            }
        }
    }

The gravity seems to update. But my character does not rotate and is unable to keep climbing the gravity road once the slope becomes too important.

My theory is that I can’t do this with the FPS character provided in default packages, and that I have to code my own. Any input would be welcome, thank you.

You’re correct about the CharacterController. It’s the one “special” collider in Unity, that doesn’t allow rotation, you will need an ordinary capsule collider to mimic that, OR, you fake it by making the CharacterController round by setting it’s hight to 0 (or nearly zero, not sure what happens with 0) and only rotate the visual according to surface normal.
Using a rigidbody you’d definitely have a lot more work first. I’d also avoid jumping and implement following surfaces first.

Thanks for helping, @Hexagonius.
At this point I might as well share my new code. it’s probably full of rookie mistakes I might learn how to correct.
I copied a few extracts from the original FPS controller and tried to go from there.

using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityStandardAssets.Utility;
using Random = UnityEngine.Random;

namespace Assets.Scripts
{

    public class FPS_Control : MonoBehaviour
    {
        [SerializeField] private bool m_IsWalking;
        [SerializeField] private float m_WalkSpeed;
        [SerializeField] private float m_RunSpeed;
        [SerializeField] private float m_StickToGroundForce;
        [SerializeField] private bool m_UseFovKick;
        [SerializeField] private FOVKick m_FovKick = new FOVKick();
        [SerializeField] private float m_GravityMultiplier;

        private Camera m_Camera;
        private bool m_Jump;
        private float m_YRotation;
        private Vector2 m_Input;
        private Vector3 m_MoveDir = Vector3.zero;
        private CollisionFlags m_CollisionFlags;

        private bool isGrounded = false;

        private Transform m_PhysicalBody;
        private Rigidbody m_PhysicalBody_rigidBody;
        private SphereCollider m_PhysicalBody_hitbox;

        Vector3 currentGravity;
        bool isOnGravRoad = false;

        // Use this for initialization
        private void Start()
        {
            m_PhysicalBody = transform.Find("TestPlayer_Sphere");
            m_PhysicalBody_rigidBody = m_PhysicalBody.GetComponent<Rigidbody>();
            m_PhysicalBody_hitbox = m_PhysicalBody.GetComponent<SphereCollider>();
            currentGravity = Physics.gravity;
        }

        private void FixedUpdate()
        {
            m_PhysicalBody_rigidBody.WakeUp();

            float speed;
            GetInput(out speed);
            // always move along the camera forward as it is the direction that it being aimed at
            Vector3 desiredMove = transform.forward * m_Input.y + transform.right * m_Input.x;

            // get a normal for the surface that is being touched to move along it
            RaycastHit hitInfo;
            Physics.SphereCast(m_PhysicalBody.position, m_PhysicalBody_hitbox.radius, currentGravity.normalized, out hitInfo,
                               (m_PhysicalBody_hitbox.radius+2f) / 2f);

            desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized;

            Debug.DrawLine(m_PhysicalBody.position + Vector3.zero, m_PhysicalBody.position + desiredMove * 20, Color.green, 1f, false);

            // attempt to change the gravity depending on surface
            if (hitInfo.collider != null)
            {
                if (hitInfo.collider.tag == "GravityRoad")
                {
                    isOnGravRoad = true;
                    if (hitInfo.normal.normalized != -currentGravity.normalized)
                    {
                        currentGravity = -(hitInfo.normal.normalized) * 9.81f;
                        Physics.gravity = currentGravity;
                    }

                }
                else
                {
                    isOnGravRoad = false;
                }
            }

            //Debug.Log(hitInfo.normal);
            Debug.DrawLine(m_PhysicalBody.position + Vector3.zero, m_PhysicalBody.position + hitInfo.normal*20, Color.red, 1f, false);

            m_MoveDir.x = desiredMove.x * speed;
            m_MoveDir.z = desiredMove.z * speed;

            if (isGrounded)
            {
                m_MoveDir.y = -m_StickToGroundForce;
            }
            else
            {
                m_MoveDir += Physics.gravity * m_GravityMultiplier * Time.fixedDeltaTime;
            }
            m_PhysicalBody_rigidBody.MovePosition(m_PhysicalBody_rigidBody.position + m_MoveDir);

            UpdateCameraPosition(speed);
        }

        private void GetInput(out float speed)
        {
            // Read input
            float horizontal = CrossPlatformInputManager.GetAxis("Horizontal");
            float vertical = CrossPlatformInputManager.GetAxis("Vertical");

            bool waswalking = m_IsWalking;

            // On standalone builds, walk/run speed is modified by a key press.
            // keep track of whether or not the character is walking or running
            m_IsWalking = !Input.GetKey(KeyCode.LeftShift);

            // set the desired speed to be walking or running
            speed = m_IsWalking ? m_WalkSpeed : m_RunSpeed;
            m_Input = new Vector2(horizontal, vertical);

            // normalize input if it exceeds 1 in combined length:
            if (m_Input.sqrMagnitude > 1)
            {
                m_Input.Normalize();
            }

            // handle speed change to give an fov kick
            // only if the player is going to a run, is running and the fovkick is to be used
            if (m_IsWalking != waswalking && m_UseFovKick && m_PhysicalBody_rigidBody.velocity.sqrMagnitude > 0)
            {
                StopAllCoroutines();
                StartCoroutine(!m_IsWalking ? m_FovKick.FOVKickUp() : m_FovKick.FOVKickDown());
            }
        }

    }
}

what value is your debug red line based of? If it’s the normal from the collision object, that’s always the direction the collision came in from. usually you need to raycast against the collider in that spot to get the actual surface normal