using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace SicknessReduction.Visual.DoF { //TODO: look at https://catlikecoding.com/unity/tutorials/advanced-rendering/depth-of-field/ or pseudocode in paper public class DynamicDoF : MonoBehaviour { private const int NUMBER_OF_RAYS = 9; // Carneige, Rhee (2015) private const float RAY_OFFSET = 6f; //Exact value not mentioned in paper [Tooltip("Max Radius of Circle of Confusion in percent of display width")] public float maxFactorRadiusCOC = 0.0175f; //Carneige, Rhee (2015) public float refocusTimePerMeter = 0.0000017f; //seconds; Carneige, Rhee (2015) public float maxFocusDistance = 30000f; //metres; Carneige, Rhee (2015 public Camera playerCamera; public VolumeProfile postProcessProfile; private Transform cameraTransform; private DepthOfField doF; private bool doFAvailable; private List rayDistances = new List(NUMBER_OF_RAYS); private void Start() { cameraTransform = playerCamera.transform; doF = (DepthOfField) postProcessProfile.components.FirstOrDefault(c => c is DepthOfField); doFAvailable = doF != null; if (doFAvailable) { // ReSharper disable once PossibleNullReferenceException doF.mode.value = DepthOfFieldMode.Bokeh; doF.focalLength.min = 0f; doF.focalLength.max = float.MaxValue; doF.aperture.max = float.MaxValue; doF.aperture.min = 0f; } else { Debug.LogWarning("No DepthOfField found in PostProcessing Profile!"); } } private void Update() { if (!doFAvailable) return; var focusDistance = CastRays(); if (focusDistance < 0) { doF.active = false; return; } /*For real-time performance, we simply assume all users will take a static 500 ms to refocus from an infinite distance to a close distance (≈ 1 m), using the value calculated in earlier work13 (assuming a typical adult’s eyes). This translates to a linear interpolation between focal distances that takes ≈ 1.7*/ doF.active = true; var timeNeededToRefocus = Mathf.Abs(focusDistance - doF.focusDistance.value) * refocusTimePerMeter; focusDistance = Mathf.Lerp(doF.focusDistance.value, focusDistance, Time.deltaTime / timeNeededToRefocus); doF.focusDistance.value = focusDistance; doF.focalLength.value = 1; doF.aperture.value = ApertureForCocAndFocusDistance(maxFactorRadiusCOC, focusDistance); } private float ApertureForCocAndFocusDistance(float coc, float focusDistance) => 1 / (1000 * coc * (focusDistance - 0.001f)); private float CastRays() { var position = cameraTransform.position; var forward = cameraTransform.forward; var start = position + forward * playerCamera.nearClipPlane; var ends = new Vector3[NUMBER_OF_RAYS]; ends[0] = position + forward * playerCamera.farClipPlane; ends[1] = ends[0] + cameraTransform.TransformDirection(new Vector3(RAY_OFFSET, 0, 0)); ends[2] = ends[0] + cameraTransform.TransformDirection(new Vector3(-RAY_OFFSET, 0, 0)); ends[3] = ends[0] + cameraTransform.TransformDirection(new Vector3(0, RAY_OFFSET, 0)); ends[4] = ends[0] + cameraTransform.TransformDirection(new Vector3(0, -RAY_OFFSET, 0)); ends[5] = ends[0] + cameraTransform.TransformDirection(new Vector3(RAY_OFFSET, RAY_OFFSET, 0)); ends[6] = ends[0] + cameraTransform.TransformDirection(new Vector3(-RAY_OFFSET, RAY_OFFSET, 0)); ends[7] = ends[0] + cameraTransform.TransformDirection(new Vector3(RAY_OFFSET, -RAY_OFFSET, 0)); ends[8] = ends[0] + cameraTransform.TransformDirection(new Vector3(-RAY_OFFSET, -RAY_OFFSET, 0)); rayDistances.Clear(); foreach (var end in ends) { if (Physics.Linecast(start, end, out var hit, Physics.DefaultRaycastLayers)) { Debug.DrawLine(start, (end - start).normalized * hit.distance, Color.green); //Debug.Log("DoF - Hit, Distance = " + hit.distance); rayDistances.Add(hit.distance); } else { Debug.DrawRay(position, position + forward * playerCamera.farClipPlane, Color.red); } } if (rayDistances.Count < 1) return -1; return Helpers.RemoveOutliers(rayDistances).Average(); } } }