<p>The issue isn't the matrix, it's the row order. In WebGPU, matrices are stored in column-major order, meaning that first elements in the array are actually in the first column of the matrix, not the first row.</p>
<p>I created the first matrix with this code:</p>
<pre><code>var shadow_matrix = new Float32Array([
    l[1], -l[0], 0,    0,
    0,     0,    0,    0,
    0,    -l[2], l[1], 0,
    0,    -1,    0,    l[1]
]);
</code></pre>
<p>but because of the column-major order, this actually meant that the matrix in the shader looked like this:
<span class="math-container">\begin{bmatrix}
l_y &amp; 0 &amp; 0 &amp; 0 \\
-l_x &amp; 0 &amp; -l_z &amp; -1 \\
0 &amp; 0 &amp; l_y &amp; 0 \\
0 &amp; 0 &amp; 0 &amp; l_y
\end{bmatrix}</span></p>
<p>The second matrix, however, was written the correct way:</p>
<pre><code>shadow_matrix[0]  = dotNL - l[0] * n[0];
shadow_matrix[4]  = -l[0] * n[1];
shadow_matrix[8]  = -l[0] * n[2];
shadow_matrix[12] = -l[0] * n[3];
//etc
</code></pre>
<p>which meant that it also worked correctly. Additionally, in the first matrix, <span class="math-container">$l_w$</span> is not present as it is by default equal to 1. It seems that the second matrix is refined so it can calculate both point lights and directional lights - if <span class="math-container">$l_w = 0$</span>, the light is directional, and if <span class="math-container">$l_w = 1$</span>, it's a point light.</p>

CC BY-SA 4.0
2024-08-09T15:54:19.720Z
2024-08-09T16:19:00.920Z
2024-08-09T16:19:00.920Z
George
<p>I have a scene with a floating cube and a plane y=0. I want to create a simple planar shadow of the cube on the plane. To do this, I just have to project the vertices of the cube to the plane. I can do that with a matrix - Many sources (such as <a href="https://www.realtimerendering.com/" rel="nofollow noreferrer">Real Time Rendering</a>) use the exact matrix below. I understand the derivation of it, and when trying a bunch of points manually it always correctly projects them onto the plane.</p>
<p><span class="math-container">\begin{bmatrix}
\vec{n} \cdot \vec{l} + d - n_x l_x &amp; - n_y l_x &amp; - n_z l_x &amp; - d l_x \\
-n_x l_y &amp; \vec{n} \cdot \vec{l} + d - n_y l_y &amp; - n_z l_y &amp; - d l_y \\
-n_x l_z &amp; - n_y l_z &amp; \vec{n} \cdot \vec{l} + d - n_z l_z &amp; - d l_z \\
-n_x  &amp; - n_y &amp; - n_z &amp; \vec{n} \cdot \vec{l}
\end{bmatrix}</span></p>
<p>There's also the more simplified version when the plane is just y=0, since its normal is [0, 1, 0]:</p>
<p><span class="math-container">\begin{bmatrix}
l_y &amp; -l_x &amp; 0 &amp; 0 \\
0 &amp; 0 &amp; 0 &amp; 0 \\
0 &amp; -l_z &amp; l_y &amp; 0 \\
0 &amp; -1 &amp; 0 &amp; l_y
\end{bmatrix}</span></p>
<p>However, both of these matrices wield the same incorrect result in my WebGPU code. This is the result when light vector is
<span class="math-container">$$
\vec{l} = \begin{bmatrix}1 &amp; 1 &amp; 0\end{bmatrix}' :
$$</span></p>
<p><a href="https://i.sstatic.net/BlfIPAzu.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/BlfIPAzu.png" alt="Incorrectly projected shadow" /></a></p>
<p>The vertices don't get correctly projected.</p>
<p>On the other hand, I found <a href="http://www.it.hiof.no/%7Eborres/j3d/explain/shadow/p-shadow.html#:%7E:text=Projection%20shadows%20are%20a%20simple,triangle%20and%20a%20light%20source." rel="nofollow noreferrer">this website</a> which features a different shadow matrix:</p>
<p><span class="math-container">\begin{bmatrix}
\vec{n} \cdot \vec{l} - n_x l_x &amp; -n_x l_y &amp; -n_x l_z &amp; -n_x l_w \\
-n_y l_x &amp; \vec{n} \cdot \vec{l} - n_y l_y &amp; -n_y l_z &amp; -n_y l_w \\
-n_z l_x &amp; -n_z l_y &amp; \vec{n} \cdot \vec{l} - n_z l_z &amp; -n_z l_w \\
-n_w l_x &amp; -n_w l_y &amp; -n_w l_z &amp; \vec{n} \cdot \vec{l} - n_w l_w
\end{bmatrix}</span></p>
<p>On the website, this matrix is not explained, saying the algorithm for it is &quot;well known&quot;, despite the matrix being different from the one above. But it projects the points perfectly!</p>
<p><a href="https://i.sstatic.net/kEQyeg2b.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/kEQyeg2b.png" alt="Correctly projected shadow" /></a></p>
<p>I'm trying to wrap my head around why the first matrix doesn't work, despite it being objectively correct, and why the second matrix does. Can anyone explain?</p>
<p>This is my vertex shader code for reference, I apply shadow transform after model transform but before view and projection transforms.</p>
<pre><code>vout.position = view_proj.m * shadow_transform.m * node_transform.m * float4(vin.position, 1.0);
</code></pre>

<projections><shadow>
Why does one matrix correctly project points to the plane, but another one doesn't?
George
# Why does one matrix correctly project points to the plane, but another one doesn't?

(Asked by: username_0 on 2024-08-09)

I have a scene with a floating cube and a plane y=0. I want to create a simple planar shadow of the cube on the plane. To do this, I just have to project the vertices of the cube to the plane. I can do that with a matrix - Many sources (such as [Real Time Rendering](https://www.realtimerendering.com/)) use the exact matrix below. I understand the derivation of it, and when trying a bunch of points manually it always correctly projects them onto the plane.

\begin{bmatrix}
\vec{n} \cdot \vec{l} + d - n\_x l\_x & - n\_y l\_x & - n\_z l\_x & - d l\_x \\
-n\_x l\_y & \vec{n} \cdot \vec{l} + d - n\_y l\_y & - n\_z l\_y & - d l\_y \\
-n\_x l\_z & - n\_y l\_z & \vec{n} \cdot \vec{l} + d - n\_z l\_z & - d l\_z \\
-n\_x & - n\_y & - n\_z & \vec{n} \cdot \vec{l}
\end{bmatrix}

There's also the more simplified version when the plane is just y=0, since its normal is [0, 1, 0]:

\begin{bmatrix}
l\_y & -l\_x & 0 & 0 \\
0 & 0 & 0 & 0 \\
0 & -l\_z & l\_y & 0 \\
0 & -1 & 0 & l\_y
\end{bmatrix}

However, both of these matrices wield the same incorrect result in my WebGPU code. This is the result when light vector is
$$
\vec{l} = \begin{bmatrix}1 & 1 & 0\end{bmatrix}' :
$$

[![Incorrectly projected shadow](https://i.sstatic.net/BlfIPAzu.png)](https://i.sstatic.net/BlfIPAzu.png)

The vertices don't get correctly projected.

On the other hand, I found [this website](http://www.it.hiof.no/%7Eborres/j3d/explain/shadow/p-shadow.html#:%7E:text=Projection%20shadows%20are%20a%20simple,triangle%20and%20a%20light%20source.) which features a different shadow matrix:

\begin{bmatrix}
\vec{n} \cdot \vec{l} - n\_x l\_x & -n\_x l\_y & -n\_x l\_z & -n\_x l\_w \\
-n\_y l\_x & \vec{n} \cdot \vec{l} - n\_y l\_y & -n\_y l\_z & -n\_y l\_w \\
-n\_z l\_x & -n\_z l\_y & \vec{n} \cdot \vec{l} - n\_z l\_z & -n\_z l\_w \\
-n\_w l\_x & -n\_w l\_y & -n\_w l\_z & \vec{n} \cdot \vec{l} - n\_w l\_w
\end{bmatrix}

On the website, this matrix is not explained, saying the algorithm for it is "well known", despite the matrix being different from the one above. But it projects the points perfectly!

[![Correctly projected shadow](https://i.sstatic.net/kEQyeg2b.png)](https://i.sstatic.net/kEQyeg2b.png)

I'm trying to wrap my head around why the first matrix doesn't work, despite it being objectively correct, and why the second matrix does. Can anyone explain?

This is my vertex shader code for reference, I apply shadow transform after model transform but before view and projection transforms.

```
vout.position = view_proj.m * shadow_transform.m * node_transform.m * float4(vin.position, 1.0);

```

**Tags:** projections, shadow


---

## 1 answers

### Answer by username_0 on 2024-08-09. Score: 0 | Accepted answer

The issue isn't the matrix, it's the row order. In WebGPU, matrices are stored in column-major order, meaning that first elements in the array are actually in the first column of the matrix, not the first row.

I created the first matrix with this code:

```
var shadow_matrix = new Float32Array([
    l[1], -l[0], 0,    0,
    0,     0,    0,    0,
    0,    -l[2], l[1], 0,
    0,    -1,    0,    l[1]
]);

```

but because of the column-major order, this actually meant that the matrix in the shader looked like this:
\begin{bmatrix}
l\_y & 0 & 0 & 0 \\
-l\_x & 0 & -l\_z & -1 \\
0 & 0 & l\_y & 0 \\
0 & 0 & 0 & l\_y
\end{bmatrix}

The second matrix, however, was written the correct way:

```
shadow_matrix[0]  = dotNL - l[0] * n[0];
shadow_matrix[4]  = -l[0] * n[1];
shadow_matrix[8]  = -l[0] * n[2];
shadow_matrix[12] = -l[0] * n[3];
//etc

```

which meant that it also worked correctly. Additionally, in the first matrix, $l\_w$ is not present as it is by default equal to 1. It seems that the second matrix is refined so it can calculate both point lights and directional lights - if $l\_w = 0$, the light is directional, and if $l\_w = 1$, it's a point light.


---

