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

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

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

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

Unity

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

解決済

[Unity]Graphics.Blitで投影テクスチャマッピングシェーダーをテクスチャに反映できない

torano
torano

総合スコア91

Unity3D

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

Unity

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

1回答

1評価

0クリップ

823閲覧

投稿2019/03/07 23:20

編集2019/03/07 23:30

何がしたいか

3Dモデルのテクスチャに直接書き込むようなペイントアプリを作成しようとしています。書き込む方法として、投影テクスチャマッピング用のシェーダーをGraphics.Blitを使いテクスチャに適用します。カメラからレイをとばしヒットした場所に書き込むようにするのですが、投影テクスチャリング用に疑似カメラを用意し、そのカメラのプロパティをシェーダーに渡すことで、特定の位置に書き込む(テクスチャを投影する)ようにしています。(投影テクスチャマッピングではなく、単にヒットした位置のUVをシェーダーに渡しその位置を中心にしてブラシ用のテクスチャも張り付ける方法も考え、試してみましたが、モデルのUVに大きく依存するので辞めました。)

何ができなかったか

投影テクスチャリングは実現できた(シェーダーコードは下にあります)のですが、Graphics.Blitで結果をテクスチャに直接反映しようとするとうまくいきません。一応塗られますが、位置が全然違います。

試したこと

まず投影テクスチャリングが失敗の原因なのかわからなかったので、以下のように、投影と投影後に動的にテクスチャに書き込むことができるスクリプトをつくってみました。このスクリプトで投影できることは確認できたのですが、投影した場所にテクスチャが書き込まれません。

スクリプトの説明と再現方法

投影されるモデルにこのスクリプトと投影用のシェーダーでつくったマテリアルを付けます。マテリアルのプロパティでブラシ用(投影される)テクスチャを適当に設定します。さらに、空のGameObjectをつくり、モデルのインスペクタからprojectorに設定します。色を設定し、プレイモードにすると、そのprojectorのオブジェクトのz方向に投影が行われます。インスペクタのねじマークからBakeを押すとGraphics.Blitによりテクスチャに投影結果が塗られます。

このスクリプトでは単に、オブジェクトのテクスチャーをレンダーテクスチャに置き換え、モデルのTransformからモデル変換行列、projectorからビュー行列、疑似カメラ用のプロパティからプロジェクション変換行列を毎フレームつくりシェーダー渡し、インスペクタからBakeが押された時だけGraphics.Blitするようにしています。本来はマウスクリックでレイを飛ばし、そのときだけ投影かつGraphics.Blitしますがこれは実験用のスクリプトですので。

Bakeしてみると、変な場所、左上あたり(uvでいう(0, 1)あたり)を中心に塗られてしまいます。projectorの位置やスケールを動かしてもほとんど塗られる位置に変更はありませんが、回転を変更するとすこし変化があることを確認しました。また、fovを変えると塗られる大きさが変わるみたいです。(ここは正しい?)どうやら、普通のレンダリングとGraphics.Blit時のものが違うためなのが原因みたいですが、どうやれば直るのかわからないです...
以下はBakeしたときの画像です。
イメージ説明
上にある通り単純なUVを使ったペイントでも同じようにGraphics.Blitでテクスチャに適用していましたが正常に動いていたので、レンダーテクスチャの一連の流れ(メインテクスチャおきかえ、バッファを利用しBlitで書き込みなど)は間違っていないような気がします。渡す行列をBlitのために特別に変える必要があるのか(シェーダー内で?)よくわからないです。

Graphics.Blitを使わずにテクスチャに変更を加える方法もあるにはあるみたいですが、いろいろ制約がありそうなのでできればこれを使いたいです。例えばGraphics.DrawMeshNowがありますが、いくつかmeshがある場合その数だけ呼ばなければならなかったりSkinnedMeshRendererの場合BakeMeshしないといけないみたいです。一応想定しているモデルはSkinnedMeshRendererコンポーネントをいくつかもつものになります。

環境など

Unity 2018.3.0f1(アップデート予定)
VRアプリで考えてますが、とりあえずwindowsでプラットフォーム設定はデフォルトです。

何日かググって調べましたがどうしてもわかりませんでした。どうかよろしくお願いいたします。

C#

using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; public class ProjectionTest : MonoBehaviour { [SerializeField] GameObject projector; [SerializeField] Color brushColor; [SerializeField] float fov = 20, aspect = 1, zNear = 0.01f, zFar = 1; Material mat; int MVPMatPropertyId, brushColorId; RenderTexture renderTexture; private void Awake() { mat = GetComponent<Renderer>()?.material; MVPMatPropertyId = Shader.PropertyToID("MVPMatForProjection"); brushColorId = Shader.PropertyToID("_BrushColor"); InitCanvas(); } void InitCanvas() { var mainTex = mat.mainTexture; // generate RenderTexture renderTexture = new RenderTexture(mainTex.width, mainTex.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default); // copy the main texture to the RenderTexture Graphics.Blit(mainTex, renderTexture); // replace the main texture on the material with the RenderTexture mat.mainTexture = renderTexture; } void SetMatProperties() { if (mat && projector) { Matrix4x4 modelMat = transform.localToWorldMatrix; Matrix4x4 projMat = GL.GetGPUProjectionMatrix(Matrix4x4.Perspective(fov, aspect, zNear, zFar), true); // camera space matches OpenGL convention: camera's forward is the negative Z axis, so negate all of the 3rd row values of the matrix // https://docs.unity3d.com/ja/current/ScriptReference/Camera-worldToCameraMatrix.html Matrix4x4 viewMat = Matrix4x4.TRS(projector.transform.position, projector.transform.rotation, projector.transform.lossyScale).inverse; viewMat.m20 *= -1f; viewMat.m21 *= -1f; viewMat.m22 *= -1f; viewMat.m23 *= -1f; Matrix4x4 mvpMat = projMat * viewMat * modelMat; // this is the same as UNITY_MATRIX_MVP in shader writing mat.SetMatrix(MVPMatPropertyId, mvpMat); mat.SetColor(brushColorId, brushColor); } } // Update is called once per frame void Update() { SetMatProperties(); } [ContextMenu("Bake")] void Bake() { Debug.Log("paint"); var renderTextureBuffer = RenderTexture.GetTemporary(renderTexture.width, renderTexture.height); // buffer Debug.Log("blit"); Graphics.Blit(renderTexture, renderTextureBuffer, mat); // first, copy renderTexture, which the painterMat will be applied to, to renderTextureBuffer because the texture can't be changed directly (and apply the material after copied Graphics.Blit(renderTextureBuffer, renderTexture); // then, copy the buffer to renderTexture } }

ShaderLab

Shader "Custom/Painter/ProjectionPaint" { Properties { _MainTex("Main Texture", 2D) = "white" {} _BrushColor("Brush Color", Color) = (1, 1, 1, 1) _ProjTex("Projection Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 projUV : TEXCOORD1; }; sampler2D _MainTex; sampler2D _ProjTex; float4 _MainTex_ST; float4 _BrushColor; uniform float4x4 MVPMatForProjection; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.projUV = ComputeGrabScreenPos(mul(MVPMatForProjection, v.vertex)); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag(v2f i) : SV_Target { fixed4 projColor = fixed4(0, 0, 0, 0); if (i.projUV.w > 0.0) { // projection to screen space i.projUV.x /= i.projUV.w; i.projUV.y /= i.projUV.w; if (i.projUV.x >= 0 && i.projUV.x <= 1 && i.projUV.y >= 0 && i.projUV.y <= 1) { projColor = tex2D(_ProjTex, i.projUV); } } fixed4 mainColor = tex2D(_MainTex, i.uv); // 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 } } }

良い質問の評価を上げる

以下のような質問は評価を上げましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

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

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

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

teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

  • プログラミングに関係のない質問
  • やってほしいことだけを記載した丸投げの質問
  • 問題・課題が含まれていない質問
  • 意図的に内容が抹消された質問
  • 過去に投稿した質問と同じ内容の質問
  • 広告と受け取られるような投稿

評価を下げると、トップページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

まだ回答がついていません

会員登録して回答してみよう

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

ただいまの回答率
87.20%

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

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

質問する

関連した質問

同じタグがついた質問を見る

Unity3D

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

Unity

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