Andrew Stacey pointed out [1] that the Rosetta Code entry "Draw a Sphere" [2] 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
[3]:
\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}
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}
25 div
to 50 div
you end up with a zoomed in version. - Simon Byrne
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
%% 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...) - Simon Byrne
I'm following the earlier post
Is there a way to tune ball shading in TikZ ?
[1], 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.
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.
<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
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}
ballshading
scale depending on the size of the ball? I'm asking because you're using fixed measurements in setting colours. - Werner