<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Carmen Cincotti</title>
    <description>The latest articles on DEV Community by Carmen Cincotti (@carmencincotti).</description>
    <link>https://dev.to/carmencincotti</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F851845%2F3a72083c-6520-469e-9eeb-9476aec4ca4b.jpeg</url>
      <title>DEV Community: Carmen Cincotti</title>
      <link>https://dev.to/carmencincotti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carmencincotti"/>
    <language>en</language>
    <item>
      <title>Lets Look At Magic LookAt Matrices</title>
      <dc:creator>Carmen Cincotti</dc:creator>
      <pubDate>Mon, 25 Apr 2022 13:12:03 +0000</pubDate>
      <link>https://dev.to/carmencincotti/lets-look-at-magic-lookat-matrices-1c7o</link>
      <guid>https://dev.to/carmencincotti/lets-look-at-magic-lookat-matrices-1c7o</guid>
      <description>&lt;h2&gt;
  
  
  The Magic of the LookAt Matrix
&lt;/h2&gt;

&lt;p&gt;I find math to sometimes be hard, sometimes fun, sometimes magical, and sometimes hard-fun-magical. &lt;strong&gt;Linear Algebra&lt;/strong&gt;¹  is the mathematics behind a lot of fun tech, such as VR, AR, graphics, machine learning, data science buzz words etc.&lt;/p&gt;

&lt;p&gt;I’ve been on a WebGPU 3D computer graphics kick lately (&lt;a href="https://dev.to/carmencincotti/drawing-a-triangle-with-webgpu-1mk3"&gt;last week I wrote about making a triangle in WebGPU&lt;/a&gt;). When it came time to implement a camera, I figured I could just instantiate some sort of camera object and move on.&lt;/p&gt;

&lt;p&gt;I quickly learned that &lt;strong&gt;the camera in 3D graphics does not exist.&lt;/strong&gt; &lt;em&gt;It’s all smoke and mirrors&lt;/em&gt;. We give the illusion that it does exist through the magic of linear algebra. Let’s see what I mean by taking &lt;em&gt;a look at&lt;/em&gt; (ha!) &lt;strong&gt;the LookAt matrix.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The&lt;/strong&gt; &lt;strong&gt;LookAt Matrix&lt;/strong&gt; is a great exercise in linear algebra. It encompasses the usage of the &lt;strong&gt;dot product&lt;/strong&gt; and &lt;strong&gt;cross product&lt;/strong&gt;. It involves &lt;strong&gt;vectors&lt;/strong&gt;. It involves &lt;strong&gt;matrices.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anyway, it turned out to be a fun learning opportunity to really drive some key math concepts down. I’d like to share this knowledge with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Theory
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The LookAt matrix&lt;/strong&gt; is a matrix that transforms something to look at a point in space. Let’s keep our discussion limited to the application of &lt;strong&gt;the LookAt Matrix&lt;/strong&gt; to &lt;em&gt;cameras&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Namely, we can use &lt;strong&gt;the LookAt matrix&lt;/strong&gt; to transform the positions of the objects within the 3D scene to &lt;em&gt;give the illusion&lt;/em&gt; that they are being viewed from the lens of the camera.&lt;/p&gt;

&lt;p&gt;Let’s take as an example a 3D scene containing a camera and a 3D ball — and we apply a LookAt matrix to the camera &lt;strong&gt;that transforms it&lt;/strong&gt; to view a red ball from a certain position in 3D, we might expect to see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XABq3ple--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AO_19UJVrvcz4lLoFzZNv2Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XABq3ple--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AO_19UJVrvcz4lLoFzZNv2Q.png" alt="A camera looking at a red ball" width="596" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A camera looking at a red ball after applying &lt;strong&gt;the LookAt matrix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Additionally, we might &lt;em&gt;instead&lt;/em&gt; want to view the world  &lt;strong&gt;from the lens of our camera&lt;/strong&gt; &lt;em&gt;(which is a much more common application)&lt;/em&gt;… so we’ll transform the ball / plane instead &lt;em&gt;to give the illusion&lt;/em&gt; that we’re viewing it from the perspective of the camera:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dnaAzLhT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A-UUltrOesmv8XMPZyBGEYQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dnaAzLhT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A-UUltrOesmv8XMPZyBGEYQ.png" alt="A red ball from the perspective of a camera" width="661" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What we might see if we looked through the camera at our scene.&lt;/p&gt;

&lt;p&gt;Over the next few sections, we’ll see how we can calculate both of these views. One where we move the camera (and the world remains constant), and where the camera remains static (at origin, looking down the negative z-axis) and the world moves instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Code
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Poof!&lt;/em&gt; Here’s the magic trick in full (I prototyped it for a WebGPU app, so it’s in Javascript).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;⚠️ &lt;em&gt;Take note that I am actually calculating the LookAt Matrix that we would use to move scene objects in relation to the camera (explanation at the end of the article).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Calculations
&lt;/h2&gt;

&lt;p&gt;Solving for this matrix is really attempting to model this camera through the careful calculation of it’s coordinate system in relation to world space. Or more simply put, we need to find the vectors &lt;code&gt;forwardVector&lt;/code&gt; , &lt;code&gt;upVector&lt;/code&gt; , and &lt;code&gt;rightVector&lt;/code&gt; of the camera in relation to the ball’s coordinate system.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This feels a bit like sorcery.&lt;/em&gt; We can take &lt;em&gt;very&lt;/em&gt; minimal information and, in the end, come up with an entire matrix representing an orthonormal coordinate system like shown in the image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jvQCcOZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A4EY6XWVxm1-Kia0lSVygAQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jvQCcOZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A4EY6XWVxm1-Kia0lSVygAQ.png" alt="A 3D scene showing a camera looking at a red ball. There is a 3D gizmo illustrating the up, right, and forward vectors." width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A representation of a 3D scene where a camera is looking at a red ball. Notice how the forward vector points towards the camera.&lt;/p&gt;

&lt;p&gt;We’ll start our journey through this &lt;em&gt;Magical Math forest,&lt;/em&gt; knowing that our camera here has a certain position in space, as does our red ball. &lt;em&gt;We’ll also assume the conventions of a right-handed coordinate system,&lt;/em&gt; so we’re all on the same page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step One: Calculate the Forward Axis direction
&lt;/h3&gt;

&lt;p&gt;This is actually very doable. Given our camera position and red ball position, we can calculate the direction of the &lt;code&gt;forwardVector&lt;/code&gt; through vector subtraction:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;forwardVector = normalize(cameraPosition — redBallPosition)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;em&gt;Remember to&lt;/em&gt; &lt;strong&gt;&lt;em&gt;normalize&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;the result of the vector subtraction, since we want the directional vector, which is a unit vector.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Step one, done. Let’s hop on our broomsticks to Step Two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Two: Calculate the Right Axis direction
&lt;/h3&gt;

&lt;p&gt;This step involves &lt;em&gt;quite a bit of&lt;/em&gt; &lt;em&gt;witchcraft&lt;/em&gt;, and I’m not a huge fan of the handwavy-ness of the steps that follow but, hey, &lt;em&gt;math is magic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s list out some things we know to be true first before arriving at our next step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  We are re-constructing an orthonormal 3D coordinate system based on the position and rotation of our camera&lt;/li&gt;
&lt;li&gt;  The right axis that we are looking for is orthogonal to the forward axis and the up axis (&lt;em&gt;which is still unknown, but disregard that for now&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was a lot to say, but given all that, we can conclude that we need to find the &lt;strong&gt;cross product&lt;/strong&gt; of the forward and up axes. Wikipedia describes the &lt;strong&gt;cross product&lt;/strong&gt; as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given two linearly independent vectors &lt;strong&gt;a&lt;/strong&gt; and &lt;strong&gt;b&lt;/strong&gt;, the cross product, &lt;strong&gt;a&lt;/strong&gt; × &lt;strong&gt;b&lt;/strong&gt; (read “a cross b”), is a vector that is perpendicular to both &lt;strong&gt;a&lt;/strong&gt; and &lt;strong&gt;b&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YeQxLLJO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AUom4SbyxSqVxhsDsN_9YNg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YeQxLLJO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AUom4SbyxSqVxhsDsN_9YNg.png" alt="A visual representation of a cross product" width="784" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An example of the cross product — using the up and forward axes, we can find a vector perpendicular to both of them — the right axis.&lt;/p&gt;

&lt;p&gt;OK, well &lt;em&gt;we know the forward vector&lt;/em&gt; (since we solved for it in Step One).&lt;/p&gt;

&lt;p&gt;BUT, we still lack the up vector. Are we doomed? Nope… &lt;strong&gt;we just need magic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here’s the trick&lt;/em&gt;: We just need any-old vector that falls within the same plane formed by the forward and up vector… &lt;em&gt;and not necessarily&lt;/em&gt; the actual up vector of the camera itself. A common convention in this case is to use (0, 1, 0) as the up vector.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LU0d6M8r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AhOcKK0nIeIAEYqm8sp2wUw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LU0d6M8r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AhOcKK0nIeIAEYqm8sp2wUw.png" alt="A visual representation of the cross product, with the temporary up vector" width="714" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trick — we can define a tempUpVector in place of the actual up axis, since it will still lie within the plane formed by the up and forward axes.&lt;/p&gt;

&lt;p&gt;Finally, we can solve for the &lt;code&gt;rightVector&lt;/code&gt; by doing the following calculation:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;tempUpVector = (0, 1, 0)&lt;br&gt;&lt;br&gt;
rightVector = normalize(cross(tempUpVector, forwardVector))&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Three: Calculate the Up Axis direction
&lt;/h3&gt;

&lt;p&gt;It’s all smooth flying from here. We now know the &lt;code&gt;rightVector&lt;/code&gt; and the &lt;code&gt;forwardVector&lt;/code&gt;… so given our earlier assumptions (these three vectors are orthonormal), we know that to find the &lt;code&gt;upVector&lt;/code&gt;&lt;em&gt;,&lt;/em&gt; we need to find the &lt;strong&gt;cross product&lt;/strong&gt; of the &lt;code&gt;forwardVector&lt;/code&gt; and &lt;code&gt;rightVector&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;upVector = normalize(cross(forwardVector, rightVector))&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And that’s that. We have successfully calculated our 3 directional vectors.&lt;/p&gt;

&lt;p&gt;Now we could start building our &lt;strong&gt;LookAt matrix&lt;/strong&gt;, but we’re still missing a key component: &lt;em&gt;the translation component&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Four: Calculate the Camera Translation Vector
&lt;/h3&gt;

&lt;p&gt;Now we need to put on our &lt;em&gt;Thinking Witch Hats&lt;/em&gt; to figure out how we will translate our camera so it gets to the position that we want it to be in our coordinate system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not just translate it by the camera position we used to calculate the &lt;code&gt;_forwardVector_&lt;/code&gt; ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s a great question. Let’s look at an example.&lt;/p&gt;

&lt;p&gt;Let’s assume that our camera was originally at the 3D position in world coordinates &lt;strong&gt;(0,4,4)&lt;/strong&gt; and our red ball is at origin &lt;strong&gt;(0,0,0).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--67z0h5uf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AEpEBqR1eQCK3IGnYzFIgEw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--67z0h5uf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AEpEBqR1eQCK3IGnYzFIgEw.png" alt="Camera looking at red ball" width="747" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A naive approach where we assume that the camera is translating along the axes defined on the bottom left (the blue/green arrows).&lt;/p&gt;

&lt;p&gt;On the face of it, this looks like we should be able to just translate the camera by &lt;strong&gt;(0,4,4)&lt;/strong&gt;… but it’s missing a key understanding of the order of matrix operations that I neglected to clarify earlier but will do now to drive home a point:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are rotating our camera first, then translating.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;em&gt;This is mainly convention, and it’s up to you to decide the order in which you want to perform matrix operations… but if you don’t follow the way I present it to you here, you will not arrive at the same result.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Basically, we need to think about how to translate the camera &lt;strong&gt;after having rotated it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pCzath2A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ARLQu6lGE4Pe0jtqrfVe5UQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pCzath2A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ARLQu6lGE4Pe0jtqrfVe5UQ.png" alt="Visual of how to calculate the camera translation vector" width="606" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The correct approach where we rotate first, then translate along our new axes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real translation vector of the camera would be (0, 0, 5.65).&lt;/strong&gt; since we are changing the coordinate basis (while maintaining its orthonormal properties).&lt;/p&gt;

&lt;p&gt;The operation that we need to use to figure this out can be simplified by using &lt;strong&gt;the dot product&lt;/strong&gt;. Read more about it &lt;a href="https://mathinsight.org/dot_product"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short: the dot product will return the magnitude in which two vectors project over each other. Therefore, it will return zero for orthogonal vectors since they are perfectly not-overlapping each other. Not even a little bit. Here’s what the calculation may look like:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;translationX = dot(positionOfCamera, rightVector);&lt;br&gt;&lt;br&gt;
translationY = dot(positionOfCamera, upVector);&lt;br&gt;&lt;br&gt;
translationZ = dot(positionOfCamera, forwardVector);&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;⚠️&lt;em&gt;Remember to define&lt;/em&gt; &lt;code&gt;_rightVector_&lt;/code&gt; &lt;em&gt;,&lt;/em&gt; &lt;code&gt;_upVector_&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code&gt;_forwardVector_&lt;/code&gt; &lt;em&gt;as unit vectors! The correct-ness of these calculations rely on it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;strong&gt;&lt;em&gt;Thought experiment — &lt;/em&gt;&lt;/strong&gt;&lt;em&gt;Starting with an object at (0,0,0),&lt;/em&gt;  &lt;em&gt;can you imagine what translating first then rotating (around the origin) will do? Can you imagine rotating (around the origin) then translating will do?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Five: Build the Matrix!
&lt;/h3&gt;

&lt;p&gt;Now just fill in the data into this matrix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yCZ9dc_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AT-SAObQaoHUTBlw5jcrVFg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yCZ9dc_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AT-SAObQaoHUTBlw5jcrVFg.png" alt="Lookat matrix calculation" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Magic LookAt Matrix&lt;/p&gt;

&lt;p&gt;That’s it, we’re done… but if you’re looking for how you can use this for a 3D application that you’re developing… well keep on reading!&lt;/p&gt;

&lt;p&gt;Recall that I said at the very beginner that we would like to view this world from the lens of the camera. The typical name for a transformation matrix that does this operation is a &lt;strong&gt;ViewMatrix.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The key thing to understand is that the camera &lt;em&gt;stays at origin&lt;/em&gt; and looks down the negative z-axis. &lt;strong&gt;It does not move&lt;/strong&gt;. To simulate camera movement, &lt;em&gt;we need to move the objects in the scene instead.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Therefore, we need to apply to the &lt;em&gt;inverse&lt;/em&gt; of the LookAt matrix to the objects in the scene rather than the camera.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the code, we calculate just that… the translation is inversed (turned negative), as is the rotation component. However, &lt;strong&gt;take note of a very important point&lt;/strong&gt; that made me bang my head at work while trying to debug a rotation issue… &lt;strong&gt;the inverse of a rotation is it’s transpose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, our final inverse LookAt Matrix… or View Matrix, would be:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3OGqIgLu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AGMLVIbbWXGE291FTG10toQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3OGqIgLu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AGMLVIbbWXGE291FTG10toQ.png" alt="View matrix calculation" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The inverse LookAt matrix is the ViewMatrix&lt;/p&gt;

&lt;p&gt;You’re a wizard, reader, be proud of yourself for getting through all of this material. Until next time!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;¹ for the uninitiated — it’s math for arrays and matrices. For the initiated — don’t crucify me for that gross vulgarization.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://carmencincotti.com/2022-04-25/cameras-theory-webgpu/"&gt;WebGPU — Cameras (theory) | Carmen Cincotti&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="http://learnwebgl.brown37.net/07_cameras/camera_introduction.html"&gt;Brown37 — Introduction to Cameras&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="http://cs.wellesley.edu/~cs307/readings/07-viewports.shtml"&gt;Wellesley — Viewports, Aspect Ratio, Depth, Unprojection&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.w3.org/TR/webgpu/#coordinate-systems"&gt;w3 — WebGPU — Coordinate Systems&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>graphics</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Drawing a Triangle with WebGPU</title>
      <dc:creator>Carmen Cincotti</dc:creator>
      <pubDate>Fri, 22 Apr 2022 14:12:19 +0000</pubDate>
      <link>https://dev.to/carmencincotti/drawing-a-triangle-with-webgpu-1mk3</link>
      <guid>https://dev.to/carmencincotti/drawing-a-triangle-with-webgpu-1mk3</guid>
      <description>&lt;p&gt;&lt;a href="https://web.dev/gpu"&gt;WebGPU&lt;/a&gt;  is part of the new generation of rendering, created for the web.&lt;/p&gt;

&lt;p&gt;If you’re experienced in other graphics APIs such as Vulkan, you’ll probably find that WebGPU isn’t &lt;em&gt;so so&lt;/em&gt;  different. However, if you come from a WebGL background, you’ll need to wrap your head around the non-state-machine-esque behavior of WebGPU… and if you’re completely new to 3D rendering inside the browser, welcome!&lt;/p&gt;

&lt;p&gt;I propose that we create a triangle to introduce ourselves to the WebGPU API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;We want to draw this bad boy in a browser that supports WebGPU rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KjfsjV8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1304/1%2AarTN0qYh4kfu8-cA3JtS0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KjfsjV8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1304/1%2AarTN0qYh4kfu8-cA3JtS0w.png" alt="A triangle rendered in WebGPU" width="652" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The awesome triangle that we would like to draw.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Here’s the code for those that just want to copy and paste, I’ll be walking through it and providing descriptions of each section later down this post.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;⚠️  &lt;em&gt;To run the code, you must use a browser with the WebGPU flag enabled as it is still not enabled by default (it’s that brand-spanking new!).&lt;/em&gt; &lt;a href="https://web.dev/gpu/#enabling-via-about:flags"&gt;&lt;em&gt;See this resource&lt;/em&gt;&lt;/a&gt; &lt;em&gt;for more information.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.carmencincotti.com/2022-04-18/index.html"&gt;Click here to view a Demo.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Adapter and Device&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We first start by ensuring the availability of WebGPU in our browser in the first few lines of the script. If it is available, we then initialize WebGPU’s  &lt;strong&gt;adapter&lt;/strong&gt; and  &lt;strong&gt;device&lt;/strong&gt;, and the HTML Canvas.&lt;/p&gt;

&lt;p&gt;What is an  &lt;strong&gt;adapter&lt;/strong&gt;  and a  &lt;strong&gt;device&lt;/strong&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Adapter&lt;/strong&gt; is like  &lt;em&gt;VkPhysicalDevice&lt;/em&gt;  (if you are experienced in Vulkan). WebGPU allows us to get a list of all the GPUs in our computer. You can also pass an argument to the function to select a GPU of a certain type. For example, you may want to select from multiple GPUs in your system, like a low-power GPU for battery-powered use and a high-powered GPU for plugged-in use.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Device&lt;/strong&gt; is like  &lt;em&gt;VkDevice&lt;/em&gt;. This is the GPU driver on the hardware graphics card and our method of communicating with it. I think of it as the API of the graphics card.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Swap Chain&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After creating the Canvas context, we configure it.&lt;/p&gt;

&lt;p&gt;These lines may look weird and abstract… we are setting up the  &lt;strong&gt;swap chain&lt;/strong&gt;  and the Canvas context at the same time.&lt;/p&gt;

&lt;p&gt;⚠️  &lt;em&gt;If you read a WebGPU tutorial from the past, you might see the use of a deprecated method where you explicitly set up the&lt;/em&gt; &lt;strong&gt;swap chain&lt;/strong&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What is a  &lt;strong&gt;swap chain&lt;/strong&gt;? The main role of the swap chain is to synchronize the presentation of our generated images with our screen refresh rate. It is a queue that contains images waiting to be displayed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Vertices&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We have to pack our array of vertices into a single giant array buffer. Each vertex consists of six floats in this case (three to represent position, and three to represent color). If we want to include other attributes, we must add more floats to each vertex.&lt;/p&gt;

&lt;p&gt;After defining our vertices, we create the  &lt;code&gt;vertexBuffer&lt;/code&gt;  which is the buffer that will live in the GPU. We are responsible for filling it at this point. The act of “mapping” a buffer is important to its operation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A  &lt;strong&gt;mapped&lt;/strong&gt; buffer means that the CPU can write to it and the GPU cannot.&lt;/li&gt;
&lt;li&gt;  Conversely, if the buffer is  &lt;strong&gt;unmapped&lt;/strong&gt;, the GPU will be able to read it, and the CPU will be prohibited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why we designate  &lt;code&gt;mappedAtCreation&lt;/code&gt;  as  &lt;code&gt;true&lt;/code&gt;  during the creation stage. We can then invoke &lt;em&gt;.set&lt;/em&gt;  to copy our vertices into the buffer. Finally, we remove the CPU’s write access, and grant GPU read access, by calling  &lt;code&gt;vertexBuffer.unmap()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The  &lt;code&gt;vertexBuffersDescriptors&lt;/code&gt;  are instructions telling the GPU how to decode the buffer. In our case, we use 32 bytes to describe all attributes of a vertex. In our shaders, the GPU will be able to find the position vector at offset 0, and the color vector at offset 16.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Vertex and Fragment Shader&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;These shaders are simple. We define them using  &lt;a href="https://www.w3.org/TR/WGSL/"&gt;WGSL&lt;/a&gt;, which is like Rust. There are no surprises in this code, and I invite you to review shader code tutorials in order to follow this bit of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Rendering Pipeline&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, we define the rendering pipeline which is just a simple, and sort’ve boilerplate-y configuration. We combine our shaders and vertex attributes while defining the type of primitive that will be generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Animation Frame and Command Buffers&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We start our animation! Another difference from WebGL is this idea of a  &lt;a href="https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Command_buffers"&gt;command buffer&lt;/a&gt;. We use a  &lt;strong&gt;command buffer&lt;/strong&gt;  to pre-record all drawing operations so that WebGPU can process them more efficiently. The advantage is that it will reduce the bandwidth between CPU and GPU (and therefore performance will improve) and we can fill this buffer in parallel using multiple threads if we choose to do so.&lt;/p&gt;

&lt;p&gt;The  &lt;code&gt;commandEncoder&lt;/code&gt;  is responsible for receiving our render commands. To create a command buffer that can be submitted to the GPU, we call  &lt;code&gt;.finish&lt;/code&gt;  on the encoder. The received command buffer is passed to the  &lt;code&gt;device&lt;/code&gt;  to be executed, then our triangle will be rendered!&lt;/p&gt;

&lt;p&gt;Finally, the image of our triangle will be written to the swap chain, then displayed on the canvas!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KjfsjV8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1304/1%2AarTN0qYh4kfu8-cA3JtS0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KjfsjV8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1304/1%2AarTN0qYh4kfu8-cA3JtS0w.png" alt="A triangle rendered in WebGPU" width="652" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you should see!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources that Definitely Helped Me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://carmencincotti.com/2022-04-18/drawing-a-webgpu-triangle/"&gt;Drawing a WebGPU Triangle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://web.dev/gpu/"&gt;web.dev WebGPU&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://vulkan-tutorial.com/"&gt;Vulkan Tutorial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.w3.org/TR/WGSL/"&gt;WGSL — W3&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://gpuweb.github.io/gpuweb/explainer/#initialization"&gt;WebGPU Explainer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.khronos.org/assets/uploads/developers/presentations/Intro-to-WebGPU_May21.pdf"&gt;Intro to WebGPU — Khoronos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://sotrh.github.io/learn-wgpu/beginner/tutorial3-pipeline/#writing-the-shaders"&gt;The Pipeline | Learn Wgpu (sotrh.github.io)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
