// These tests relies on RayTracingResources.Load() so cannot run in a player. #if UNITY_EDITOR using System.Runtime.InteropServices; using NUnit.Framework; using Unity.Mathematics; using UnityEditor; using UnityEngine.PathTracing.Core; using UnityEngine.Rendering; using UnityEngine.Rendering.Sampling; using UnityEngine.Rendering.UnifiedRayTracing; namespace UnityEngine.PathTracing.Tests { internal class PathIteratorTests { private static class MeshUtil { internal static Mesh CreateQuadMesh() { Mesh mesh = new Mesh(); Vector3[] vertices = { new(-0.5f, -0.5f, 0.0f), new(0.5f, -0.5f, 0.0f), new(-0.5f, 0.5f, 0.0f), new(0.5f, 0.5f, 0.0f) }; mesh.vertices = vertices; Vector3[] normals = { Vector3.forward, Vector3.forward, Vector3.forward, Vector3.forward }; mesh.normals = normals; Vector2[] uv = { new(0, 0), new(1, 0), new(0, 1), new(1, 1) }; mesh.uv = uv; int[] tris = { 0, 2, 1, 2, 3, 1 }; mesh.triangles = tris; return mesh; } } [StructLayout(LayoutKind.Sequential)] public struct TestRay { public float3 Origin; public float3 Direction; } private World _world; private RayTracingContext _rayTracingContext; private CommandBuffer _cmd; private SamplingResources _samplingResources; private RTHandle _emptyExposureTexture; private const RayTracingBackend _backend = RayTracingBackend.Compute; [SetUp] public void Setup() { if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore || SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3) { Assert.Ignore("PathTracingCore is incompatible with OpenGL as it places a harsh limitation of buffer count."); return; } var resources = new RayTracingResources(); resources.Load(); _rayTracingContext = new RayTracingContext(_backend, resources); _cmd = new CommandBuffer(); _cmd.name = "PathTracingCore/PathIteratorTests"; var worldResources = new WorldResourceSet(); worldResources.LoadFromAssetDatabase(); _world = new World(); _world.Init(_rayTracingContext, worldResources); _samplingResources = new SamplingResources(); _samplingResources.Load((uint)SamplingResources.ResourceType.All); _emptyExposureTexture = RTHandles.Alloc(1, 1, enableRandomWrite: true, name: "Empty EV100 Exposure"); } [TearDown] public void TearDown() { _world?.Dispose(); _rayTracingContext?.Dispose(); _cmd?.Dispose(); _emptyExposureTexture?.Release(); _samplingResources?.Dispose(); } [Test] [TestCase(1, 1, 1, 1u)] [TestCase(10, 20, 30, 1u)] [TestCase(10, 20, 30, 8u)] [TestCase(0, 42, 0, 1u)] [TestCase(0, 42, 0, 8u)] public void EmptyWorldWithEnvironmentLight_ShouldOutputEnvironmentLight(float envRed, float envGreen, float envBlue, uint sampleCount) { using var deviceInputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, Marshal.SizeOf()); { var inputHost = new Ray[1]; inputHost[0].origin = float3.zero; inputHost[0].direction = new float3(0, 1, 0); deviceInputBuffer.SetData(inputHost); } using var deviceOutputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(float) * 3); var cubemapMat = new Material(Shader.Find("PathIteratorTesting/UniformCubemap")); cubemapMat.SetVector("_Radiance", new Vector4(envRed, envGreen, envBlue, 1.0f)); _world.SetEnvironmentMaterial(cubemapMat); GraphicsBuffer buildScratchBuffer = null; _world.Build(new Bounds(), _cmd, ref buildScratchBuffer, _samplingResources, true); var shader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader"); Util.BindWorld(_cmd, shader, _world, 8); Util.BindPathTracingInputs(_cmd, shader, false, 1, false, 4, 1.0f, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyExposureTexture); shader.SetIntParam(_cmd, Shader.PropertyToID("g_SampleCount"), (int)sampleCount); shader.SetBufferParam(_cmd, Shader.PropertyToID("_Output"), deviceOutputBuffer); shader.SetBufferParam(_cmd, Shader.PropertyToID("_InputRay"), deviceInputBuffer); GraphicsBuffer traceScratchBuffer = null; RayTracingHelper.ResizeScratchBufferForTrace(shader, 1, 1, 1, ref traceScratchBuffer); shader.Dispatch(_cmd, traceScratchBuffer, 1, 1, 1); Graphics.ExecuteCommandBuffer(_cmd); var hostOutputBuffer = new float3[1]; deviceOutputBuffer.GetData(hostOutputBuffer); const float tolerance = 0.001f; Assert.AreEqual(envRed, hostOutputBuffer[0].x, tolerance); Assert.AreEqual(envGreen, hostOutputBuffer[0].y, tolerance); Assert.AreEqual(envBlue, hostOutputBuffer[0].z, tolerance); CoreUtils.Destroy(cubemapMat); buildScratchBuffer.Dispose(); traceScratchBuffer.Dispose(); } [Test] [TestCase(1, 0, 0, 1u)] [TestCase(0, 1, 0, 1u)] [TestCase(0, 0, 1, 1u)] [TestCase(0, 0, 1, 8u)] public void RayHittingPlaneLitByWhiteEnvironmentLight_ShouldMatchAnalyticDerivation(float albedoRed, float albedoGreen, float albedoBlue, uint sampleCount) { Assert.Ignore("Fails on MacEditor ARM64, Windows x64 and other platforms, probably due to it looking for output in the wrong place. See https://jira.unity3d.com/browse/GFXLIGHT-1796"); using var deviceInputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, Marshal.SizeOf()); { var hostInputBuffer = new Ray[1]; hostInputBuffer[0].origin = new float3(0.1f, 0.1f, -1); // We avoid numerical precision issues at the diagonal. hostInputBuffer[0].direction = new float3(0, 0, 1); deviceInputBuffer.SetData(hostInputBuffer); } using var deviceOutputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(float) * 3); var cubemapMaterial = new Material(Shader.Find("PathIteratorTesting/UniformCubemap")); cubemapMaterial.SetVector("_Radiance", new Vector4(1.0f, 1.0f, 1.0f, 1.0f)); GraphicsBuffer buildScratchBuffer = null; var mesh = MeshUtil.CreateQuadMesh(); var material = new Material(Shader.Find("PathIteratorTesting/UniformAlbedoMetaPass")); material.SetColor("_Albedo", new Color(albedoRed, albedoGreen, albedoBlue, 1)); var matDesc = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material); _world.SetEnvironmentMaterial(cubemapMaterial); var matHandle = _world.AddMaterial(matDesc, UVChannel.UV0); var mask = World.GetInstanceMask(ShadowCastingMode.On, true, RenderedGameObjectsFilter.OnlyStatic); _world.AddInstance( mesh, new [] { matHandle }, new[] { mask }, 1, Matrix4x4.identity, new Bounds(Vector3.zero, Vector3.one * 20.0f), true, RenderedGameObjectsFilter.OnlyStatic, true); _world.Build(new Bounds(), _cmd, ref buildScratchBuffer, _samplingResources, true); var shader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader"); Util.BindWorld(_cmd, shader, _world, 8); Util.BindPathTracingInputs(_cmd, shader, false, 1, false, 4, 1.0f, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyExposureTexture); shader.SetIntParam(_cmd, Shader.PropertyToID("g_SampleCount"), (int)sampleCount); shader.SetBufferParam(_cmd, Shader.PropertyToID("_Output"), deviceOutputBuffer); shader.SetBufferParam(_cmd, Shader.PropertyToID("_InputRay"), deviceInputBuffer); GraphicsBuffer traceScratchBuffer = null; RayTracingHelper.ResizeScratchBufferForTrace(shader, 1, 1, 1, ref traceScratchBuffer); shader.Dispatch(_cmd, traceScratchBuffer, 1, 1, 1); Graphics.ExecuteCommandBuffer(_cmd); var outputHost = new float3[1]; deviceOutputBuffer.GetData(outputHost); // We have a non-emissive lambertian plane with albedo c which receives light from a uniform environment light with radiance 1. How much light is reflected in the direction of the normal of the plane? // The incoming light L_i is 1 and the BRDF is c/π. // L_o = ∫ c/π L_i(ω) cos(θ) dω = c/π ∫ cos(θ) dω = c. // The last equality holds because the hemispherical integral of cos(θ) is π. Assert.AreEqual(albedoRed, outputHost[0].x); Assert.AreEqual(albedoGreen, outputHost[0].y); Assert.AreEqual(albedoBlue, outputHost[0].z); CoreUtils.Destroy(cubemapMaterial); CoreUtils.Destroy(material); buildScratchBuffer.Dispose(); traceScratchBuffer.Dispose(); } } } #endif