<?xml version="1.0" encoding="UTF-8" standalone="yes"?><oembed><version><![CDATA[1.0]]></version><provider_name><![CDATA[The ryg blog]]></provider_name><provider_url><![CDATA[https://fgiesen.wordpress.com]]></provider_url><author_name><![CDATA[fgiesen]]></author_name><author_url><![CDATA[https://fgiesen.wordpress.com/author/fgiesen/]]></author_url><title><![CDATA[Frustum planes from the projection&nbsp;matrix]]></title><type><![CDATA[link]]></type><html><![CDATA[<p>Another quick one. Now this is another old trick, but it&#8217;s easy to derive and still not as well-known as it deserves to be, so here goes.</p>
<p>All modern graphics APIs ultimately expect vertex coordinates to end up in one common coordinate system where clipping is done &#8211; clip space. That&#8217;s the space that vertices passed to the rasterizer are expected in &#8211; and hence, the space that Vertex Shaders (or Geometry Shaders, or Domain/Tessellation Evaluation Shaders) transform to. These shaders can do what they want, but the usual setup matches the original fixed-function pipeline and splits vertex transformations into at least two steps: The projection transform and the model-view transform, both of which can be represented as homogeneous 4&#215;4 matrices.</p>
<p>The projection transform is the part that transforms vertices from camera view space to clip space. A view-space input vertex position v is transformed with the projection matrix P and gives us the position of the vertex in clip space:</p>
<p><img src="https://s0.wp.com/latex.php?latex=%5Cdisplaystyle+%5Cbegin%7Bpmatrix%7D+x+%5C%5C+y+%5C%5C+z+%5C%5C+w+%5Cend%7Bpmatrix%7D+%3D+P+v+%3D+%5Cbegin%7Bpmatrix%7D+p_1%5ET+%5C%5C+p_2%5ET+%5C%5C+p_3%5ET+%5C%5C+p_4%5ET+%5Cend%7Bpmatrix%7D+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=%5Cdisplaystyle+%5Cbegin%7Bpmatrix%7D+x+%5C%5C+y+%5C%5C+z+%5C%5C+w+%5Cend%7Bpmatrix%7D+%3D+P+v+%3D+%5Cbegin%7Bpmatrix%7D+p_1%5ET+%5C%5C+p_2%5ET+%5C%5C+p_3%5ET+%5C%5C+p_4%5ET+%5Cend%7Bpmatrix%7D+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=%5Cdisplaystyle+%5Cbegin%7Bpmatrix%7D+x+%5C%5C+y+%5C%5C+z+%5C%5C+w+%5Cend%7Bpmatrix%7D+%3D+P+v+%3D+%5Cbegin%7Bpmatrix%7D+p_1%5ET+%5C%5C+p_2%5ET+%5C%5C+p_3%5ET+%5C%5C+p_4%5ET+%5Cend%7Bpmatrix%7D+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="&#92;displaystyle &#92;begin{pmatrix} x &#92;&#92; y &#92;&#92; z &#92;&#92; w &#92;end{pmatrix} = P v = &#92;begin{pmatrix} p_1^T &#92;&#92; p_2^T &#92;&#92; p_3^T &#92;&#92; p_4^T &#92;end{pmatrix} v" class="latex" /></p>
<p>Here, I&#8217;ve split P up into its four row vectors p<sub>1</sub><sup>T</sup>, &hellip;, p<sub>4</sub><sup>T</sup>. Now, in clip space, the view frustum has a really simple form, but there&#8217;s two slightly different formulations in use. GL uses the symmetric form:</p>
<p><img src="https://s0.wp.com/latex.php?latex=-w+%5Cle+x+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=-w+%5Cle+x+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=-w+%5Cle+x+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="-w &#92;le x &#92;le w" class="latex" /><br />
<img src="https://s0.wp.com/latex.php?latex=-w+%5Cle+y+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=-w+%5Cle+y+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=-w+%5Cle+y+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="-w &#92;le y &#92;le w" class="latex" /><br />
<img src="https://s0.wp.com/latex.php?latex=-w+%5Cle+z+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=-w+%5Cle+z+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=-w+%5Cle+z+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="-w &#92;le z &#92;le w" class="latex" /></p>
<p>whereas D3D replaces the last row with <img src="https://s0.wp.com/latex.php?latex=0+%5Cle+z+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=0+%5Cle+z+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=0+%5Cle+z+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="0 &#92;le z &#92;le w" class="latex" />. Either way, we get 6 distinct inequalities, each of which corresponds to exactly one clip plane: <img src="https://s0.wp.com/latex.php?latex=-w+%5Cle+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=-w+%5Cle+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=-w+%5Cle+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="-w &#92;le x" class="latex" /> is the left clip plane, <img src="https://s0.wp.com/latex.php?latex=x+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=x+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=x+%5Cle+w&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="x &#92;le w" class="latex" /> is the right clip plane, and so forth. Now from the equation above we know that <img src="https://s0.wp.com/latex.php?latex=x%3Dp_1%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=x%3Dp_1%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=x%3Dp_1%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="x=p_1^T v" class="latex" /> and <img src="https://s0.wp.com/latex.php?latex=w%3Dp_4%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=w%3Dp_4%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=w%3Dp_4%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="w=p_4^T v" class="latex" /> and hence</p>
<p><img src="https://s0.wp.com/latex.php?latex=-w+%5Cle+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=-w+%5Cle+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=-w+%5Cle+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="-w &#92;le x" class="latex" /><br />
<img src="https://s0.wp.com/latex.php?latex=%5CLeftrightarrow+0+%5Cle+w+%2B+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=%5CLeftrightarrow+0+%5Cle+w+%2B+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=%5CLeftrightarrow+0+%5Cle+w+%2B+x&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="&#92;Leftrightarrow 0 &#92;le w + x" class="latex" /><br />
<img src="https://s0.wp.com/latex.php?latex=%5CLeftrightarrow+0+%5Cle+p_4%5ET+v+%2B+p_1%5ET+v+%3D+%28p_4%5ET+%2B+p_1%5ET%29+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=%5CLeftrightarrow+0+%5Cle+p_4%5ET+v+%2B+p_1%5ET+v+%3D+%28p_4%5ET+%2B+p_1%5ET%29+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=%5CLeftrightarrow+0+%5Cle+p_4%5ET+v+%2B+p_1%5ET+v+%3D+%28p_4%5ET+%2B+p_1%5ET%29+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="&#92;Leftrightarrow 0 &#92;le p_4^T v + p_1^T v = (p_4^T + p_1^T) v" class="latex" /></p>
<p>Or in words, v lies in the non-negative half-space defined by the plane p<sub>4</sub><sup>T</sup>+p<sub>1</sub><sup>T</sup> &#8211; we have a view-space plane equation for the left frustum plane! For the right plane, we similarly get</p>
<p><img src="https://s0.wp.com/latex.php?latex=x+%5Cle+w+%5CLeftrightarrow+0+%5Cle+w+-+x+%5CLeftrightarrow+0+%5Cle+%28p_4%5ET+-+p_1%5ET%29+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=x+%5Cle+w+%5CLeftrightarrow+0+%5Cle+w+-+x+%5CLeftrightarrow+0+%5Cle+%28p_4%5ET+-+p_1%5ET%29+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=x+%5Cle+w+%5CLeftrightarrow+0+%5Cle+w+-+x+%5CLeftrightarrow+0+%5Cle+%28p_4%5ET+-+p_1%5ET%29+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="x &#92;le w &#92;Leftrightarrow 0 &#92;le w - x &#92;Leftrightarrow 0 &#92;le (p_4^T - p_1^T) v" class="latex" /></p>
<p>and in general, for the GL-style frustum we find that the six frustum planes in view space are exactly the six planes p<sub>4</sub><sup>T</sup>&plusmn;p<sub>i</sub><sup>T</sup> for i=1, 2, 3 &#8211; all you have to do to get the plane equations is to add (or subtract) the right rows of the projection matrix! For a D3D-style frustum, the near plane <img src="https://s0.wp.com/latex.php?latex=0+%5Cle+z&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=0+%5Cle+z&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=0+%5Cle+z&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="0 &#92;le z" class="latex" /> is different, but it takes the even simpler form <img src="https://s0.wp.com/latex.php?latex=0+%5Cle+p_3%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002" srcset="https://s0.wp.com/latex.php?latex=0+%5Cle+p_3%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002 1x, https://s0.wp.com/latex.php?latex=0+%5Cle+p_3%5ET+v&#038;bg=ffffff&#038;fg=000&#038;s=0&#038;c=20201002&#038;zoom=4.5 4x" alt="0 &#92;le p_3^T v" class="latex" />, so it&#8217;s simply defined by the third row of the projection matrix.</p>
<p>Deriving frustum planes from your projection matrix in this way has the advantage that it&#8217;s nice and general &#8211; it works with any projection matrix, and is guaranteed to agree with the clipping / culling done by the GPU, as long as the planes are in fact derived from the projection matrix used for rendering.</p>
<p>And if you need the frustum planes in some other space, say in model space: not too worry! We didn&#8217;t use any special properties of P &#8211; the derivation works for <em>any</em> 4&#215;4 matrix. The planes obtained this way are in whatever space the input matrix transforms to clip space &#8211; in the case of P, view space, but it can be anything. To give an example, if you have a model-view matrix M, then PM is the combined matrix that takes us from model-space to clip-space, and extracting the planes from PM instead of P will result in model-space clip planes.</p>
]]></html></oembed>