title: Billboard using the Geometry shader
body: <p>I am trying to render billboards using a geometry-shader which takes points as input and outputs a triangle stream (using DirectX11). However the current result is not what I expect it to be.</p>
  
  <p>For comparison, here are two screenshots, rendering the very same set of particles, viewed from the same direction (more or less, I had to rotate the camera  by ~90°) and distance, once rendered as points and once rendered with my billboard shader:</p>
  
  <p><img src="https://i.stack.imgur.com/TTWSD.png" alt="Particles rendered with billboard shader">
  <img src="https://i.stack.imgur.com/0pJcY.png" alt="Particles rendered as points"></p>
  
  <p>As you can see in the second picture, when rendered as points, one can clearly see that the particles' are moving away from the center, covering nearly the entire screen, whereas when rendered with the billboard shader, they change in scale slightly, but always remain stationary.</p>
  
  <p>Unfortunately, I don't have a clue as to what is causing this. I've been following tutorials such as <a href="http://www.evolution3d.net/my3dstuff/papers/papers.php?key=billboards" rel="nofollow noreferrer">this</a>, which explains how the matrix is supposed to be set-up, however the result shows that either my expectations or implementation is wrong.</p>
  
  <p>The following is the code for the billboard shader, which builds the matrix to describe the particle's orientation and then emits the two triangles for the billboard:</p>
  
  <pre><code>#include &lt;Materials/SceneConstants.hlsl&gt;
  #include &lt;Materials/ModelConstants.hlsl&gt;
  #include &lt;Particles/Particle.hlsl&gt;
  
  Texture2D diffuseTexture : register(ps, t[0]);
  SamplerState diffuseSampler : register(ps, s[0]);
  
  struct PS_IN
  {
      float4 Position : SV_POSITION;
      float4 Color : COLOR;
      float2 TexCoord : TEXCOORD;
  };
  
  Particle vs(Particle input)
  {
      return input;
  }
  
  [maxvertexcount(4)]
  void gs(point Particle particles[1], inout TriangleStream&lt;PS_IN&gt; triStream)
  {
      // We need to create a matrix for the local coordinate system for the billboard of the given particle.
      // One axis points from the particle to the camera, one axis is the camera's side axis (for example to
      // the left) and the third one is perpendicular to both.
      Particle particle = particles[0];
  
      float3 zAxis = normalize(CameraPosition - particle.Position);
      float3 xAxis = normalize(cross(float3(0, 1, 0), zAxis));
      float3 yAxis = cross(zAxis, xAxis);
  
      // The matrix to describe the local coordinate system is easily constructed:
      float4x4 localToWorld;
      localToWorld._11 = xAxis.x;
      localToWorld._21 = xAxis.y;
      localToWorld._31 = xAxis.z;
      localToWorld._12 = yAxis.x;
      localToWorld._22 = yAxis.y;
      localToWorld._32 = yAxis.z;
      localToWorld._13 = zAxis.x;
      localToWorld._23 = zAxis.y;
      localToWorld._33 = zAxis.z;
      localToWorld._41 = particle.Position.x;
      localToWorld._42 = particle.Position.y;
      localToWorld._43 = particle.Position.z;
      localToWorld._14 = 0;
      localToWorld._24 = 0;
      localToWorld._34 = 0;
      localToWorld._44 = 1;
  
      // And the matrix to transform from local to screen space...
      float4x4 transform = localToWorld * World * ViewProjection;
  
      // The positions of that quad is easily described in the local coordinate system:
      // -z points towards the camera, y points upwards and x towards the right.
      // The position marks the center of the quad, hence (0, 0, 0) is the center of the quad in
      // local coordinates and the quad has an edge-length of particle.Size to either side.
      PS_IN v1, v2, v3, v4;
      //float size = particle.Size / 2;
      float size = 0.5f;
      v1.Position = mul(float4(-size, size, 0, 1), transform);
      v1.TexCoord = float2(0, 0);
      v1.Color    = particle.Color;
      v2.Position = mul(float4(size, size, 0, 1), transform);
      v2.TexCoord = float2(1, 0);
      v2.Color    = particle.Color;
      v3.Position = mul(float4(-size,-size, 0, 1), transform);
      v3.TexCoord = float2(0, 1);
      v3.Color    = particle.Color;
      v4.Position = mul(float4(size, -size, 0, 1), transform);
      v4.TexCoord = float2(1, 1);
      v4.Color    = particle.Color;
  
      triStream.Append(v1);
      triStream.Append(v2);
      triStream.Append(v3);
      triStream.Append(v4);
  }
  
  float4 ps(PS_IN input) : SV_TARGET0
  {
      /*float4 texel = diffuseTexture.Sample(diffuseSampler, input.TexCoord);
      return input.Color * texel;*/
      return float4(1, 1, 1, 1);
  }
  </code></pre>
  
  <p>For reference, here is the shader-code for rendering the particles as simple points as well:</p>
  
  <pre><code>#include &lt;Materials/SceneConstants.hlsl&gt;
  #include &lt;Materials/ModelConstants.hlsl&gt;
  #include &lt;Particles/Particle.hlsl&gt;
  
  
  struct PS_IN
  {
      float4 Position : SV_POSITION;
      float4 Color : COLOR;
  };
  
  PS_IN vs(Particle input)
  {
      PS_IN output;
  
      float4 posWorld = mul(float4(input.Position, 1), World);
      output.Position = mul(posWorld, ViewProjection);
      output.Color = input.Color;
  
      return output;
  }
  
  float4 ps(PS_IN input) : SV_TARGET0
  {
      //return input.Color;
      return float4(1, 1, 1, 1);
  }
  </code></pre>
  
  <p>Another oddity which I noticed is that my billboards are not facing the camera, at least not always. From the way I setup the matrix, I would expect them to, however. Instead, they face the camera only when viewed from two opposing directions, and then decrease in width as soon as I begin to rotate the camera.</p>
  
  <p>This led me to believe that I made a mistake building the matrix, however I am unable to spot it.</p>
  
  <p>Hopefully you guys can help me find the problem. Thanks in advance for your help!</p>
  
  <p><strong>Edit</strong></p>
  
  <p>I seem to have found a solution to this problem, however I do not understand why this is one. For some bizarre reason, I cannot multiply the localToWorld matrix with the ViewProjection matrix. Instead, I have to separate it into two steps like so:</p>
  
  <pre><code>v1.Position = mul(float4(-size, size, 0, 1), localToWorld);
  v1.Position = mul(v1.Position, ViewProjection);
  </code></pre>
  
  <p>I do not understand why this is the case, maybe it's connected with using row_major matrices instead of the default, column_major. But as it is now, this behaviour does not make any sense to me, at all: Matrix multiplication should be associative, and therefore the snippet above should result in the same outcome as the original code, but it clearly does not. Maybe some of you can shed some light on what is going on here.</p>