2022年11月28日月曜日

定数バッファ

定数バッファでハマったのでそれについてのメモ。

定数バッファの種類


フレーム依存定数バッファ


最初に自作ライブラリで用意したのはフレーム数に依存した定数バッファ。
ダブルバッファならフレーム数は2で、定数バッファのサイズを256の倍数に補正したサイズをフレーム数倍した領域をCreateCommittedResourceしたもの。

D3D12_HEAP_TYPE_UPLOADでCreateCommittedResourceしたリソースから、Mapでマッピングしたポインタをリソース破棄まで保持する。
データの書き換えは、このポインタに書き込む。

これは毎フレーム頻繁に値の書き換えがあるような用途で、用意した前半部分と後半部分のメモリをフレームの切り替えで書き込むオフセットを制御する。

読み取り専用定数バッファ


シェーダの内容によってはパラメータとして設定はするんだけど、値自体は最初に設定した以降変わらないというパターンのものもある。
フレーム数に依存した定数バッファでこのパターンのシェーダを使う場合、毎フレームの更新は不必要なため、最初に1フレームと、2フレーム目に同じデータを設定して使うことになる。また、大抵の場合データは書き換えの必要がない。
データの領域がフレーム数倍必要になり、初期化時にフレーム数分書き込む必要がある。

これはかなり無駄なので、このパターンに対応するため次に用意したのは読み取り専用の定数バッファ。フレーム数分定数バッファを用意するのはマイクロソフトのサンプルがそうなっていたからで、常にフレーム数分用意しないといけないと思いこんでいたけど、値を書き換えないなら1つ分でも行けるかなと思い試してみたところ問題なく動作した。

領域は必要な定数バッファサイズを256の倍数に補正したサイズのみ。
作成時にテクスチャと同様、データをGPUに書き込んでしまう。
D3D12_HEAP_TYPE_DEFAULTでCreateCommittedResourceしたリソースに、一時的に作成したD3D12_HEAP_TYPE_UPLOADのリソースを使って、CopyBufferRegionでコピーする。
Uploadでマッピングしたポインタは必要ないのでUnmapする。

32Bit定数バッファ


いろいろなサンプルを見ている中で見つけた定数バッファ。
ルート署名内に「RootConstants( num32BitConstants=1, b0)」と書けば、別途リソースの準備をする必要なく定数バッファを扱える。

テクスチャにミップマップを生成するコンピュートシェーダを用意したとき、別途定数バッファリソースの準備が不要なので使ってみたのがきっかけ。
前回のぼかしのシェーダで、パラメータをこの定数バッファで渡そうとしたものの、エラーがでてうまくいかず、読み取り専用定数バッファでとりあえず済ませた。

その時のエラーがこれ。
D3D12 ERROR: ID3D12CommandList::SetGraphicsRoot32BitConstants: No root signature has been set, so setting root constants doesn't make sense and is invalid. [ EXECUTION ERROR #709: SET_ROOT_CONSTANT_INVALID]

ルート署名に32Bit定数が存在しているのはPIXで確認できる。

PIXのパイプライン情報

このエラー文や32Bit定数バッファにまつわるキーワードで検索しまくったが解決につながる情報は得られなかった。
ネットのサンプルでも普通に使われているし、ミップマップのコンピュートシェーダでは普通に使えている。
コンピュートシェーダで使っているのはSetComputeRoot32BitConstantsで、今回使おうとしているのはSetGraphicsRoot32BitConstants


万策尽きて諦めかけてたとき、ふと思いついた。

BeginRenderという関数を用意して、レンダリング開始時にコマンドアロケータ、コマンドリストをリセットしているが、うまく行ってるミップマップのシェーダでは、このBeginRender後にSetComputeRoot32BitConstantsを呼び出している。対して、うまく行ってないシェーダではBeginRender前に呼び出していた。

コマンドリストのResetから、Closeの間で呼び出す必要があったというのが原因。
定数バッファのリソースは存在せず、シェーダに付属しているイメージなので常に書き込めるわけではないようだ。

もう1つ分かったことは、32Bit定数はフレーム依存定数バッファと同じ使い方ということ。
前回のぼかしのシェーダでは、用途としては読み取り専用定数バッファだったため、ぼかし強度を変更したタイミングで定数バッファを書き換え、毎フレームは書き換えてはいなかった。32Bit定数バッファでも同じように1回だけ書き込んで、次回以降書き込まずにいたら、結果が思い通りにならない。前回書き込んだ値は保持されず、毎フレーム書き込まないといけない模様(GPUによる?)

2023/8/27追記

この頃は定数バッファと同様にバッファが独立してあって、そこに値を設定する関数がSet[Graphics|Compute]Root32BitConstantsだと思っていた。
この関数はコマンドリストに追記する関数なので、当然コマンドリストが記録状態になっていないと使えない。


フレーム毎の書き込み


フレーム依存定数バッファ:不要 ※最初に全フレーム分書き込んだ場合


ダブルバッファの場合、前回、前々回のデータは保持している。
フレーム単位に参照する場所を切り替える関係上、更新を止めるなら全フレームのデータが同じになっていないと動作がおかしくなる。
実質フレーム毎の更新は必要。

読み取り専用定数バッファ:不可


作成時にGPU側にデータをコピーして、仕組み上書き換えできなくしているのでフレーム毎の書き込みはできない。

32Bit定数バッファ:必須


前回の値が保持されないため、毎フレーム書き込む必要がある。


データサイズ


フレーム依存定数バッファ:256byte単位×フレーム数


一定以上のデータ量でフレーム毎に書き換えが必要な場合に選択する。
また、フレーム専用の領域を用意しているので、32Bit定数よりも高速に動作(して欲しい)

読み取り専用定数バッファ:256byte単位


GPU側に書き込んでいるため、大きいデータでも高速に参照ができる。

32Bit定数バッファ:32Bit(4Byte)単位


少量のデータを渡す場合に適している。
コンピュートシェーダの場合は読み取り専用と同じような使い方もできる。

2022年11月27日日曜日

軽量なぼかし処理

以前DoFにチャレンジした際、ぼかし処理にガウシアンブラーを使ってぼかして見たが、思っていたほどボケなかった。
それにもかかわらず結構なGPUパワーを使うので、もっといい方法を探していた。

そこで見つけたのがこのサイト。

ガウシアンブラー単体だと、ブラーをかける対象のサイズが大きいと処理の負担も大きい。
DoFでは1/4の縮小画像を作っていきながらブラーをかけて、その画像を拡大して使う。
ほぼそれと同じ発想なんだけど、ブラーの処理が例えば8×8ブラーだと16回サンプリングする必要があるのに対し、8回で済ませる。
あと、縮小画像1/16の画像と、1/64の画像の2段階で処理を行っていた。

最初真似して2段階、その後3段階まで試してみたけど、どうも結果が汚い。どんどん縮小させていって、最後に拡大するとどうしても粗が目立つ。
逆に最初から落としたいレベルまで縮小させてしまってから、縮小画像を4倍に拡大するようにしてみたところ、納得行く感じになった。

処理の流れ


PSシェーダ


#define DefRS "RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | DENY_HULL_SHADER_ROOT_ACCESS | DENY_DOMAIN_SHADER_ROOT_ACCESS | DENY_GEOMETRY_SHADER_ROOT_ACCESS),DescriptorTable( Sampler(s0),visibility=SHADER_VISIBILITY_PIXEL),DescriptorTable( SRV(t0, flags=DATA_STATIC),visibility=SHADER_VISIBILITY_PIXEL),DescriptorTable( CBV(b0, flags=DATA_STATIC),visibility=SHADER_VISIBILITY_PIXEL)"

SamplerState Sampler : register(s0) ;

Texture2D TexColor : register(t0) ;

struct _Param { 
	float2	Scale ;		// 倍率
} ;
ConstantBuffer<_Param> Param : register(b0) ;

static const int BLUR_SAMPLE_COUNT = 8 ;
static const float2 BLUR_KERNEL[ BLUR_SAMPLE_COUNT ] = {
	float2(-1.0f, -1.0f),
	float2(-1.0f,  1.0f),
	float2( 1.0f, -1.0f),
	float2( 1.0f,  1.0f),
	float2(-0.5f,  0.0f),
	float2( 0.0f,  0.5f),
	float2( 0.5f,  0.0f),
	float2( 0.0f, -0.5f),
} ;

struct PSInput
{
	float4 Pos	: SV_POSITION ;
	float2 UV	: TEXCOORD ;
} ;
struct PSOutput
{
	float4 Col : SV_TARGET ;
} ;
PSOutput PSMain( PSInput In )
{
	PSOutput Out ;

	float4 color = 0 ;
	for( int j = 0 ; j < BLUR_SAMPLE_COUNT ; j++ ) {
		color += TexColor.Sample( Sampler, In.UV + BLUR_KERNEL[j] * Param.Scale ) ;
} Out.Col = float4( color.rgb / float( BLUR_SAMPLE_COUNT ), 1.0f ) ; return Out ; }

Param.Scaleに「1.0f / 元のテクスチャサイズ * ぼかし強度」を指定する。
1/4096の場合は、元のテクスチャ幅、高さそれぞれ64で割ったサイズの画像を用意して、ぼかし強度に64を指定してレンダリングをした後、元のテクスチャ幅、高さそれぞれ32で割ったサイズの画像を用意して、ぼかし強度に32を指定してレンダリングする。


出力結果


無料の写真素材「ぱくたそ」からお借りした画像

この画像をそれぞれの強度でぼかした結果がこれ。

1/16

1/64

1/256

1/1024

1/4096

中間バッファ


1/1024と1/4096の中間バッファはこんな感じ

1/1024の中間バッファ

1/4096の中間バッファ

この時点ではかなりカクカクしているけど、この後それぞれ1/512、1/2048の画像にアップスケーリングして、間をなだらかにするので元のサイズに戻してもあまり違和感がない状態になっている。


パフォーマンス


ガウシアンブラーは4KでGPU使用率が40%ぐらいに対し、今回の方式だと20%で半分になっていた。
PIXで測ってみると、1024×1024のサイズ同士ではガウシアンブラーが646usに対し、1/16縮小が365us、1/4096縮小が178usだった。
ボケの強度を強くするほど中間バッファのサイズが小さくなるので処理が軽くなる。

ガウシアンブラーは期待していたボケ具合にはならず、更に重い処理だったが、今回の方式であればボケ具合を調整できる上に、処理時間も半分以下ので済むので満足。

2022年11月24日木曜日

サンプラー見本

サンプラーはテクスチャを貼り付けるときに必要なリソースで、主にフィルターと、アドレスモードを設定する。(そのほかの値は比較関数以外は固定値で使いこなせてない)

フィルター


D3D12_FILTERのenum値で、定義値を見るとごちゃごちゃしていてよくわからない。
最初に定義されているのが「D3D12_FILTER_MIN_MAG_MIP_POINT」で、D3D12_FILTERの後に、MIN、MAG、MIP、POINTという名前がついている。
MINがテクスチャの縮小時の挙動、MAGがテクスチャの拡大時の挙動、MIPがミップレベルの挙動を指していて、最後のPOINTがその挙動。
挙動にはPOINTとLINERとANISOTROPICがあり、ANISOTROPICはちょっと特殊。
POINTはポイントサンプリング、LINERはサンプリングに線形補間を使用。

改めて「D3D12_FILTER_MIN_MAG_MIP_POINT」を見ると、拡大縮小、ミップレベルですべてポイントサンプリングするということになる。
別の定義「D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR」は、縮小時は線形補間、拡大時はポイントサンプリング、ミップレベルは線形補間を利用する。
ただリファレンスには「拡大または縮小のどちらを行うかの選択があいまいな場合に、未定義の動作が発生します。」とあるので、実質使うのは「D3D12_FILTER_MIN_MAG_MIP_POINT」と「D3D12_FILTER_MIN_MAG_MIP_LINER」だけでいいのではないか?

残りのANISOTROPICは異方性補完という方法で、視線も考慮した補完方法らしい。
線形補間よりも品質は良くなるがパフォーマンスも落ちるので、今のところは未使用。
定義も、「D3D12_FILTER_ANISOTROPIC」で、拡大縮小、ミップレベルの補完をいい具合にしてくれるみたい。

アドレスモード


D3D12_TEXTURE_ADDRESS_MODEのenum値で全部で5種類ある。

D3D12_TEXTURE_ADDRESS_MODE_WRAP

並べて表示

D3D12_TEXTURE_ADDRESS_MODE_MIRROR

反転して表示

D3D12_TEXTURE_ADDRESS_MODE_CLAMP

範囲外は端の色が続く

D3D12_TEXTURE_ADDRESS_MODE_BORDER

範囲外はD3D12_SAMPLER_DESCの色で塗りつぶされる
アルファを0にすると、範囲外は何も描かれなくなる

D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE

1回だけ反転して、それ以降はCLAMPの動作


アドレスモードはD3D12_SAMPLER_DESCに3つ指定することになる。
1つ目は横方向、2つ目は縦方向、3つ目はよくわからない。


横と縦に同じアドレスモードを指定した見本


貼り付けるテクスチャ画像はこれ。
テクスチャ画像

このテクスチャを5×5で表示(UVの値は-2~2を指定)

WRAP/LINER

ドットがしっかりした画像の場合は、線形補間するとぼやけてしまう。
以降はポイントサンプリング。

WRAP/POINT

MIRROR/POINT

CLAMP/POINT

BORDER/POINT

MIRROR ONE/POINT



横と縦に別のアドレスモードを指定した見本


横にWRAPを指定


WRAP/MIRROR/POINT

WRAP/CLAMP/POINT

WRAP/BORDER/POINT

WRAP/MIRROR ONE/POINT


横にMIRRORを指定


MIRROR/WRAP/POINT

MIRROR/CLAMP/POINT

MIRROR/BORDER/POINT

MIRROR/MIRROR ONE/POINT


横にCLAMPを指定


CLAMP/WRAP/POINT

CLAMP/MIRROR/POINT

CLAMP/BORDER/POINT

CLAMP/MIRROR ONE/POINT


横にBORDERを指定



BORDER/WRAP/POINT

BORDER/MIRROR/POINT

BORDER/CLAMP/POINT

BORDER/MIRROR ONE/POINT


横にMIRROR ONEを指定


MIRROR ONE/WRAP/POINT

MIRROR ONE/CLAMP/POINT

MIRROR ONE/BORDER/POINT

MIRROR ONE/MIRROR/POINT


2022年10月30日日曜日

Android ワイヤレスデバッグ

携帯側の設定



[設定]-[システム]-<詳細設定>-[開発者向けオプション]を開く。

開発者向けオプション

デバッグの項目に「ワイヤレスデバッグ」という項目があるのでチェックをOnにする。

デバイスとのペア設定

「ワイヤレスデバッグ」を選択すると詳細画面が開くので、「ペア設定コードによるデバイスペア設定」を選択する。
ペアリングするための「IPアドレスとポート」と、「Wi-Fiペア設定コード」が表示される。

ワイヤレスデバッグ

「ワイヤレスデバッグ」では接続するための「IPアドレスとポート」が表示される。

 

接続コマンド



ペアリング

adb pair ペアリング用IPアドレス:ポート番号 Wi-Fiペア設定コード
「デバイスとのペア設定」に表示されているIPアドレスとポート番号を入力する。
「ワイヤレスデバッグ」に表示されているIPアドレスとポート番号ではない。

接続

adb connect 接続用IPアドレス:ポート番号
「ワイヤレスデバッグ」に表示されているIPアドレスとポート番号を入力する。

接続確認

adb devices
接続されているデバイスの一覧が表示される。

2022年8月23日火曜日

Tiling Noise

前回の草が揺れる風にノイズテクスチャを使っていたけど、uvのオフセットが端に行った時草の動きが激しくなる。草むらの中で動物が走ってるような感じ。

ノイズ画像を並べて見る


ノイズ画像を並べた時こんな感じになる。

ValueNoise

PerlinNoise

CellularNoise

VoroNoise

SimplexNoise

1枚の四角にUVを0~3で表示すると、3回ラップして表示される。
真ん中と隣接する絵がつながっていない。

ノイズ作成のパラメータには画像サイズとGridサイズを渡す。
画像サイズが256で、Gridサイズが64とすると、256÷64=4つ分のグリッドでノイズを生成する。
ノイズ関数は渡されたx,yを整数部分と小数部分に分解するが、この整数部分を画像サイズ÷Gridサイズの値でmodしてやるとつなぎ目がなくなる。

TilingValueNoise

TilingPerlinNoise

TilingCellularNoise

TilingVoroNoise

ただし、シンプレックスノイズだけはうまくいかない。
歪ませた三角形で処理するためmodが使えない。

Tiling Simplex Noise


ネット上でいくつかシンプレックスノイズを繋げる方法を見つけたが、うまく自分のソースに適用できなかった。
シンプレックスノイズは諦めようかと思った時、このサイトを見つけた。

4次元のシンプレックスノイズ関数を使って、縦と横にぐるっと回り込むように参照することで実現するようだ。

パラメータの指定方法などが現状とかなり違っていたので調整に時間がかかった。最初はシンプレックスノイズ単体でしか出来なくて、fBMを通すと同じように境目が出来てしまったんだけど、いろいろやっているうちにfBMを使ってもつながるようになった。

TilingSimplexNoise

2022年8月20日土曜日

Geometry Shader


今回はジオメトリシェーダについて。

GSを使うレンダリングパイプラインの経路は2パターンがある。
VS-GS-PS
VS-HS-DS-GS-PS

ジオメトリシェーダはVSから直接か、DS経由で呼ばれる。
前回やった距離に応じたテッセレーションの結果を元に、ジオメトリシェーダで草を生やしたいと思い準備を進めてきた。

発端はこの動画(動画1、動画2、動画3)
この動画では距離に関係なく細分化して草を生やして、近くと遠くでポリコン数が違う草を生やすということをしている。
そもそも草の生やし方がわからないので、まずはそこから。

Geometry Shaderの関数定義


関数の定義はこんな感じ。

	[maxvertexcount(「出力頂点数」)]
	void GSMain( 「プリミティブ」 GSInput In[「添字」], inout 「ストリーム」<GSOutput> Out )

出力頂点数


出力する頂点の最大数を指定する。

プリミティブ


point、line、triangle、lineadj、triangleadjが指定できる。
triangle以外は使い方を理解できていない。

添字


プリミティブに指定した型により決まる。
pointの時1、lineの時2、triangleの時3、lineadjの時4、triangleadjの時6。

ストリーム


プリミティブに指定した型により決まる。
pointの時PointStream、line、lineadjの時LineStream、triangle、triangleadjの時TriangleStream。

三角形を出力


GSInputで渡された点の1つを使って三角形を作ってみる。

	float3 pos = In[0].Pos.xyz ;

	GSOutput o[3] ;
	
	o[0].Pos = float4( pos + float3(  0.5f, 0.0f, 0.0f ), 1 ) ;
	o[1].Pos = float4( pos + float3( -0.5f, 0.0f, 0.0f ), 1 ) ;
	o[2].Pos = float4( pos + float3(  0.0f, 4.0f, 0.0f ), 1 ) ;

	o[0].Pos = mul( gCam.TransPV, o[0].Pos ) ;
	o[1].Pos = mul( gCam.TransPV, o[1].Pos ) ;
	o[2].Pos = mul( gCam.TransPV, o[2].Pos ) ;

	Out.Append( o[0] ) ;
	Out.Append( o[1] ) ;
	Out.Append( o[2] ) ;
	Out.RestartStrip() ;
posを基準として、左右に0.5ずつずらした点を底辺、上に4.0ずらした点を頂点とした。

頂点データの関して


前回のハルシェーダ、ドメインシェーダでは、今まで通り頂点シェーダでProjectionとView行列を掛けた頂点を使っていた。でもいろいろ見ていくと、どうやらHS~GSをやる場合は、VSでは変換せず、PS直前のシェーダで変換するのが普通みたい。


ジオメトリで出力すると、それまでの点は消えてしまうらしい。
ジオメトリでAppendした分だけが次のステージに進むということか。


テッセレータで近くの点を増やした場合もそのまま動作している。


ただ、横に回り込んで見ようとするとカリングされて消えてしまう。


ビルボード


ビルボードの仕組みを使えば、常にカメラの方を向くので消えないと思い色々調べてみた。
結構たくさん見つかって、ほとんどの方法がViewの逆行列を用意して移動部分の_41、_42、_43を0にして掛けるというもの。参考にしながらいろいろ試したけどうまくいかなかった。

ただ、今回やりたいことはカメラの向きに対して基準点を中心に回転させたいだけなので、自力で計算できるかもしれない。


カメラの位置と、基準点の位置のxとzの差分をatan2に渡すとラジアンが得られる。
これにsin、cosを使ってずらせば行けるはず。
	void BillboardGrass( in float3 pos, in float a, in float y, in float w, out float4 p1, out float4 p2 )
	{
		float x = -cos( a ) ;
		float z = sin( a ) ;
		p1.x = pos.x - x * w ;
		p1.z = pos.z - z * w ;
		p2.x = pos.x + x * w ;
		p2.z = pos.z + z * w ;
		p1.y = p2.y = pos.y + y ;
		p1.w = p2.w = 1.0f ;
	}

Excelで計算した結果、xに-cos、zにsinを足せばOKっぽい。
	GSOutput o[3] ;
	float a = atan2( pos.x - gCam.Eye.x, pos.z - gCam.Eye.z ) ;
	BillboardGrass( pos, a, 0.0f, 0.5f, o[0].Pos, o[1].Pos ) ;
	o[2].Pos = float4( pos + float3(  0.0f, 4.0f, 0.0f ), 1 ) ;

呼び元でatan2を呼んで、幅(半径0.5f)を指定すると常にカメラを向く状態で三角形を作り出すことが出来た。


草ポリゴン



ただの三角形から頂点を増やして草っぽくするために頂点を4つ増やす。

	GSOutput o[3] ;
	float a = atan2( pos.x - gCam.Eye.x, pos.z - gCam.Eye.z ) ;
	BillboardGrass( pos, a, 0.0f, 0.3f, o[0].Pos, o[1].Pos ) ;
	BillboardGrass( pos, a, 1.5f, 0.2f, o[2].Pos, o[3].Pos ) ;
	BillboardGrass( pos, a, 3.0f, 0.15f, o[4].Pos, o[5].Pos ) ;
	o[6].Pos = float4( pos + float3(  0.0f, 4.0f, 0.0f ), 1 ) ;

先程用意した関数を使って、4点追加する。

出力結果も想定通りになった。


ランダム要素


最初に用意した頂点は11×11の正方形で規則正しく並んだ状態。
テッセレータで細分化されたものを上から見てみると、こちらも規則正しく分割されている。


この状態では不自然なのでランダム要素を加えていろいろなものを補正しようと思う。

準備


2つのノイズテクスチャを用意する。
1つは単純なランダム要素がほしいのでホワイトノイズ。
2つ目はなめらかな状態がほしいので、フラクタルノイズがかかっているもの。ベースはなんでもいいんだけどシンプレックスノイズにしてみた。

ホワイトノイズを使って、xz位置の補正、草の高さの補正、色の補正をする。

xz位置の補正


	float ofs = TexRandNoise.SampleLevel( Sampler, uv, 0 ).r * 2.0f - 1.0f ;
	pos.x += ofs * 5.0f ;
	pos.z += ofs * 2.5f ;

ジオメトリシェーダではTextureのSampleが使えないのでSampleLevelで代用。
テクスチャの値と取り出し、-1~1の範囲に補正した値をofsに代入。
xzそれぞれにofsを足す。

草の高さの補正


	float oy = ( ofs * 1.0f ) ;
	BillboardGrass( pos, a, 0.0f, 0.3f, o[0].Pos, o[1].Pos ) ;
	BillboardGrass( pos, a, 1.5f+oy, 0.2f, o[2].Pos, o[3].Pos ) ;
	BillboardGrass( pos, a, 3.0f+oy, 0.15f, o[4].Pos, o[5].Pos ) ;
	o[6].Pos = float4( pos + float3( 0, 4.0f+oy, 0 ), 1 ) ;

高さ補正用のoyを2~6の点にそれぞれ足す。


色の補正


	o[0].UV = float2( 0.0f, 0.0f ) ;
	o[1].UV = float2( 1.0f, 0.0f ) ;
	o[2].UV = float2( 0.2f, 0.1f ) ;
	o[3].UV = float2( 0.8f, 0.1f ) ;
	o[4].UV = float2( 0.4f, 0.25f ) ;
	o[5].UV = float2( 0.6f, 0.25f ) ;
	o[6].UV = float2( 0.5f, 0.50f + ( ofs * 0.5 )) ;

設定していなかったuvを設定。 草の先端部分は長い場合黄色っぽく、短い場合は緑になるようにずらしておく。


	Out.Col = lerp( float4(0.20f + In.Type * 0.5f,0.40f,0.1f,1.0f ), float4(0.75f,1.0f,0.25f,1.0f ), In.UV.y ) ;

ピクセルシェーダを渡されたuvから色を算出するようにする。


D3D12_FILL_MODE_WIREFRAMEをD3D12_FILL_MODE_SOLIDに戻してレンダリングしたもの。
位置、高さがバラけて、先端の色も黄色っぽいのと緑が混在している感じになった。


風を吹かせる


今のところただまっすぐ突っ立ってるだけなので、風にそよいでいる感を出してみる。
用意したノイズテクスチャのもう1つを使う。

	float Wind = TexNoise.SampleLevel( Sampler, uv + gSP.Time*0.0005f, 0 ).r * 2.0f - 1.0f
	o[2].Pos.xz += Wind * 1.0f ;
	o[3].Pos.xz += Wind * 1.0f ;
	o[4].Pos.xz += Wind * 1.5f ;
	o[5].Pos.xz += Wind * 1.5f ;
	o[6].Pos.xz += Wind * 2.0f ;

gSP.Timeは定数バッファで、起動時からのFrame数が渡ってくる様にしてある。
これをuvのオフセットとして使い、毎フレーム少しずつずらした値をWindとして取得する。
底辺以外の頂点にWindを足す。掛け目は上の方ほど影響が大きくなるようにする。



遠くは手を抜く


カメラとの距離を測って、3つの表現方法に切り替えてみる。
手前は頂点7つ版、真ん中は頂点3つ版、奥はビルボードで草のテクスチャを貼り付ける。

位置により手を抜く

草テクスチャ

ジオメトリシェーダの出力構造体に「uint Type」を追加して、それぞれの識別番号を入れる。
ピクセルシェーダで識別番号をみて、ビルボードとそれ以外に分けて処理をする。


三角部分に赤みを足して見たのがこれ。


上から見下ろすとこんな感じ。


上からのカメラがある場合は使い物にならないけど、キャラクタ視点だけなら使えそう。

PIX結果



ハルシェーダのEdgeFactorを12.25でPIXのスナップショットを取ってみたところ、869.583usだった。1msかかってないので、なかなか優秀かも。