🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Unity3D

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

Unity

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

Q&A

解決済

1回答

1963閲覧

Unityで重なってる部分をずらして見えるようにしたい

Asagiri

総合スコア9

Unity3D

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

Unity

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

0グッド

1クリップ

投稿2019/12/10 06:32

現在Unityで水に入れたら屈折するというのを再現するものを作成しています。
透明なシリンダーに棒を入れて重なっている部分だけずれて見えるようにしたいです。
また、棒は移動できるようにしたいのでずれるとこだけ別々で作るというのは無理です。
実現方法のアイデアでもよろしいのでヒントだけでも教えてもらえませんか?

イメージ説明

使用しているのはUnity2018です。

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

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

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

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

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

guest

回答1

0

ベストアンサー

どの程度の正確さが必要でしょうか?
そのまま透過させただけだと水らしさに欠けるので、ちょっとゆがみを加えたいといった程度なのか、あるいは屈折がゲームプレイ上または表現上重要な役割を持っていて、なるべく正確に屈折像を描画したいのかによって難しさが変わってきそうです。

屈折表現は未だにやっかいな部類の処理だと思います。正確に描こうと考え始めると...

  • 光線はカメラに向いている面だけでなく背面でも屈折する可能性があるが、普通のレンダリング方法では背面の屈折を考慮した描画はやりにくい。
  • 媒質の境界面を通過するたびに光線は屈折成分と反射成分に分かれ、計算するべき光の経路は境界面を通過するたびにねずみ算式に増える。
  • 物体が落とす影も屈折の影響を受ける。プールの底に網目状の模様が生じたり虫眼鏡が太陽の光を集めるように、影の中に周りよりも明るくなる部分が生じたりするので(コースティック)、単純な光源からの遮蔽判定だけでは美しく描画できない。
  • よりリアルな映像にしたい場合、光の波長によって屈折の程度が微妙に異なることによる色ズレ(分散色収差)を考慮する必要がある。

といった面倒な処理を行うことになり、かなり重くなってしまうでしょう。できるだけ妥協して「軽量な屈折もどき」でごまかしたいところです。
屈折もどきであれば「Unity 屈折」あたりのキーワードで検索していただきますと、けっこう多くの参考情報が見つかるかと思います。
たとえば【Unity】GrabPassを用いた屈折(Refraction)シェーダー - Qiitaなどは面白そうですね。これを使って透明な円柱を通して不透明な角柱を見てみると下図のようになりました。

図1

屈折像としてどの程度妥当なのか気になるかもしれません。さすがに実際に円柱型の水の塊を宙に浮かせて、突き刺した棒を波を立てずに回転させてみる...なんて実験はしたことはありませんので、本当はどんな屈折像になるのかは分かりませんが、レイトレーシング法ならば物理的にもっともらしい屈折像を描かせることができると思います。
外部ソフトウェアで似せて作ったシーンをレイトレーシング法でレンダリングしてみたところ、下図のような屈折像になりました。

図2

ひずみ方がけっこう違っていることを見て取れるかと思います。この屈折像にもう少し近づけられないかと思い、下図のようなマテリアルを作成してみたところ...

ShaderLab

1Shader "Custom/FauxRefraction" 2{ 3 Properties 4 { 5 _Color ("Color", Color) = (0.8, 0.5, 0.5, 0.1) 6 _MainTex ("Albedo (RGB)", 2D) = "white" {} 7 _Glossiness ("Smoothness", Range(0,1)) = 0.5 8 _Metallic ("Metallic", Range(0,1)) = 0.0 9 _Refraction ("Refraction Index", Range(1.0, 3.0)) = 1.33 10 _Distance ("Refraction Distance", Float) = 10.0 11 } 12 SubShader 13 { 14 Tags { "Queue"="Transparent" "RenderType"="Transparent" } 15 16 GrabPass {} 17 18 CGPROGRAM 19 #pragma surface surf Standard alpha 20 #pragma target 3.0 21 22 sampler2D _MainTex; 23 sampler2D _GrabTexture; 24 sampler2D _CameraDepthTexture; 25 26 struct Input 27 { 28 float2 uv_MainTex; 29 float3 worldPos; 30 float3 worldNormal; 31 }; 32 33 half _Glossiness; 34 half _Metallic; 35 fixed4 _Color; 36 float _Refraction; 37 float _Distance; 38 39 void surf(Input IN, inout SurfaceOutputStandard o) 40 { 41 #define DISTANCE_DIVISOR 16.0 42 #define ITERATION_COUNT 32 43 #define DEPTH_THRESHOLD 0.1 44 #define EDGE_BLENDING_EXPONENT 4.0 45 46 // まず屈折方向を求め... 47 float3 eyeDir = normalize(IN.worldPos - _WorldSpaceCameraPos); 48 float3 refractionDir = refract(eyeDir, normalize(IN.worldNormal), 1.0 / _Refraction); 49 50 // 透明物体の表面を起点に、少しずつ屈折方向へ進みながら 51 // デプスをチェックしていき、他のオブジェクトと衝突する点を求める 52 float stride = _Distance / DISTANCE_DIVISOR; 53 float s = 1.0; 54 float4 refractionPos = float4(IN.worldPos, 1.0); 55 float4 refractionViewPos; 56 float4 refractionClipPos; 57 float backgroundDepth; 58 float hit = 0.0; 59 for (int i = 0; i < ITERATION_COUNT; i++) 60 { 61 refractionPos.xyz += s * stride * refractionDir; 62 refractionViewPos = mul(UNITY_MATRIX_V, refractionPos); 63 refractionClipPos = mul(UNITY_MATRIX_P, refractionViewPos); 64 backgroundDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, ComputeScreenPos(refractionClipPos))); 65 if (s * (backgroundDepth + refractionViewPos.z) < 0.0) 66 { 67 // もし衝突が検出されたら、進行方向を逆転させ 68 // 進行スピードを半減させて衝突しなくなるまで戻る 69 // 衝突しなくなったら再び進行方向逆転・スピード半減...を繰り返し 70 // なるべく正確な接触位置を求める 71 hit = 1.0; 72 s *= -1.0; 73 stride *= 0.5; 74 } 75 } 76 77 // このようなデプスだけを頼りにした衝突判定では、画面内のオブジェクトが奥行き方向に 78 // どれだけの厚みを持っているかわからないので、本来であればオブジェクトの背後を 79 // 素通りしていくべき屈折光経路まで衝突していると判定されてしまうことになる 80 // しょうがないので「衝突している」と判定されたとしても、オブジェクトのデプスと 81 // 検出された衝突点のデプスがある程度ずれているようなら誤検出と見なして検出を取り消す 82 // また、判定境界の部分に発生するノイズを軽減するため、境界付近では判定値をなめらかに変える 83 float depthDelta = abs(backgroundDepth + refractionViewPos.z); 84 hit = hit * saturate(1.0 - pow(depthDelta / DEPTH_THRESHOLD, EDGE_BLENDING_EXPONENT)); 85 86 // 衝突していると判定された場合はグラブテクスチャの色を、そうでなければ 87 // リフレクション用キューブマップの色を屈折色として採用する 88 float4 grabTexColor = tex2Dproj(_GrabTexture, ComputeGrabScreenPos(refractionClipPos)); 89 float4 backgroundColor = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, refractionDir); 90 float4 refractionColor = lerp(backgroundColor, grabTexColor, hit); 91 fixed4 surfaceColor = tex2D(_MainTex, IN.uv_MainTex) * _Color; 92 93 o.Albedo = surfaceColor.rgb * surfaceColor.a; 94 o.Metallic = _Metallic; 95 o.Smoothness = _Glossiness; 96 o.Alpha = 1.0; 97 o.Emission = refractionColor.rgb * surfaceColor.rgb * (1.0 - surfaceColor.a); 98 } 99 ENDCG 100 } 101}

Unity上でのレンダリング結果は下図のようになりました。

図3

遠目で見ればいくらかはレイトレーシング法の結果に近づいたかもしれませんが、オブジェクトのエッジをきれいにしきれておらずブサイクなノイズが現れています(多少は軽減する措置は加えたのですが...)。総合的に見れば品質は悪化してしまいました。
結局、透明な物体の「透明っぽさ」を表現する目的でしたら@aa_debdebさんのようなシンプルな方法が計算コストも小さく実用的だと思います。

投稿2019/12/12 14:07

編集2019/12/12 21:36
Bongo

総合スコア10811

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問