質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

Q&A

解決済

1回答

1840閲覧

指定したpixelの太さの線を描画するShader

concern12

総合スコア18

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

0グッド

0クリップ

投稿2022/07/30 12:16

編集2022/07/31 09:41

指定したpixelの太さの線を描画するShaderそのものがネット上に探してもなかったため、下記サイトのShaderを改修して、指定したPixelの太さの線を描画するShaderを実装しようとしているのですが、当方Shaderど素人につき、行き詰まったため質問を投稿いたしました。
【Unity】カメラからどれだけ離れても、最低1ピクセルは表示されるように保障する - 神様は有休消化中です。

上記サイトのShaderを使ったMeshに対してズームすると、当然ながらそのObjectが大きく表示されます。
指定したpixelの太さの線を描画することが目的のため、ズームしても大きくならないようにする方法を自分なりに調べたところ、UnityObjectToClipPos(v.vertex)によってズーム後の頂点座標になってしまっているからだと考えているのですが、正しいでしょうか?

その場合、指定したpixelの太さにするために、ズーム後の頂点座標にしないようにするためにはどうすればよろしいでしょうか?

初歩的な質問となりますが、どうぞよろしくお願いいたします。

使用しているMesh生成のコード

cs

1 public static Mesh CreateLineMesh(Vector3 center, float length, float thickness) 2 { 3 var mesh = new Mesh(); 4 var triangles = new int[6]; 5 triangles[0] = 0; 6 triangles[1] = 1; 7 triangles[2] = 2; 8 triangles[3] = 2; 9 triangles[4] = 3; 10 triangles[5] = 0; 11 var vertices = new Vector3[4]; 12 13 var left = center.x - length / 2.0f; 14 var right = center.x + length / 2.0f; 15 var bottom = center.y - thickness / 2; 16 var top = center.y + thickness / 2; 17 18 vertices[0] = new Vector3(left, bottom, center.z); 19 vertices[1] = new Vector3(left, top, center.z); 20 vertices[2] = new Vector3(right, top, center.z); 21 vertices[3] = new Vector3(right, bottom, center.z); 22 23 var nornals = vertices.ToArray(); 24 25 mesh.SetVertices(vertices); 26 mesh.SetTriangles(triangles, 0); 27 mesh.SetNormals(nornals); 28 29 mesh.RecalculateBounds(); 30 31 return mesh; 32 }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

最初から2つの三角形が組み合わさって線になっているメッシュに対してUnityObjectToClipPos(v.vertex)で変換すると、確かにズーム後の位置へ変換されてしまうかと思います。
メッシュ自体は太さのない線(MeshTopologyLinesまたはLineStripのメッシュ)としておいて、シェーダー上で線の頂点をクリップ空間に変換した後で、線が太さを持つ長方形になるよう三角形を生成させるのはどうでしょうか。

ShaderLab

1Shader "Unlit/ConstantWidthLine" 2{ 3 Properties 4 { 5 _Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0) 6 _LineWidth ("Line Width", Range(0.0, 16.0)) = 4.0 7 } 8 SubShader 9 { 10 Tags { "RenderType" = "Opaque" } 11 12 Pass 13 { 14 CGPROGRAM 15 #pragma vertex vert 16 #pragma geometry geom 17 #pragma fragment frag 18 #include "UnityCG.cginc" 19 20 struct v2g 21 { 22 float4 screenPos : TEXCOORD0; 23 }; 24 25 struct g2f 26 { 27 float4 vertex : SV_POSITION; 28 }; 29 30 v2g vert(float4 vertex : POSITION) 31 { 32 v2g o; 33 34 // バーテックスシェーダー上で折れ線の各頂点をクリップ座標に変換する 35 o.screenPos = UnityObjectToClipPos(vertex); 36 37 // さらに、ついでにスクリーンの大きさに合わせて引き伸ばしておく 38 o.screenPos.xy *= _ScreenParams.xy * 0.5; 39 40 return o; 41 } 42 43 float _LineWidth; 44 45 // トポロジーが線のメッシュ(太さのない線)から線分の両端の2頂点を受け取り、 46 // トポロジーが三角形のメッシュ(太さを持つ長方形)として4隅の頂点を出力する 47 [maxvertexcount(4)] 48 void geom(line v2g input[2], inout TriangleStream<g2f> outStream) 49 { 50 // 線分の両端の座標をwで割ってスクリーン座標を求める 51 float2 p0 = input[0].screenPos.xy / input[0].screenPos.w; 52 float2 p1 = input[1].screenPos.xy / input[1].screenPos.w; 53 54 // 線分に平行なベクトルを求めておく 55 float2 tangent = p1 - p0; 56 float sqrLength = dot(tangent, tangent); 57 if (sqrLength > 0.0) 58 { 59 // tangentを90°回転してnormalとする 60 float2 normal = tangent.yx * float2(-1.0, 1.0); 61 62 // normal、tangentの長さを線幅の半分にして... 63 float scale = (_LineWidth * 0.5) / sqrt(sqrLength); 64 normal *= scale; 65 tangent *= scale; 66 67 // p0とp1をtangent、normalの方向にずらした4頂点を作り、 68 // クリップ座標に戻して出力する 69 g2f o; 70 float2 s = (_ScreenParams.zw - 1.0) * 2.0; 71 o.vertex = input[0].screenPos; 72 o.vertex.xy = (p0 + normal - tangent) * o.vertex.w * s; 73 outStream.Append(o); 74 o.vertex.xy = (p0 - normal - tangent) * o.vertex.w * s; 75 outStream.Append(o); 76 o.vertex = input[1].screenPos; 77 o.vertex.xy = (p1 + normal + tangent) * o.vertex.w * s; 78 outStream.Append(o); 79 o.vertex.xy = (p1 - normal + tangent) * o.vertex.w * s; 80 outStream.Append(o); 81 } 82 } 83 84 fixed4 _Color; 85 86 fixed4 frag(g2f i) : SV_Target 87 { 88 return _Color; 89 } 90 ENDCG 91 } 92 } 93}

図

ただしこの方法ですと、「ジオメトリシェーダ・コンピュートシェーダのモバイル対応状況【Unity】|アマガミナブログ」で挙げられているような一部環境(たとえばグラフィックスシステムにMetalが使われているような)では動かないかと思います。その場合は別の手を考えてみようと思いますが、おそらくメッシュの作り方にも手を加える必要があるように予想されますので、メッシュを作る部分のスクリプトもご提示いただけると参考になりそうです。

追記

まず、メッシュ生成部分を下記のようにしてみました。

C#

1 // CreateLineMeshの引数のthicknessは廃止し、マテリアル側で設定することにする 2 public static Mesh CreateLineMesh(Vector3 center, float length) 3 { 4 var mesh = new Mesh(); 5 var triangles = new int[6]; 6 triangles[0] = 0; 7 triangles[1] = 1; 8 triangles[2] = 2; 9 triangles[3] = 2; 10 triangles[4] = 3; 11 triangles[5] = 0; 12 var vertices = new Vector3[4]; 13 14 // bottomとtopはcenter.yとし、現時点では太さのない潰れた長方形の状態にしておく 15 var left = center.x - length / 2.0f; 16 var right = center.x + length / 2.0f; 17 var bottom = center.y; 18 var top = center.y; 19 20 vertices[0] = new Vector3(left, bottom, center.z); 21 vertices[1] = new Vector3(left, top, center.z); 22 vertices[2] = new Vector3(right, top, center.z); 23 vertices[3] = new Vector3(right, bottom, center.z); 24 25 var nornals = vertices.ToArray(); 26 27 // 各頂点に追加情報を埋め込む 28 // まずXYZには線分の逆末端の座標をいれておく 29 // (left側頂点にはright側の、right側頂点にはleft側の座標) 30 var otherVertices = new Vector4[vertices.Length]; 31 for (var i = 0; i < otherVertices.Length; i++) 32 { 33 otherVertices[i] = vertices[vertices.Length - i - 1]; 34 } 35 36 // そしてWには0~3の数値を入れておく 37 // これは各頂点が4隅のうちどれであるかを特定する目的のもので、さしあたり 38 // 1ビット目がbottomかtopかを、2ビット目がleftかrightかを示すことにした 39 otherVertices[0].w = 0; 40 otherVertices[1].w = 1; 41 otherVertices[2].w = 3; 42 otherVertices[3].w = 2; 43 44 mesh.SetVertices(vertices); 45 mesh.SetTriangles(triangles, 0); 46 mesh.SetNormals(nornals); 47 48 // 追加情報の埋め込み先はUVを使わせてもらうことにした 49 mesh.SetUVs(0, otherVertices); 50 51 mesh.RecalculateBounds(); 52 53 return mesh; 54 }

そしてマテリアルは下記のようにしました。

ShaderLab

1Shader "Unlit/Line" 2{ 3 Properties 4 { 5 _Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0) 6 _Thickness ("Thickness", Range(0.0, 128.0)) = 4.0 7 } 8 SubShader 9 { 10 Tags { "RenderType" = "Opaque" } 11 12 Pass 13 { 14 CGPROGRAM 15 #pragma vertex vert 16 #pragma fragment frag 17 #include "UnityCG.cginc" 18 19 struct appdata 20 { 21 float4 vertex : POSITION; 22 float4 otherVertex : TEXCOORD0; 23 }; 24 25 float _Thickness; 26 27 float4 vert(appdata v) : SV_POSITION 28 { 29 float4 p0 = UnityObjectToClipPos(v.vertex); 30 float4 p1 = UnityObjectToClipPos(float4(v.otherVertex.xyz, 1.0)); 31 32 // 線分の両端の座標をスクリーン座標に変換する 33 float2 sp0 = (p0.xy * _ScreenParams.xy) * (0.5 / p0.w); 34 float2 sp1 = (p1.xy * _ScreenParams.xy) * (0.5 / p1.w); 35 36 // 線分に平行なベクトルを求めておく 37 // このとき、頂点がleftかrightかによって向きを調整する 38 float2 tangent = (sp0 - sp1) * (((int)v.otherVertex.w & 2) - 1); 39 float sqrLength = dot(tangent, tangent); 40 if (sqrLength > 0.0) 41 { 42 // tangentを90°回転してnormalとする 43 float2 normal = tangent.yx * float2(-1.0, 1.0); 44 45 // normalの長さを線幅の半分にして... 46 normal *= (_Thickness * 0.5) / sqrt(sqrLength); 47 48 // 頂点をnormalの方向にずらす 49 // このとき、頂点がbottomかtopかによって向きを調整する 50 p0.xy = (sp0 - normal * (((int)v.otherVertex.w & 1) * 2 - 1)) * p0.w * (_ScreenParams.zw - 1.0) * 2.0; 51 } 52 53 return p0; 54 } 55 56 fixed4 _Color; 57 58 fixed4 frag() : SV_Target 59 { 60 return _Color; 61 } 62 ENDCG 63 } 64 } 65}

図2

投稿2022/07/31 08:30

編集2022/07/31 21:34
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

concern12

2022/07/31 09:42

ご回答ありがとうございます。 そうですね。私もそのリンク先を読んでジオメトリシェーダーはではない手法(質問文の頂点シェーダー)を使おうとしていました。 質問文に記載するべきでした。申し訳ありません。 Meshのコードにつきましては、質問の方に追記いたしました。 お手数をおかけし申し訳ありませんが、よろしくお願い致します。
Bongo

2022/07/31 21:34

コードご提示ありがとうございます。ジオメトリシェーダー抜きの案を検討してみましたがいかがでしょうか。 メッシュ側の各頂点に追加情報として線分の末端座標や長方形上の角の位置を埋め込むことで、vert上で頂点を移動させる際の手がかりにする方針でやってみました。
concern12

2022/08/01 03:56

コードと理解にしやすい詳細なコメントありがとうございます! 実際に試したところ、Game View上では、Edit->Project Settings->Anti Aliasingの設定がDisabledだと描画されません。 どうやら`x2 Multi Sampling`以上であれば描画されるようです。 これはどういった理由によるものでしょうか? 重ねての質問になり恐縮ですが、もしご存知であれば教えていただければ幸いです。
Bongo

2022/08/01 22:27

すみません、まだ私の方で描画されない現象を再現できておらず(Windows版2022.1.4f1とMac版2022.1.11f1で試してみたのですが、マルチサンプリングありでもDisabledでも描画されていたようでした)、原因不明な状態です... 何か手がかりを掴みたいところですが、新規にプロジェクトを作成して今回の線関係のファイルだけ用意し、シーンに線オブジェクトを配置しただけの状態でもアンチエイリアスの有無によって描画されたりされなかったりするでしょうか? あるいは、消えてしまう現象はカメラと線の位置関係だとかが関係ありそうですかね?回答のコードでは考慮していなかったのですが、「線の末端の一方がカメラの前方、もう一方がカメラの後方」という状況だと座標計算が狂って線が欠けてしまうかもしれません。
concern12

2022/08/02 02:02

検証してくださりありがとうございます。 色々と試してみました。 すべてUnity 2021.2.19f1で実行しました。 新規3D Projectで設定変更なし→描画される 新規3D ProjectでAnti Aliasing Disabled→描画される 新規3D ProjectでProject Settings->Qualityの設定をすべて新規2D Projectのものにする→描画される 新規2D Projectで設定変更なし→描画される 新規2D ProjectでAnti Aliasing Disabled→描画されない 新規2D ProjectでAnti Aliasing Disabledでビルド→描画されない 新規2D ProjectでProject Settings->Editor->Modeを2Dに切り替えて1回目の再生→描画されない 新規2D ProjectでProject Settings->Editor->Modeを3Dに切り替えて2回目以降の再生→描画される (Project Settings->Editor->Modeの切り替えは切り替えた直後の再生では反映されないのかもしれません) カメラの位置を調整してもとくに変化はありませんでした。 2D設定か3D設定かで、レンダリングの工程が違うのかもしれません。 ただ、下のサイトを読んでも特にそれっぽい記述はありませんでした。 [2D/3D モード設定 \- Unity マニュアル](https://docs.unity3d.com/ja/2019.4/Manual/2DAnd3DModeSettings.html)
Bongo

2022/08/03 18:43

お待たせしてしまいすみません。さまざまな条件をご提示いただいて参考になりました。 おっしゃるようにレンダリング工程の違いが起因しているかもしれません。アンチエイリアスの設定だとか2D/3Dモードの違いによっては(確かにマニュアルでは特に言及されていないようですね)、メッシュをダイレクトにスクリーンへレンダリングするか、または一旦レンダーテクスチャ上にレンダリングしてから後処理を加えつつスクリーンへレンダリングするかが切り替わっているように見えました。 「様々なグラフィックス API のシェーダーの作成 - Unity マニュアル」(https://docs.unity3d.com/ja/current/Manual/SL-PlatformDifferences.html )の説明にありますように、レンダーテクスチャ上への描画の場合は環境に応じて座標系の上下が反転される場合があり、反転の有無によって線を太くする際の引き伸ばし方向が影響を受け、メッシュが裏返った場合は線が消えてしまうんだろうと思います。 とりあえず、シェーダーコード中の... float2 normal = tangent.yx * float2(-1.0, 1.0); という部分を、 float2 normal = tangent.yx * float2(-1.0, 1.0) * -sign(UNITY_MATRIX_P._22); に変更するとどうなるかお試しいただけますでしょうか。normalの向きにY軸の符号を反映させればいいんじゃないかと考えまして、私の試した限りではこれでアンチエイリアスの有無によらず描画できたように見えました。ですがまだ見落としがあるかもしれませんので、もし不具合を見つけましたらコメントください。
concern12

2022/08/04 00:37

ご回答ありがとうございます。 描画されることを確認しました。 とても助かりました! 親切ご丁寧に対応いただきありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問