DensityVolumeList PrepareVisibleDensityVolumeList(HDCamera hdCamera, CommandBuffer cmd, float time) { DensityVolumeList densityVolumes = new DensityVolumeList(); if (!Fog.IsVolumetricFogEnabled(hdCamera)) { return(densityVolumes); } using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.PrepareVisibleDensityVolumeList))) { Vector3 camPosition = hdCamera.camera.transform.position; Vector3 camOffset = Vector3.zero;// World-origin-relative if (ShaderConfig.s_CameraRelativeRendering != 0) { camOffset = camPosition; // Camera-relative } m_VisibleVolumeBounds.Clear(); m_VisibleVolumeData.Clear(); // Collect all visible finite volume data, and upload it to the GPU. var volumes = DensityVolumeManager.manager.PrepareDensityVolumeData(cmd, hdCamera, time); for (int i = 0; i < Math.Min(volumes.Count, k_MaxVisibleVolumeCount); i++) { DensityVolume volume = volumes[i]; // TODO: cache these? var obb = new OrientedBBox(Matrix4x4.TRS(volume.transform.position, volume.transform.rotation, volume.parameters.size)); // Handle camera-relative rendering. obb.center -= camOffset; // Frustum cull on the CPU for now. TODO: do it on the GPU. // TODO: account for custom near and far planes of the V-Buffer's frustum. // It's typically much shorter (along the Z axis) than the camera's frustum. if (GeometryUtils.Overlap(obb, hdCamera.frustum, 6, 8)) { // TODO: cache these? var data = volume.parameters.ConvertToEngineData(); m_VisibleVolumeBounds.Add(obb); m_VisibleVolumeData.Add(data); } } m_VisibleVolumeBoundsBuffer.SetData(m_VisibleVolumeBounds); m_VisibleVolumeDataBuffer.SetData(m_VisibleVolumeData); // Fill the struct with pointers in order to share the data with the light loop. densityVolumes.bounds = m_VisibleVolumeBounds; densityVolumes.density = m_VisibleVolumeData; return(densityVolumes); } }
// Returns 'true' if the OBB intersects (or is inside) the frustum, 'false' otherwise. public static bool Overlap(OrientedBBox obb, Frustum frustum, int numPlanes, int numCorners) { bool overlap = true; // Test the OBB against frustum planes. Frustum planes are inward-facing. // The OBB is outside if it's entirely behind one of the frustum planes. // See "Real-Time Rendering", 3rd Edition, 16.10.2. for (int i = 0; overlap && i < numPlanes; i++) { Vector3 n = frustum.planes[i].normal; float d = frustum.planes[i].distance; // Max projection of the half-diagonal onto the normal (always positive). float maxHalfDiagProj = obb.extentX * Mathf.Abs(Vector3.Dot(n, obb.right)) + obb.extentY * Mathf.Abs(Vector3.Dot(n, obb.up)) + obb.extentZ * Mathf.Abs(Vector3.Dot(n, obb.forward)); // Positive distance -> center in front of the plane. // Negative distance -> center behind the plane (outside). float centerToPlaneDist = Vector3.Dot(n, obb.center) + d; // outside = maxHalfDiagProj < -centerToPlaneDist // outside = maxHalfDiagProj + centerToPlaneDist < 0 // overlap = overlap && !outside overlap = overlap && (maxHalfDiagProj + centerToPlaneDist >= 0); } if (numCorners == 0) { return(overlap); } // Test the frustum corners against OBB planes. The OBB planes are outward-facing. // The frustum is outside if all of its corners are entirely in front of one of the OBB planes. // See "Correct Frustum Culling" by Inigo Quilez. // We can exploit the symmetry of the box by only testing against 3 planes rather than 6. Plane[] planes = new Plane[3]; planes[0].normal = obb.right; planes[0].distance = obb.extentX; planes[1].normal = obb.up; planes[1].distance = obb.extentY; planes[2].normal = obb.forward; planes[2].distance = obb.extentZ; for (int i = 0; overlap && i < 3; i++) { Plane plane = planes[i]; // We need a separate counter for the "box fully inside frustum" case. bool outsidePos = true; // Positive normal bool outsideNeg = true; // Reversed normal // Merge 2 loops. Continue as long as all points are outside either plane. for (int j = 0; j < numCorners; j++) { float proj = Vector3.Dot(plane.normal, frustum.corners[j] - obb.center); outsidePos = outsidePos && (proj > plane.distance); outsideNeg = outsideNeg && (-proj > plane.distance); } overlap = overlap && !(outsidePos || outsideNeg); } return(overlap); }
private void SetupProbePositions() { if (!this.gameObject.activeInHierarchy) { return; } float debugProbeSize = Gizmos.probeSize; int probeCount = parameters.resolutionX * parameters.resolutionY * parameters.resolutionZ; Vector3[] positions = new Vector3[probeCount]; OrientedBBox obb = new OrientedBBox(Matrix4x4.TRS(this.transform.position, this.transform.rotation, parameters.size)); Vector3 probeSteps = new Vector3(parameters.size.x / (float)parameters.resolutionX, parameters.size.y / (float)parameters.resolutionY, parameters.size.z / (float)parameters.resolutionZ); // TODO: Determine why we need to negate obb.forward but not other basis vectors in order to make positions start at the {left, lower, back} corner // and end at the {right, top, front} corner (which our atlasing code assumes). Vector3 probeStartPosition = obb.center - obb.right * (parameters.size.x - probeSteps.x) * 0.5f - obb.up * (parameters.size.y - probeSteps.y) * 0.5f + obb.forward * (parameters.size.z - probeSteps.z) * 0.5f; Quaternion rotation = Quaternion.identity; Vector3 scale = new Vector3(debugProbeSize, debugProbeSize, debugProbeSize); // Debugging objects start here int maxBatchSize = 1023; int probesInCurrentBatch = System.Math.Min(maxBatchSize, probeCount); int indexInCurrentBatch = 0; // Everything around cached matrices for the probe spheres m_DebugProbeMatricesList = new List <Matrix4x4[]>(); Matrix4x4[] currentprobeMatrices = new Matrix4x4[probesInCurrentBatch]; int[] indices = new int[probesInCurrentBatch]; // Everything around point meshes for non-selected ProbeVolumes m_DebugProbePointMeshList = new List <Mesh>(); int[] currentProbeDebugIndices = new int[probesInCurrentBatch]; Vector3[] currentProbeDebugPositions = new Vector3[probesInCurrentBatch]; int processedProbes = 0; for (int z = 0; z < parameters.resolutionZ; ++z) { for (int y = 0; y < parameters.resolutionY; ++y) { for (int x = 0; x < parameters.resolutionX; ++x) { Vector3 position = probeStartPosition + (probeSteps.x * x * obb.right) + (probeSteps.y * y * obb.up) + (probeSteps.z * z * -obb.forward); positions[processedProbes] = position; currentProbeDebugIndices[indexInCurrentBatch] = indexInCurrentBatch; currentProbeDebugPositions[indexInCurrentBatch] = position; Matrix4x4 matrix = new Matrix4x4(); matrix.SetTRS(position, rotation, scale); currentprobeMatrices[indexInCurrentBatch] = matrix; indexInCurrentBatch++; processedProbes++; int probesLeft = probeCount - processedProbes; if (indexInCurrentBatch >= 1023 || probesLeft == 0) { Mesh currentProbeDebugMesh = new Mesh(); currentProbeDebugMesh.SetVertices(currentProbeDebugPositions); currentProbeDebugMesh.SetIndices(currentProbeDebugIndices, MeshTopology.Points, 0); m_DebugProbePointMeshList.Add(currentProbeDebugMesh); m_DebugProbeMatricesList.Add(currentprobeMatrices); // More sets follow, reallocate if (probesLeft > 0) { probesInCurrentBatch = System.Math.Min(maxBatchSize, probesLeft); currentProbeDebugPositions = new Vector3[probesInCurrentBatch]; currentProbeDebugIndices = new int[probesInCurrentBatch]; currentprobeMatrices = new Matrix4x4[probesInCurrentBatch]; indexInCurrentBatch = 0; } } } } } UnityEditor.Experimental.Lightmapping.SetAdditionalBakedProbes(GetID(), positions); }
ProbeVolumeList PrepareVisibleProbeVolumeList(ScriptableRenderContext renderContext, HDCamera hdCamera, CommandBuffer cmd) { ProbeVolumeList probeVolumes = new ProbeVolumeList(); if (ShaderConfig.s_ProbeVolumesEvaluationMode == ProbeVolumesEvaluationModes.Disabled) { return(probeVolumes); } if (!hdCamera.frameSettings.IsEnabled(FrameSettingsField.ProbeVolume)) { return(probeVolumes); } var settings = hdCamera.volumeStack.GetComponent <ProbeVolumeController>(); bool octahedralDepthOcclusionFilterIsEnabled = settings.leakMitigationMode.value == LeakMitigationMode.OctahedralDepthOcclusionFilter; using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.PrepareProbeVolumeList))) { ClearProbeVolumeAtlasIfRequested(cmd); Vector3 camPosition = hdCamera.camera.transform.position; Vector3 camOffset = Vector3.zero;// World-origin-relative if (ShaderConfig.s_CameraRelativeRendering != 0) { camOffset = camPosition; // Camera-relative } m_VisibleProbeVolumeBounds.Clear(); m_VisibleProbeVolumeData.Clear(); // Collect all visible finite volume data, and upload it to the GPU. List <ProbeVolume> volumes = ProbeVolumeManager.manager.volumes; int probeVolumesCount = Math.Min(volumes.Count, k_MaxVisibleProbeVolumeCount); int sortCount = 0; // Sort probe volumes smallest from smallest to largest volume. // Same as is done with reflection probes. // See LightLoop.cs::PrepareLightsForGPU() for original example of this. for (int probeVolumesIndex = 0; (probeVolumesIndex < volumes.Count) && (sortCount < probeVolumesCount); probeVolumesIndex++) { ProbeVolume volume = volumes[probeVolumesIndex]; #if UNITY_EDITOR if (!volume.IsAssetCompatible()) { continue; } #endif if (ShaderConfig.s_ProbeVolumesAdditiveBlending == 0 && volume.parameters.volumeBlendMode != VolumeBlendMode.Normal) { // Non-normal blend mode volumes are not supported. Skip. continue; } float probeVolumeDepthFromCameraWS = Vector3.Dot(hdCamera.camera.transform.forward, volume.transform.position - camPosition); if (probeVolumeDepthFromCameraWS >= volume.parameters.distanceFadeEnd) { // Probe volume is completely faded out from distance fade optimization. // Do not bother adding it to the list, it would evaluate to zero weight. continue; } // TODO: cache these? var obb = new OrientedBBox(Matrix4x4.TRS(volume.transform.position, volume.transform.rotation, volume.parameters.size)); // Handle camera-relative rendering. obb.center -= camOffset; // Frustum cull on the CPU for now. TODO: do it on the GPU. if (GeometryUtils.Overlap(obb, hdCamera.frustum, hdCamera.frustum.planes.Length, hdCamera.frustum.corners.Length)) { var logVolume = CalculateProbeVolumeLogVolume(volume.parameters.size); m_ProbeVolumeSortKeys[sortCount++] = PackProbeVolumeSortKey(volume.parameters.volumeBlendMode, logVolume, probeVolumesIndex); } } CoreUnsafeUtils.QuickSort(m_ProbeVolumeSortKeys, 0, sortCount - 1); // Call our own quicksort instead of Array.Sort(sortKeys, 0, sortCount) so we don't allocate memory (note the SortCount-1 that is different from original call). for (int sortIndex = 0; sortIndex < sortCount; ++sortIndex) { // In 1. we have already classify and sorted the probe volume, we need to use this sorted order here uint sortKey = m_ProbeVolumeSortKeys[sortIndex]; int probeVolumesIndex; UnpackProbeVolumeSortKey(sortKey, out probeVolumesIndex); ProbeVolume volume = volumes[probeVolumesIndex]; // TODO: cache these? var obb = new OrientedBBox(Matrix4x4.TRS(volume.transform.position, volume.transform.rotation, volume.parameters.size)); // Handle camera-relative rendering. obb.center -= camOffset; // TODO: cache these? var data = volume.parameters.ConvertToEngineData(); // Note: The system is not aware of slice packing in Z. // Need to modify scale and bias terms just before uploading to GPU. // TODO: Should we make it aware earlier up the chain? data.scale.z = data.scale.z / (float)m_ProbeVolumeAtlasSHRTDepthSliceCount; data.bias.z = data.bias.z / (float)m_ProbeVolumeAtlasSHRTDepthSliceCount; m_VisibleProbeVolumeBounds.Add(obb); m_VisibleProbeVolumeData.Add(data); } s_VisibleProbeVolumeBoundsBuffer.SetData(m_VisibleProbeVolumeBounds); s_VisibleProbeVolumeDataBuffer.SetData(m_VisibleProbeVolumeData); // Fill the struct with pointers in order to share the data with the light loop. probeVolumes.bounds = m_VisibleProbeVolumeBounds; probeVolumes.data = m_VisibleProbeVolumeData; // For now, only upload one volume per frame. // This is done: // 1) To timeslice upload cost across N frames for N volumes. // 2) To avoid creating a sync point between compute buffer upload and each volume upload. const int volumeUploadedToAtlasSHCapacity = 1; int volumeUploadedToAtlasOctahedralDepthCapacity = octahedralDepthOcclusionFilterIsEnabled ? 1 : 0; int volumeUploadedToAtlasSHCount = 0; int volumeUploadedToAtlasOctahedralDepthCount = 0; for (int sortIndex = 0; sortIndex < sortCount; ++sortIndex) { uint sortKey = m_ProbeVolumeSortKeys[sortIndex]; int probeVolumesIndex; UnpackProbeVolumeSortKey(sortKey, out probeVolumesIndex); ProbeVolume volume = volumes[probeVolumesIndex]; if (volumeUploadedToAtlasSHCount < volumeUploadedToAtlasSHCapacity) { bool volumeWasUploaded = EnsureProbeVolumeInAtlas(renderContext, cmd, volume); if (volumeWasUploaded) { ++volumeUploadedToAtlasSHCount; } } if (volumeUploadedToAtlasOctahedralDepthCount < volumeUploadedToAtlasOctahedralDepthCapacity) { bool volumeWasUploaded = EnsureProbeVolumeInAtlasOctahedralDepth(renderContext, cmd, volume); if (volumeWasUploaded) { ++volumeUploadedToAtlasOctahedralDepthCount; } } if (volumeUploadedToAtlasSHCount == volumeUploadedToAtlasSHCapacity && volumeUploadedToAtlasOctahedralDepthCount == volumeUploadedToAtlasOctahedralDepthCapacity) { // Met our capacity this frame. Early out. break; } } return(probeVolumes); } }