【Unity】ガッシュ画風シェーダーを描いてみたかった【Shader】
美術には疎いのでガッシュ風というのが適切かわかりませんが、不透明水彩画風シェーダーに挑戦中です。
まだ荒いですが、雰囲気は出てるかと思います。
やってること
- スキニングメッシュから頂点情報を取得し、配列化
- ComputeBuffer を生成し配列をセット
- CommandBuffer を生成し、DrawProceduralで描画コマンドとComputeBuffer を設定してカメラに追加
- 毎フレーム 頂点情報を ComputeBuffer に設定する。
- シェーダーは配列を参照して描画する。
シェーダーは1スクエアごと
このようなテクスチャーと色を乗算して描画してます。
スクリプト側
頂点情報は
struct BrusheStruct
{
public int triID;
public Vector3 vertex;
public Vector3 normal;
public Vector4 color;
public float size;
} // struct BrusheStruct
このような構造体を用意して頂点分確保し、ComputeBuffer に渡します。
var buffer_length = m_BrusheStructArray.Length;
m_ComputeBuffer = new ComputeBuffer( buffer_length, System.Runtime.InteropServices.Marshal.SizeOf( typeof( BrusheStruct)));
m_ComputeBuffer.SetData( m_BrusheStructArray);
その後、CommandBuffer を生成し DrawProcedural で スクエアを表示するようカメラに登録します。
var materialPropertyBlock = new MaterialPropertyBlock();
materialPropertyBlock.SetTexture( "_BrusheTexture", m_BrusheTexture);
materialPropertyBlock.SetBuffer( "_Params", m_ComputeBuffer);
var camera = Camera.main;
m_CommandBuffer = new CommandBuffer();
m_CommandBuffer.DrawProcedural(
transform.localToWorldMatrix, // 使用するトランスフォーム(変換)マトリックス
m_Material, // 使用するマテリアル
-1, // 使用するシェーダーのパス (あるいはすべてに -1 を渡します。)
MeshTopology.Quads, // プロシージャルジオメトリのトポロジー
buffer_length*4, // 描画する頂点数
1, // レンダリングするインスタンス数
materialPropertyBlock // レンダリングの前にだけ適用するマテリアルプロパティーを追加します。 MaterialPropertyBlock を参照してください。
);
camera.AddCommandBuffer( CameraEvent.BeforeImageEffectsOpaque, m_CommandBuffer);
m_Material には スクエアを描画するシェーダーを割り当ててます。
m_Material = new Material( Shader.Find( "Hidden/Neko/Gouache Brushe"));
あとは Update 時に SkinnedMeshRenderer から BakeMesh で頂点情報をひっぱてきて m_ComputeBuffer を更新します。
var skinnedMeshRenderer = gameObject?.GetComponent<SkinnedMeshRenderer>();
Mesh mesh = new Mesh();
skinnedMeshRenderer.BakeMesh( mesh);
/* 頂点情報を更新 */
m_ComputeBuffer.SetData( m_BrusheStructArray);
シェーダー側
スクリプトで用意した構造体と同じものを用意して StructuredBuffer で定義します。
struct BrusheStruct
{
int triID;
float3 vertex;
float3 normal;
float4 color;
float size;
}; // struct BrusheStruct
StructuredBuffer<BrusheStruct> _Params;
頂点シェーダーでは SV_VertexID を使って インデックス番号だけ受け取っています。頂点座標は不要。
頂点計算は パーリンノイズ などで多少ブレさせて出力しています。
struct appdata
{
uint index : SV_VertexID;
};
v2f vert( appdata v)
{
v2f o = (v2f)0;
uint inst = v.index / 4; // スクエアの番号
uint id = v.index % 4; // スクエアの4隅の番号
// 頂点取得
float4 vertex = float4( _Params[inst].vertex, 1);
// id 0~3 ごとに座標をオフセットする
// 略
// 色を取得
o.color = _Params[inst].color;
return o;
}
ピクセルシェーダーでは 元の色から 色相 を多少ずらした色を組み合わせて元の色を出しています。
この色を出したい場合は
この2つのブラシを描画して
このように見せてます。本当は少しずらして表示している。
ライト計算など今までと変わらないです。
法線なども配列に突っ込んでこれまでと同じ計算をしています。
例えばスペキュラを追加してハイライト的な描写も可能です。
今回の肝は頂点バッファで座標を受け取らず、コンピュートバッファで受け取った情報を利用して描画する部分だと思います。
最後に
スクリプトのUpdate で SkinnedMeshRenderer から情報を取ってくる部分が非常に無駄に感じており、シェーダーだけで完結できないか模索中です。
RWStructuredBuffer を使って頂点情報を上書きすることも考えたのですが、render target が 5.0 以上と座敷が高くなるので現在保留中。
コンピュートシェーダーを使ってなんとかできないが検討中。
コンピュートバッファとコマンドバッファは色々できてとても面白いです。