I like the Jarod42's C++14 solution based over overload and make_overload but has (Jarod: correct me if I'm wrong) a drawback: calling a operator() of a overload object works if one and only one operator() is available in inherited classes.
So you have to pass the second lambda (the generic one) as follows
[&](auto& container, shader& effect)
-> std::enable_if_t<!std::is_base<Object3D,
        std::decay_t<decltype(*container.begin())>>::value>
{
    for (const auto& key : objects) {
        auto& obj = *container[key];
    }
}
so enabling it only when is disabled the first lambda, when there is no other reason to disable it except to avoid a "collision" with the first lambda.
I think should be preferable permit that more than one lambda is enabled with a particular set of argument and that is called the first available.
So I propose a recursive lambda_overload
template <typename...>
struct lambda_overload;
with a ground case that is substantially identically to the Jarod42 ground overload and use the operator() of the last lambda (no risk of following "collisions")
template <typename L>
struct lambda_overload<L> : public L
 {
   lambda_overload (L l) : L{std::move(l)}
    { };
   using L::operator();
 };
but that is a little different in the recursive version.
It define a template operator() that call a func(), passing 0 (a int) as first argument and forwarding the other arguments received
   template <typename ... As>
   auto operator() (As && ... as)
    { return func(0, std::forward<As>(as)...); }
A preferred func() (the first argument is an int) is SFINAE enabled if (and only if) the first lambda accept the given argument list for an operator()
   template <typename ... As>
   auto func (int, As && ... as)
      -> decltype( std::declval<L0>()(std::forward<As>(as)...) )
    { return L0::operator()(std::forward<As>(as)...); }
and a backup func() (the first argument is a long) is ever defined and call the operator() in the following recursion level for lambda_overload
   template <typename ... As>
   auto func (long, As && ... as)
    { return lambda_overload<Ls...>::operator()(std::forward<As>(as)...); }
This way there isn't risk of "collision" because if more than operator()'s is available, it's executed the first one available.
So make_lambda_overload() could be called as follows
auto update = make_lambda_overload(
    [&](auto& container, shader& effect)
    -> std::enable_if_t<std::is_base<Object3D,
          std::decay_t<decltype(*container.begin())>>::value>
{
    for (const auto& key : objects) {
        auto& obj = *container[key];
        if (obj.HasAnyGeometry()) {
            m_GeometryDrawCalls.push_back({ &obj, effect });
        }
    }
},
[&](auto& container, shader& effect)
{
    for (const auto& key : objects) {
        auto& obj = *container[key];
    }
});
avoiding the SFINAE disable part for the second generic lambda.
The following is a full (but simplified) example
#include <iostream>
template <typename...>
struct lambda_overload;
template <typename L>
struct lambda_overload<L> : public L
 {
   lambda_overload (L l) : L{std::move(l)}
    { };
   using L::operator();
 };
template <typename L0, typename ... Ls>
struct lambda_overload<L0, Ls...> : public L0, public lambda_overload<Ls...>
 {
   lambda_overload (L0 l0, Ls ... ls)
      : L0{std::move(l0)}, lambda_overload<Ls...>{std::move(ls)...}
    { };
   // backup version (ever defined!)
   template <typename ... As>
   auto func (long, As && ... as)
    { return lambda_overload<Ls...>::operator()(std::forward<As>(as)...); }
   // preferred version (defined only if operator() defined for L0 type)
   template <typename ... As>
   auto func (int, As && ... as)
      -> decltype( std::declval<L0>()(std::forward<As>(as)...) )
    { return L0::operator()(std::forward<As>(as)...); }
   template <typename ... As>
   auto operator() (As && ... as)
    { return func(0, std::forward<As>(as)...); }
 };
template <typename ... Ls>
auto make_lambda_overload (Ls && ... ls)
 { return lambda_overload<Ls...>{ std::forward<Ls>(ls)... }; }
int main()
 {
   auto l1 = [&](auto const & t) -> decltype((void)t.size())
    { std::cout << "-- with size() version - " << t.size() << std::endl; };
   auto l2 = [&](auto const & t)
    { std::cout << "-- generic version (also no size())" << std::endl; };
   auto lo = make_lambda_overload(std::move(l1), std::move(l2));
   lo(std::string{"0"});  // print "with size() version - 1
   lo(1);                 // print "generic version (also no size()="
 }