metafizzy / zdog

Flat, round, designer-friendly pseudo-3D engine for canvas & SVG
https://zzz.dog
10.31k stars 391 forks source link

Perspective #2

Open desandro opened 5 years ago

desandro commented 5 years ago

Currently Zdog uses orthogonal perspective, where all projected lines are parallel with no vanishing point. This works nice with stroke volume — as stroke volume is applied to the entire shape uniformly. So, with the same shape, a corner close to the camera will have the same stroke as the corner far from the camera.

That said, adding perspective would be a good feature to add. It's a relatively low-complexity addition that provides a wide range of capability. Vanishing point perspective is an expected feature of any 3D library. Zdog may be special, but it should get it the same.

Add a 👍 reaction to this issue if you would like to see this feature added. Do not add +1 comments — They will be deleted.

timo-jj commented 5 years ago

Took a look to the doc and If I’m not wrong there is no shadow and light parameter.. It could add a nice depth to the shapes especially if they are moving. Maybe a suggestion to explore :)

motionarray commented 5 years ago

Yes, this. It'd be great to have shapes change perspective in relationship to where they are on a webpage as you scroll down.

shaunlebron commented 5 years ago

It's a relatively low-complexity addition

Is it though? How would you draw a simple circle, rotated on any 3D axis? With orthographic, rotated circles are just ellipses. With perspective, you either need a polygon conversion or some way to compute a spline to fit it. ·(I’d be curious to follow the spline math if that’s what you’re thinking)

jrus commented 5 years ago

The tricky thing about perspective is that the representation here is cubic Bézier segments. These are affine-invariant, which means that to apply any arbitrary affine transformation we can transform just the control points and the transformation of any arbitrary point on the curve will be correct. That is, if we call our affine transformation A, and our Bézier curve with control points [a, b, c, d] a parametric function f_[a, b, c, d] (t), we have

A(f_[a, b, c, d] (t)) = f_[A(a), A(b), A(c), A(d)] (t)

for every parameter value t. But perspective transformations are not in general affine.

Therefore, to apply a perspective transformation we need to do more work: in general there is no exact Bézier-curve (or multi-segment Bézier spline) representation of a perspective-transformed Bézier curve, so it is necessary to approximate the perspective-transformed curve by multiple Bézier segments to within some desired level of tolerance.

To implement this feature therefore requires (1) an algorithm for determining the control points of the best (or at least some) approximation of a perspective-transformed segment by a single Bézier segment, possibly under some reparametrization, (2) some kind of error metric defining the difference between two curves, (3) some scheme for subdividing the original curve, optionally optimizing the split points to reduce the number of required curves.

It turns out to be just about as easy (or hard) to support applying arbitrary transformation functions as it is to just support perspective transforms.

Let me recommend as one pretty performant method, https://observablehq.com/@jrus/bezplot

@desandro if you want help with this feature shoot me an email or something.

jrus commented 5 years ago

It's a relatively low-complexity addition

Is it though? How would you draw a simple circle, rotated on any 3D axis? With orthographic, rotated circles are just ellipses. With perspective, you either need a polygon conversion or some way to compute a spline to fit it. ·(I’d be curious to follow the spline math if that’s what you’re thinking)

Many perspective transformations of circles are ellipses. Of course, if you go far enough you get hyperbolas instead, and then need to approximate those with cubic polynomial segments.

desandro commented 5 years ago

FWIW My approach would be to add a simple scale multiply on render points given the z-distance from the origin. Definitely not mathematically accurate, but it could cover 80% of use cases in like 4 lines of code. Zdog is not trying to be the ultimate 3D engine, more like a lightweight 3D illustration tool.

benthillerkus commented 5 years ago

So, I've just tried out doing this the super naive way:

let fov = -150;
function scale(z) {return fov/(fov+z)};

And then on each wrapper function for canvas rendering I added:

CanvasRenderer.line = function( ctx, elem, point ) {
  let s = scale(point.z);
  ctx.lineTo( point.x * s, point.y * s);
};

Obviously this can't handle tapering of strokes, setting the fov in the factory / as a property or have the user set custom clipping planes... Besides that this does work pretty well and I can't really see any weird distortions or wobbles on beziers.

image image image

It does however break the cone shape completely (for some reason it even animates itself), produces slight wobbling on Polygons (aka the Z-Dog-Charme) and it pretty much renders every single occlusion hack useless. image image

Still I'd be super glad if you've decided to implement this properly and ship it in the next version of Z-Dog ✌