使用 TikZ/PGFPlots 实现 3D 照明和阴影

使用 TikZ/PGFPlots 实现 3D 照明和阴影

我正在尝试使用基本的 3D 照明模型来对 3D 参数曲面进行着色,但是这个答案说 TikZ 不支持照明。唯一的选择是使用颜色渐变来为顶点着色。这对于某些图形来说可能是可以接受的,但当您尝试显示 3D 对象的实际形状时则不行。

但是 TikZ 显然拥有所有可用数据来执行此操作。可以为每个顶点计算导数(即用户无需手动为每个表面分析导出它们)。然后可以使用它们来构建法线。让用户指定点光源位置,然后,您就会获得漫射照明。确定相机位置,您也会获得镜面照明。

我知道像 Asymptote 这样的软件包可以创建漂亮的 3D 图像,但是这些解决方案会创建光栅图形,而我需要矢量图形。

如何向 TikZ 添加 3D 灯光和阴影?它不存在有什么原因吗?我不知道 TikZ 是如何实现的,而且我之前也没有写过任何扩展,所以我不知道如何添加这个功能。

答案1

在此处输入图片描述

它是通过 TikZ 内部的直接计算获得的。表面是参数化的,但 LaTeX 内存限制几乎已经触及。代码如下,改编自我在在 TikZ 中对圆环进行着色。那里有一些解释。由于我添加了阴影,我再次给出代码。

坐标是无阴影的;尝试一下盎司轴...也许 DJP 用来sagetex进行计算的想法是合理的。

\documentclass[margin=10pt]{standalone}
\usepackage{ifthen}
\usepackage[rgb]{xcolor}
\usepackage{tikz}
\usetikzlibrary{cd, arrows, matrix, intersections, math, calc}
\xdefinecolor{O}{RGB}{255, 102, 17}
\xdefinecolor{B}{RGB}{17, 87, 221}

\begin{document}

\tikzmath{%
  real \slongit, \slatit, \sunx, \suny, \sunz;  % towards the light source 
  real \longit, \latit, \tox, \toy, \toz;
  real \newxx, \newxy, \newyx, \newyy, \newzx, \newzy;  
  \slongit = 100; \slatit = 45;
  \sunx = sin(\slongit)*cos(\slatit);
  \suny = sin(\slatit);
  \sunz = cos(\slongit)*cos(\slatit);
  \longit = 25;  \latit = 36;  % 35;
  \tox = sin(\longit)*cos(\latit);
  \toy = sin(\latit);
  \toz = cos(\longit)*cos(\latit);
  \newxx = cos(\longit); \newxy = -sin(\longit)*sin(\latit);
  \newyy = cos(\latit);
  \newzx = -sin(\longit); \newzy = -cos(\longit)*sin(\latit);
  real \ry, \rz;
  \ry = 4;
  \rz = 1.5;  
  integer \Ny, \Nz, \j, \k, \prevj, \prevk, \aj, \ak;
  % j moves around Oy and k moves around Oz.
  % They describe full circles of radii \ry and \rz respectively.
  \Nz = 48;  % 24;  % 60;
  \Ny = 80;  % 36;  % 120;
  \ktmp = \Nz-1; 
  \jtmp = \Ny-1;
  \aj = 10;
  \ak = 0;
  function isSeen(\j, \k) {
    let \px = cos(360*(\k/\Nz))*cos(360*(\j/\Ny));
    let \py = -sin(360*(\k/\Nz));
    let \pz = cos(360*(\k/\Nz))*sin(360*(\j/\Ny));
    let \res = \px*\tox + \py*\toy + \pz*\toz;
    if \res>0 then {return 1;} else {return 0;};
  };
  function inLight(\j, \k) {%
    let \px = cos(360*(\k/\Nz))*cos(360*(\j/\Ny));
    let \py = -sin(360*(\k/\Nz));
    let \pz = cos(360*(\k/\Nz))*sin(360*(\j/\Ny));
    return {\px*\sunx + \py*\suny + \pz*\sunz};
  };
  function projX(\j, \k) {%
    let \px = \ry+\rz*cos(360*(\k/\Nz))*cos(360*(\j/\Ny));
    let \py = -\rz*sin(360*(\k/\Nz));
    let \t = -(\rz+\py)/\suny;
    return {\px + \t*\sunx};
  };
  function projZ(\j, \k) {%
    let \py = -\rz*sin(360*(\k/\Nz));
    let \pz = \ry+\rz*cos(360*(\k/\Nz))*sin(360*(\j/\Ny));
    let \t = -(\rz+\py)/\suny;
    return {\pz + \t*\sunz};
  };
  function T(\j, \k) {%
    let \py = -\rz*sin(360*(\k/\Nz));
    let \pz = \ry+\rz*cos(360*(\k/\Nz))*sin(360*(\j/\Ny));
    return {\rz*(-1+sin(360*(\k/\Nz)))/\suny};
  };
}


\begin{tikzpicture}[every node/.style={scale=.8},
  x={(\newxx cm, \newxy cm)},
  y={(0 cm, \newyy cm)},
  z={(\newzx cm, \newzy cm)},
  evaluate={%
    % int \j, \k;
    real \tmp;
    for \j in {0, 1, ..., \Ny}{%
      for \k in {0, 1, ..., \Nz}{%
        \test{\j,\k} = isSeen(\j, \k);
        if \test{\j,\k}>0 then {%
          \tmp{\j,\k} = int(100*inLight(\j,\k)));
          if \tmp{\j,\k}>0 then {%
            \tmpW{\j,\k}=int(100*inLight(\j,\k)^2);
          }
          else {%
            \tmpK{\j,\k}=-int(100*inLight(\j,\k));
          };
        } else {};
      };
    };
  }]

  % points (P-\j-\k)
  \foreach \j in {0, ..., \Ny}{%
    \foreach \k in {0, ..., \Nz}{%
      \path
      ( {( \ry+\rz*cos(360*(\k/\Nz)) )*cos(360*(\j/\Ny))},
      {-\rz*sin(360*(\k/\Nz))},
      {( \ry+\rz*cos(360*(\k/\Nz)) )*sin(360*(\j/\Ny))} )
      coordinate (P-\j-\k);
    }
  }


  % shadow
  \foreach \k [remember=\k as \prevk (initially 0)] in {1, ..., \Nz}{%
    \foreach \j [remember=\j as \prevj (initially 0)] in {1, ..., \Ny}{%
      \fill[gray!70!black, opacity={.4*abs(inLight(\j,\k))}]
      ($(P-\j-\prevk)+T(\j,\prevk)*(\sunx, \suny, \sunz)$)
      -- ($(P-\prevj-\prevk)+T(\prevj,\prevk)*(\sunx, \suny, \sunz)$)
      -- ($(P-\prevj-\k)+T(\prevj,\k)*(\sunx, \suny, \sunz)$)
      -- ($(P-\j-\k)+T(\j,\k)*(\sunx, \suny, \sunz)$) -- cycle;
    }
  }
  
  % coordinate system $Oxyz$; first layer
  \draw[green!50!black]
  (0, 0, 0) -- (\ry, 0, 0)
  (0, 0, 0) -- (0, 0, \ry);

  % "squares"---the mesh
  \foreach \k [remember=\k as \prevk (initially 0)] in {1, ..., \Nz}{%
    \foreach \j [remember=\j as \prevj (initially 0)] in {1, ..., \Ny}{%
      \ifthenelse{\test{\j,\k}=1}{
        \ifthenelse{\tmp{\j,\k}>0}{
          \filldraw[white!\tmpW{\j,\k}!B]
          (P-\j-\prevk) -- (P-\prevj-\prevk)
          -- (P-\prevj-\k) --(P-\j-\k) -- cycle;
        }{%
          \filldraw[black!\tmpK{\j,\k}!B]
          (P-\j-\prevk) -- (P-\prevj-\prevk)
          -- (P-\prevj-\k) --(P-\j-\k) -- cycle;
        }
      }{}
    }
  }

  % longitude cycle
  \foreach \k [remember=\k as \prevk (initially 0)] in {1, ..., \Nz}{%
    \ifthenelse{\test{\aj,\k}=1}{
      \draw[red, thick] (P-\aj-\k) -- (P-\aj-\prevk);
    }{
      \draw[red, very thin, opacity=.4] (P-\aj-\k) -- (P-\aj-\prevk);
    }
  }

  % latitude cycle
  \foreach \j [remember=\j as \prevj (initially 0)] in {1, ..., \Ny}{%
    \ifthenelse{\test{\j,\ak}=1}{
      \draw[red, thick] (P-\j-\ak) -- (P-\prevj-\ak);
    }{
      \draw[red, very thin, opacity=.3] (P-\j-\ak) -- (P-\prevj-\ak);
    }
  }
  
  % coordinate system $Oxyz$; second layer
  \draw[green!50!black, -{Latex[length=5pt, width=5pt]}]
  (\ry+\rz, 0, 0) -- (8, 0, 0) node[right] {$x$};
  \draw[green!50!black, -{Latex[length=5pt, width=5pt]}]
  (0, 0, 0) -- (0, 6, 0) node[above] {$y$};
  \draw[green!50!black, -{Latex[length=5pt, width=5pt]}]
  (0, 0, \ry+\rz) -- (0, 0, 8) node[below left] {$z$};
\end{tikzpicture} 
\end{document}

相关内容