The code I mentioned before for multiple convex cast results, in case someone wants it. And I realize now the reason I thought it wasn’t possible to draw the collision shapes directly was because my code created the convex bullet collision shapes directly rather than using Urho3D::CollisionShape.
#include <BulletCollision/CollisionShapes/btSphereShape.h>
#include <Urho3D/Physics/PhysicsWorld.h>
#include <Urho3D/Physics/CollisionShape.h>
#include <Urho3D/ThirdParty/Bullet/BulletCollision/CollisionShapes/btCollisionShape.h>
#include <Urho3D/ThirdParty/Bullet/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
#include <Urho3D/ThirdParty/Bullet/btBulletDynamicsCommon.h>
#include <Urho3D/ThirdParty/Bullet/btBulletCollisionCommon.h>
#include <Urho3D/ThirdParty/Bullet/btBulletDynamicsCommon.h>
#include <Urho3D/Physics/PhysicsUtils.h>
struct AllHitsConvexResultCallback : public btCollisionWorld::ConvexResultCallback
{
    AllHitsConvexResultCallback(const btVector3& rayFromWorld, const btVector3& rayToWorld)
        : m_convexFromWorld(rayFromWorld),
          m_convexToWorld(rayToWorld)
    {
    }
    btAlignedObjectArray<const btCollisionObject*> m_collisionObjects;
    btVector3 m_convexFromWorld;  //used to calculate hitPointWorld from hitFraction
    btVector3 m_convexToWorld;
    btAlignedObjectArray<btVector3> m_hitNormalWorld;
    btAlignedObjectArray<btVector3> m_hitPointWorld;
    btAlignedObjectArray<btScalar> m_hitFractions;
    virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& rayResult, bool normalInWorldSpace)
    {
        m_collisionObjects.push_back(rayResult.m_hitCollisionObject);
        btVector3 hitNormalWorld;
        if (normalInWorldSpace)
        {
            hitNormalWorld = rayResult.m_hitNormalLocal;
        }
        else
        {
            ///need to transform normal into worldspace
            hitNormalWorld = rayResult.m_hitCollisionObject->getWorldTransform().getBasis() * rayResult.m_hitNormalLocal;
        }
        m_hitNormalWorld.push_back(hitNormalWorld);
        btVector3 hitPointWorld;
//        hitPointWorld.setInterpolate3(m_rayFromWorld, m_rayToWorld, rayResult.m_hitFraction);
//        m_hitPointWorld.push_back(hitPointWorld);
        m_hitPointWorld.push_back(rayResult.m_hitPointLocal);
        m_hitFractions.push_back(rayResult.m_hitFraction);
        return m_closestHitFraction; // i.e. 1.0
    }
};
namespace Urho3D {
void SphereCastMultiple(PhysicsWorld* pw, Vector<PhysicsRaycastResult>& results, const Ray& ray, float radius, float maxDistance, unsigned collisionMask)
{
    if (maxDistance >= M_INFINITY)
        URHO3D_LOGWARNING("Infinite maxDistance in physics sphere cast is not supported");
    btSphereShape shape(radius);
    Vector3 endPos = ray.origin_ + maxDistance * ray.direction_;
    AllHitsConvexResultCallback
            convexCallback(ToBtVector3(ray.origin_), ToBtVector3(endPos));
    convexCallback.m_collisionFilterGroup = (short)0xffff;
    convexCallback.m_collisionFilterMask = (short)collisionMask;
    pw->GetWorld()->convexSweepTest(&shape, btTransform(btQuaternion::getIdentity(), convexCallback.m_convexFromWorld),
                                    btTransform(btQuaternion::getIdentity(), convexCallback.m_convexToWorld), convexCallback);
    for (unsigned i = 0; (int)i < convexCallback.m_hitFractions.size(); ++i)
    {
        PhysicsRaycastResult result;
        result.body_ = static_cast<RigidBody*>(convexCallback.m_collisionObjects[i]->getUserPointer());
        result.position_ = ToVector3(convexCallback.m_hitPointWorld[i]);
        result.normal_ = ToVector3(convexCallback.m_hitNormalWorld[i]);
        result.distance_ = convexCallback.m_hitFractions[i] * (endPos - ray.origin_).Length();
        result.hitFraction_ = convexCallback.m_hitFractions[i];
        results.Push(result);
    }
}
void ConvexCastMultiple(PhysicsWorld* pw, Vector<PhysicsRaycastResult>& results, const Ray& ray, CollisionShape* shape, float maxDistance, unsigned collisionMask)
{
    if (!pw || !shape)
        return;
    const auto& btShape = shape->GetCollisionShape();
    if (!btShape || !btShape->isConvex())
        return;
    const btConvexShape* btConvex = static_cast<const btConvexShape*>(btShape);
    ConvexCastMultiple(pw,results,ray,btConvex,maxDistance,collisionMask);
}
void ConvexCastMultiple(PhysicsWorld* pw, Vector<PhysicsRaycastResult>& results, const Ray& ray, const btConvexShape* shape, float maxDistance, unsigned collisionMask)
{
    if (!pw || !shape)
        return;
    if (maxDistance >= M_INFINITY)
        URHO3D_LOGWARNING("Infinite maxDistance in physics convex cast is not supported");
    Vector3 endPos = ray.origin_ + maxDistance * ray.direction_;
    AllHitsConvexResultCallback
            convexCallback(ToBtVector3(ray.origin_), ToBtVector3(endPos));
    convexCallback.m_collisionFilterGroup = (short)0xffff;
    convexCallback.m_collisionFilterMask = (short)collisionMask;
    pw->GetWorld()->convexSweepTest(shape, btTransform(btQuaternion::getIdentity(), convexCallback.m_convexFromWorld),
                                    btTransform(btQuaternion::getIdentity(), convexCallback.m_convexToWorld), convexCallback);
    for (unsigned i = 0; (int)i < convexCallback.m_hitFractions.size(); ++i)
    {
        PhysicsRaycastResult result;
        result.body_ = static_cast<RigidBody*>(convexCallback.m_collisionObjects[i]->getUserPointer());
        result.position_ = ToVector3(convexCallback.m_hitPointWorld[i]);
        result.normal_ = ToVector3(convexCallback.m_hitNormalWorld[i]);
        result.distance_ = convexCallback.m_hitFractions[i] * (endPos - ray.origin_).Length();
        result.hitFraction_ = convexCallback.m_hitFractions[i];
        results.Push(result);
    }
}
}