52

Andrew Stacey pointed out that the Rosetta Code entry "Draw a Sphere" doesn't have a TikZ entry yet.

Is there a way to draw a "properly" shaded sphere using TikZ? The ball shading would seem an obvious choice, but it uses a circular highlight, which is not what it would look like in reality. So: is there a way to make a sphere that would resemble a white, matte (i.e. non-shiny) ball that is illuminated by a single white light source?

This would mainly require getting a non-circular, correctly oriented highlight, I believe. Both Yori's and Altermundus' answer already do a great job at using a "softer" highlight, which looks more believable, but Altermundus' highlight is still circular, and while Yoris' is elliptic, it's not oriented correctly, I think.

Compare a circle with the ball shading to the example made with Perl 6:

\documentclass{article}

\usepackage{tikz}

\begin{document}

\begin{minipage}{2.5cm}\centering
\scriptsize That's not a sphere.

\tikz{
    \fill [black] (-1,-1) rectangle (1,1);
    \shade [ball color=white] (0,0) circle [radius=1cm];
}
\end{minipage}%
%
\begin{minipage}{2.5cm}\centering
\scriptsize \emph{That's} a sphere.

\includegraphics[width=2cm]{Sphere-perl6.png}
\end{minipage}
\end{document}
6
  • I think that the better sphere is the first one. The second example seems to be a white mushroom. I prefer this one : commons.wikimedia.org/wiki/… or this one webangel.fr/fr/tutaux/sphere.html Commented May 3, 2012 at 18:28
  • I would agree with Altermundus, there is a problem with the second one, he saw a mushroom and I am seeing a knob!
    – yannisl
    Commented May 3, 2012 at 19:08
  • Does this mushroom-y knob require the 3d tag?
    – Werner
    Commented May 3, 2012 at 20:05
  • @Altermundus, @ YiannisLazarides: To me, the first one looks like what you'd see when you're sitting at the bottom of a well looking up, and the sun is not directly overhead. And if I'm not mistaken, the left one can't be correct, because the highlight can't be circular: Think of the extreme case when the light is coming directly from the side: The edge of the highlight would be a straight line. So for light coming at an angle, the edge of the highlight has to be something between a circle and a straight line (an ellipsoid, presumably).
    – Jake
    Commented May 3, 2012 at 20:07
  • 1
    I think the contrast is too high in the second picture. You want a sphere realistic ? hyperrealist ? with a impressionism style ? a billiard ball with a soft light ? the moon ? an eclipse ? The question should be more specific ! but it will not be easy to master all the effects. Commented May 3, 2012 at 21:29

4 Answers 4

28

I'm following the earlier post Is there a way to tune ball shading in TikZ ?, particularly Stefan Kottwitz's answer. He showed how to use \pgfdeclareradialshading to change the radial shading. Changing the parameters for the radial and adding some clipping, I can produce this:

Is that sphere enough? Perhaps with some more tweaking it is possible to get an even better result. The code to produce this is:

\documentclass{article}
\usepackage{tikz}

\begin{document}
\makeatletter
\pgfdeclareradialshading[tikz@ball]{ball}{\pgfqpoint{-20bp}{20bp}}{%
 color(0bp)=(tikz@ball!0!white);
 color(17bp)=(tikz@ball!0!white);
 color(21bp)=(tikz@ball!70!black);
 color(25bp)=(black!70);
 color(30bp)=(black!70)}
\makeatother

\begin{tikzpicture}
    \fill [black] (-1,-1) rectangle (1,1);
    \begin{scope}
        \clip (0,0) circle (1);
        \shade [ball color=white] (-0.1,0) ellipse (1.2 and 1);
    \end{scope}
\end{tikzpicture}

\end{document}

Or, with a more ellipsoidal shading, one can get this:

The for this is:

\documentclass{article}

\usepackage{tikz}

\begin{document}

\makeatletter
\pgfdeclareradialshading[tikz@ball]{ball}{\pgfqpoint{0bp}{0bp}}{%
 color(0bp)=(tikz@ball!0!white);
 color(10bp)=(tikz@ball!0!white);
 color(15bp)=(tikz@ball!70!black);
 color(20bp)=(black!70);
 color(30bp)=(black!70)}
\makeatother

\begin{tikzpicture}

    \fill [black] (-1,-1) rectangle (1,1);

    \begin{scope}
        \clip (0,0) circle (1);
        \draw [fill=black!70] (0, 0) circle (1);
        \begin{scope}[transform canvas={rotate=45}]
        \shade [ball color=white] (0,0.5) ellipse (1.8 and 1.6);
        \end{scope}
    \end{scope}

\end{tikzpicture}

\end{document}

It's a matter of experimenting and fiddling around with the parameters to see what looks best according to you (judging from the discussion above, this seems quite subjective matter). Notice that you need to specify transform canvas={rotate=45} in the inner scope, not rotate=45 because the latter does not rotate the fill.

2
  • That looks great! However, is there a way of getting the orientation of the shading right? At the moment, the ellipse is oriented horizontally, but I guess it would need to be at a 45 degree angle?
    – Jake
    Commented May 3, 2012 at 21:38
  • @Jake: see my edit. As said, it's a matter of fiddling around a little bit to find what looks best. I think the second one looks okayish, although not that much different from the first one.
    – yori
    Commented May 4, 2012 at 1:30
43

If the light source is a long way away, the intensity at each point is the dot product between the position vector and light source vector (in R3), thresholded at 0.

This uses the \pgfdeclarefunctionalshading command from percusse's answer. Of course, we're only given two elements of each vector, so we need to first compute the third.

For some reason, shadings for circles need to be 50bp by 50bp, otherwise this doesn't work.

\documentclass[border=5mm]{standalone}
\usepackage{tikz}

\pgfdeclarefunctionalshading{sphere}{\pgfpoint{-25bp}{-25bp}}{\pgfpoint{25bp}{25bp}}{}{
%% calculate unit coordinates
25 div exch
25 div exch
%% copy stack
2 copy 
%% compute -z^2 of the current position 
dup mul exch
dup mul add
1.0 sub
%% and the -z^2 of the light source 
0.3 dup mul
-0.5 dup mul add
1.0 sub
%% now their sqrt product
mul abs sqrt
%% and the sum product of the rest
exch 0.3 mul add
exch -0.5 mul add
%% max(dotprod,0)
dup abs add 2.0 div 
%% matte-ify
0.6 mul 0.4 add
%% currently there is just one number in the stack.
%% we need three corresponding to the RGB values
dup
0.4
}

\begin{document}
\begin{tikzpicture}
  \shade[shading=sphere] (0,0) circle [radius=5cm];
\end{tikzpicture}
\end{document}

sphere

6
  • 1
    Nice! I've also tried something similar but for some reason they (including this one) don't work on Sumatra PDF or a few others than Acrobat. That's why I didn't touch the previous answer. Regarding the 50bp case, it seems working if I increase all 25s to 50s too.
    – percusse
    Commented Oct 9, 2012 at 16:44
  • ImageMagick didn't like it too much either. Mac preview handled it fine though. Commented Oct 10, 2012 at 10:32
  • 1
    I've also tried to find which calculation starts to make it problematic but apparently none of them are causing any problems. Maybe the problem is about numerical sensitivity. I have no idea why my answer doesn't cause any problems and yours is only available in AR. Same thing happened with the fireworks question too. In that case only Sumatra managed to show the proper output.
    – percusse
    Commented Oct 10, 2012 at 19:15
  • 2
    This looks really nice. And when I tried it today, SumatraPDF could display it correctly (Windows 7 64bit, SumatraPDF version 2.2.1). I'd like to use this shading in some pictures of molecular systems I want to create with pgf but I don't know anything about postscript. Is there an easy way to get this shading working with different colors or would this require a modification of the whole postscript calculation?
    – Philipp
    Commented Jun 16, 2013 at 14:34
  • 4
    You only need to modify the last few lines to change the colour: at the comment %% matte-ify there is 1 number (between 0 and 1) in the stack: you need 3 (being the RGB values). Try playing around with the lines below this point and see how you go (if you get stuck, you can always ask another question...) Commented Jun 18, 2013 at 13:03
25

To grease up PS gears, I modified the functional shading given in the PGF manual. I am pretty sure that PSTricks and its foot soldiers are ridiculously better at these type of drawings but Acrobat and Sumatra render pretty impressively.

\documentclass[border=5mm]{standalone}
\usepackage{tikz}
\pgfdeclarefunctionalshading{eightball}{\pgfpointorigin}{\pgfpoint{100bp}{100bp}}{}{
% Compute distance difference (horizontally weighted twice). (50bp,50bp) is the center
65 sub dup mul exch               %Change the coordinate to move vertically
40 sub dup mul 0.5 mul add sqrt   %Change the coordinate to move horizontally
% In MATLAB notation : d=distance diff
% x=1.003^(-d^2)
dup mul neg 1.003 exch exp
% x is the only variable in the stack now but we need 3 values at the top of the stack
% so we duplicate these values putting new values in the stack.
dup % Duplicates with the current value and pushes the stack down (value of green)
dup % Duplicates with the current value and pushes the stack down (value of blue)
}

\begin{document}
\begin{tikzpicture}
\shade[shading=eightball] (0,0) circle (5cm);
\end{tikzpicture}
\end{document}

You can play around with the last dup commands by replacing with values in the interval [0.0,1.0] from black to white. For example being a colorblind, if I make these values 0.3,0.2 respectively, my brain just quits rendering with a painful error code.

enter image description here

8
  • 1
    Ooooh! I didn't know you could use PS code for the shadings. Looks really impressive!
    – Jake
    Commented May 4, 2012 at 4:58
  • The list of the ps operations are given in the pgfmanual (shading part). You can use the basic operations but you can't program. It's the big difference between ps and PDF. Very fine result from percusse !! Commented May 4, 2012 at 5:06
  • @Jake Indeed, it's quite surprising. As Altermundus commented it's quite laborious to make it flexible and some things are just not possible. I've experimented with the <init code> for colors and there is hope colorwise, not much though. As a side note, for some reason if I do the ->PS->PDF route, the pixelation is too visible. The sampling rate gets too low I suppose.
    – percusse
    Commented May 4, 2012 at 5:15
  • I recently saw this in the PGF manual and so when thinking about this question wondered if this would a way to get a right solution - I'm pleased to see someone figure it out! Really nice. I think I'd add some "ambient light" so that it doesn't shade to black. Commented May 4, 2012 at 10:00
  • @AndrewStacey Indeed I did some experiments and many options regarding the colors are possible. If I can concentrate enough to keep track of the stack entries and permutations, I will introduce a fadeout color. This is very much like Assembly language.
    – percusse
    Commented May 4, 2012 at 10:18
19

Perhaps something like that

\documentclass[11pt]{scrartcl}
\usepackage{tikz}
\begin{document}

\pgfdeclareradialshading{ballshading}{\pgfpoint{-10bp}{10bp}}
 {color(0bp)=(gray!40!white); 
 color(9bp)=(gray!75!white);
 color(18bp)=(gray!70!black); 
 color(25bp)=(gray!50!black); 
 color(50bp)=(black)}

\begin{pgfpicture}
  \pgfpathcircle{\pgfpoint{0cm}{0cm}}{2cm}
  \pgfshadepath{ballshading}{20}
  \pgfusepath{}
\end{pgfpicture}  

\end{document}

enter image description here

3
  • Since I've unfamiliar with TikZ, here's a question: Does this ballshading scale depending on the size of the ball? I'm asking because you're using fixed measurements in setting colours.
    – Werner
    Commented May 3, 2012 at 19:22
  • Well ... I don't know ! I used an example from the pgfmanual and I played with the numbers. I changed the radius of the sphere from 1cm to 2cm. Commented May 3, 2012 at 19:45
  • 1
    @Altermundus: Nice, the softness looks much better than the default! However, I still think that a circular highlight doesn't look right (and isn't correct physically, assuming your light source is circular).
    – Jake
    Commented May 3, 2012 at 21:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .