概要
ジオメトリシェーダーの出力形式は3種類あります.1
Type | 説明 |
---|---|
PointStream | 点 |
LineStream | 線分 |
TriangleStream | 三角形 |
普段使うのはTriangleStream
ですが,エディタ表示用に線をたくさん書きたくなったので,LineStream
の使い方を調べました.
基本的な使い方
使い方はTriangleStream
のときと同じです.線分なので面の向きとかはありませんが,UVは使えます(1次元なのでuだけです).
Shader "Test/LineTest1"
{
Properties {}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma multi_compile_instancing
#pragma target 4.6
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2g
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
// sizeof(g2f) <= 7
struct g2f
{
float4 vertex : SV_POSITION;
float uv : TEXCOORD0;
UNITY_VERTEX_OUTPUT_STEREO
};
float3 h2rgb(float h)
{
return ((clamp(abs(frac(h + float3(0, 2, 1) / 3.0) * 6.0 - 3.0) - 1.0, 0.0, 1.0) - 1.0) + 1.0);
}
v2g vert (appdata v)
{
v2g o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = v.vertex;
return o;
}
[maxvertexcount(2)]
void geom (point v2g IN[1], uint primitive_index : SV_PrimitiveID, inout LineStream<g2f> stream)
{
// 1つだけ図形を描く
if (primitive_index > 0)
{
return;
}
g2f o;
UNITY_SETUP_INSTANCE_ID(IN[0]);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN[0]);
UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(IN[0], o);
// 書きたい線分の端点
float3 p0 = float3(0, 0, 0);
float3 p1 = float3(1, 0, 0);
// 点をAppendする
o.vertex = UnityObjectToClipPos(float4(p0, 1));
o.uv = 0.0;
stream.Append(o);
o.vertex = UnityObjectToClipPos(float4(p1, 1));
o.uv = 1.0;
stream.Append(o);
// 図形を終了
stream.RestartStrip();
}
float4 frag (g2f i) : SV_Target
{
return float4(h2rgb(i.uv), 1.0);
}
ENDCG
}
}
}
Cubeとかにアタッチすると線分が表示されます.幅は1ピクセルです.調節はできません.
RestartStrip のタイミング
TriangleStream
のときは,三角形の頂点をAppend
したら,その都度RestartStrip()
しますが,LineStream
のときは,線を切りたいタイミングでRestartStrip()
します.
// geom 以外は LineTest1.shader と同じ
[maxvertexcount(6)]
void geom (point v2g IN[1], uint primitive_index : SV_PrimitiveID, inout LineStream<g2f> stream)
{
// 1つだけ図形を描く
if (primitive_index > 0)
{
return;
}
g2f o;
UNITY_SETUP_INSTANCE_ID(IN[0]);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN[0]);
UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(IN[0], o);
// 点をAppendする
[unroll(6)]
for (float i = 0; i < 6; i++)
{
float angle = 2.0 * (i / 5.0f) * UNITY_TWO_PI;
float3 p = float3(-sin(angle), cos(angle), 0);
o.vertex = UnityObjectToClipPos(float4(p, 1));
o.uv = i / 6.0f;
stream.Append(o);
}
// 図形を終了
stream.RestartStrip();
}
RestartStrip()
するまでは線がつながって描画されます.
ちなみに,RestartStrip()
しなくても,geom
を抜けると強制的に図形が終了します.そのため,複数の入力プリミティブにわたって連続した線を描画したい場合は,前回終了時の頂点から描画を再開しないと線が途切れてしまうので注意が必要です.
雑記
デバッグ用に線の表示をしたい場合は普通,スクリプトでDebug.DrawLine()
とかDebug.DrawRay()
を使って表示しますが,表示したい線の本数が数十万オーダー & Custom Render Texture の内容を読み取ってリアルタイムに表示したい,という事情から,シェーダーだけで表示する方法を調べていました.
一度テンプレートを作ると結構使いやすくて重宝しています.