Here is an answer which produces something long these lines. The key ingredient is the perspective library. It utilizes a nice trick by Symbol 1 (and, as usual, the really cool things do not have much votes... ;-) which allows us to avoid to write tpp cs:x=...,y=...,z=..., we can just coordinates and the switch switch on perspective. Since decorations cause dimension too large errors, the color gradient and change of line width is achieved through loops, which is why this takes some time to compile (12s on a 5 year old MacBook Pro). A faster, decoration-based answer that works for not too curvy paths can be found below.
\documentclass[tikz,border=3mm]{standalone}
\usetikzlibrary{calc,perspective,3d}
\makeatletter
\tikzset{switch on perspective/.code={\def\tikz@parse@splitxyz##1##2##3,##4,{%
\def\pgfutil@next{\tikz@scan@one@point##1(tpp cs:x={##2},y={##3},z={##4})}% https://tex.stackexchange.com/a/365418/194703
}}}
\makeatother
\begin{document}
\begin{tikzpicture}[3d view={-70}{15}]
\begin{scope}[perspective={p = {(20,0,0)}, q = {(0,20,0)}},switch on perspective]
\path let \p1=($(0,2,0)-(0,0,0)$),\p2=($(20,2,0)-(20,0,0)$),
\n1={atan2(\y1,\x1)/2+atan2(\y2,\x2)/2} in
[left color=black,right color=gray!80!black,shading angle=\n1]
(0,-3,0) -- (0,3,0) -- (20,3,0) -- (20,-3,0) -- cycle;
\begin{scope}
\clip (1,-3,0) -- (1,3,0) -- (20,3,0) -- (20,-3,0) -- cycle;
\foreach \X [count=\Y] in {2,1.2,-1.2,-2}
{\foreach \Z [evaluate=\Z as \CF using {int(90-\Z/3)}] in {1,...,95}
{\draw let
\p1=($(0.8+\Z/5,\X+0.5,0)-(0.8+\Z/5,\X-0.5,0)$),
\n1={sqrt(\x1*\x1+\y1*\y1)} in [line width=0.1*\n1,gray!\CF!black]
plot[variable=\t,domain=0.8+\Z/5:0.8+\Z/5+0.4,samples=5,smooth]
(\t,{\X+0.2*pow(-1,\Y+1)*isodd(int(\t/2.05))},0) ;}}
\path foreach \X [count=\Y] in {2,1.2,-1.2,-2}
{(1,{\X+pow(-1,\Y+1)*0.2*isodd(int(1/2.05))},0) coordinate (aux\Y) };
\end{scope}
\end{scope}
\begin{scope}[canvas is xz plane at y=0]
\fill[rotate=-15] foreach \X in {1,...,4} {(aux\X) circle[x radius=5pt,y radius=1pt]};
\end{scope}
\end{tikzpicture}
\end{document}

For straight cylinders there is no problem, the decoration is well-behaved.
\documentclass[tikz,border=3mm]{standalone}
\usetikzlibrary{calc,decorations,perspective,3d}
\makeatletter
\tikzset{switch on perspective/.code={\def\tikz@parse@splitxyz##1##2##3,##4,{%
\def\pgfutil@next{\tikz@scan@one@point##1(tpp cs:x={##2},y={##3},z={##4})}% https://tex.stackexchange.com/a/365418/194703
}}}
\makeatother
% the following decoration is based on
% https://tex.stackexchange.com/a/14295/128068 and
% https://tex.stackexchange.com/a/471222
\pgfkeys{/pgf/decoration/.cd,
start color/.store in=\startcolor,
start color=black,
end color/.store in=\endcolor,
end color=black,
varying line width steps/.initial=100
}
\pgfdeclaredecoration{width and color change}{initial}{
\state{initial}[width=0pt, next state=line, persistent precomputation={%
\pgfmathparse{\pgfdecoratedpathlength/\pgfkeysvalueof{/pgf/decoration/varying line width steps}}%
\let\increment=\pgfmathresult%
\def\x{0}%
}]{}
\state{line}[width=\increment pt, persistent postcomputation={%
\pgfmathsetmacro{\x}{\x+\increment}
},next state=line]{%
\pgfmathparse{varyinglw(100*(\x/\pgfdecoratedpathlength))}
\pgfsetlinewidth{\pgfmathresult pt}%
\pgfpathmoveto{\pgfpointorigin}%
\pgfmathsetmacro{\steplength}{1.4*\increment}
\pgfpathlineto{\pgfqpoint{\steplength pt}{0pt}}%
\pgfmathsetmacro{\y}{100*(\x/\pgfdecoratedpathlength)}
\pgfsetstrokecolor{\endcolor!\y!\startcolor}%
\pgfusepath{stroke}%
}
\state{final}{%
\pgfsetlinewidth{\pgflinewidth}%
\pgfpathmoveto{\pgfpointorigin}%
\pgfmathsetmacro{\y}{100*(\x/\pgfdecoratedpathlength)}
\color{\endcolor!\y!\startcolor}%
\pgfusepath{stroke}%
}
}
\begin{document}
\begin{tikzpicture}[3d view={-70}{15}]
\begin{scope}[perspective={p = {(20,0,0)}, q = {(0,20,0)}},switch on perspective]
\path let \p1=($(0,2,0)-(0,0,0)$),\p2=($(20,2,0)-(20,0,0)$),
\n1={atan2(\y1,\x1)/2+atan2(\y2,\x2)/2} in
[left color=black,right color=gray!80!black,shading angle=\n1]
(0,-3,0) -- (0,3,0) -- (20,3,0) -- (20,-3,0) -- cycle;
\begin{scope}
\clip (1,-3,0) -- (1,3,0) -- (20,3,0) -- (20,-3,0) -- cycle;
\foreach \X [count=\Y] in {2,1.2,-1.2,-2}
{\draw[decorate,decoration={width and color change}] let
\p1=($(10,\X+0.5,0)-(10,\X-0.5,0)$),\p2=($(1,\X+0.5,0)-(1,\X-0.5,0)$),
\n1={sqrt(\x1*\x1+\y1*\y1)},\n2={sqrt(\x2*\x2+\y2*\y2)} in
[declare function={varyinglw(\x)=0.1*\n1+0.1*(\n2-\n1)*\x/100;},
/pgf/decoration/start color=gray!70!black,/pgf/decoration/end color=gray]
(20,\X,0) -- (1,\X,0) coordinate (aux\Y);}
\end{scope}
\end{scope}
\begin{scope}[canvas is xz plane at y=0]
\fill[rotate=-15] foreach \X in {1,...,4} {(aux\X) circle[x radius=5pt,y radius=1pt]};
\end{scope}
\end{tikzpicture}
\end{document}

Probably one can find parameter regions in which the curved paths work, too, but it might be a better investment of time to try to teach these decorations fpu.
asymptoteyou can draw nice pipes, see e.g. https://tex.stackexchange.com/a/404274. – Mar 19 '20 at 23:03