using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace SicknessReduction.Visual.DoF { [Serializable] public struct CocRadius { public float factor; public float min; public float max; } //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 public CocRadius cocRadius; //Carneige, Rhee (2015) public float refocusTimePerMeter = 0.0000017f; //seconds; Carneige, Rhee (2015) public float maxFocusDistance = 30000f; //metres; Carneige, Rhee (2015 [Tooltip("Offset in degrees of the ray from the center of the image (+ upwards/- downwards")] public float offsetFromCenter = 10f; public Camera playerCamera; public VolumeProfile postProcessProfile; private Transform cameraTransform; private DepthOfField doF; private bool doFAvailable; private readonly Vector3[] ends = new Vector3[NUMBER_OF_RAYS]; private float maxCocRadius; private readonly List rayDistances = new List(NUMBER_OF_RAYS); //TODO: debug, remove //public GameObject gizmoPrefab; //private GameObject[] hits = new GameObject[NUMBER_OF_RAYS]; private void Start() { cameraTransform = playerCamera.transform; //Debug.Log($"Screen Width = {playerCamera.pixelWidth}, scaled = {playerCamera.scaledPixelWidth}"); //maxCocRadius = maxFactorRadiusCOC * playerCamera.scaledPixelWidth; FIXME: waaaay to small /*for (int i = 0; i < NUMBER_OF_RAYS; i++) { hits[i] = Instantiate(gizmoPrefab); hits[i].SetActive(false); } */ //TODO: debug, remove 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) focusDistance = doF.focusDistance.value; //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; //var coc = maxFactorRadiusCOC * 1 / Mathf.Pow(focusDistance, pow); var coc = Mathf.Clamp(cocRadius.factor * (-Mathf.Log(focusDistance) + 1.9f * Mathf.Exp(1)), cocRadius.min, cocRadius.max); //Debug.Log($"COC = {coc}, focusDistance = {focusDistance}"); //var coc = maxFactorRadiusCOC * 1 - () doF.aperture.value = ApertureForCocAndFocusDistance(coc, focusDistance); } private float ApertureForCocAndFocusDistance(float coc, float focusDistance) { return 1 / (1000 * coc * (focusDistance - 0.001f)); } private float CastRays() { var position = cameraTransform.position; var forward = cameraTransform.forward; var up = cameraTransform.up; var adjustedForward = Vector3.RotateTowards(forward, up, offsetFromCenter * Mathf.Deg2Rad, 0f); var start = position + forward * playerCamera.nearClipPlane; ends[0] = position + adjustedForward * 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(); for (var i = 0; i < ends.Length; i++) { var end = ends[i]; if (Physics.Linecast(start, end, out var hit, Physics.DefaultRaycastLayers)) /*Debug.DrawLine(start, hit.point, Color.green); hits[i].transform.position = hit.point; hits[i].transform.localScale = Vector3.one * (hit.distance * 0.01f); hits[i].SetActive(true);*/ //Debug.Log("DoF - Hit, Distance = " + hit.distance); rayDistances.Add(hit.distance); /*else { hits[i].SetActive(false); Debug.DrawRay(position, position + forward * playerCamera.farClipPlane, Color.red); }*/ } if (rayDistances.Count < 1) return -1; return Helpers.RemoveOutliers(rayDistances).Average(); } } }