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

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

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

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

Q&A

解決済

1回答

3831閲覧

ビューポートがおかしい

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

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

0グッド

0クリップ

投稿2019/08/12 17:12

編集2019/08/18 07:20

前提・実現したいこと

ビューポート座標の変換で意図した通りにならないのでご教示お願いします。

質問1

C#

1 Vector3 worldpos = Camera.main.ViewportToWorldPoint(new Vector3(0.8f, 0.6f, 0)); 2 Vector3 viewpos = Camera.main.WorldToViewportPoint(worldpos); 3 Debug.Log(viewpos);

上記、(0.8f, 0.6f, 0)の出力を期待していたのですが、実際の出力結果は下記のようになってしまいました。
何故でしょうか?

(0.5, 0.5, 0.0)

試したこと

0の近似値で試してみました。少し値が変わりましたが、やはり意図した座標ではありません。

C#

1Vector3 worldpos = Camera.main.ViewportToWorldPoint(new Vector3(0.8f, 0.6f, 0.000001f)); 2//(0.6, 0.5, 0.0)

上記よりちょっと値を上げたら、意図した座標になりましたが、これは何故でしょうか?

C#

1Vector3 worldpos = Camera.main.ViewportToWorldPoint(new Vector3(0.8f, 0.6f, 0.00001f)); 2//(0.8, 0.6, 0.0)

リファレンスを見たら、
nearClipPlaneというのがあったので、ビューポートのZ値の最小値はnearClipPlaneということであれば、納得がいったのですが、
nearClipPlaneは0.3だったので、成功した上記は0.3よりもはるかに小さい値だったので、今一つ、腑に落ちません。
また、0はダメだけど、0の近似値ならばOKという場合だったら、何故、0.000001fではうまくいかなかったのでしょうか。

C#

1Camera.main.ViewportToWorldPoint(new Vector3(0.8f, 0.6f, Camera.main.nearClipPlane)); 2//(0.8, 0.6, 0.3)

質問2

質問1がよくわからなかったので、Camera.main.nearClipPlaneでコードを進めてみることにしました。
下記コードを組んだのですが、コメントのような出力結果になってしまいました。

C#

1 Vector3 worldpos = Camera.main.ViewportToWorldPoint(new Vector3(0.8f, 0.6f, Camera.main.nearClipPlane)); 2 cube.transform.position = worldpos + Camera.main.transform.forward * 5; 3 Vector3 cubeViewport = Camera.main.WorldToViewportPoint(cube.transform.position); 4 Debug.Log("cubeViewport.x:" + cubeViewport.x); //0.5169811 5 Debug.Log("cubeViewport.y:" + cubeViewport.y); //0.5056604 6 Debug.Log("cubeViewport.z:" + cubeViewport.z); //5.299999

Camera.main.transform.forward * 5 は、x,yに影響を与えないかと思っていたのですが、
なぜ、cubeViewport.xが0.8fに、cubeViewport.yが0.6fにならないのでしょうか?
誤差にしては大き過ぎると思いました。

追記

C#

1 Vector3 worldpos = Camera.main.ViewportToWorldPoint(new Vector3(0.8f, 0.6f, 5f)); 2 cube.transform.position = worldpos; 3 Vector3 cubeViewport = Camera.main.WorldToViewportPoint(cube.transform.position); 4 Debug.Log("cubeViewport.x:" + cubeViewport.x); //0.7999997 5 Debug.Log("cubeViewport.y:" + cubeViewport.y); //0.5999997 6 Debug.Log("cubeViewport.z:" + cubeViewport.z); //5 7 Debug.Log(Vector3.Distance(Camera.main.transform.position, cube.transform.position)); //6.727412 8 9 Vector3 dir = cube.transform.position - Camera.main.transform.position; 10 cube.transform.position = cube.transform.position + Vector3.Normalize(dir)*3; 11 Vector3 cubeViewport2 = Camera.main.WorldToViewportPoint(cube.transform.position); 12 Debug.Log("cubeViewport2.x:" + cubeViewport2.x); //0.7999997 13 Debug.Log("cubeViewport2.y:" + cubeViewport2.y); //0.5999997 14 Debug.Log("cubeViewport2.z:" + cubeViewport2.z); //7.229684 15 Debug.Log(Vector3.Distance(Camera.main.transform.position, cube.transform.position)); //9.727413

追記②

cosの図が間違ってたので修正しました。
イメージ説明

メモ

nDir・forward = cosθ
v=nDir/cosθ
v=nDir/nDir・forward

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

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

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

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

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

guest

回答1

0

ベストアンサー

#質問1について
これについては仕方ないんじゃないでしょうかね...?
ビューポート座標をカメラから極めて近い平面に投影するということは、結果として得られる座標は下図のように非常に小さい長方形の中の一点を指すはずです。いかにも誤差が影響しそうなスケールであることを想像いただけますでしょうか?

図1

また、カメラからの距離をどの程度まで近づけると結果が狂い出すかはカメラの位置にも依存するでしょう。
float型は目安として有効数字6桁程度までは表現できるはずです。もしカメラがワールド原点に据えられていればビューポート座標の変換結果であるワールド座標は(0.0000??????..., 0.0000??????..., 0.0000??????...)という風になり、有効数字を十分使うことができるので結構小さな距離でも精度が維持されそうですが、カメラが(100, 100, 100)に据えられていれば、結果はたとえば(100.0000??????..., 100.0000??????..., 100.0000??????...)となってしまうでしょう。正しくビューポート座標を復元するには??????...部分に十分な精度が必要ですが、このようなケースだと100.000の部分に精度を消費してしまい小数点以下の微妙な量が不正確となって、正しく復元できなくなってしまうと思われます(お時間がありましたらカメラを移動して結果を比較してみるといいかと思います)。

この復元できる限界はnearClipPlaneの設定値とは関係ないはずです。設定値を変えて実験してみても、結果に変化はないんじゃないでしょうか。とはいえ、距離の下限の目安にすることはできると思います。こちらも極端に小さい値にすると描画不具合を生じる可能性があり、デフォルトではおっしゃる通り0.3に設定されているはずです。ViewportToWorldPointの距離下限値もこれにならって、せいぜい小数点以下1~2桁までに抑えておくのが無難ではないでしょうか。

#質問2について
これはビューポート座標が変わるのが自然だと思います。下図のようにまっすぐカメラ前方の向きへオブジェクトを移動すれば、遠くへ行くほどオブジェクトの画面上の座標は画面中心の消失点へ近づいていくんじゃないでしょうか?

図2

ビューポート上の座標を変化させないためには、オブジェクトとカメラを結ぶ直線に沿ってオブジェクトを遠ざけていくべきかと思います。あるいは、ワールド座標に変換する前のビューポート座標の段階でZ成分に値を加算するかでしょうね。

図3

#追記

コメントで申し上げたnormalizedDirをnDir、Camera.main.transform.forwardをforward、2つのベクトルの間の角度をθとすると、これらは下図のような関係になります。

図4

nDirとforwardの内積の値は |nDir||forward|cosθ ですが、nDirもforwardも長さが1なので値はcosθです。
斜辺と隣辺の角度がθの直角三角形を作ると、cosθは「斜辺に対する隣辺の長さの比」と見ることもできますので、cosθは下図の部分の長さを表しているとも言えるでしょう。

図5

ここでnDirをcosθで割ってみると、向きがnDirと同じで長さが1/cosθのベクトルができます。これを仮にvとすると、vとforwardの内積は |v||forward|cosθ であり、|v|は1/cosθ、|forward|は1なので内積の値は1になるはずです。つまり、forward方向の成分が1のベクトルを作ったと言えるでしょう。

図6

そしてオブジェクトをv * 3だけ移動させてみます。すると下図のように、forward方向へはちょうど3移動しているのがお分かりいただけるでしょうか?

図7

投稿2019/08/13 13:40

編集2019/08/15 23:05
Bongo

総合スコア10807

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

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

退会済みユーザー

退会済みユーザー

2019/08/14 14:05

ご回答ありがとうございます。 質問1について。 ViewportToWorldPointの距離下限値を小数点以下1~2桁までに抑えておこうと思いました。 ありがとうございます。 質問2について。 なるほど、ご提示いただいたgit動画でZ方向に動いたときに消失点へ近づくことが 理解できました。ありがとうございます。 追記に記載したのですが、 オブジェクトとカメラを結ぶ直線に沿ってオブジェクトを遠ざけると、 ビューポート上の座標が変化しないことも確認できました。 1点、疑問に思ったのですが、 viewportのZ値は、「ワールド座標系単位でのカメラからの位置」だったと思うのですが、 これは、オブジェクトの位置の場合、 カメラとオブジェクトの距離とは違いますか? 追記のコードに書いたVector3.Distanceの距離と、ビューポートのZ値が一致しないのが 不思議に思いました。 もしかしたら自分が何か理解できていないのかもしれませんが、 間違っている点などありましたら教えていただけませんか?
Bongo

2019/08/14 19:58

そうですね、そこで言う「ワールド座標系単位でのカメラからの位置」というのは「カメラと座標投影先の平面の符号付き距離」...つまりカメラの正面に直線を伸ばし、平面にぶつかった点までの距離を表しますので、これと「カメラとオブジェクトの距離」が一致するのはオブジェクトのビューポート座標が画面中心...(0.5, 0.5)の時だけでしょう(もちろんDistanceは常にプラスなのに対してビューポートZはマイナスの値も許されるので、符号が逆になる場合はありますが...)。 ビューポート座標のZを意図通り増減するには、中心からのずれを考慮する必要があるでしょう。ご提示のコードの後に... cube.transform.position = worldpos; Vector3 normalizedDir = Vector3.Normalize(cube.transform.position - Camera.main.transform.position); cube.transform.position = cube.transform.position + (normalizedDir / Mathf.Abs(Vector3.Dot(Camera.main.transform.forward, normalizedDir))) * 3; Vector3 cubeViewport3 = Camera.main.WorldToViewportPoint(cube.transform.position); Debug.Log("cubeViewport3.x:" + cubeViewport3.x); Debug.Log("cubeViewport3.y:" + cubeViewport3.y); Debug.Log("cubeViewport3.z:" + cubeViewport3.z); Debug.Log(Vector3.Distance(Camera.main.transform.position, cube.transform.position)); とでもしていただきますと、ビューポート座標のZが3増えるのを確認いただけるかと思います。 または回答中で申し上げたように、単純にビューポート座標に変換した上でZ成分に3を足してもいいと思います。実用上はこっちの方がシンプルでわかりやすいかもしれませんね。 cube.transform.position = worldpos; cube.transform.position = Camera.main.ViewportToWorldPoint(Camera.main.WorldToViewportPoint(cube.transform.position) + Vector3.forward * 3); Vector3 cubeViewport4 = Camera.main.WorldToViewportPoint(cube.transform.position); Debug.Log("cubeViewport4.x:" + cubeViewport4.x); Debug.Log("cubeViewport4.y:" + cubeViewport4.y); Debug.Log("cubeViewport4.z:" + cubeViewport4.z); Debug.Log(Vector3.Distance(Camera.main.transform.position, cube.transform.position));
退会済みユーザー

退会済みユーザー

2019/08/15 17:38

ご回答ありがとうございます。 後半の「単純にビューポート座標に変換した上でZ成分に3を足す」、 こちら理解できました。ありがとうございます。 「ワールド座標系単位でのカメラからの位置」が 「カメラの正面に直線を伸ばし、平面にぶつかった点までの距離」ということもおそらく理解できたと思います。 試しに、 Vector3 centerWorld = Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, cubeViewport.z)); Debug.Log(Vector3.Distance(Camera.main.transform.position, centerWorld)); や、 Vector3 centerWorld2 = Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, cubeViewport2.z)); Debug.Log(Vector3.Distance(Camera.main.transform.position, centerWorld2)); で、ログをとり、ビューポートのZ値とほぼ同じ値が出力されることを確認できました。 ありがとうございます。 すみません、下記がどういった処理をされているかわかりませんでした。 cube.transform.position = cube.transform.position + (normalizedDir / Mathf.Abs(Vector3.Dot(Camera.main.transform.forward, normalizedDir))) * 3; の、 (normalizedDir / Mathf.Abs(Vector3.Dot(Camera.main.transform.forward, normalizedDir)))の箇所です。 Aベクトル/AベクトルとBベクトルの内積 がどういった図のイメージになるかがわかりませんでした。 内積に関しては、 https://docs.unity3d.com/ja/current/Manual/UnderstandingVectorArithmetic.html の資料をみたり、 a・b=|a||b|cosθであることがわかりました。 資料にあった「このスカラーは二つのベクトルの大きさを乗算したもの、およびベクトルのなす角のコサインと等しくなります。」 の解釈としては、aベクトル・bベクトル = 0.5だったら、aベクトルとbベクトルのなす角は60度という認識で合っていますか? a・b=|a||b|cosθより、 normalizedDir/|normalizedDir||Cameraforward|cosθ =1/|Cameraforward|cosθ と考えたのですが、今1つイメージできず、 また追記の図を描いてみて考えたのですが、やはりイメージできずという状態です。 AベクトルとBベクトルの内積は、スカラーらしいので、 Aベクトル/AベクトルとBベクトルの内積 は、Aベクトルの方向は変わらず、Aベクトルの大きさだけが小さくなっているみたい という予想はつきましたが、どういったイメージで小さくなっているのかわからないです。 (上記もその考えが合っているか自信ないですが。) >中心からのずれを考慮する必要がある こちらがイメージできていないかもしれません。 高校数学のお話で申し訳ないですが、もし可能でしたら、 イメージ的なものとか、どういった処理をされているのか教えていただけませんか?
Bongo

2019/08/15 23:15

内積に関してはそれで問題なさそうに思います。念のため申し上げますと、「aベクトル・bベクトル = 0.5だったら、aベクトルとbベクトルのなす角は60度」と言うためには、aベクトルもbベクトルも長さが1でないといけませんが... normalizedDirを内積で割る件については、ちょっと図を描いてみましたので追記しました。あんなイメージでいかがでしょうか? なお、先のコメント中のコードではオブジェクトがカメラの背後にある場合を考慮して内積の絶対値を使っていますが、追記中では省略しています。
退会済みユーザー

退会済みユーザー

2019/08/16 17:05 編集

ご回答ありがとうございます。 >「aベクトル・bベクトル = 0.5だったら、aベクトルとbベクトルのなす角は60度」と言うためには、 >aベクトルもbベクトルも長さが1でないといけませんが... ご指摘ありがとうございます。長さが1でないといけない条件に気づいてませんでした。 そうしますと、aベクトルとbベクトルをそれぞれノーマライズド(正規化)したベクトル同士の 内積をとると、cosの値が取れて、そのcosの値により、なす角が判定できるというわけですね。 ベクトルの内積は苦手意識があり、勉強するのを放置していたのですが、今回、理解できて 実用的な使い方も見えてきました、ありがとうございます。 追記のご説明ありがとうございます。 全体的な流れは理解できたのですが、すみません、細かい所でわからない点があったので 質問させていただけたらと思います。 ・質問1。  日本語の理解力が乏しくて申し訳ないのですが、  「Aに対するBの比」の値はB/Aで合っていますか(A/Bではないですよね)?。 ・質問2。  >cosθは下図の部分の長さを表しているとも言えるでしょう。  nDirの長さが1だから、cosθは隣辺の長さになるという解釈で合っていますか? ・質問3。  >ここでnDirをcosθで割ってみると、向きがnDirと同じで長さが1/cosθのベクトルができます。  これもやはり、nDirの長さが1だから、cosθで割ると、長さが1/cosθになるという解釈で合っていますか? ・質問4。  (normalizedDir / Mathf.Abs(Vector3.Dot(Camera.main.transform.forward, normalizedDir)))  が、ご説明のvベクトルということで合っていますか? 余談ですが、forward方向の成分が1のベクトルとセットのvベクトルを作り上げる方法、すごいですね。 自分は何時間もかけてご説明の内容を考えて、ご説明の全体的な流れを掴むのがやっとでした。 最初、forwardは元々、長さが1なので、「forward方向の成分が1のベクトルを作る」ということに 混乱してしまったのですが、それとセットのvベクトルの作成の為だったのですね。 とても勉強になります、ありがとうございます。
Bongo

2019/08/16 21:10

・質問1について はい、「Aに対するBの比」と言われたらB/Aの方を意味するかと思います。いざはっきり問われてしまいますと、もしかして私の解釈はずっと間違っていたのでは...なんて考えてしまいましたが、「比 - Wikipedia」( https://ja.wikipedia.org/wiki/%E6%AF%94 )の真ん中あたりの「比の値」に関する言及を見ますとどうやら合っていたようです。 「「3に対する2の比」は,「2:3」 | メタメタの日」( https://ameblo.jp/metameta7/entry-12059352506.html )といった記事を見ますと、比に関する表現のややこしさは昔から問題視されていたみたいですね。 ・質問2について はい、そういうことです。「三角関数 - Wikipedia」の「単位円による定義」 ( https://ja.wikipedia.org/wiki/%E4%B8%89%E8%A7%92%E9%96%A2%E6%95%B0#%E5%8D%98%E4%BD%8D%E5%86%86%E3%81%AB%E3%82%88%E3%82%8B%E5%AE%9A%E7%BE%A9 )の節に、それぞれの三角関数の値が示す大きさを表現した図がありましたので、ご参考になるかと思います。 ・質問3と質問4について はい、これらもその通りです。
退会済みユーザー

退会済みユーザー

2019/08/17 15:05 編集

ご回答ありがとうございます。 質問1~4についてご回答いただきありがとうございます。 理解できました。 長くなってしまってすみませんが、最後の質問にさせていただけたらと思います。 ご教示いただいたおかげで仕組みは理解できたので、コードの組み方を 自分なりに簡単にイメージできて暗記する方法はないかと思って考えたのですが、 下記は合っていますか? 「斜辺ベクトル / (斜辺ベクトル・隣辺ベクトル(斜辺ベクトルと隣辺ベクトルの内積)の絶対値)」は、 「斜辺ベクトルの向きは変わらず、斜辺ベクトルの長さが隣辺ベクトルの長さを基準にしたときの  相対的な長さの斜辺ベクトル」になる。  今回は隣辺の単位ベクトルを基準にしたかったので、Camera.main.transform.forwardにした。 補足しますと、 「斜辺ベクトル」を「斜辺の単位ベクトル」としないとダメかなと思ったのですが、 下記のように検証したところ、斜辺ベクトルは単位ベクトルじゃなくてもよさそうでした。 Vector3 dir = cube.transform.position - Camera.main.transform.position; cube.transform.position = cube.transform.position + (dir / Mathf.Abs(Vector3.Dot(Camera.main.transform.forward, dir))) * 3; Vector3 cubeViewport3 = Camera.main.WorldToViewportPoint(cube.transform.position); Debug.Log("cubeViewport3.x:" + cubeViewport3.x); Debug.Log("cubeViewport3.y:" + cubeViewport3.y); Debug.Log("cubeViewport3.z:" + cubeViewport3.z); 間違っている点があったり、おかしな表現でしたら、ご教示いただけたらと思います。
Bongo

2019/08/17 19:20

はい、それでも大丈夫ですね。 分子のベクトルdirは「向きがnormalizedDirで長さが|dir|」、一方分母のスカラーは「|forward||dir|cosθ」で|forward|は1なので「|dir|cosθ」となり、分子と分母の|dir|が打ち消しあって同じ結果になるでしょう。 この方がdirの正規化をしなくていいので計算量を節約できそうですね。
退会済みユーザー

退会済みユーザー

2019/08/18 01:59

ご回答ありがとうございます。 たくさんのご教示ありがとうございました。 ビューポートとベクトルの内積、とても理解が深まりました。 ありがとうございました。
退会済みユーザー

退会済みユーザー

2019/08/18 04:36 編集

すみません、ベストアンサーが付いた後の質問で申し訳ないですが、 よく考えて気づいたのですが、 隣辺ベクトル側は今回に限らず単位ベクトルでないとダメということですよね? (Bongo様は暗黙のうちにそれを認識してお答えいただいていたと思うのですが、 自分は気づかず勘違いしていました。「|forward|は1なので」と書かれていたので。) なので、 「斜辺ベクトル / (斜辺ベクトル・隣辺の単位ベクトル(斜辺ベクトルと隣辺の単位ベクトルの内積)の絶対値)」は、 「斜辺ベクトルの向きは変わらず、斜辺ベクトルの長さが隣辺の単位ベクトルの長さを基準にしたときの  相対的な長さの斜辺ベクトル」になる。 という表現が正しいということですよね。 (斜辺ベクトルが大きさ2だとしたら、大きさ2のベクトル/(2cosθ・1)のように1でないと正しい相対値にならない気がしました。) この辺りの内積の割り算とベクトルの相対的な長さの算出を正しく理解して暗記しておけば、 他の場面でもいろいろ応用できると思って、気になってしまいました。 長くなってすみません、いかがでしょうか?
Bongo

2019/08/18 05:36

はい、おっしゃる通り隣辺側は単位ベクトル...つまり長さが1のベクトルでないとうまくいかないでしょうね。その解釈で問題ないかと思います。
退会済みユーザー

退会済みユーザー

2019/08/18 05:42

ご回答ありがとうございます。 ご確認いただき、安心しました。 重ね重ねありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問