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

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

新規登録して質問してみよう
ただいま回答率
85.53%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

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

Q&A

解決済

3回答

770閲覧

unityで三人称視点版のポータルガンが作りたい!

pimen

総合スコア6

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

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

0グッド

0クリップ

投稿2023/11/05 17:08

編集2023/12/10 20:27

実現したいこと

unityで3人称視点でポータルガンのようにポータルの先の景色が見えるようなレンダーテクスチャを作成したいです。

前提

ここに質問の内容を詳しく書いてください。
https://www.youtube.com/watch?v=IZ4mjwZBWtk
https://www.youtube.com/watch?v=PkGjYig8avo&t=189s
これらの動画を参考にポータルの景色のレンダーテクスチャを作るためにカメラの回転や位置の制御を実装できたのですがカメラの距離がポータルから離れた状態だとポータルやポータルより手前のオブジェクトが映ってしまうのですが、動画では何故かうまくいっているので何が原因でうまくいかないのか悩んでいます。

該当のソースコード

追記 ソースコード更新しました!

C#

1using UnityEngine; 2 3[RequireComponent(typeof(Camera))] 4public class test2 : MonoBehaviour 5{ 6 // 視錐台の切断面となるオブジェクト 7 [SerializeField] private Transform planeTransform; 8 public GameObject cupcel; 9 public GameObject portal; 10 public GameObject Otherportal; 11 private readonly Vector3[] clipCorners = new Vector3[8]; 12 private new Camera camera; 13 14 private void Start() 15 { 16 this.camera = this.GetComponent<Camera>(); 17 } 18 19 private void LateUpdate() 20 { 21 if (this.planeTransform == null) 22 { 23 return; 24 } 25 26 var plane = new Plane(this.planeTransform.up, this.planeTransform.position); 27 var planeWorldVector = (Vector4)plane.normal; 28 planeWorldVector.w = plane.distance; 29 // ワールド空間でのニアクリップ面ベクトルを作成する 30 31 var diff = portal.transform.position - cupcel.transform.position; 32 transform.position = Otherportal.transform.position + diff; 33 transform.LookAt(Otherportal.transform.position); 34 35 // ビュー変換行列を取得する 36 var worldToView = this.camera.worldToCameraMatrix; 37 38 // ニアクリップ面ベクトルをビュー座標に直す 39 var planeViewVector = worldToView.inverse.transpose * planeWorldVector; 40 41 // プロジェクション行列を正しく加工するには、切断面の法線が画面奥を 42 // 向いている必要があるため、法線のZ成分の符号に応じて面を反転する 43 44 // 斜めに加工したプロジェクション行列を作る 45 var obliqueMatrix = this.camera.CalculateObliqueMatrix(planeViewVector); 46 47 // カメラにプロジェクション行列をセットする 48 this.camera.projectionMatrix = obliqueMatrix; 49 50 // クリップ空間を視覚的に確認するため、角のワールド座標を求める 51 var clipToView = obliqueMatrix.inverse; 52 var viewToWorld = worldToView.inverse; 53 for (var z = 0; z < 2; z++) 54 { 55 for (var y = 0; y < 2; y++) 56 { 57 for (var x = 0; x < 2; x++) 58 { 59 var p = clipToView.MultiplyPoint((new Vector3(x, y, z) * 2.0f) - Vector3.one); 60 p *= Mathf.Sign(-p.z); 61 this.clipCorners[(z << 2) | (y << 1) | x] = viewToWorld.MultiplyPoint3x4(p); 62 } 63 } 64 } 65 } 66 67 private void OnDrawGizmos() 68 { 69 // シーンビュー上に枠線を描く 70 Gizmos.color = Color.red; 71 Gizmos.DrawLine(this.clipCorners[0], this.clipCorners[1]); 72 Gizmos.DrawLine(this.clipCorners[1], this.clipCorners[3]); 73 Gizmos.DrawLine(this.clipCorners[3], this.clipCorners[2]); 74 Gizmos.DrawLine(this.clipCorners[2], this.clipCorners[0]); 75 Gizmos.color = new Color(1.0f, 0.5f, 0.0f); 76 Gizmos.DrawLine(this.clipCorners[0], this.clipCorners[4]); 77 Gizmos.DrawLine(this.clipCorners[1], this.clipCorners[5]); 78 Gizmos.DrawLine(this.clipCorners[2], this.clipCorners[6]); 79 Gizmos.DrawLine(this.clipCorners[3], this.clipCorners[7]); 80 Gizmos.color = Color.yellow; 81 Gizmos.DrawLine(this.clipCorners[4], this.clipCorners[5]); 82 Gizmos.DrawLine(this.clipCorners[5], this.clipCorners[7]); 83 Gizmos.DrawLine(this.clipCorners[7], this.clipCorners[6]); 84 Gizmos.DrawLine(this.clipCorners[6], this.clipCorners[4]); 85 } 86}

試したこと

AIやグループLINEに質問してみましたがなかなか解決できず、悩んでいます。
再追記
イメージ説明
動画バージョン
https://38.gigafile.nu/1216-62c595b9a4ccfd60149ad52230543472

補足情報(FW/ツールのバージョンなど)

unity 2021.3.22f1
3D環境です。

カメラの視錐台を斜め?にするために動画ではVector4などを使っていてよく理解できなかったので、可能であればカメラやVector4関係の学習が初心者にもできるようなサイトなどを教えていただければ嬉しいです。

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

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

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

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

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

Bongo

2023/11/14 10:20

ご提示のコードでは、後者の「Fully Functional Portals in Unity URP」で紹介されているVector4をからめた計算やCalculateObliqueMatrixを使った斜めの視錐台など( https://github.com/daniel-ilett/portals-urp/blob/main/Assets/Scripts/PortalCamera.cs )を組み込んでいらっしゃらないご様子ですが、組み込もうとしたものの行き詰まってしまった...といった感じでしょうか?
pimen

2023/11/16 15:57

そうです。カメラの回転と座標移動のあとに視錐台の計算をしたかったのですが、見たこと無い型や計算だらけで、一応gitのコードをコピペしてもうまくいかなかったので質問させていただきました。ご質問ありがとうございます。
guest

回答3

0

一方のポータルに他方のポータルの先の風景を映す例

まず、以前の回答で私が例示しましたスクリプト(ご質問者さんの場合はtest2)はメインカメラを使って視錐台の切断を実演するためだけのものですので、実際にポータルの映像を撮影する上では不要でしょうからメインカメラからスクリプトを削除しました。

ポータルの映像を撮影するためには、メインカメラではなくてポータル用カメラを適切にコントロールしてやる必要があるはずです。そこで、私の場合は下記のようなスクリプトを「Plane (1)」、「Plane (2)」にそれぞれアタッチしてみました。

C#

1using UnityEngine; 2 3[RequireComponent(typeof(MeshRenderer))] 4public class Portal : MonoBehaviour 5{ 6 // インスペクター上で、ここにもう一方のポータルをセットしておきます 7 [SerializeField] private Transform otherPortal; 8 9 private Camera portalCamera; 10 private Camera mainCamera; 11 12 private void Start() 13 { 14 // もう一つのポータルをセットし忘れているとカメラの位置を決められませんので、 15 // その場合は処理を中止することにします 16 if (this.otherPortal == null) 17 { 18 Debug.LogError("Other portal not set."); 19 this.enabled = false; 20 return; 21 } 22 23 // 子オブジェクトの中からカメラを探して、それをこのポータル用のカメラとして使うことにします 24 this.portalCamera = this.GetComponentInChildren<Camera>(); 25 if (this.portalCamera == null) 26 { 27 Debug.LogError("Portal camera not found."); 28 this.enabled = false; 29 return; 30 } 31 32 // メインカメラも探しておきます 33 this.mainCamera = Camera.main; 34 if (this.mainCamera == null) 35 { 36 Debug.LogError("Main camera not found."); 37 this.enabled = false; 38 return; 39 } 40 41 // ポータルカメラはメインカメラよりも先にレンダリングすることにします 42 this.portalCamera.depth = this.mainCamera.depth - 1; 43 } 44 45 private void LateUpdate() 46 { 47 // ポータルカメラの位置・向きを更新します 48 // まずは、ポータルのローカル座標系から見てメインカメラがどの位置・向きなのかを調べます 49 var portalTransform = this.transform; 50 var mainCameraTransform = this.mainCamera.transform; 51 var cameraLocalPosition = portalTransform.InverseTransformPoint(mainCameraTransform.position); 52 var cameraLocalRotation = Quaternion.Inverse(portalTransform.rotation) * mainCameraTransform.rotation; 53 54 // もう一つのポータルに対してその位置・向きになるようにポータルカメラを 55 // 据えたいのですが、それに先立って、撮影するのはポータルの正面側で 56 // あるべきなので、これら位置・向きを180°回転させておきます 57 // ポータルはPlaneを90°倒したオブジェクトを使っているので、回転軸としては 58 // Z軸を使うのが妥当だろうと思います 59 var zRotation = new Quaternion(0.0f, 0.0f, 1.0f, 0.0f); 60 cameraLocalPosition = zRotation * cameraLocalPosition; 61 cameraLocalRotation = zRotation * cameraLocalRotation; 62 63 // もう一つのポータルを基準に、ポータルカメラの位置を決めます 64 this.portalCamera.transform.SetPositionAndRotation( 65 this.otherPortal.TransformPoint(cameraLocalPosition), 66 this.otherPortal.rotation * cameraLocalRotation); 67 68 // もう一つのポータルを切断面として、ポータルカメラの視錐台を切り落とします 69 var plane = new Plane(this.otherPortal.up, this.otherPortal.position); 70 var planeWorldVector = (Vector4)plane.normal; 71 planeWorldVector.w = plane.distance; 72 var worldToView = this.portalCamera.worldToCameraMatrix; 73 var planeViewVector = worldToView.inverse.transpose * planeWorldVector; 74 var obliqueMatrix = this.portalCamera.CalculateObliqueMatrix(planeViewVector); 75 this.portalCamera.projectionMatrix = obliqueMatrix; 76 } 77}

インスペクター上のOther Portalにはペアとなる相手をセットしてやります。つまり「Plane (1)」のOther Portalは「Plane (2)」、「Plane (2)」のOther Portalは「Plane (1)」とします。

図1

また「Plane (1)」や「Plane (2)」のマテリアルは、普通のマテリアルを使ったのではまずいかと思います。一般的なオブジェクトと同様のテクスチャマッピングをしてしまうと、離れた空間をのぞき込んでいるというよりも映像が映されたモニターを見ているかのような平坦感が生じてしまうでしょう。
そこで、私の場合は下記のシェーダーを作って...

ShaderLab

1Shader "Custom/Portal" 2{ 3 Properties 4 { 5 _FringeSize ("Fringe Size", Range(0.0, 1.0)) = 0.1 // ポータル外周のグラデーション幅 6 _HaloSize ("Halo Size", Range(0.0, 1.0)) = 0.2 // 縁の光のグラデーション幅 7 [HDR] _Color ("Halo Color", Color) = (0.0, 1.0, 2.0, 1.0) // 縁の光 8 _MainTex ("Portal View", 2D) = "black" {} // ポータルの向こうの景色 9 } 10 SubShader 11 { 12 Tags { "RenderType"="Transparent" "Queue"="Transparent" } 13 14 CGPROGRAM 15 16 #pragma surface surf Lambert alpha 17 18 sampler2D _MainTex; 19 20 struct Input 21 { 22 float2 uv_MainTex; 23 float4 screenPos; 24 }; 25 26 float _FringeSize; 27 float _HaloSize; 28 float4 _Color; 29 30 void surf(Input IN, inout SurfaceOutput o) 31 { 32 float distance = 1.0 - length(IN.uv_MainTex * 2.0 - 1.0); 33 float alpha = smoothstep(0.0, _FringeSize, distance); 34 clip(alpha - 0.01); 35 o.Alpha = alpha; 36 37 // ポータルの向こうの景色はスクリーン空間を基準にサンプリングします 38 float3 portalView = tex2Dproj(_MainTex, IN.screenPos).rgb; 39 40 o.Emission = lerp(_Color.rgb, portalView, smoothstep(0.0, _HaloSize, distance)); 41 } 42 ENDCG 43 } 44}

マテリアル「1」や「2」のシェーダーをそれに切り替え、適宜インスペクター上のパラメーターを調整しました。

図2

これでいくらかポータルっぽい見た目になるんじゃないでしょうか。ちょっとシーン上のオブジェクトを増やして、ポータルの正面にはCylinderやSphereを、ポータルの背面には赤いCubeを置いて実行してみました。

図3

赤いCubeは完全にポータルの背後側なのですが、PortalスクリプトのLateUpdate末尾にある視錐台切断処理がないと、赤Cubeがポータルの先の風景に映り込んでしまうはずです。

図4

対して視錐台切断処理を行うと、赤いCubeが映り込まなくなり自然な見た目になるかと思います。

図5

現状ではポータルを再帰的に撮影する仕組みを作っておりませんので、ポータルを合わせ鏡のように向かい合わせてもポータルの中にポータルは映りませんが、ポータル同士がそのような位置関係にならないようなゲームデザインならばおそらく問題ないかと思います。

投稿2023/12/11 15:03

編集2023/12/11 22:07
Bongo

総合スコア10807

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

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

0

ベストアンサー

本題のポータルの先の映像に邪魔な物体が映り込んでしまう件については、すみませんが現時点の情報だけですとちょっと原因を判断しかねますのでコメントは差し控えさせていただき、「カメラの視錐台を斜め?にするために動画ではVector4などを使っていてよく理解できなかったので...」との部分について、何かお力になれればと思いまして検索してみました。

ご不明なのはVector4とかMatrix4x4が登場する計算式自体でしょうか?
Unityの場合はゲーム制作で頻出するたいていの処理は直感的に使いやすいような形で提供されていますので、一般的な3Dグラフィックスの解説サイト・書籍でよく出てくる、行列やらを使った計算式はほとんど裏方に隠れてしまっていますね。結果として入門者向けの記事ではUnityの提供する便利機能をいかに使うかという点が主眼になり、裏方の理屈を解説する記事は需要がない...といった現象が起こっているのかもしれません。
どのサイトを紹介するべきか考えあぐねていたのですが、Redpoll's 60さんの「3Dプログラミング入門」が面白そうに感じました。全部詳しく読破したわけではないのですが、Unityの提供する機能の代わりに行列を前面に出した教材を用意して解説なさっているようです。
「3Dグラフィックス 座標 変換」のようなキーワードで検索すると大学の講義資料らしき記事が出てきたりしますが、これらはやはり専門家の先生方が書いているでしょうから内容がしっかりしているものの、環境の前提がOpenGLのことが多いように思います。
OpenGLを直接扱うのとUnityとでは微妙な流儀の違いがありますので、Unityに合わせて読み替えるにはいくらか慣れが必要かと思いますが、Redpoll's 60さんの記事はUnityを前提としていますので読み替えがいらず楽なんじゃないでしょうか。

それともCamera.CalculateObliqueMatrixをどう使ったらいいかわかりづらい...といった感じでしょうか。こちらも検索してみたものの、需要が少ない機能だからなのかこれぞという記事が見つかりませんでしたが、Indie Visual Labの「Unity Graphics Programming」第3巻、fuqunagaさんによる「第7章 PortalをUnityで実装してみた」がご参考になるかもしれません。
CalculateObliqueMatrix自体についての解説記事ではないですが、本記事もPortalの再現をテーマにしており、コード中でCalculateObliqueMatrixを使用しているようです。Daniel Ilettさんの「Fully Functional Portals in Unity URP」だけだと行き詰まってしまうようでしたら、こちらのやり方も試してみてはいかがでしょうか。

Ilettさんもfuqunagaさんもカメラの投影行列をポータル面で切断するかのように斜めに加工していますが、ビュー空間上のポータル面の求め方がちょっと異なっています。特にIlettさんの方式は、私はこのたびのご質問で初めて拝見しました。これでちゃんとポータル面が求められるのか興味深く思いまして確認してみました。

なお、以下の数式画像では行列をボールド大文字として、転置は右上にT、逆行列は右上に-1を入れました。
ベクトルはボールド小文字を使い、ベクトルを行列内に書く時は列ベクトル・行ベクトルは区別せず、占める区画に背景を描いて範囲を表すことにしました。

worldToCameraMatrixM とすると、 Mアフィン変換行列ですので

式1

という形で表現できるかと思います。なお、 b が平行移動成分で A が回転・拡縮・スキュー成分ですが、ビュー変換は視点を変えているだけですので拡縮やスキューは起こらないはずです。リファレンスによるとZ軸が反転するようですが、反転なら基底の長さは変わりません。つまり A の3つの基底は長さが1で互いに直交しているはずですから、 A直交行列となります。

カメラから見たポータル面を求めるとなると、素直にやるならポータルのワールド座標とワールド法線をそれぞれビュー座標に直してPlaneを作り、その法線と距離を取り出して組み合わせVector4にまとめることになるかと思います。fuqunagaさんはこの方式(VirtualCamera.CalcPlaneによるとPlaneは作らずダイレクトにVector4を構築しておりますが)を採用なさったご様子で、まずはその過程を式で表してみることにします。

ポータルのワールド座標を pw、ポータル面のワールド法線を nw とすると、ポータルのビュー座標 pv とビュー法線 nv

式2

となります。
Planeのコードをご覧いただきたいのですが、平面上の一点と法線を与えてPlaneを作ると

C#

1 // Creates a plane. 2 [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] 3 public Plane(Vector3 inNormal, Vector3 inPoint) 4 { 5 m_Normal = Vector3.Normalize(inNormal); 6 m_Distance = -Vector3.Dot(m_Normal, inPoint); 7 }

となりますので、求めたいビュー空間でのポータル平面を表すベクトル vv

式3

ということになります。

次に、Daniel Ilettさんの計算過程を見てみます。 M はアフィン変換ですので、その逆行列は

式4

となり、さらに転置すると

式5

となります。 A は直交行列ですので逆行列と転置行列は等しく、逆転置は元の行列に戻ります。ですので

式6

とすることができます。

ワールド空間でのポータル平面を表すベクトル vw

式7

であり、これを M の逆転置行列で変換すると

式8

となります。ここで A は直交行列であり、変換前後でドット積の値が変わらないという特徴を利用し、w成分に出てくるベクトルを全部 A で変換してしまいます(ついでに項や因子の順序を入れ替えて整理します)。すると

式9

となります。これで①と②が同じ形になりましたので、Ilettさんのやり方でもビュー空間上のポータル平面を求めることができそうです。

視錐台を斜めに切断した時のカメラの映像

斜めのプロジェクション行列を使うとカメラの映像がどう見えるか試してみましたので追記いたします。
カメラには下記のようなスクリプトをアタッチしました。

C#

1using UnityEngine; 2 3[RequireComponent(typeof(Camera))] 4public class NearPlaneExperiment : MonoBehaviour 5{ 6 // 視錐台の切断面となるオブジェクト 7 [SerializeField] private Transform planeTransform; 8 9 private readonly Vector3[] clipCorners = new Vector3[8]; 10 private new Camera camera; 11 12 private void Start() 13 { 14 this.camera = this.GetComponent<Camera>(); 15 } 16 17 private void LateUpdate() 18 { 19 if (this.planeTransform == null) 20 { 21 return; 22 } 23 24 // ワールド空間でのニアクリップ面ベクトルを作成する 25 var plane = new Plane(this.planeTransform.forward, this.planeTransform.position); 26 var planeWorldVector = (Vector4)plane.normal; 27 planeWorldVector.w = plane.distance; 28 29 // ビュー変換行列を取得する 30 var worldToView = this.camera.worldToCameraMatrix; 31 32 // ニアクリップ面ベクトルをビュー座標に直す 33 var planeViewVector = worldToView.inverse.transpose * planeWorldVector; 34 35 // プロジェクション行列を正しく加工するには、切断面の法線が画面奥を 36 // 向いている必要があるため、法線のZ成分の符号に応じて面を反転する 37 planeViewVector *= Mathf.Sign(-planeViewVector.z); 38 39 // 斜めに加工したプロジェクション行列を作る 40 var obliqueMatrix = this.camera.CalculateObliqueMatrix(planeViewVector); 41 42 // カメラにプロジェクション行列をセットする 43 this.camera.projectionMatrix = obliqueMatrix; 44 45 // クリップ空間を視覚的に確認するため、角のワールド座標を求める 46 var clipToView = obliqueMatrix.inverse; 47 var viewToWorld = worldToView.inverse; 48 for (var z = 0; z < 2; z++) 49 { 50 for (var y = 0; y < 2; y++) 51 { 52 for (var x = 0; x < 2; x++) 53 { 54 var p = clipToView.MultiplyPoint((new Vector3(x, y, z) * 2.0f) - Vector3.one); 55 p *= Mathf.Sign(-p.z); 56 this.clipCorners[(z << 2) | (y << 1) | x] = viewToWorld.MultiplyPoint3x4(p); 57 } 58 } 59 } 60 } 61 62 private void OnDrawGizmos() 63 { 64 // シーンビュー上に枠線を描く 65 Gizmos.color = Color.red; 66 Gizmos.DrawLine(this.clipCorners[0], this.clipCorners[1]); 67 Gizmos.DrawLine(this.clipCorners[1], this.clipCorners[3]); 68 Gizmos.DrawLine(this.clipCorners[3], this.clipCorners[2]); 69 Gizmos.DrawLine(this.clipCorners[2], this.clipCorners[0]); 70 Gizmos.color = new Color(1.0f, 0.5f, 0.0f); 71 Gizmos.DrawLine(this.clipCorners[0], this.clipCorners[4]); 72 Gizmos.DrawLine(this.clipCorners[1], this.clipCorners[5]); 73 Gizmos.DrawLine(this.clipCorners[2], this.clipCorners[6]); 74 Gizmos.DrawLine(this.clipCorners[3], this.clipCorners[7]); 75 Gizmos.color = Color.yellow; 76 Gizmos.DrawLine(this.clipCorners[4], this.clipCorners[5]); 77 Gizmos.DrawLine(this.clipCorners[5], this.clipCorners[7]); 78 Gizmos.DrawLine(this.clipCorners[7], this.clipCorners[6]); 79 Gizmos.DrawLine(this.clipCorners[6], this.clipCorners[4]); 80 } 81}

シーン上にはQuadを置き、別のスクリプトを使ってゆらゆらと回転させました。
このQuadの位置と法線を使ってカメラの視錐台を切断したところ、下図のような映像になりました。シーンビュー上の赤~黄色で描いた枠線が変形された視錐台で、この範囲内がカメラに映ることになります。

図

投稿2023/11/16 15:14

編集2023/11/19 12:06
Bongo

総合スコア10807

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

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

pimen

2023/11/17 04:53

丁寧な回答ありがとうございます。平面を求める内部処理もわからなかったので大変助かりました!しかし僕が悩んでいることがほかにもいくつかありまして、まず (二番目の動画内のスクリプト) Plane p = new Plane(-outTransform.forward, outTransform.position); Vector4 clipPlaneWorldSpace = new Vector4(p.normal.x, p.normal.y, p.normal.z, p.distance); Vector4 clipPlaneCameraSpace =              Matrix4x4.Transpose(Matrix4x4.Inverse(portalCamera.worldToCameraMatrix)) * clipPlaneWorldSpace; var newMatrix = mainCamera.CalculateObliqueMatrix(clipPlaneCameraSpace); portalCamera.projectionMatrix = newMatrix; の部分の Vector4 clipPlaneWorldSpaceの平面の法線と距離をまとめたVector4をMatrix4x4.TransposeでInverce? したカメラのプロジェクションマトリックスらしきものにかけたものをmainCamera.CalculateObliqueMatrixに入れるまでの式で何をしているのかが理解できない状況です... https://qiita.com/yuji_yasuhara/items/98e6bd84b82e666496ce https://mem-archive.com/2018/02/26/post-30/ これらのサイトを参考に mainCamera.CalculateObliqueMatrixには内部パラメータをちょこっといじったカメラの4*4行列を入れると動作することまでは理解できたのですが(なぜm02,m12の部分を変えるとそうなるのかは理解してない)まだぼんやりとしか理解できていないのでより深く理解したいです! まだ高一なのですが線形代数の基礎とかをやらないと理解は難しいですか?よろしくお願いします!
Bongo

2023/11/19 12:11

高校1年生にしてこんな表現技法に挑戦なさるとは、意欲的で素晴らしいと思います。コメントいただきましたコードの疑問点については、回答中の後半のIlettさんとfuqunagaさんの方法の比較でIlettさんの計算方法について申し上げた部分が該当するかと思うのですが、もし行列の出てくる数式にまだなじみがないようでしたら、あれだとちょっと分かりづらい書き方になってしまったかもしれません。「この式からこの式への変形の過程を詳しく知りたい」といったご不明点がありましたら、なるべくお返事いたしますのでご遠慮なくコメントください。 線形代数学について学習を進めるのも役に立つと思います。行列を使った計算は高校のカリキュラムに入ったり抜けたりしているようですので、高校生にとっては上級者向けの知識になるかと思います。授業の中で登場するのは遅いかもしれませんので、他の学習の障りにならない範囲で別途線形代数学をやってみますと、きっとゲームプログラミングにも数学にも損はないんじゃないでしょうか。 Yuji YASUHARAさんの記事は確かに視錐台の変形をテーマにしておりますが、どちらかというとレンズシフト(https://docs.unity3d.com/ja/current/Manual/ObliqueFrustum.html )方面の知識になるでしょうから(ドキュメント末尾の「スクリプトを使った錐台の傾斜の設定」とYASUHARAさんのコード中の「斜め投影行列設定ここから」が似ているのを見て取れますでしょうか)、ポータル面での切り落としには向かないんじゃないかな...と思います。 YASUHARAさんの記事中でも「鏡面や水面の反射を作るときは便利なAPIと思いますが」とおっしゃっていますが、水面や鏡の作成例の方がポータルと近いんじゃないでしょうか。たとえば「平面の鏡面反射 │ 空の缶詰」の「Scriptの改良」(https://karanokan.info/2018/10/17/post-1284/#outline__3 )あたりの記事はCalculateObliqueMatrixの挙動をイメージするのに役立つかもしれません。私もちょっと視錐台の斜めカットした映像を撮影してみましたので、回答に追記しました(いまさら白状しますと、私は今回のご質問で初めてCalculateObliqueMatrixを使ってみたところでして、自身の学習としても一度やってみるのがよさそうだと思ってのこともあります)。 「透視投影変換とは | NO MORE! 車輪の再発明」もいい記事だとは思いますが、ちょっと分野の違う内容のような気がします。役割としては「内部パラメータ」がプロジェクション変換行列に相当するはずですが、次元が減って3x3行列で表現されています。「カメラ内部パラメータとは | NO MORE! 車輪の再発明」(https://mem-archive.com/2018/02/21/post-157/ )も併せてご覧いただきたいのですが、この形ですと図で示されているようにビュー空間の3次元座標が一発で2次元座標に投影されることになります。無駄を省いて合理的ではありますが、3Dグラフィックスの分野でこのように奥行き次元を完全に潰してしまうと、たとえばZバッファ法(https://ja.wikipedia.org/wiki/Z%E3%83%90%E3%83%83%E3%83%95%E3%82%A1 )で前後関係を表現したりするには具合が悪いかと思います。3Dグラフィックスの文脈で透視投影変換と言ったら「opengl-tutorial 」「チュートリアル3:行列」「射影行列」(https://www.opengl-tutorial.org/jp/beginners-tutorials/tutorial-3-matrices/#%E5%B0%84%E5%BD%B1%E8%A1%8C%E5%88%97 )の図のように視錐台の範囲を4x4行列を使って直方体に変換するまでにとどめて、そこから先の2次元映像へ押し潰すのはグラフィックスシステムに任せるのが普通かと思います。
pimen

2023/12/08 14:16

お久しぶりです。少し質問なのですが、このポータルの向こう側の景色を移す仕組みというのはニアクリップ面をポータルの面としてポータルより手前にあるオブジェクトを移さないようにして、かつカメラの視野の四隅をポータルの面の四隅に設定してレンダーテクスチャに映像を出力しているということですよね?(前提が間違ってたら申し訳ないですがもう一度説明してもらえるととてもありがたいです)その時、四隅を定義しているのがおそらく上記のコードの48行目あたりから始まるfor文だと思うのですが、なぜこのコードで四隅が定義できるのかがわからないので教えてほしいです。
Bongo

2023/12/09 01:08

はい、そういうことになりますね。回答中ではメインカメラの視錐台を斜めに変形するところまでしか試していませんが、移動先ポータルの後方にあるポータル用カメラの視錐台を変形してレンダーテクスチャにレンダリングし、移動元ポータルのマテリアルでデカールのようにプロジェクションマッピングしてやれば(あるいはIlettさんの https://danielilett.com/2019-12-14-tut4-2-portal-rendering/#portal-rendering のように、ステンシルを併用してポータル上だけにポストエフェクト的に映像を上乗せしてやれば)ポータルらしい外観を作れるんじゃないかと思います。 48行目はおっしゃる通りクリップ空間の角を求めている箇所ですね。3重のループでx、y、zをそれぞれ0、1に切り替えながら反復することで、クリップ空間上の立方体の角である... 0 (000): (-1, -1, -1) 1 (001): ( 1, -1, -1) 2 (010): (-1,  1, -1) 3 (011): ( 1,  1, -1) 4 (100): (-1, -1,  1) 5 (101): ( 1, -1,  1) 6 (110): (-1,  1,  1) 7 (111): ( 1,  1,  1) の8つの座標を作っています。ビュー空間上の座標をプロジェクション行列で変換するとクリップ空間上の座標になりますが、逆にクリップ空間上の座標をプロジェクション行列の逆行列で変換すれば、ビュー空間上の座標が得られるはずです。それをさらにビュー行列の逆行列で変換するとワールド座標になるというわけです。 なお、回答中のコードではビュー座標に直した後でZ成分の符号をもとにビュー座標の向きを修正していますが、これはカメラとポータル面の向きによってはクリップ空間がとても歪む場合があり、おそらく計算誤差のために座標がカメラの反対側の位置になってしまうケースがあったためです。回答に枠線の図を示す上で見栄えを良くしようと思ってむりやり座標を修正したのですが、実際にポータルを描く上ではこのような逆向きの座標変換はおそらく必要ないでしょうから、お気になさらずともけっこうです。
pimen

2023/12/09 06:04

毎度丁寧な回答感謝しています!bongoさんのコードをコピペで実行するとなぜか画面下半分が切り取られた状態になり、ニアクリップ面であるはずの画面中央のキューブの後ろにある面ではなく、すごくカメラに近い場所にニアクリップ面のしるしである赤いGizmosが表示されてしまいます。原因がわかりましたら教えていただきたいです。あと、56行目は単にGizmosを描くためだけのコードという認識であっていますか?よろしくお願いいたします。
Bongo

2023/12/09 14:57 編集

それは確かにおかしいですね...どういう条件ならそのような映像になるか試してみたところ、切断面をカメラよりわずかに後ろに配置した時に、ご提示のスクリーンショットのように映像の下半分が途切れた様子になりました。ですがご質問者さんは切断面となるオブジェクトを明らかにちゃんとカメラ前方に置いているようですので、なぜこんなことになったのか不思議です... Cinemachineを併用していらっしゃるようですから、もしかしたらそれが何か干渉しているかもしれないとも考えたのですが、私の試した限りではCinemachineがあっても特に意図に反した挙動はしていないようでした。 もっと手がかりが欲しいところです。もし差し支えなければ、現状のUnityプロジェクトをどこかにアップロードしていただければこちらでも状況を再現できてありがたいのですが、ご使用のアセットのライセンスの都合上などの理由で不特定多数に公開してしまうのはまずい...といった事情もあるかもしれませんので無理にとは申しません。 その場合は代わりにまずは切断面オブジェクトが何なのか、カメラとの位置関係がどうなっているのか、を知りたく思います。私の例示しましたコードで言うところの... // ワールド空間でのニアクリップ面ベクトルを作成する var plane = new Plane(this.planeTransform.forward, this.planeTransform.position); var planeWorldVector = (Vector4)plane.normal; planeWorldVector.w = plane.distance; に相当する部分の直前に下記のように書き加えて、 Debug.Log($"Plane: {this.planeTransform.name}, Position: {this.planeTransform.position}, Forward: {this.planeTransform.forward}"); Debug.Log($"Camera: {this.camera.name}, Position: {this.camera.transform.position}, Forward: {this.camera.transform.forward}"); // ワールド空間でのニアクリップ面ベクトルを作成する var plane = new Plane(this.planeTransform.forward, this.planeTransform.position); var planeWorldVector = (Vector4)plane.normal; planeWorldVector.w = plane.distance; とでもしていただき、コンソールに情報を出力させていただけますでしょうか。コードはLateUpdate内で常時実行しているためコンソール上にはたくさん出力されてしまうでしょうが、異常が起こっている時の適当なタイミングだけでかまいませんので、出力内容をご提示いただきたく思います。 なお、56行目(「// クリップ空間を視覚的に確認するため、角のワールド座標を求める」以下の部分ということでよろしいでしょうかね?)はおっしゃる通りGizmosを描くためにしか使っておらず、視錐台切断には関与しておりません。
Bongo

2023/12/10 02:52

プロジェクトご提示ありがとうございます。調査がやりやすくて助かりました。 切断面に使用している「Plane (3)」は、X軸周りに90°倒したPlaneメッシュだったのですね。私の例示しましたコードは平面の法線がforward方向であることを暗黙の前提としていましたので、あのような奇妙な切断のされ方になってしまったのだと思います。up方向を法線として切断したい場合は、ワールド平面作成部分を下記のように変更してみてはいかがでしょうか? // ワールド空間でのニアクリップ面ベクトルを作成する // Quadメッシュの法線はforwardなのに対して、Planeメッシュの場合は // 法線がup方向であるため、切断面の法線もupを使用する var plane = new Plane(this.planeTransform.up, this.planeTransform.position); var planeWorldVector = (Vector4)plane.normal; planeWorldVector.w = plane.distance;
pimen

2023/12/10 20:30

こんにちは。指示通りに修正するとうまくいきました!ありがとうございます。しかし、視錐台もカメラの移動も正しいはずなのですが本来のポータルのような画面になりません。ポータルのサイズに画角がぴったりフィットしていないのが原因ですか?
Bongo

2023/12/11 22:05 編集

スクリーンショットを拝見しますに、テクスチャのマッピングの仕方の問題じゃないかという気がしますね... 私も自身の勉強を兼ねて、ご質問者さんのプロジェクトをベースにポータルの先の映像を撮影するよう手を加えてみました。あいにく回答欄の字数が尽きてしまいましたので、別回答として追記いたしますがご容赦ください。 カメラも視錐台も正しいはずとのことでしたら、おそらくマテリアル周りの部分をなんとかしてやればいけるんじゃないかと思うのですが...ご参考になりましたら幸いです。
guest

0

C#

1void Start() 2{ 3 // クリッププレーンの設定 4 porcam1.nearClipPlane = 0.01f; // ポータルからの最小表示距離 5 porcam1.farClipPlane = Vector3.Distance(portal1.transform.position, maincam1.transform.position); // ポータルまでの距離 6 7 // これにより、カメラがポータルからの最小/最大距離内でしかオブジェクトを表示しません。 8}

投稿2023/11/06 06:24

isimasa

総合スコア291

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.53%

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

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

質問する

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

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

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