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

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

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

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

Unity

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

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

Q&A

解決済

1回答

2499閲覧

Unityの3D空間上に存在するZwriteをOffにしたSpriteとTransparentの描画順を正したい

bh_evan

総合スコア18

Unity3D

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

Unity

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

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

0グッド

0クリップ

投稿2022/03/28 09:43

編集2022/03/28 10:34

前提

Unityで3D空間に設置したSpriteが、Transparentと重なった時に描画順が崩れます(Transparentが必ず前面に来る)。

以前質問したもの(Unity3Dで描画順を設定できるSpriteをSkyboxより手前に表示したい)と同じで、キャラクターとして表示しているSpriteのため、同じマテリアルを持つオブジェクトを複数重ねて配置しています。
そのため、パーツ毎に描画順を設定する必要があります。

同じシェーダーに関する質問なので、コピペみたいな内容になってしまいますがお許しください。

実現したいこと

  • 3D空間に配置されたSpriteが
  • 他オブジェクトの影や光の影響を受け
  • 同一のシェーダーを持った他Spriteとの表示順を設定でき
  • Transparentと干渉しない

という状況を作りたいです。

発生している問題

ZwriteをOffにすることでOrder in Layerから表示順を設定していましたが、Transparentのオブジェクトを配置した際に題目の問題が発生しました。

該当のソースコード

Spriteのシェーダーは以下です。

HLSL

1Shader "Custom/Character" 2{ 3 Properties{ 4 _Cutoff("Cutoff", Range(0,1)) = 0.5 5 _Color("Color", Color) = (1,1,1,1) 6 _MainTex("Albedo (RGB)", 2D) = "white" {} 7 _Glossiness("Smoothness", Range(0,1)) = 0.5 8 _Metallic("Metallic", Range(0,1)) = 0.0 9 } 10 11 SubShader{ 12 Tags { "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" } 13 LOD 200 14 Cull Off 15 ZWrite Off 16 17 CGPROGRAM 18 #pragma surface surf Standard alphatest:_Cutoff addshadow fullforwardshadows 19 #pragma target 3.0 20 21 sampler2D _MainTex; 22 23 struct Input 24 { 25 float2 uv_MainTex; 26 }; 27 28 half _Glossiness; 29 half _Metallic; 30 fixed4 _Color; 31 32 void surf(Input IN, inout SurfaceOutputStandard o) 33 { 34 fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; 35 o.Albedo = c.rgb; 36 o.Metallic = _Metallic; 37 o.Smoothness = _Glossiness; 38 o.Alpha = c.a; 39 } 40 ENDCG 41 } 42 FallBack "Diffuse" 43}

正確には裏面の陰影を表示するため、以下のコードも含まれています。

HLSL

1Cull Front 2 3CGPROGRAM 4 5#pragma surface surf Standard alphatest:_Cutoff fullforwardshadows vertex:vert 6#pragma target 3.0 7 8sampler2D _MainTex; 9 10struct Input 11{ 12 float2 uv_MainTex; 13}; 14 15void vert(inout appdata_full v) 16{ 17 v.normal.xyz = v.normal * -1; 18} 19 20half _Glossiness; 21half _Metallic; 22fixed4 _Color; 23 24void surf(Input IN, inout SurfaceOutputStandard o) 25{ 26 fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; 27 o.Albedo = c.rgb; 28 o.Metallic = _Metallic; 29 o.Smoothness = _Glossiness; 30 o.Alpha = c.a; 31} 32ENDCG

Transparent側はUnityChanShaderのToonColor_DoubleShadeWithFeather_Transparentですが、他のTransparent系シェーダーでも同様の問題が発生しました。

ユニティちゃんトゥーンシェーダー2.0

試したこと

ZwriteをOnにすることで回避できますが、表示順が距離依存になってしまうので、この方法だと正確な描写は難しいと判断しました。

Skyboxと干渉した時のように、カメラを複数配置することで解決を試みましたが無理でした。

Problem with ZWrite and sorting objects - Unity Forum

上記のページも参考にしましたが、シェーダーの知識が足りず、解決には至りませんでした。

SpriteのZwriteをOnにしたものでも一応実装はできているので、よほど難解だと判明した場合に諦める準備はできました。

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

Unity 2021.2.8f1

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

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

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

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

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

guest

回答1

0

ベストアンサー

おっしゃる通りですね...ZWrite Offに設定したのでスプライトのデプスは書き込まれず、Transparentオブジェクトを描画する時点ではスプライトが存在しないのと同じになってしまい、容赦なく上書きされてしまうようです。

ちょっと作戦を変更しました。まず以前の回答で申し上げた二段カメラはなしにして、元の単一カメラに戻します。Clear Flagsも元通りメインカメラがスカイボックスを描画するようにします。

図1

この時点では下図のように元通りスカイボックスによる上書きが発生するはずです。また、スプライトの背後に半透明のシリンダーを置きましたが、これもスプライトの上に描画されてしまいました。

図2

そこで、スプライトキャラクターのルート(今回の実験では「Sprite Man」オブジェクト)に下記のようなスクリプトをアタッチします。

C#

1using System.Collections.Generic; 2using UnityEngine; 3using UnityEngine.Rendering; 4 5public class ZWriter : MonoBehaviour 6{ 7 [SerializeField] private Material zWriterMaterial; 8 private readonly List<ZWriterHelper> helpers = new List<ZWriterHelper>(); 9 10 private void OnEnable() 11 { 12 // Enable時に、子オブジェクトのスプライトに後述のZWriterHelperをアタッチする 13 var spriteRenderers = this.GetComponentsInChildren<SpriteRenderer>(true); 14 foreach (var spriteRenderer in spriteRenderers!) 15 { 16 var helper = spriteRenderer!.GetComponent<ZWriterHelper>(); 17 if (helper == null) 18 { 19 helper = spriteRenderer.gameObject.AddComponent<ZWriterHelper>(); 20 } 21 22 this.helpers!.Add(helper); 23 } 24 } 25 26 private void OnDisable() 27 { 28 // Disable時にZWriterHelperは削除する 29 foreach (var helper in this.helpers!) 30 { 31 if (helper != null) 32 { 33 Destroy(helper); 34 } 35 } 36 } 37 38 [DisallowMultipleComponent] 39 [RequireComponent(typeof(SpriteRenderer))] 40 private class ZWriterHelper : MonoBehaviour 41 { 42 private readonly Dictionary<Camera, CommandBuffer> commandBuffers = new Dictionary<Camera, CommandBuffer>(); 43 private new SpriteRenderer renderer; 44 private ZWriter zWriter; 45 46 private void OnEnable() 47 { 48 this.zWriter = this.GetComponentInParent<ZWriter>(); 49 if (this.zWriter == null) 50 { 51 this.enabled = false; 52 return; 53 } 54 55 this.renderer = this.GetComponent<SpriteRenderer>(); 56 } 57 58 private void OnDisable() 59 { 60 this.RemoveCommandBuffers(); 61 } 62 63 private void OnWillRenderObject() 64 { 65 if ((this.zWriter == null) || !this.zWriter.enabled || (this.zWriter.zWriterMaterial == null)) 66 { 67 this.RemoveCommandBuffers(); 68 return; 69 } 70 71 var currentCamera = Camera.current; 72 if (currentCamera == null) 73 { 74 return; 75 } 76 77 if (this.commandBuffers!.ContainsKey(currentCamera)) 78 { 79 return; 80 } 81 82 // スカイボックスレンダリング前にスプライトをzWriterMaterialを使って描画するよう 83 // カメラにCommandBufferを仕込む 84 var commandBuffer = new CommandBuffer 85 { 86 name = $"Write {this.name} Depth" 87 }; 88 commandBuffer.DrawRenderer(this.renderer, this.zWriter.zWriterMaterial); 89 currentCamera.AddCommandBuffer(CameraEvent.BeforeSkybox, commandBuffer); 90 this.commandBuffers.Add(currentCamera, commandBuffer); 91 } 92 93 private void RemoveCommandBuffers() 94 { 95 foreach (var (targetCamera, commandBuffer) in this.commandBuffers!) 96 { 97 if (targetCamera != null) 98 { 99 targetCamera.RemoveCommandBuffer(CameraEvent.BeforeSkybox, commandBuffer); 100 } 101 102 commandBuffer?.Dispose(); 103 } 104 105 this.commandBuffers.Clear(); 106 } 107 } 108}

また、下記のようにスプライトのシェーダーと同様のアルファカットオフを行いつつ、ZWrite Onかつカラーバッファには書き込みを行わないシェーダーを用意します。

ShaderLab

1Shader "Custom/ZWriter" 2{ 3 Properties{ 4 _Cutoff("Cutoff", Range(0,1)) = 0.5 5 _Color("Color", Color) = (1,1,1,1) 6 _MainTex("Albedo (RGB)", 2D) = "white" {} 7 } 8 9 SubShader{ 10 Tags { "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" } 11 LOD 200 12 Cull Off 13 ZWrite On 14 ColorMask 0 15 16 Pass 17 { 18 CGPROGRAM 19 #pragma vertex vert 20 #pragma fragment frag 21 #include "UnityCG.cginc" 22 23 struct appdata 24 { 25 float4 vertex : POSITION; 26 float2 uv : TEXCOORD0; 27 float4 color : COLOR; 28 }; 29 30 struct v2f 31 { 32 float4 vertex : SV_POSITION; 33 float2 uv : TEXCOORD0; 34 float4 color : TEXCOORD1; 35 }; 36 37 sampler2D _MainTex; 38 float4 _MainTex_ST; 39 fixed4 _Color; 40 float _Cutoff; 41 42 v2f vert(appdata v) 43 { 44 v2f o; 45 o.vertex = UnityObjectToClipPos(v.vertex); 46 o.uv = TRANSFORM_TEX(v.uv, _MainTex); 47 o.color = _Color * v.color; 48 return o; 49 } 50 51 fixed4 frag(v2f i) : SV_Target 52 { 53 clip(tex2D(_MainTex, i.uv).a * i.color.a - _Cutoff); 54 return 0; 55 } 56 ENDCG 57 } 58 } 59 FallBack "Diffuse" 60}

これを使ったマテリアルを作り、ZWriterzWriterMaterialにセットしておきます。

図3

この状態で実行すれば、スカイボックスによる上書きもTransparentオブジェクトによる上書きも発生しなくなるかと思います。つまり、スプライトの各部位の前後関係を正しくするにはデプスを書き込んではならず、スカイボックスやTransparentによる上書きを防ぐにはデプスが必要であるのなら、スプライトのレンダリング時にはデプスを書き込まず、スカイボックスを描画する前にあらためてデプスを書き込んでやればいいんじゃないかと思ったわけです。

図4

ただし、ゲームビューではスプライトのレンダリングがスカイボックスよりも前なので意図通りになるのですが、シーンビューではスプライトよりもスカイボックスが先にレンダリングされるため、スプライトのシェーダーをZWrite Onにしたときと同様に各部位の重なりが不確定になってちらつきが発生する弱点があります...

図5

投稿2022/03/28 21:57

編集2022/03/29 22:25
Bongo

総合スコア10807

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

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

bh_evan

2022/03/29 14:35

2度に渡り解答していただき頭が上がりません。本当にありがとうございます。 お陰で完全に理想通りの実装ができました。 C#から書き込む方法もあるのですね。 勉強になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問