The question of how to draw a torus with TikZ is a rather old one and has several excellent answers. And the most spectacular outputs have (IMHO) been achieved with asymptote, which has, unlike TikZ, a 3d engine. However, it turns out that if one aims at 3d vector graphics, the efforts required drawing 3d tori are more substantial than one may naively expect.
This raises the question of whether or not it is possible to make TikZ discriminate between visible and "hidden" points on the torus surface. After all, the analogous discrimination has been achieved for spheres. The answer is yes.
Part I of the answer: how can one draw the contour of a torus? Given a parametization of the torus, T(\u,\v)=(cos(\u)*(\R + \r*cos(\v),(\R + \r*cos(\v))*sin(\u),\r*sin(\v))
, one can compute the tangents and then the normal at a given point. The boundary of the torus is determined by the requirement that the normal be orthogonal to the normal of the screen. The resulting curve is then a function T(\u,vcrit(\u))
. The critical \v
values have a very simple representation:
vcrit1(\u,\th)=atan(tan(\th)*sin(\u));% first critical v value
vcrit2(\u,\th)=180+atan(tan(\th)*sin(\u));% second critical v value
They determine where the visible and/or hidden pieces of cycles wrapping the torus start or end. Note, however, that the contour vcrit2
may, depending on the view angle \tdplotmaintheta
, have self-interactions. This is why there is a discriminant in the below code.
\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{tikz-3dplot}
\begin{document}
\tdplotsetmaincoords{70}{0}
\tikzset{declare function={torusx(\u,\v,\R,\r)=cos(\u)*(\R + \r*cos(\v));
torusy(\u,\v,\R,\r)=(\R + \r*cos(\v))*sin(\u);
torusz(\u,\v,\R,\r)=\r*sin(\v);
vcrit1(\u,\th)=atan(tan(\th)*sin(\u));% first critical v value
vcrit2(\u,\th)=180+atan(tan(\th)*sin(\u));% second critical v value
disc(\th,\R,\r)=((pow(\r,2)-pow(\R,2))*pow(cot(\th),2)+%
pow(\r,2)*(2+pow(tan(\th),2)))/pow(\R,2);% discriminant
umax(\th,\R,\r)=ifthenelse(disc(\th,\R,\r)>0,asin(sqrt(abs(disc(\th,\R,\r)))),0);
}}
\begin{tikzpicture}[tdplot_main_coords]
\pgfmathsetmacro{\R}{4}
\pgfmathsetmacro{\r}{1}
\draw[thick,fill=gray,even odd rule,fill opacity=0.2] plot[variable=\x,domain=0:360,smooth,samples=71]
({torusx(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)})
plot[variable=\x,
domain={-180+umax(\tdplotmaintheta,\R,\r)}:{-umax(\tdplotmaintheta,\R,\r)},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)})
plot[variable=\x,
domain={umax(\tdplotmaintheta,\R,\r)}:{180-umax(\tdplotmaintheta,\R,\r)},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)});
\draw[thick] plot[variable=\x,
domain={-180+umax(\tdplotmaintheta,\R,\r)/2}:{-umax(\tdplotmaintheta,\R,\r)/2},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)});
\foreach \X in {240,300}
{\draw[thick,dashed]
plot[smooth,variable=\x,domain={360+vcrit1(\X,\tdplotmaintheta)}:{vcrit2(\X,\tdplotmaintheta)},samples=71]
({torusx(\X,\x,\R,\r)},{torusy(\X,\x,\R,\r)},{torusz(\X,\x,\R,\r)});
\draw[thick]
plot[smooth,variable=\x,domain={vcrit2(\X,\tdplotmaintheta)}:{vcrit1(\X,\tdplotmaintheta)},samples=71]
({torusx(\X,\x,\R,\r)},{torusy(\X,\x,\R,\r)},{torusz(\X,\x,\R,\r)})
node[below]{$C\ifnum\X=300 '\fi$};
}
\draw[thick] plot[smooth,variable=\x,domain=60:420,samples=71]
({torusx(-15+15*cos(\x),80+45*sin(\x),\R,\r)},
{torusy(-15+15*cos(\x),80+45*sin(\x),\R,\r)},
{torusz(-15+15*cos(\x),80+45*sin(\x),\R,\r)})
node[above left]{$C''$};
\end{tikzpicture}
\end{document}

As you can see, the visible (solid) or hidden (dashed) contours run between vcrit1
and vcrit2
, which are functions of \u
and the view angle.
One can then vary the positions of the cycle(s) and the view angle.
\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{tikz-3dplot}
\begin{document}
\foreach \X in {0,10,...,350}
{\tdplotsetmaincoords{65+10*sin(\X)}{0}
\tikzset{declare function={torusx(\u,\v,\R,\r)=cos(\u)*(\R + \r*cos(\v));
torusy(\u,\v,\R,\r)=(\R + \r*cos(\v))*sin(\u);
torusz(\u,\v,\R,\r)=\r*sin(\v);
vcrit1(\u,\th)=atan(tan(\th)*sin(\u));% first critical v value
vcrit2(\u,\th)=180+atan(tan(\th)*sin(\u));% second critical v value
disc(\th,\R,\r)=((pow(\r,2)-pow(\R,2))*pow(cot(\th),2)+%
pow(\r,2)*(2+pow(tan(\th),2)))/pow(\R,2);% discriminant
umax(\th,\R,\r)=ifthenelse(disc(\th,\R,\r)>0,asin(sqrt(abs(disc(\th,\R,\r)))),0);
}}
\begin{tikzpicture}[tdplot_main_coords]
\pgfmathsetmacro{\R}{4}
\pgfmathsetmacro{\r}{1}
\path[tdplot_screen_coords,use as bounding box]
(-1.3*\R,-1.3*\R) rectangle (1.3*\R,1.3*\R);
\draw[thick,fill=gray,even odd rule,fill opacity=0.2] plot[variable=\x,domain=0:360,smooth,samples=71]
({torusx(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit1(\x,\tdplotmaintheta),\R,\r)})
plot[variable=\x,
domain={-180+umax(\tdplotmaintheta,\R,\r)}:{-umax(\tdplotmaintheta,\R,\r)},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)})
plot[variable=\x,
domain={umax(\tdplotmaintheta,\R,\r)}:{180-umax(\tdplotmaintheta,\R,\r)},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)});
\draw[thick] plot[variable=\x,
domain={-180+umax(\tdplotmaintheta,\R,\r)/2}:{-umax(\tdplotmaintheta,\R,\r)/2},smooth,samples=51]
({torusx(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusy(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)},
{torusz(\x,vcrit2(\x,\tdplotmaintheta),\R,\r)});
\draw[thick,dashed]
plot[smooth,variable=\x,domain={360+vcrit1(\X,\tdplotmaintheta)}:{vcrit2(\X,\tdplotmaintheta)},samples=71]
({torusx(\X,\x,\R,\r)},{torusy(\X,\x,\R,\r)},{torusz(\X,\x,\R,\r)});
\draw[thick]
plot[smooth,variable=\x,domain={vcrit2(\X,\tdplotmaintheta)}:{vcrit1(\X,\tdplotmaintheta)},samples=71]
({torusx(\X,\x,\R,\r)},{torusy(\X,\x,\R,\r)},{torusz(\X,\x,\R,\r)});
\end{tikzpicture}}
\end{document}

The current limitations are:
- The theta angle has to be larger than 90 degrees and large enough that the torus has hole. (This restriction has been lifted in this post.)
- The phi angle is 0. This is not a true limitation because of the symmetry of the torus. It could be overcome by shifting all
\v
values by minus \tdplotmainphi
, if this is necessary (but at this point I do not see a motivation for this).
With all these preparations we can tackle the second part of the question, namely how to achieve a shading. As long as one does not insist on a realistic shading, one can use e.g. this answer. The main purpose of this discussion is not the shading, but the question how to use the above with pgfplots. To my own surprise it is absolutely straightforward. This is because pgfplots
is extremely well written and all the necessary angles are stored in pgf keys.
\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\tikzset{declare function={torusx(\u,\v,\R,\r)=cos(\u)*(\R + \r*cos(\v));
torusy(\u,\v,\R,\r)=(\R + \r*cos(\v))*sin(\u);
torusz(\u,\v,\R,\r)=\r*sin(\v);
vcrit1(\u,\th)=atan(tan(\th)*sin(\u));% first critical v value
vcrit2(\u,\th)=180+atan(tan(\th)*sin(\u));% second critical v value
disc(\th,\R,\r)=((pow(\r,2)-pow(\R,2))*pow(cot(\th),2)+%
pow(\r,2)*(2+pow(tan(\th),2)))/pow(\R,2);% discriminant
umax(\th,\R,\r)=ifthenelse(disc(\th,\R,\r)>0,asin(sqrt(abs(disc(\th,\R,\r)))),0);
}}
\begin{document}
\begin{tikzpicture}
\pgfmathsetmacro{\R}{4}
\pgfmathsetmacro{\r}{1}
\begin{axis}[colormap/blackwhite,
view={30}{60},axis lines=none
]
\addplot3[surf,shader=interp,
samples=61, point meta=z+sin(2*y),
domain=0:360,y domain=0:360,
z buffer=sort]
({torusx(x,y,\R,\r)},
{torusy(x,y,\R,\r)},
{torusz(x,y,\R,\r)});
\pgfplotsinvokeforeach{300,360}{%
\draw[thick,dashed]
plot[smooth,variable=\x,domain={360+vcrit1(#1-\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})}:{vcrit2(#1-\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})},samples=71]
({torusx(#1-\pgfkeysvalueof{/pgfplots/view/az},\x,\R,\r)},{torusy(#1-\pgfkeysvalueof{/pgfplots/view/az},\x,\R,\r)},{torusz(#1-\pgfkeysvalueof{/pgfplots/view/az},\x,\R,\r)});
\draw[thick]
plot[smooth,variable=\x,domain={vcrit2(#1-\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})}:{vcrit1(#1-\pgfkeysvalueof{/pgfplots/view/az},\pgfkeysvalueof{/pgfplots/view/el})},samples=71]
({torusx(#1-\pgfkeysvalueof{/pgfplots/view/az},\x,\R,\r)},{torusy(#1-\pgfkeysvalueof{/pgfplots/view/az},\x,\R,\r)},{torusz(#1-\pgfkeysvalueof{/pgfplots/view/az},\x,\R,\r)})
node[below]{$C\ifnum#1=360 '\fi$};
}
\draw[thick] plot[smooth,variable=\x,domain=60:420,samples=71]
({torusx(25+15*cos(\x),80+45*sin(\x),\R,\r)},
{torusy(25+15*cos(\x),80+45*sin(\x),\R,\r)},
{torusz(25+15*cos(\x),80+45*sin(\x),\R,\r)})
node[above left]{$C''$};
\end{axis}
\end{tikzpicture}
\end{document}

site:tex.stackexchange.com asymptote torus
you will find many more examples.