DirectX11 で点を表示してみる（点群表示機能を自作してみる その２）

[「点群表示機能を自作してみる」](https://qiita.com/yumimatoba14/items/a89b01145b3af4ce9451)のその２です。
DirectX11 を用いて点を表示するところまでやってみます。

# 出来上がり図

[GitHub](https://github.com/yumimatoba14/DirectXMfc.git)
> Revision: 35a9bc826086073a877b6a34ecea8386213a3fbb
> Message:
> implemented drawn point size.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124939/cc0ba453-b785-bd08-adbf-534703ab25c3.png)

* 点の大きさは3次元空間中の長さ(今回は0.1m)としています。

 座標値は[前回](https://qiita.com/yumimatoba14/items/e2eb77f82cac7a161c30)のポリゴンと同じです。菱形右端の点は色を黒から白に変えました。

点の大きさを3次元空間中の長さとすることで、近い点をより大きく表示するようにしています。 (画面上の点の大きさは視点までの距離によって変わります。)
別の流儀としてはスクリーン中のサイズ(ドット数)で表すこともあります。 今回のプログラムでは `m_graphics.SetPointSize(-5)` などと負の数を指定することでドット数を指定できるようにしました。

# 説明

## 概要
特に難しいことはありませんが、一点だけ。DirectX11 では「点のサイズ」という設定がないようです。(要出典)
従って点を自分でポリゴン化して表示することにします。この時のポリゴン(三角形)は画面に正対して欲しい(斜めから見たくない)ため、バーテックスシェーダーの時点では点を与え、ジオメトリシェーダでポリゴン化します。(こうすればバーテックスバッファを変更せずに自由な向きで描画することができます。)

## 点で描画する

[このリビジョン](https://github.com/yumimatoba14/DirectXMfc/commit/a6f46e9b0b546a7c2f9bcc2d7a393e9beaedfb86) でビルドすると、DirectX の素の点の描画がされます。 以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124939/a20b7996-80cd-5a3a-e9b0-fdf621721da6.png)

点のサイズは1ピクセルで、ほとんど見えません。

## 描画処理
描画処理は以下の通り。 ID3D11DeviceContext::Draw() を使います。
```C++:D3dGraphics.cpp
void D3DGraphics::DrawPointList(
	const D3DShaderContext& sc, const D3DBufferPtr& pVertexBuf, size_t vertexSize, size_t nVertex
)
{
	P_ASSERT(nVertex <= UINT_MAX);
	m_pDC->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
	ID3D11Buffer* apVB[1] = { pVertexBuf.Get() };
	UINT aVertexSize[1] = { (UINT)vertexSize };
	UINT aOffset[1] = { 0 };
	m_pDC->IASetVertexBuffers(0, 1, apVB, aVertexSize, aOffset);

	SetShaderContext(sc);
	m_pDC->Draw((UINT)nVertex, 0);
}
```

## ジオメトリシェーダ

点の描画ではあんまりなので、自分でポリゴン化します。4角形でよいでしょう。
ジオメトリシェーダで点を三角形化します。
* 参考： [Geometry-Shader オブジェクト](https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-geometry-shader)

```hsls:SampleShader.hlsl
[maxvertexcount(4)]   // ジオメトリシェーダーで出力する最大頂点数
// ジオメトリシェーダー
void gsMain(point PS_INPUT inPoint[1],                       // ポイント プリミティブの入力情報
	inout TriangleStream<PS_INPUT> triStream  // トライアングル プリミティブの出力ストリーム
)
{
	PS_INPUT outPoint;
	const float halfSizeX = decideHalfPointSize(pointSizeX, pixelSizeX, inPoint[0].Pos[3]);
	const float halfSizeY = decideHalfPointSize(pointSizeY, pixelSizeY, inPoint[0].Pos[3]);

	outPoint.Pos = float4(inPoint[0].Pos[0] + halfSizeX, inPoint[0].Pos[1] + halfSizeY, inPoint[0].Pos[2], inPoint[0].Pos[3]);
	outPoint.Col = inPoint[0].Col;
	triStream.Append(outPoint);

	outPoint.Pos = float4(inPoint[0].Pos[0] - halfSizeX, inPoint[0].Pos[1] + halfSizeY, inPoint[0].Pos[2], inPoint[0].Pos[3]);
	outPoint.Col = inPoint[0].Col;
	triStream.Append(outPoint);

	outPoint.Pos = float4(inPoint[0].Pos[0] + halfSizeX, inPoint[0].Pos[1] - halfSizeY, inPoint[0].Pos[2], inPoint[0].Pos[3]);
	outPoint.Col = inPoint[0].Col;
	triStream.Append(outPoint);

	outPoint.Pos = float4(inPoint[0].Pos[0] - halfSizeX, inPoint[0].Pos[1] - halfSizeY, inPoint[0].Pos[2], inPoint[0].Pos[3]);
	outPoint.Col = inPoint[0].Col;
	triStream.Append(outPoint);

	triStream.RestartStrip();
}
```

一つの点に対し4つの頂点が生成されることになります。メモリ使用量が増えるのが気になるところですが、パイプラインで処理されているなかで4倍になるだけなので、まあ大丈夫でしょう。きっと。(この辺のパフォーマンスへの影響は次回計測してみましょう。)

今回は四角い板で点を表現しましたが、四角錐はいずれやってみたい。

### decideHalfPointSize()

```hsls:SampleShader.hlsl
float decideHalfPointSize(float givenPointSize, float pixelSize, float w)
{
	if (givenPointSize < 0) {
		return -0.5f * givenPointSize * w;	// givenPointSize has included pixelSize.
	}
	// draw as 1 pixel at minimum.
	return 0.5f * max(givenPointSize, pixelSize * w);
}
```

givenPointSize が負の値の時は、点のサイズは `-givenPointSize` ピクセルで描画するという仕様です。 `-0.5f * givenPointSize * w` という式で w (=`inPoint[0].Pos[3]`) を掛けています。これは頂点が同次座標系であることによります。(x や y の値を w で割った値が、画面上の座標値になる。)

点の大きさは3次元の長さで指定している、と説明していましたが、今回は最小でも1ピクセルで描画することにしました。 これはあまり小さすぎると点が描画されなくなるだろうこと、また1ピクセルで描画することで後に工夫の余地ができることが理由になります。