凹みTipsさんの方式は「ポリゴン表面を起点に、視線に沿ってテクスチャをサンプリングしていく」というスタイルですね。ということは積分の起点はキューブの前面であることが暗黙の前提になっていて、単純に両面描画に切り替えただけではうまくいかなそうです(キューブの内部から背面を描画するというシチュエーションでは、背面を起点にキューブの外側へ向かって積分していってしまうはずです)。
対策として、逆にキューブ背面をレンダリングすることを前提としたスタイルはいかがでしょうか。以前別の方の「2D Spriteを立体に表示したい」とのご質問に挑戦したことがあるのですが、その際のコードを流用して下記のように改変してみました。
ShaderLab
1Shader "VolumeRendering/VolumeRendering"
2{
3 Properties
4 {
5 [Header(Rendering)]
6 _Volume("Volume", 3D) = "" {}
7 _Color("Color", Color) = (1, 1, 1, 1)
8 _Iteration("Iteration", Int) = 100
9 _Intensity("Intensity", Range(0.0, 1.0)) = 0.1
10
11 [Header(Ranges)]
12 _MinX("MinX", Range(0, 1)) = 0.0
13 _MaxX("MaxX", Range(0, 1)) = 1.0
14 _MinY("MinY", Range(0, 1)) = 0.0
15 _MaxY("MaxY", Range(0, 1)) = 1.0
16 _MinZ("MinZ", Range(0, 1)) = 0.0
17 _MaxZ("MaxZ", Range(0, 1)) = 1.0
18 }
19
20 CGINCLUDE
21 #include "UnityCG.cginc"
22
23 struct appdata
24 {
25 float4 vertex : POSITION;
26 };
27
28 struct v2f
29 {
30 float4 vertex : SV_POSITION;
31 float3 localPos : TEXCOORD0;
32 float3 worldPos : TEXCOORD1;
33 float3 localViewDir : TEXCOORD2;
34 float3 worldViewDir : TEXCOORD3;
35 float4 screenPos : TEXCOORD4;
36 };
37
38 sampler3D _Volume;
39 fixed4 _Color;
40 int _Iteration;
41 fixed _Intensity;
42 fixed _MinX, _MaxX, _MinY, _MaxY, _MinZ, _MaxZ;
43
44 #define INTEGRATION_THRESHOLD 0.01
45 #define DIRECTIONAL_EPSILON 0.0009765625
46 #define ALPHA_THRESHOLD 0.01
47
48 sampler2D _CameraDepthTexture;
49
50 // キューブに関するtransform.InverseTransformPoint
51 float3 worldToObjectPos(float3 worldPos) {return mul(unity_WorldToObject, float4(worldPos, 1.0)).xyz;}
52
53 // キューブに関するtransform.InverseTransformVector
54 float3 worldToObjectVec(float3 worldVec) {return mul((float3x3)unity_WorldToObject, worldVec);}
55
56 // キューブに関するtransform.TransformVector
57 float3 objectToWorldVec(float3 localVec) {return mul((float3x3)unity_ObjectToWorld, localVec);}
58
59 // カメラのtransform.position
60 float3 getWorldCameraPos() {return UNITY_MATRIX_I_V._14_24_34;}
61
62 // カメラのtransform.forward
63 float3 getWorldCameraDir() {return -UNITY_MATRIX_V._31_32_33;}
64
65 // カメラのnearClipPlane
66 float getWorldCameraNear() {return _ProjectionParams.y;}
67
68 // カメラ投影法が透視投影かどうか
69 bool isPerspective() {return any(UNITY_MATRIX_P._41_42_43);}
70
71 // カメラが透視投影ならカメラの位置、平行投影ならカメラ位置を通る平面上のworldPosを正面にとらえる位置
72 float3 getWorldCameraOrigin(float3 worldPos)
73 {
74 return isPerspective()
75 ? getWorldCameraPos()
76 : worldPos + dot(getWorldCameraPos() - worldPos, getWorldCameraDir()) * getWorldCameraDir();
77 }
78
79 // キューブのtransform.position
80 float3 getWorldBoundsCenter() {return unity_ObjectToWorld._14_24_34;}
81
82 // カメラの位置からキューブを見て、2枚の平面で前後にキューブを挟んだ時の長さを求める
83 float getWorldBoundsDepth(float3 worldCameraDir)
84 {
85 float3 worldCornerVec1 = objectToWorldVec(float3(0.5, 0.5, 0.5));
86 float3 worldCornerVec2 = objectToWorldVec(float3(-0.5, 0.5, 0.5));
87 float3 worldCornerVec3 = objectToWorldVec(float3(0.5, -0.5, 0.5));
88 float3 worldCornerVec4 = objectToWorldVec(float3(-0.5, -0.5, 0.5));
89 float2 lengths1 = abs(float2(dot(worldCornerVec1, worldCameraDir), dot(worldCornerVec2, worldCameraDir)));
90 float2 lengths2 = abs(float2(dot(worldCornerVec3, worldCameraDir), dot(worldCornerVec4, worldCameraDir)));
91 float2 lengths = max(lengths1, lengths2);
92 return max(lengths.x, lengths.y) * 2.0;
93 }
94
95 // 上記のように挟んだ時の平面の位置を求める
96 float2 getWorldBoundsNearFar(float worldBoundsDepth)
97 {
98 float center = isPerspective()
99 ? distance(getWorldBoundsCenter(), getWorldCameraPos())
100 : dot(getWorldBoundsCenter() - getWorldCameraPos(), getWorldCameraDir());
101 return float2(-0.5, 0.5) * worldBoundsDepth + center;
102 }
103
104 // キューブの面に関するPlane.Raycastを3方向まとめて行う
105 float3 getLocalBoundsFaces(float3 localPos, float3 localViewDir, float faceOffset)
106 {
107 float3 signs = sign(localViewDir);
108 return -(signs * localPos + faceOffset) / (abs(localViewDir) + (1.0 - abs(signs)) * DIRECTIONAL_EPSILON);
109 }
110
111 // キューブ前面までの距離を求める
112 float getLocalBoundsFrontFace(float3 localPos, float3 localViewDir)
113 {
114 float3 lengths = getLocalBoundsFaces(localPos, localViewDir, 0.5);
115 return max(max(max(lengths.x, lengths.y), lengths.z), 0.0);
116 }
117
118 // デプステクスチャをもとに他の不透明オブジェクトのZ位置を算出する
119 // キューブ内に他の不透明オブジェクトが貫入している場合に対応するため使用する
120 float sampleOpaqueZ(float4 screenPos)
121 {
122 float rawDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(screenPos));
123 return isPerspective()
124 ? LinearEyeDepth(rawDepth)
125 : -dot(unity_CameraInvProjection._33_34, float2(_ProjectionParams.x * (rawDepth * 2.0 - 1.0), 1.0));
126 }
127
128 fixed sample(float3 pos)
129 {
130 fixed x = step(pos.x, _MaxX) * step(_MinX, pos.x);
131 fixed y = step(pos.y, _MaxY) * step(_MinY, pos.y);
132 fixed z = step(pos.z, _MaxZ) * step(_MinZ, pos.z);
133 return tex3D(_Volume, pos).a * x * y * z;
134 }
135
136 v2f vert(appdata v)
137 {
138 v2f o;
139 o.vertex = UnityObjectToClipPos(v.vertex);
140 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
141 o.localPos = v.vertex.xyz;
142 o.worldViewDir = isPerspective() ? -UnityWorldSpaceViewDir(o.worldPos) : getWorldCameraDir();
143 o.localViewDir = worldToObjectVec(o.worldViewDir);
144 o.screenPos = ComputeScreenPos(o.vertex);
145 return o;
146 }
147
148 fixed4 frag(v2f i) : SV_Target
149 {
150 // カメラのニアプレーンから他の不透明オブジェクト表面までの範囲で、かつキューブ内である範囲を求める
151 float3 worldViewDir = normalize(i.worldViewDir);
152 float3 localViewDir = normalize(i.localViewDir);
153 float peripheralFactor = 1.0 / dot(getWorldCameraDir(), worldViewDir);
154 float3 worldCameraOrigin = getWorldCameraOrigin(i.worldPos);
155 float3 localCameraOrigin = worldToObjectPos(worldCameraOrigin);
156 float worldFrontFace = dot(objectToWorldVec(getLocalBoundsFrontFace(localCameraOrigin, localViewDir) * localViewDir), worldViewDir);
157 float worldBackFace = dot(i.worldPos - worldCameraOrigin, worldViewDir);
158 float worldCameraNear = getWorldCameraNear();
159 float worldOpaque = sampleOpaqueZ(i.screenPos) * peripheralFactor;
160 float worldEnter = max(worldFrontFace, worldCameraNear);
161 float worldExit = min(worldBackFace, worldOpaque);
162
163 // 不要な領域をクリッピングする
164 clip(worldExit - worldEnter);
165
166 // 視線方向の色積分の開始・終了ステップ番号を求める
167 float worldBoundsDepth = getWorldBoundsDepth(getWorldCameraDir());
168 float2 worldBoundsNear = getWorldBoundsNearFar(worldBoundsDepth).x;
169 float worldLineOrigin = worldBoundsNear * peripheralFactor;
170 float worldLineLength = worldBoundsDepth * peripheralFactor;
171 float worldStepLength = worldLineLength / _Iteration;
172 float worldLineOriginToEnter = worldEnter - worldLineOrigin;
173 float worldLineOriginToExit = worldExit - worldLineOrigin;
174 int startingIndex = (int)ceil(worldLineOriginToEnter / worldStepLength);
175 int terminalIndex = (int)(worldLineOriginToExit / worldStepLength);
176
177 float3 localStep = worldToObjectVec(worldStepLength * worldViewDir);
178 float3 localLineOriginPos = worldToObjectPos(worldCameraOrigin + worldViewDir * worldLineOrigin);
179 fixed output = 0.0;
180
181 [loop]
182 for (int j = startingIndex; j <= terminalIndex; j++)
183 {
184 float3 lpos = localLineOriginPos + j * localStep;
185 fixed a = sample(lpos + 0.5);
186 if (a < ALPHA_THRESHOLD)
187 {
188 continue;
189 }
190 output += (1.0 - output) * a * _Intensity;
191 if (output > 1.0 - INTEGRATION_THRESHOLD)
192 {
193 break;
194 }
195 }
196
197 return _Color * output;
198 }
199 ENDCG
200
201 SubShader
202 {
203 Tags
204 {
205 "Queue" = "Transparent"
206 "RenderType" = "Transparent"
207 }
208
209 Pass
210 {
211 Cull Front
212 ZWrite Off
213 ZTest Always
214 Blend One OneMinusSrcAlpha
215 Lighting Off
216
217 CGPROGRAM
218 #pragma vertex vert
219 #pragma fragment frag
220 ENDCG
221 }
222 }
223}

バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/03/06 06:19