
Multicolumn[Graphics3D[{SurfaceAppearance["TextureShading", Texture[disks]],
Tube[BSplineCurve[KnotData[#, "SpaceCurve"] /@ Subdivide[0, 2 Pi, 100],
SplineClosed -> True], .4]},
Boxed -> False, ImageSize -> Medium, ViewPoint -> {0, 0, 5}] & /@
{{"PretzelKnot", {5, 3, 2}}, "FigureEight",
{"TorusKnot", {3, 5}}, {"TorusKnot", {4, 9}}}, 2]
Update: In versions 12.1+, we can use the directive SurfaceAppearance["TextureShading", Texture[img]] to texturize any surface with img:
reg = TriangulateMesh[BoundaryDiscretizeRegion[Rectangle[]], MaxCellMeasure -> .02];
disks = Graphics[{Red, MeshPrimitives[reg, 2] /. Polygon -> (Apply[Disk] @* Insphere)}];
Graphics3D[{SurfaceAppearance["TextureShading", Texture[disks]],
KnotData["Trefoil", "ImageData"]},
Boxed -> False, ImageSize -> Large]

We can construct a Tube with the desired radius using KnotData["Trefoil", "SpaceCurve"]:
Graphics3D[{SurfaceAppearance["TextureShading", Texture[disks]],
Tube[BSplineCurve[KnotData["Trefoil", "SpaceCurve"] /@ Subdivide[0, 2 Pi, 100],
SplineClosed -> True], .5]},
Boxed -> False, ImageSize -> Large]

Alternatively, we can use SurfaceAppearance["TextureShading", Texture[disks]] as the setting for PlotStyle in ParametricPlot3D in cvgmt's answer:
ParametricPlot3D[curve3[t] + .6 (Cos[u] normal + Sin[u] binormal),
{u, 0, 2 π}, {t, 0, 2 π}, PlotPoints -> 80, Mesh -> None,
Boxed -> False, Axes -> False,
PlotStyle -> SurfaceAppearance["TextureShading", Texture[disks]],
ViewPoint -> {0.2, -0.3, 3.3}, Lighting->"Neutral"]

Original answer:
We can use the new-in-12.1 directive HalfToneShading:
Graphics3D[{HalftoneShading[#, Red], KnotData["Trefoil", "ImageData"]},
Lighting -> "Neutral", ImageSize -> 250, Boxed -> False,
ViewPoint -> {1.5, -1.5, 4.}] & /@ {.3, .5, .7} // Row

Needless to say, this approach is not match for cvgmt's approach in terms of flexibility and beauty of the pictures produced.
To get some flexibility in controlling the density of shapes, we can use the options of SurfaceAppearance to define a directive with options:
Options[surfaceAppearance] = {"StepCount" -> 1, "Tiling" -> {5, 5},
"FeatureColor" -> Red, "UseScreenSpace" -> 0, "IsTwoTone" -> 1,
"LuminanceModifier" -> 0.0, "Shape" -> "Disk"};
surfaceAppearance[opts : OptionsPattern[surfaceAppearance]] :=
SurfaceAppearance["RampShading",
Sequence @@ FilterRules[{opts, Options[surfaceAppearance]}, Except["Shape"]],
"Arguments" -> {"HalftoneShading", 0.5, Red, OptionValue["Shape"]},
EdgeForm[], Texture["HalftoneShading" <> OptionValue["Shape"]]]
Examples:
Graphics3D[{surfaceAppearance[], KnotData["Trefoil", "ImageData"]},
Lighting -> "Accent", Boxed -> False, ViewPoint -> {1.5, -1.5, 4.}]

Use surfaceAppearance["Tiling" -> {15, 15}] to get:

Use surfaceAppearance["UseScreenSpace" -> 1, "StepCount" -> 2, "Tiling" -> {7, 7}] to get:

Use surfaceAppearance["Tiling" -> {15, 15}, "Shape"->"Triangle"] to get:

Use surfaceAppearance["StepCount" -> 3,"Tiling" -> {10,10},"Shape" -> "Hexagon"] to get:
