Bug 23014 - Backface-visibility cannot be tested by only looking at m33
Backface-visibility cannot be tested by only looking at m33
Status: NEW
Product: CSS
Classification: Unclassified
Component: Transforms
unspecified
PC All
: P2 normal
: ---
Assigned To: Simon Fraser
public-css-bugzilla
:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2013-08-19 22:00 UTC by Shawn Singh
Modified: 2015-03-31 23:58 UTC (History)
7 users (show)

See Also:


Attachments
Example of a transform that needs to account for perspective for correct backface-visibility. (1.01 KB, text/html)
2013-08-19 22:00 UTC, Shawn Singh
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Shawn Singh 2013-08-19 22:00:54 UTC
Created attachment 1386 [details]
Example of a transform that needs to account for perspective for correct backface-visibility.

According to the current spec, backface-visibility must be checked by testing whether the m33 element of the matrix is positive or negative. But the problem is that this only works without a perspective component.

Actually, it seems like this is already acknowledged since the spec says "before accounting for perspective". However, backface-visibility will often need to be tested on matrices that have perspective in them:
  - by definition of the "accumulated 3d transformation matrix" the perspective will be included in that matrix
  - a user may use the matrix3d() transform function in their CSS to specify arbitrary perspective on an element, even if it is not in a 3d rendering context.

I attached an example page with a layer rotated just more than 90 degrees.  In this configuration, the layer's backface would be visible if there is an orthographic projection.  Under perspective projection, if set up correctly, the layer's front side is actually visible.

The accumulated 3d transformation matrix of that example is:

[ +1.0574 +0.0000 +2.4037 +88.5277
  +1.2310 +1.0000 +0.4341 +53.7981
  -0.4924 +0.0000 -0.1736 +98.4808
  +0.0025 +0.0000 +0.0009 +0.5076 ]

The m33 element is negative, but because of perspective, we should interpret that the front side of the layer is facing the camera.

Instead of checking m33, there are at least two ways to correctly implement backface visibility:

(1) Inspect vertices of the element being drawn for clockwise / counter-clockwise, similar to how game engines often perform backface culling

(2) Rigorously apply the transform the z normal, and check the transformed normal is still facing the camera (i.e. it's z value is positive or negative).  However, we cannot just apply the transform to the z normal by doing a basic matrix*vector operation --> that is why checking m33 only is not correct.  Instead, mathematically if we want to transform a surface normal, we must do inverseTranspose(matrix) * normalVector.

Knowing that the normalVector is (0, 0, 1, 0), we don't have to compute a heavy full inverse, and the implementation can remain mostly lightweight.  You can see chrome's implementation here:
  https://code.google.com/p/chromium/codesearch#chromium/src/ui/gfx/transform.cc&q=gfx::Transform::IsBackFaceVisible&sq=package:chromium&type=cs&l=219

I have not tested how the attached example works in IE.  Firefox, Safari, and Chrome seem to get backface visibility correct on the attached example page -- which implies that no browser is checking for (m33 < 0) anyway.

So, would it work just to change the spec to be mathematically correct and to reflect what browsers already implement?
Comment 1 Dirk Schulze 2013-09-25 21:09:40 UTC
(In reply to Shawn Singh from comment #0)
> Created attachment 1386 [details]
> Example of a transform that needs to account for perspective for correct
> backface-visibility.
> 
> According to the current spec, backface-visibility must be checked by
> testing whether the m33 element of the matrix is positive or negative. But
> the problem is that this only works without a perspective component.
> 
> Actually, it seems like this is already acknowledged since the spec says
> "before accounting for perspective". However, backface-visibility will often
> need to be tested on matrices that have perspective in them:
>   - by definition of the "accumulated 3d transformation matrix" the
> perspective will be included in that matrix
>   - a user may use the matrix3d() transform function in their CSS to specify
> arbitrary perspective on an element, even if it is not in a 3d rendering
> context.
> 
> I attached an example page with a layer rotated just more than 90 degrees. 
> In this configuration, the layer's backface would be visible if there is an
> orthographic projection.  Under perspective projection, if set up correctly,
> the layer's front side is actually visible.
> 
> The accumulated 3d transformation matrix of that example is:
> 
> [ +1.0574 +0.0000 +2.4037 +88.5277
>   +1.2310 +1.0000 +0.4341 +53.7981
>   -0.4924 +0.0000 -0.1736 +98.4808
>   +0.0025 +0.0000 +0.0009 +0.5076 ]
> 
> The m33 element is negative, but because of perspective, we should interpret
> that the front side of the layer is facing the camera.
> 
> Instead of checking m33, there are at least two ways to correctly implement
> backface visibility:
> 
> (1) Inspect vertices of the element being drawn for clockwise /
> counter-clockwise, similar to how game engines often perform backface culling
> 
> (2) Rigorously apply the transform the z normal, and check the transformed
> normal is still facing the camera (i.e. it's z value is positive or
> negative).  However, we cannot just apply the transform to the z normal by
> doing a basic matrix*vector operation --> that is why checking m33 only is
> not correct.  Instead, mathematically if we want to transform a surface
> normal, we must do inverseTranspose(matrix) * normalVector.
> 
> Knowing that the normalVector is (0, 0, 1, 0), we don't have to compute a
> heavy full inverse, and the implementation can remain mostly lightweight. 
> You can see chrome's implementation here:
>  
> https://code.google.com/p/chromium/codesearch#chromium/src/ui/gfx/transform.
> cc&q=gfx::Transform::IsBackFaceVisible&sq=package:chromium&type=cs&l=219
> 
> I have not tested how the attached example works in IE.  Firefox, Safari,
> and Chrome seem to get backface visibility correct on the attached example
> page -- which implies that no browser is checking for (m33 < 0) anyway.
> 
> So, would it work just to change the spec to be mathematically correct and
> to reflect what browsers already implement?

We already seek requests from browser vendors for that. Thank you very much for your input. I agree that we need to take the perspective into account. Looking at m33 is not enough. I still need to check the math. Do you have a source for the suggested method other than a source file in Chrome? I suggest posting this conclusion on the mailing list so that hopefully more people will read this.
Comment 2 Tien-Ren Chen 2015-03-31 23:58:10 UTC
Replying to Shawn's comment. The proposal (1) of using winding direction won't work because we won't be able to distinguish rotateY(180deg) and scaleX(-1) that way. Both will change the winding direction, but rotateY(180deg) should be considered the plane being rotated to the back face while scaleX(-1) should be considered the contents on the plane being mirrored.

However I found there is an alternative to proposal (2) when I studied some perspective bug in Chromium. Here is my abandoned CL: https://codereview.chromium.org/1047463002/

For a short summary,
Proposal (2) calculates det|M33| * det|M| < 0
My proposal calculates det|M33| * det|M34| > 0

Both formulas behave the same on regular matrices that does xyz transform first and applying a positive perspective at last. The difference stands where they handle negative perspective. i.e. The farther in z, the bigger the object seems. The proposal (2) prefers xyz space, where my formula prefers xyw space.

That is, for perspective(-1px), proposal (2) says front, my proposal says back.

Note that my formula runs 3x faster with finite perspective, and 2x faster with infinite perspective. Due to avoiding to calculate 4x4 determinant.

I think the spec should be clear about how to handle the case where the forth row of the matrix can't be generated by simple positive perspective. (Negative perspective is one problem. We can also have w depending on other rows.)