質問編集履歴

2 追記の追記

torano

torano score 91

2019/04/15 09:52  投稿

[Unity]UV共有してる面にもUV2を使って一か所だけにペイントしたい
### やりたいこと
動的に3Dモデルのテクスチャに書き込むペイントアプリを作成中です。対象のモデルはユニティちゃんのようなSkinnedMeshRendererやMeshRenderer(MeshFilter)をいくつかもっているもので、マウスをクリックしそこからレイをとばしモデルに書き込むようなアプリを考えています。
どのようにして書き込むかわからなかったのですが、こちらで質問した結果、ありがたいことに丁寧に教えてくださった方がいたので書き込むまでは実装することができました。
[[Unity]Graphics.Blitで投影テクスチャマッピングシェーダーをテクスチャに反映できない](https://teratail.com/questions/178300)
[[Unity]深度バッファを使って表面のみに投影テクスチャマッピング](https://teratail.com/questions/181122)
(コードや実装方法などリンク先に書いてあります。)
対象のテクスチャをレンダーテクスチャに置き換え、投影テクスチャマッピングをサブカメラから行い、CommandBuffer.DrawRendererを使い直接書き込むような仕組みとなっています。これだとモデルの裏側にも書き込んでしまうので深度情報を使いサブカメラから見えるエリアにのみ塗れるようにしています。
### 問題点
さて、ここまででなんとかペイントはできるようになったのですが、最初の質問の回答者様のおっしゃる通り、UVが重なっている場合(テクスチャの同じ領域を複数の三角形で使っている場合)、うまく塗ることができません。塗りたくないところも塗られてしまいます。そこで、これをどうにか直したいです。
そもそもモデル作成の時点でUVが重ならないようにすればいい話ではありますが、できれば既存のいろんなモデルに対してもこのアプリを使いたいと思っているので、アプリ側でなんとかしたいです。
### 自分でやったみたことと、何ができなかったか
以下のサイトを参考に、モデルのGenerate Lightmap UVsをオンにしシェーダーではUV2を使用することでうまくいかないか試してみました。
[ モデルの頂点をUV展開した先の位置に任意の絵をテクスチャに描き込む](http://edom18.hateblo.jp/entry/2018/11/08/084143)
結果思うようにいかなかったです。全頂点に1対1対応するUVの位置がUV2になるとのことでしたが、どうしても変な位置にかきこみされてしまいます。
まず今回の実装ですが、上の質問のリンクにあるものとは少し変えていて、モデルにはデフォルトのUnlitシェーダーを、そしてモデルとは別の空のGameObjectに深度用のセカンドカメラとペイント用のスクリプト(インスペクタからペイント用のマテリアルを設定)をつけ、モデルをマウスクリックするたびに、モデルのほうへそのカメラを向け、深度カメラのレンダリングをしつつペイント用のマテリアルにプロジェクション行列やペイントに必要な値を渡しつつコマンドバッファで直接塗りを行っています。(長くなるので今回はC#のソースは書きませんが、もしこれだけでわかりづらい場合は言っていただければ公開します。)
シェーダーコードは一番下に置いておきますが、前回の質問時に得られたものからあまりかわりません。重要な部分はuvをuv2に変えた部分です。しかしこれだとうまくいきません。そこでモデルのシェーダーもuvをuv2にかえてみたら書き込み自体はうまくいくのですが、今度はモデルにうまくメインテクスチャが張り付けられていません。
![ああああ](bcff2af0f8f7e3dc4666417c83dcecf5.png)
左が通常のUnlitシェーダーで、右はuv2にかえたものです。御覧の通りおかしくなっています。
ライトマップの仕組みがよくわからないのですが、上のサイトによると重ならないようなUV配置を自動でつくるのがGenerate Light UVsなのだという理解です。UVを重ねるというのは、無駄な領域を減らしテクスチャサイズを減らすのが目的だと思うので、どうあがいてもテクスチャ一個だと「重ならないようにUV再配置をし、かつそれぞれの頂点は同じ位置を参照する」ことはできないと思うので、重ならないようにUV配置したとき参照する位置はバラバラ(=見た目がおかしくなる)のは必然といえば必然だと思うのですが、しかし上記のサイトでは画像を見る限りUV2を使いつつもテクスチャはちゃんと想定通りに貼ってあるようにみえます。これをどうやっているのか知りたいです。(上記のサイトではCommandBuffer.DrawRendererではなくGraphics.DrawMeshNowで行っていたので実際に書いてある通りやってみましたがうまくいかず...)
シェーダーコード
```
Shader "Painter/ProjectivePaint"
{
   Properties
   {
       _BrushColor("Brush Color", Color) = (1, 1, 1, 1)
       _BrushTex("Projection Texture", 2D) = "white" { }
       _DepthTex("Depth Texture", 2D) = "white" { }
   }
       SubShader
   {
       Tags { "RenderType" = "Opaque" }
       LOD 100
       Pass
       {
       // for back facing triangle
       Cull Off
       CGPROGRAM
       #pragma vertex vert
       #pragma fragment frag
       #include "UnityCG.cginc"
       struct appdata
       {
           float4 vertex: POSITION;
           float2 uv : TEXCOORD0;
           float2 uv2 : TEXCOORD1;   // use the lightmapping uv
       };
       struct v2f
       {
           float2 uv: TEXCOORD0;
           float2 uv2 : TEXCOORD1;
           float4 vertex: SV_POSITION;
           float4 projUV: TEXCOORD2;
       };
       sampler2D _ModelMainTex; //CommandBuffer.SetGlobalTextureを使いメインテクスチャをセットしている
       sampler2D _BrushTex;
       sampler2D _DepthTex;
       float4 _ModelMainTex_ST;
       float4 _BrushColor;
       uniform float4x4 ProjectorVPMat;
       v2f vert(appdata v)
       {
           v2f o;
           // regard uv as a vertex position when painting
           float2 position = v.uv2 * 2.0 - 1.0; /// uv2を使用
           #if UNITY_UV_STARTS_AT_TOP
           // reverse y if DirectX
           position.y *= -1.0;
           #endif
           o.vertex = float4(position, 0.0, 1.0);
           float4x4 mvpMat = mul(ProjectorVPMat, unity_ObjectToWorld);
           o.projUV = ComputeGrabScreenPos(mul(mvpMat, v.vertex));
           //o.uv = TRANSFORM_TEX(v.uv, _ModelMainTex);
           o.uv = v.uv;
           o.uv2 = v.uv2;
           return o;
       }
       fixed4 frag(v2f i) : SV_Target
       {
           /// 本質問にあまり関係ないデプスの処理
           #ifdef UNITY_REVERSED_Z
           float inverseZ = 1.0;
           #else
           float inverseZ = 0.0;
           #endif
           fixed4 projColor = fixed4(0, 0, 0, 0);
           float depth = 1.0 - inverseZ;
           if (i.projUV.w > 0.0)
           {
               // projection to screen space
               i.projUV /= i.projUV.w;
               if (i.projUV.x >= 0 && i.projUV.x <= 1 && i.projUV.y >= 0 && i.projUV.y <= 1)
               {
                   // sample the main color and depth at the same position
                   projColor = tex2D(_BrushTex, i.projUV);
                   depth = tex2D(_DepthTex, i.projUV).r;
               }
           }
           // make the range of both the clip coordinate z and depth [0, 1]
           float near = UNITY_NEAR_CLIP_VALUE;
           float far = 1.0 - inverseZ;
           float normalizedZ = (i.projUV.z - near) / (far - near);
           #ifdef UNITY_REVERSED_Z
           float normalizedDepth = 1 - depth;
           #else
           float normalizedDepth = depth;
           #endif
           // discard the current pixel if depth is less than the current z coordinate
           float avoidZFighting = 0.001;
           clip(normalizedDepth - normalizedZ + avoidZFighting);
       /// デプスの処理終わり
           fixed4 mainColor = tex2D(_ModelMainTex, i.uv2); // uv2で色を取得
           // if _BrushColor.a = 0 or projColor.a = 0, then mainColor. if both 1, then _BrushColor.
           return mainColor * (1 - _BrushColor.a * projColor.a) + _BrushColor * _BrushColor.a * projColor.a;
       }
       ENDCG
   }
   }
}
```
以上です。よろしくお願いします。
### 追記
Bongoさんが実装されていたようにUV2用のテクスチャを動的に作成し、uvをuv2に置き換えてみたところ正常にテクスチャがマッピングされ、かつペイントができるようになりましたが、ここで新たな問題として、テクスチャが劣化し以下のように黒い線が目立つようになりました。
![イメージ説明](1d932fa64e3fd5797eb331a2c8f52682.png)
これをどうにかできないか考えていたのですが、少し改善できたので報告します。
edo_m18さんの記事でも紹介されている、[Unity Graphics Programming vol.2](https://indievisuallab.stores.jp/items/5ae077b850bbc30f3a000a6d)(有料)
こちらにもペイントのプロジェクトがあり、参考になるものがないかと探していたのですが、「ペイントした際できてしまうメッシュの隙間を埋めるシェーダー」を使用していて、ソースコードはGithubで公開されていました。
[ソース](https://github.com/IndieVisualLab/UnityGraphicsProgramming2/blob/master/Assets/ProjectionSpray/01_SimpleDraw/Shaders/FillCrack.shader)
こちらでは、ピクセルの色のアルファが一定より低ければ近傍の色を使う、といった実装になっています。試しにこれを使ってみましたら、多少改善したのですが、メインカメラがMSAAありだとまだ黒い線が目立っています。
![イメージ説明](0b27c81c0c13aee124e4d7ee44d90d7e.png)
そこで、少し遠くの近傍テクセルをとってくるようにしたら以下のように。
```
float2 d = _MainTex_TexelSize.xy * 5;
```
![イメージ説明](66389df6a696823f30fce350b410f444.png)
手にはまだ少し黒い線が残ってますが、顔のはほとんど見えません。
なんとか黒い線をなんとか少なくすることはできたのですが、UV2テクスチャにした時点で以下のような劣化も発生しており、これを直す方法はまだわかりません。
![イメージ説明](d7ebc3136a43ee979049de3900e0e824.png)
Unityちゃんの服やリボンなどに変な模様が...
Unityちゃんの服やリボンなどに変な模様が...
![イメージ説明](46b3a1c1c07e0ef2e69a4cbda8702272.png)
テクスチャには変な模様はないのですが、モデルをみるとついています。
  • Unity

    11812 questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • Unity3D

    3815 questions

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

1 改善と新たな問題の追記

torano

torano score 91

2019/04/15 09:40  投稿

[Unity]UV共有してる面にもUV2を使って一か所だけにペイントしたい
### やりたいこと
動的に3Dモデルのテクスチャに書き込むペイントアプリを作成中です。対象のモデルはユニティちゃんのようなSkinnedMeshRendererやMeshRenderer(MeshFilter)をいくつかもっているもので、マウスをクリックしそこからレイをとばしモデルに書き込むようなアプリを考えています。
どのようにして書き込むかわからなかったのですが、こちらで質問した結果、ありがたいことに丁寧に教えてくださった方がいたので書き込むまでは実装することができました。
[[Unity]Graphics.Blitで投影テクスチャマッピングシェーダーをテクスチャに反映できない](https://teratail.com/questions/178300)
[[Unity]深度バッファを使って表面のみに投影テクスチャマッピング](https://teratail.com/questions/181122)
(コードや実装方法などリンク先に書いてあります。)
対象のテクスチャをレンダーテクスチャに置き換え、投影テクスチャマッピングをサブカメラから行い、CommandBuffer.DrawRendererを使い直接書き込むような仕組みとなっています。これだとモデルの裏側にも書き込んでしまうので深度情報を使いサブカメラから見えるエリアにのみ塗れるようにしています。
### 問題点
さて、ここまででなんとかペイントはできるようになったのですが、最初の質問の回答者様のおっしゃる通り、UVが重なっている場合(テクスチャの同じ領域を複数の三角形で使っている場合)、うまく塗ることができません。塗りたくないところも塗られてしまいます。そこで、これをどうにか直したいです。
そもそもモデル作成の時点でUVが重ならないようにすればいい話ではありますが、できれば既存のいろんなモデルに対してもこのアプリを使いたいと思っているので、アプリ側でなんとかしたいです。
### 自分でやったみたことと、何ができなかったか
以下のサイトを参考に、モデルのGenerate Lightmap UVsをオンにしシェーダーではUV2を使用することでうまくいかないか試してみました。
[ モデルの頂点をUV展開した先の位置に任意の絵をテクスチャに描き込む](http://edom18.hateblo.jp/entry/2018/11/08/084143)
結果思うようにいかなかったです。全頂点に1対1対応するUVの位置がUV2になるとのことでしたが、どうしても変な位置にかきこみされてしまいます。
まず今回の実装ですが、上の質問のリンクにあるものとは少し変えていて、モデルにはデフォルトのUnlitシェーダーを、そしてモデルとは別の空のGameObjectに深度用のセカンドカメラとペイント用のスクリプト(インスペクタからペイント用のマテリアルを設定)をつけ、モデルをマウスクリックするたびに、モデルのほうへそのカメラを向け、深度カメラのレンダリングをしつつペイント用のマテリアルにプロジェクション行列やペイントに必要な値を渡しつつコマンドバッファで直接塗りを行っています。(長くなるので今回はC#のソースは書きませんが、もしこれだけでわかりづらい場合は言っていただければ公開します。)
シェーダーコードは一番下に置いておきますが、前回の質問時に得られたものからあまりかわりません。重要な部分はuvをuv2に変えた部分です。しかしこれだとうまくいきません。そこでモデルのシェーダーもuvをuv2にかえてみたら書き込み自体はうまくいくのですが、今度はモデルにうまくメインテクスチャが張り付けられていません。
![ああああ](bcff2af0f8f7e3dc4666417c83dcecf5.png)
左が通常のUnlitシェーダーで、右はuv2にかえたものです。御覧の通りおかしくなっています。
ライトマップの仕組みがよくわからないのですが、上のサイトによると重ならないようなUV配置を自動でつくるのがGenerate Light UVsなのだという理解です。UVを重ねるというのは、無駄な領域を減らしテクスチャサイズを減らすのが目的だと思うので、どうあがいてもテクスチャ一個だと「重ならないようにUV再配置をし、かつそれぞれの頂点は同じ位置を参照する」ことはできないと思うので、重ならないようにUV配置したとき参照する位置はバラバラ(=見た目がおかしくなる)のは必然といえば必然だと思うのですが、しかし上記のサイトでは画像を見る限りUV2を使いつつもテクスチャはちゃんと想定通りに貼ってあるようにみえます。これをどうやっているのか知りたいです。(上記のサイトではCommandBuffer.DrawRendererではなくGraphics.DrawMeshNowで行っていたので実際に書いてある通りやってみましたがうまくいかず...)
シェーダーコード
```
Shader "Painter/ProjectivePaint"
{
   Properties
   {
       _BrushColor("Brush Color", Color) = (1, 1, 1, 1)
       _BrushTex("Projection Texture", 2D) = "white" { }
       _DepthTex("Depth Texture", 2D) = "white" { }
   }
       SubShader
   {
       Tags { "RenderType" = "Opaque" }
       LOD 100
       Pass
       {
       // for back facing triangle
       Cull Off
       CGPROGRAM
       #pragma vertex vert
       #pragma fragment frag
       #include "UnityCG.cginc"
       struct appdata
       {
           float4 vertex: POSITION;
           float2 uv : TEXCOORD0;
           float2 uv2 : TEXCOORD1;   // use the lightmapping uv
       };
       struct v2f
       {
           float2 uv: TEXCOORD0;
           float2 uv2 : TEXCOORD1;
           float4 vertex: SV_POSITION;
           float4 projUV: TEXCOORD2;
       };
       sampler2D _ModelMainTex; //CommandBuffer.SetGlobalTextureを使いメインテクスチャをセットしている
       sampler2D _BrushTex;
       sampler2D _DepthTex;
       float4 _ModelMainTex_ST;
       float4 _BrushColor;
       uniform float4x4 ProjectorVPMat;
       v2f vert(appdata v)
       {
           v2f o;
           // regard uv as a vertex position when painting
           float2 position = v.uv2 * 2.0 - 1.0; /// uv2を使用
           #if UNITY_UV_STARTS_AT_TOP
           // reverse y if DirectX
           position.y *= -1.0;
           #endif
           o.vertex = float4(position, 0.0, 1.0);
           float4x4 mvpMat = mul(ProjectorVPMat, unity_ObjectToWorld);
           o.projUV = ComputeGrabScreenPos(mul(mvpMat, v.vertex));
           //o.uv = TRANSFORM_TEX(v.uv, _ModelMainTex);
           o.uv = v.uv;
           o.uv2 = v.uv2;
           return o;
       }
       fixed4 frag(v2f i) : SV_Target
       {
           /// 本質問にあまり関係ないデプスの処理
           #ifdef UNITY_REVERSED_Z
           float inverseZ = 1.0;
           #else
           float inverseZ = 0.0;
           #endif
           fixed4 projColor = fixed4(0, 0, 0, 0);
           float depth = 1.0 - inverseZ;
           if (i.projUV.w > 0.0)
           {
               // projection to screen space
               i.projUV /= i.projUV.w;
               if (i.projUV.x >= 0 && i.projUV.x <= 1 && i.projUV.y >= 0 && i.projUV.y <= 1)
               {
                   // sample the main color and depth at the same position
                   projColor = tex2D(_BrushTex, i.projUV);
                   depth = tex2D(_DepthTex, i.projUV).r;
               }
           }
           // make the range of both the clip coordinate z and depth [0, 1]
           float near = UNITY_NEAR_CLIP_VALUE;
           float far = 1.0 - inverseZ;
           float normalizedZ = (i.projUV.z - near) / (far - near);
           #ifdef UNITY_REVERSED_Z
           float normalizedDepth = 1 - depth;
           #else
           float normalizedDepth = depth;
           #endif
           // discard the current pixel if depth is less than the current z coordinate
           float avoidZFighting = 0.001;
           clip(normalizedDepth - normalizedZ + avoidZFighting);
       /// デプスの処理終わり
           fixed4 mainColor = tex2D(_ModelMainTex, i.uv2); // uv2で色を取得
           // if _BrushColor.a = 0 or projColor.a = 0, then mainColor. if both 1, then _BrushColor.
           return mainColor * (1 - _BrushColor.a * projColor.a) + _BrushColor * _BrushColor.a * projColor.a;
       }
       ENDCG
   }
   }
}
```
以上です。よろしくお願いします。
以上です。よろしくお願いします。
### 追記
Bongoさんが実装されていたようにUV2用のテクスチャを動的に作成し、uvをuv2に置き換えてみたところ正常にテクスチャがマッピングされ、かつペイントができるようになりましたが、ここで新たな問題として、テクスチャが劣化し以下のように黒い線が目立つようになりました。
![イメージ説明](1d932fa64e3fd5797eb331a2c8f52682.png)
これをどうにかできないか考えていたのですが、少し改善できたので報告します。
edo_m18さんの記事でも紹介されている、[Unity Graphics Programming vol.2](https://indievisuallab.stores.jp/items/5ae077b850bbc30f3a000a6d)(有料)
こちらにもペイントのプロジェクトがあり、参考になるものがないかと探していたのですが、「ペイントした際できてしまうメッシュの隙間を埋めるシェーダー」を使用していて、ソースコードはGithubで公開されていました。
[ソース](https://github.com/IndieVisualLab/UnityGraphicsProgramming2/blob/master/Assets/ProjectionSpray/01_SimpleDraw/Shaders/FillCrack.shader)
こちらでは、ピクセルの色のアルファが一定より低ければ近傍の色を使う、といった実装になっています。試しにこれを使ってみましたら、多少改善したのですが、メインカメラがMSAAありだとまだ黒い線が目立っています。
![イメージ説明](0b27c81c0c13aee124e4d7ee44d90d7e.png)
そこで、少し遠くの近傍テクセルをとってくるようにしたら以下のように。
```
float2 d = _MainTex_TexelSize.xy * 5;
```
![イメージ説明](66389df6a696823f30fce350b410f444.png)
手にはまだ少し黒い線が残ってますが、顔のはほとんど見えません。
なんとか黒い線をなんとか少なくすることはできたのですが、UV2テクスチャにした時点で以下のような劣化も発生しており、これを直す方法はまだわかりません。
![イメージ説明](d7ebc3136a43ee979049de3900e0e824.png)
Unityちゃんの服やリボンなどに変な模様が...
  • Unity

    11812 questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • Unity3D

    3815 questions

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

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る