何がしたいか
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#
1using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEditor; 5 6 public class ProjectionTest : MonoBehaviour { 7 [SerializeField] GameObject projector; 8 [SerializeField] Color brushColor; 9 [SerializeField] float fov = 20, aspect = 1, zNear = 0.01f, zFar = 1; 10 11 Material mat; 12 int MVPMatPropertyId, brushColorId; 13 RenderTexture renderTexture; 14 15 private void Awake() { 16 mat = GetComponent<Renderer>()?.material; 17 MVPMatPropertyId = Shader.PropertyToID("MVPMatForProjection"); 18 brushColorId = Shader.PropertyToID("_BrushColor"); 19 20 InitCanvas(); 21 } 22 23 void InitCanvas() { 24 25 var mainTex = mat.mainTexture; 26 27 // generate RenderTexture 28 renderTexture = new RenderTexture(mainTex.width, mainTex.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default); 29 // copy the main texture to the RenderTexture 30 Graphics.Blit(mainTex, renderTexture); 31 32 // replace the main texture on the material with the RenderTexture 33 mat.mainTexture = renderTexture; 34 } 35 36 void SetMatProperties() { 37 if (mat && projector) { 38 Matrix4x4 modelMat = transform.localToWorldMatrix; 39 Matrix4x4 projMat = GL.GetGPUProjectionMatrix(Matrix4x4.Perspective(fov, aspect, zNear, zFar), true); 40 41 // camera space matches OpenGL convention: camera's forward is the negative Z axis, so negate all of the 3rd row values of the matrix 42 // https://docs.unity3d.com/ja/current/ScriptReference/Camera-worldToCameraMatrix.html 43 Matrix4x4 viewMat = Matrix4x4.TRS(projector.transform.position, projector.transform.rotation, projector.transform.lossyScale).inverse; 44 viewMat.m20 *= -1f; 45 viewMat.m21 *= -1f; 46 viewMat.m22 *= -1f; 47 viewMat.m23 *= -1f; 48 49 50 Matrix4x4 mvpMat = projMat * viewMat * modelMat; // this is the same as UNITY_MATRIX_MVP in shader writing 51 mat.SetMatrix(MVPMatPropertyId, mvpMat); 52 mat.SetColor(brushColorId, brushColor); 53 } 54 } 55 56 // Update is called once per frame 57 void Update() { 58 SetMatProperties(); 59 } 60 61 [ContextMenu("Bake")] 62 void Bake() { 63 64 Debug.Log("paint"); 65 66 67 var renderTextureBuffer = RenderTexture.GetTemporary(renderTexture.width, renderTexture.height); // buffer 68 69 70 Debug.Log("blit"); 71 72 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 73 Graphics.Blit(renderTextureBuffer, renderTexture); // then, copy the buffer to renderTexture 74 75 } 76 }
ShaderLab
1Shader "Custom/Painter/ProjectionPaint" 2 { 3 Properties 4 { 5 _MainTex("Main Texture", 2D) = "white" {} 6 _BrushColor("Brush Color", Color) = (1, 1, 1, 1) 7 _ProjTex("Projection Texture", 2D) = "white" {} 8 } 9 SubShader 10 { 11 Tags { "RenderType" = "Opaque" } 12 LOD 100 13 14 Pass 15 { 16 CGPROGRAM 17 #pragma vertex vert 18 #pragma fragment frag 19 20 #include "UnityCG.cginc" 21 22 struct appdata 23 { 24 float4 vertex : POSITION; 25 float2 uv : TEXCOORD0; 26 }; 27 28 struct v2f 29 { 30 float2 uv : TEXCOORD0; 31 float4 vertex : SV_POSITION; 32 float4 projUV : TEXCOORD1; 33 }; 34 35 sampler2D _MainTex; 36 sampler2D _ProjTex; 37 float4 _MainTex_ST; 38 float4 _BrushColor; 39 40 uniform float4x4 MVPMatForProjection; 41 42 v2f vert(appdata v) 43 { 44 v2f o; 45 o.vertex = UnityObjectToClipPos(v.vertex); 46 o.projUV = ComputeGrabScreenPos(mul(MVPMatForProjection, v.vertex)); 47 o.uv = TRANSFORM_TEX(v.uv, _MainTex); 48 return o; 49 } 50 51 fixed4 frag(v2f i) : SV_Target 52 { 53 fixed4 projColor = fixed4(0, 0, 0, 0); 54 55 if (i.projUV.w > 0.0) { 56 // projection to screen space 57 i.projUV.x /= i.projUV.w; 58 i.projUV.y /= i.projUV.w; 59 60 if (i.projUV.x >= 0 && i.projUV.x <= 1 && i.projUV.y >= 0 && i.projUV.y <= 1) { 61 projColor = tex2D(_ProjTex, i.projUV); 62 } 63 64 } 65 66 fixed4 mainColor = tex2D(_MainTex, i.uv); 67 68 // if _BrushColor.a = 0 or projColor.a = 0, then mainColor. if both 1, then _BrushColor. 69 return mainColor * (1 - _BrushColor.a * projColor.a) + _BrushColor * _BrushColor.a * projColor.a; 70 } 71 ENDCG 72 } 73 } 74 }
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/03/09 06:06
2019/03/09 07:19
2019/03/09 18:01
2019/03/14 07:47