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

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

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

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

Unity

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

Q&A

解決済

2回答

5965閲覧

Unity URP環境でボリュームライト(光の軌道)を作る方法

mokotan

総合スコア3

Unity3D

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

Unity

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

0グッド

1クリップ

投稿2022/04/28 07:12

編集2022/04/28 07:32

実現したいこと

参考画像のような、いわゆる「ボリュームライト」や「光の軌跡」のようなものを作りたいです。
イメージ説明
イメージ説明

MayaやBlenderで円錐、円柱を作るらしいのですが、その範囲にだけ光を当てるのでしょうか?
下記URLのようなアセットを使わないと難しいのでしょうか?
https://tsubakit1.hateblo.jp/entry/2014/10/01/013054

宜しくお願い致します。

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

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

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

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

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

guest

回答2

0

ベストアンサー

パート2

レンダリング用のシェーダーを用意しました。これは以前別の方の「[Unity] 3DTextureを付与したオブジェクト内にカメラが侵入してもボリュームレンダリングの結果を描画したい」の時に使ったものを改修したもので、視線に沿ってライトの色を積算していく素朴なやり方になっています。明らかにスポットライトの範囲外の領域でも色を求めるような単純な作りですので無駄が多く、そのへんをうまく工夫すればもっと速度を向上できるかもしれません。
また、先ほど申し上げたように私はURPの知識が足りず、不適切な書き方をしている可能性もありますがご容赦ください。

ShaderLab

1Shader "Effect/VolumetricSpotlights" 2{ 3 Properties 4 { 5 } 6 SubShader 7 { 8 Pass 9 { 10 Name "VolumetricSpotlights" 11 12 Cull Front 13 ZWrite Off 14 ZTest Always 15 Blend One One, Zero One 16 17 HLSLPROGRAM 18 #define DIRECTIONAL_EPSILON 0.0009765625 19 #define SPOTLIGHT_CAPACITY 8 20 #define MAX_STEPS 128 21 #define DENSITY 8.0 22 23 #pragma vertex vert 24 #pragma fragment frag 25 #pragma multi_compile _ADDITIONAL_LIGHTS 26 #pragma multi_compile_fragment _ADDITIONAL_LIGHT_SHADOWS 27 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" 28 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" 29 30 struct Varyings 31 { 32 float4 positionHCS : SV_POSITION; 33 float3 positionWS : TEXCOORD0; 34 float4 positionNDCXYWAndDepth : TEXCOORD1; 35 }; 36 37 Varyings vert(float4 positionOS : POSITION) 38 { 39 Varyings OUT; 40 OUT.positionWS = TransformObjectToWorld(positionOS.xyz); 41 OUT.positionHCS = TransformWorldToHClip(OUT.positionWS); 42 float4 ndc = OUT.positionHCS * 0.5; 43 OUT.positionNDCXYWAndDepth.xy = float2(ndc.x, ndc.y * _ProjectionParams.x) + ndc.w; 44 OUT.positionNDCXYWAndDepth.z = -dot(GetWorldToViewMatrix()[2], float4(OUT.positionWS, 1.0)); 45 OUT.positionNDCXYWAndDepth.w = OUT.positionHCS.w; 46 return OUT; 47 } 48 49 TEXTURE2D(_CameraDepthTexture); 50 SAMPLER(sampler_CameraDepthTexture); 51 int _LightsCount; 52 53 float3 worldToObjectPos(float3 worldPos) {return mul(unity_WorldToObject, float4(worldPos, 1.0)).xyz;} 54 float3 worldToObjectVec(float3 worldVec) {return mul((float3x3)unity_WorldToObject, worldVec);} 55 float3 objectToWorldVec(float3 localVec) {return mul((float3x3)unity_ObjectToWorld, localVec);} 56 float3 getCameraPositionWS() {return UNITY_MATRIX_I_V._14_24_34;} 57 float3 getCameraDirectionWS() {return -UNITY_MATRIX_V._31_32_33;} 58 bool isPerspective() {return any(UNITY_MATRIX_P._41_42_43);} 59 60 float3 getCameraOriginWS(float3 positionWS) 61 { 62 return isPerspective() 63 ? getCameraPositionWS() 64 : positionWS + dot(getCameraPositionWS() - positionWS, getCameraDirectionWS()) * getCameraDirectionWS(); 65 } 66 67 float3 getWorldBoundsCenter() {return unity_ObjectToWorld._14_24_34 + unity_ObjectToWorld._11_22_33 * 0.5;} 68 69 float getBoundsDepthWS(float3 cameraDirectionWS) 70 { 71 float3 cornerVectorWS1 = objectToWorldVec(float3(0.5, 0.5, 0.5)); 72 float3 cornerVectorWS2 = objectToWorldVec(float3(-0.5, 0.5, 0.5)); 73 float3 cornerVectorWS3 = objectToWorldVec(float3(0.5, -0.5, 0.5)); 74 float3 cornerVectorWS4 = objectToWorldVec(float3(-0.5, -0.5, 0.5)); 75 float2 lengths1 = abs(float2(dot(cornerVectorWS1, cameraDirectionWS), dot(cornerVectorWS2, cameraDirectionWS))); 76 float2 lengths2 = abs(float2(dot(cornerVectorWS3, cameraDirectionWS), dot(cornerVectorWS4, cameraDirectionWS))); 77 float2 lengths = max(lengths1, lengths2); 78 return max(lengths.x, lengths.y) * 2.0; 79 } 80 81 float2 getBoundsNearFarWS(float boundsDepthWS) 82 { 83 float center = isPerspective() 84 ? distance(getWorldBoundsCenter(), getCameraPositionWS()) 85 : dot(getWorldBoundsCenter() - getCameraPositionWS(), getCameraDirectionWS()); 86 return float2(-0.5, 0.5) * boundsDepthWS + center; 87 } 88 89 float3 getBoundsFacesOS(float3 positionOS, float3 viewDirectionOS, float faceOffset) 90 { 91 float3 signs = sign(viewDirectionOS); 92 return -(signs * (positionOS - 0.5) + faceOffset) / (abs(viewDirectionOS) + (1.0 - abs(signs)) * DIRECTIONAL_EPSILON); 93 } 94 95 float getBoundsFrontFaceOS(float3 positionOS, float3 viewDirectionOS) 96 { 97 float3 lengths = getBoundsFacesOS(positionOS, viewDirectionOS, 0.5); 98 return max(max(max(lengths.x, lengths.y), lengths.z), 0.0); 99 } 100 101 float sampleOpaqueZ(float2 uv) 102 { 103 float rawDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, uv).r; 104 return isPerspective() 105 ? LinearEyeDepth(rawDepth, _ZBufferParams) 106 : -dot(unity_CameraInvProjection._33_34, float2(_ProjectionParams.x * (rawDepth * 2.0 - 1.0), 1.0)); 107 } 108 109 float2 inverseLerp(float2 a, float2 b, float2 value) {return (value - a) / (b - a);} 110 float random(float2 st) { return frac(sin(dot(st, float2(12.9898, 78.233))) * 43758.5453123);} 111 112 float4 frag(Varyings IN) : SV_Target 113 { 114 float2 positionSS = IN.positionNDCXYWAndDepth.xy / IN.positionNDCXYWAndDepth.w; 115 bool isPerspectiveProjection = isPerspective(); 116 float3 viewDirectionWS = isPerspectiveProjection ? -GetWorldSpaceNormalizeViewDir(IN.positionWS) : getCameraDirectionWS(); 117 float3 viewDirectionOS = worldToObjectVec(viewDirectionWS); 118 float peripheralFactor = 1.0 / dot(getCameraDirectionWS(), viewDirectionWS); 119 float3 cameraOriginWS = getCameraOriginWS(IN.positionWS); 120 float3 cameraOriginOS = worldToObjectPos(cameraOriginWS); 121 float frontFaceWS = dot(objectToWorldVec(getBoundsFrontFaceOS(cameraOriginOS, viewDirectionOS) * viewDirectionOS), viewDirectionWS); 122 float backFaceWS = dot(IN.positionWS - cameraOriginWS, viewDirectionWS); 123 float opaqueWS = sampleOpaqueZ(positionSS) * peripheralFactor; 124 float enterWS = max(frontFaceWS, 0.0); 125 float exitWS = min(backFaceWS, opaqueWS); 126 clip(exitWS - enterWS); 127 float boundsDepthWS = getBoundsDepthWS(getCameraDirectionWS()); 128 float stepLength = boundsDepthWS / MAX_STEPS; 129 float2 boundsNearFarWS = getBoundsNearFarWS(boundsDepthWS); 130 int2 stepEnterExit = (int2)clamp(ceil(inverseLerp(boundsNearFarWS.x, boundsNearFarWS.y, float2(enterWS, exitWS)) * MAX_STEPS), 0, MAX_STEPS); 131 float3 integratedColor = 0.0; 132 float jitter = stepLength * random(positionSS); 133 for (int i = stepEnterExit.x; i < stepEnterExit.y; i++) 134 { 135 float3 p = cameraOriginWS + viewDirectionWS * ((i + jitter) * stepLength); 136 for (uint lightIndex = 0u; lightIndex < _LightsCount; lightIndex++) 137 { 138 Light light = GetAdditionalPerObjectLight(lightIndex, p); 139 float shadowAttenuation = AdditionalLightRealtimeShadow(lightIndex, p, light.direction); 140 integratedColor += light.color * (light.distanceAttenuation * shadowAttenuation); 141 } 142 } 143 return float4(integratedColor * DENSITY / (boundsDepthWS * MAX_STEPS), 1.0); 144 } 145 ENDHLSL 146 } 147 } 148}

Renderer FeaturesにRenderVolumetricSpotlightsを追加、Volumetric Spotlights Shaderの項目に上記のシェーダーをセットし...

図1

シーン上に配置した3色のスポットライトにVolumetricSpotlightをアタッチしたところ下図のように描画されました。

図2

また、シェーダーコード中のDENSITY64.0に上げて軌跡を強めてみますと下図のようになりました。キューブによって遮蔽された部分は軌跡が薄れているのが見えやすいかと思います。

図3

投稿2022/05/03 14:49

Bongo

総合スコア10816

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

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

mokotan

2022/05/16 12:58 編集

ありがとうございます! やりたいことがありすぎて手が回らないですが、落ち着いたら参考にさせていただきます
guest

0

「MayaやBlenderで円錐、円柱を作るらしいのですが」とおっしゃるのは、おそらくたとえば「Unity Volumetric Light Shader Graph LWRP」(投稿者さんによるファイル類)のようなアプローチを指すんじゃないかと思います(URPでなくてLWRPと呼ばれていた頃の作例のようですが、現行のシェーダーグラフでも大丈夫そうに思います)。
遮蔽物で影を作ったりするようなことは難しいかもしれませんが、描画コストもさほど大きくないでしょうから役に立ちそうな気がします。

影も欲しい場合はそういった機能もあるアセットを探すか、自前でなんとかするかでしょうね。私はまだURPになじみがなくて、練習にちょうどいいかもしれないと思って挑戦してみました...が、重いわりにはさほど品質もよくない出来になってしまって、やはりアセットストアだとかで探した方がいいかもしれません。
また、私の場合は練習目的でちょっと試しただけですから、いろいろな条件でも不具合なく描画できるかどうかはまるで自信がないです。対してストアで公開されているようなレベルであれば(有料であればなおさら)それに見合った品質、速度、対応可能な条件を期待してもいいんじゃないでしょうか。

あくまでもご参考までにということでお願いしたいです。まず各スポットライトには下記スクリプトをアタッチすることにしました。スポットライト群を包み込む直方体領域を求める役割になります。

C#

1using System.Collections.Generic; 2using System.Linq; 3using System.Runtime.InteropServices; 4using Unity.Burst; 5using Unity.Collections; 6using Unity.Jobs; 7using Unity.Mathematics; 8using UnityEngine; 9using UnityEngine.Rendering; 10#if UNITY_EDITOR 11using UnityEditor; 12#endif 13 14[RequireComponent(typeof(Light))] 15[ExecuteAlways] 16public class VolumetricSpotlight : MonoBehaviour 17{ 18 public const int Capacity = 8; 19 20 private static readonly HashSet<Light> Spotlights = new HashSet<Light>(); 21 private static NativeArray<SpotlightInfo> spotlightInfos; 22 private static NativeArray<float3> boundsMinMax; 23 24 public static Mesh BoxMesh { get; private set; } 25 26 private new Light light; 27 28 private void OnEnable() 29 { 30 AllocateResources(); 31 this.light = this.GetComponent<Light>(); 32 this.light!.type = LightType.Spot; 33 Spotlights!.Add(this.light); 34 } 35 36 private void OnDisable() 37 { 38 Spotlights!.Remove(this.light); 39 if (Spotlights.Count == 0) 40 { 41 DisposeResources(); 42 } 43 } 44 45 public static int GatherActiveSpotlights( 46 out NativeArray<SpotlightInfo> spotlights, 47 out float3 boundsMin, 48 out float3 boundsMax) 49 { 50 var count = 0; 51 foreach (var light in Spotlights!.Where(l => (l != null) && l.isActiveAndEnabled && (l.type == LightType.Spot)).Take(Capacity)) 52 { 53 var t = light!.transform; 54 spotlightInfos[count] = new SpotlightInfo 55 { 56 Position = (Vector4)t.position, 57 Y = (Vector4)(t.up * (light.range * math.tan(math.radians(light.spotAngle * 0.5f)))), 58 Z = (Vector4)(t.forward * light.range), 59 Intensity = math.float4((Vector3)(Vector4)light.color, 1.0f) * light.intensity 60 }; 61 count++; 62 } 63 64 if (count > 0) 65 { 66 new CalculateBoundsJob 67 { 68 Count = count, 69 Infos = spotlightInfos, 70 MinMax = boundsMinMax 71 }.Schedule().Complete(); 72 boundsMin = boundsMinMax[0]; 73 boundsMax = boundsMinMax[1]; 74 } 75 else 76 { 77 boundsMin = float3.zero; 78 boundsMax = float3.zero; 79 } 80 81 spotlights = spotlightInfos; 82 return count; 83 } 84 85 private static void AllocateResources() 86 { 87 if (!spotlightInfos.IsCreated) 88 { 89 spotlightInfos = new NativeArray<SpotlightInfo>(Capacity, Allocator.Persistent); 90 } 91 92 if (!boundsMinMax.IsCreated) 93 { 94 boundsMinMax = new NativeArray<float3>(2, Allocator.Persistent); 95 } 96 97 if (BoxMesh == null) 98 { 99 BoxMesh = CoreUtils.CreateCubeMesh(Vector3.zero, Vector3.one); 100 BoxMesh!.hideFlags = HideFlags.HideAndDontSave; 101 } 102 } 103 104 private static void DisposeNativeArray<T>(ref NativeArray<T> nativeArray) where T : struct 105 { 106 if (nativeArray.IsCreated) 107 { 108 nativeArray.Dispose(); 109 } 110 } 111 112 private static void DisposeResources() 113 { 114 DisposeNativeArray(ref spotlightInfos); 115 DisposeNativeArray(ref boundsMinMax); 116 CoreUtils.Destroy(BoxMesh); 117 } 118 119 [BurstCompile] 120 private struct CalculateBoundsJob : IJob 121 { 122 public int Count; 123 [ReadOnly] public NativeArray<SpotlightInfo> Infos; 124 [WriteOnly] public NativeArray<float3> MinMax; 125 126 public void Execute() 127 { 128 var boundsMin = math.float3(float.PositiveInfinity); 129 var boundsMax = math.float3(float.NegativeInfinity); 130 for (var i = 0; i < this.Count; i++) 131 { 132 var info = this.Infos[i]; 133 var y = info.Y.xyz; 134 var z = info.Z.xyz; 135 var m = math.float3x3(math.cross(y, z) / math.length(z), y, z); 136 for (var sx = -1; sx <= 1; sx += 2) 137 { 138 for (var sy = -1; sy <= 1; sy += 2) 139 { 140 for (var sz = 0; sz <= 1; sz++) 141 { 142 var p = info.Position.xyz + math.mul(m, math.float3(sx, sy, sz)); 143 boundsMin = math.min(boundsMin, p); 144 boundsMax = math.max(boundsMax, p); 145 } 146 } 147 } 148 } 149 150 this.MinMax[0] = boundsMin; 151 this.MinMax[1] = boundsMax; 152 } 153 } 154 155 [StructLayout(LayoutKind.Sequential)] 156 public struct SpotlightInfo 157 { 158 public float4 Position; 159 public float4 Y; 160 public float4 Z; 161 public float4 Intensity; 162 } 163}

そして直方体領域を描画するためのRenderer Featureと...

C#

1using UnityEngine; 2using UnityEngine.Rendering; 3using UnityEngine.Rendering.Universal; 4 5public class RenderVolumetricSpotlights : ScriptableRendererFeature 6{ 7 [SerializeField] private Shader volumetricSpotlightsShader; 8 private Material material; 9 private Pass pass; 10 private static readonly int LightsCountProperty = Shader.PropertyToID("_LightsCount"); 11 12 private bool GetMaterial() 13 { 14 if (this.material != null) 15 { 16 return true; 17 } 18 19 if (this.volumetricSpotlightsShader == null) 20 { 21 return false; 22 } 23 24 this.material = CoreUtils.CreateEngineMaterial(this.volumetricSpotlightsShader); 25 return this.material != null; 26 } 27 28 public override void Create() 29 { 30 this.pass = new Pass(); 31 this.GetMaterial(); 32 } 33 34 protected override void Dispose(bool disposing) 35 { 36 CoreUtils.Destroy(this.material); 37 } 38 39 public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) 40 { 41 if (!this.GetMaterial()) 42 { 43 return; 44 } 45 46 this.pass!.Material = this.material; 47 renderer!.EnqueuePass(this.pass); 48 } 49 50 private class Pass : ScriptableRenderPass 51 { 52 public Material Material; 53 54 public Pass() 55 { 56 this.renderPassEvent = RenderPassEvent.AfterRenderingSkybox; 57 } 58 59 public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) 60 { 61 var count = VolumetricSpotlight.GatherActiveSpotlights( 62 out _, 63 out var boundsMin, 64 out var boundsMax); 65 if (count <= 0) 66 { 67 return; 68 } 69 this.Material!.SetInt(LightsCountProperty, renderingData.lightData.additionalLightsCount); 70 var cmd = CommandBufferPool.Get(); 71 cmd!.Clear(); 72 cmd.DrawMesh( 73 VolumetricSpotlight.BoxMesh, 74 Matrix4x4.TRS(boundsMin, Quaternion.identity, boundsMax - boundsMin), 75 this.Material); 76 context.ExecuteCommandBuffer(cmd); 77 CommandBufferPool.Release(cmd); 78 } 79 } 80}

(すみませんが字数制限に到達してしまうためパート2に移ります...)

投稿2022/05/03 14:47

Bongo

総合スコア10816

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.31%

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

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

質問する

関連した質問