8pm STUDIOS ๐Ÿฟ

8pm Studios intro

ํŒ€๋ช…: 8pm
ํŒ€์›: ๊น€ํ˜„์šฐ, ๊น€์ง€์œค, ์‹ ์ฒ ํ˜ธ
๊ฐœ๋ฐœ ํ™˜๊ฒฝ: Unity 2020.1 URP, Visual Studio, GitLab
์ œ์ž‘ ๊ธฐ๊ฐ„: 2020.12.28 ~ 2021.02.08
YouTube: ์˜ํ™” โ€˜์•„์ €์”จโ€™์˜ โ€œ์ด๊ฑฐ ๋ฐฉํƒ„ ์œ ๋ฆฌ์•ผโ€ ์ดฌ์˜


Index

1. ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

2. ํ”„๋กœ์ ํŠธ ๊ธฐ๋Šฅ


ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

8pm STUDIOS๋Š” ๋ฐฐ์šฐ, ์ž‘๊ฐ€, ์—ฐ์ถœ, ์ดฌ์˜๊ฐ๋…์„ ์œ„ํ•œ ๊ฐ€์ƒ ์˜ํ™” ์ดฌ์˜ ํ”Œ๋žซํผ์ด๋‹ค.

๋ชฉ์ 

๊ฒฝ์ œ์ , ๋ฌผ๋ฆฌ์  ์ œ์•ฝ์œผ๋กœ ์ธํ•ด ์ž์‹ ์˜ ์ž‘ํ’ˆ์„ ๋งŒ๋“ค๊ธฐ ์–ด๋ ค์šด ์˜ํ™”์ธ๊ณผ ์˜ํ™”์ธ ์ง€๋ง์ƒ๋“ค์ด VR ์ƒ์—์„œ ์ž์œ ๋กญ๊ฒŒ ๋ฐฐ๊ฒฝ ๋ฐ ์†Œํ’ˆ์„ ์‚ฌ์šฉํ•˜๊ณ  ์„œ๋กœ ๋„คํŠธ์›Œํ‚นํ•˜์—ฌ ์ž‘ํ’ˆ์„ ์ดฌ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ˆ์ฆˆ

  • ๋‹ค์–‘ํ•œ ์ž‘ํ’ˆ์˜ ํ™˜๊ฒฝ ์•ˆ์—์„œ ์—ฐ๊ธฐํ•˜๊ณ  ์‹ถ์€ ๋ฐฐ์šฐ
  • ์˜ค๋ฆฌ์ง€๋„ ์ปจํ…์ธ ๋ฅผ ์„ ๋ณด์ด๊ณ  ์‹ถ์€ ๊ฐ๋… ๋ฐ ์ž‘๊ฐ€
  • ํ”„๋ฆฌ ๋น„์ฃผ์–ผ(Pre-Visualization)์„ ํ†ตํ•œ ๋™์˜์ƒ ์ฝ˜ํ‹ฐ ์ œ์ž‘
  • ์ปจํ…์ธ ๋ฅผ ๋ฐœ๊ตดํ•ด์•ผ ํ•˜๋Š” ์ œ์ž‘์ž ๋“ฑ

๊ธฐ๋Œ€ ํšจ๊ณผ

  • ์˜ํ™” ์ œ์ž‘ ์ง„์ž… ์žฅ๋ฒฝ์„ ๋‚ฎ์ถค
  • ์˜ํ™” ๋ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ œ์ž‘์„ ์œ„ํ•œ ๊ฐ€์ƒํ˜„์‹ค ์ฝ˜ํ‹ฐ ๋“ฑ์œผ๋กœ ํ™œ์šฉ์„ ๋„๋ชจ
  • 2์ฐจ ์ฐฝ์ž‘์„ ํ†ตํ•ด ์˜ํ™”ํŒฌ๋“ค์—๊ฒŒ ์˜ํ™” ์† ์ฃผ์ธ๊ณต์ด ๋œ ๋“ฏํ•œ ์ฒดํ—˜์„ ์ œ๊ณต

๊ตฌ์กฐ

์‚ฌ์šฉ์ž๋“ค์€ ์ œ์ž‘ ์˜ํ™”์˜ ์ฃผ์ œ ๋˜๋Š” ํ‚ค์›Œ๋“œ๋ณ„๋กœ ๋ฐฉ์„ ๋งŒ๋“ค๊ณ  ํ•„์š”ํ•œ ์—ญํ• ์„ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค. ํŒ€์ด ๊ตฌ์„ฑ๋˜๋ฉด ๋ฐฐ์šฐ๋Š” ๊ฐ€์ƒ ํ™˜๊ฒฝ ์†์—์„œ ์—ฐ๊ธฐ๋ฅผ ํ•˜๊ณ  ์—ฐ์ถœ๊ฐ๋…์€ ํ™˜๊ฒฝ ์„ธํŒ…์„ ํ•˜๋ฉฐ ์ดฌ์˜๊ฐ๋…์€ ์ดฌ์˜์„ ์ง„ํ–‰ํ•œ๋‹ค. ์ „์ฒด์ ์ธ ํ”Œ๋กœ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์—ญํ•  ๋ถ„๋‹ด ๋ฐ ์ˆ˜ํ–‰ ์ ˆ์ฐจ

: ํŒ€ ๊ตฌ์„ฑ์›
๊น€ํ˜„์šฐ ๊น€์ง€์œค ์‹ ์ฒ ํ˜ธ
์นด๋ฉ”๋ผ ๊ธฐ๋Šฅ, ์ปจํŠธ๋กค ๋ฐ์Šคํฌ ์ฝ˜ํ…์ธ  ๊ธฐํš, ์ธ๋ฒคํ† ๋ฆฌ, ๋กœ๋น„ ๋„คํŠธ์›Œํฌ, ํ”Œ๋ ˆ์ด์–ด ์ธํ„ฐ๋ ‰์…˜
: ๊ฐœ๋ฐœ ์ผ์ •

ํ”„๋กœ์ ํŠธ ๊ธฐ๋Šฅ

1. FilmCamera

Camera Functions
Fig 1. Camera Functions.

๊ฐ€์ƒ ์˜ํ™” ์ดฌ์˜ ์‹œ ์ดฌ์˜๊ฐ๋…์ด ์‚ฌ์šฉํ•  ์นด๋ฉ”๋ผ์ด๋‹ค. ๋ฌผ๋ฆฌ์ ์ธ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  VR ๋ฒ„ํŠผ๊ณผ ์กฐ์ด์Šคํ‹ฑ ์ธํ„ฐ๋ ‰์…˜์„ ํ†ตํ•ด ์นด๋ฉ”๋ผ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์˜ ๋ชฐ์ž…๊ฐ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์˜ ํ€„๋ฆฌํ‹ฐ๋„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ์นด๋ฉ”๋ผ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉ์ž์˜ ๋‹ˆ์ฆˆ์— ๋งž๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋„ˆ๋ฌด ๋งŽ์€ ๋ฒ„ํŠผ์„ ํ•œ ๋ฒˆ์— ์ œ๊ณต ํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์›ํ•˜๋Š” ๋ฒ„ํŠผ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„ํ•˜์˜€๋‹ค.

Camera Functions

  • Primary Button : Record; Start, Stop
  • Secondary Button : Record; Cancel
  • Menu Button : Control Detail Functions
  • Joystick : Zoom; In/Out, On/Off
  • Focus : On/Off
  • Stabilizer : Always On
  • Clapper Board : Auto Update Info
  • Preview Screen : Show Camera State

a. ๊ตฌ์กฐ

FilmCamera์˜ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ๋•Œ โ€˜Dependency Cycleโ€˜์„ ํ•ญ์ƒ ์—ผ๋‘์— ๋‘๊ณ  ์ข…์†์„ฑ์„ ๋Š์–ด ์ฃผ๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ–ˆ๋‹ค. ๋”ฐ๋ผ์„œ FilmCameraController์—์„œ ์นด๋ฉ”๋ผ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์นด๋ฉ”๋ผ์˜ ๊ธฐ๋Šฅ๋“ค์ด ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ๊ฐ ๊ธฐ๋Šฅ์ด ์•Œ๋งž์€ ์ฝœ๋ฐฑํ•จ์ˆ˜์— ๊ตฌ๋…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„ํ•˜์˜€๋‹ค. ์ด๋ ‡๊ฒŒ ์„ค๊ณ„ํ•จ์œผ๋กœ์จ FilmCameraController๊ฐ€ ์นด๋ฉ”๋ผ ๊ธฐ๋Šฅ๋“ค๋กœ๋ถ€ํ„ฐ ์ž์œ ๋กœ์›Œ์ง€๊ณ  ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ฝ”๋“œ์˜ ์ˆ˜์ • ์—†์ด ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋บ„ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

: Delegate Functions
#region Delegates
public delegate void FilmCamButton();
public delegate void FilmCamJoystick(float val);

public FilmCamButton PrimaryButtonAction;
public FilmCamButton SecondaryButtonAction;
public FilmCamButton MenuButtonAction;
public FilmCamJoystick JoystickAction;
#endregion
: FilmCamera Sequence Diagram

b. CameraZoom

Smooth Zoom Control
Fig 2. SmoothZoom Control.

์นด๋ฉ”๋ผ ์คŒ์€ FixedLensZoom, SmoothZoom ๋‘๊ฐ€์ง€ ๋ชจ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
์กฐ์ด์Šคํ‹ฑ์„ Z์ถ• ๋ฐฉํ–ฅ์œผ๋กœ๋งŒ ์›€์ง์ผ ์ˆ˜ ์žˆ๊ณ  ๋‹น๊ธฐ๋ฉด ์คŒ์ธ ๋ฐ€๋ฉด ์คŒ์•„์›ƒ์ด ๋œ๋‹ค.

CameraZoom.cs
#region Subscribed Functions
/// <summary>
/// Perform smooth zoom.
/// </summary>
/// <param name="dir"></param>
private void Zoom(float dir)
{
    var fov = _cam.fieldOfView + dir * zoomAmount * Time.deltaTime;
    _cam.fieldOfView = Mathf.Clamp(fov,
        lensPresets.Lens[lensPresets.Lens.Length - 1].FoV,
            lensPresets.Lens[0].FoV);
}

/// <summary>
/// Perform fixed lens zoom.
/// </summary>
/// <param name="dir"></param>
private void FixedLens(float dir)
{
    var fov = _cam.fieldOfView + dir * zoomAmount * Time.deltaTime;
    var fovClamp = Mathf.Clamp(fov,
        lensPresets.Lens[lensPresets.Lens.Length - 1].FoV,
            lensPresets.Lens[0].FoV);
    var index = lensPresets.GetLensIndex(fovClamp);
    _cam.fieldOfView = lensPresets.Lens[index].FoV;
}
#endregion

๋‘ ํ•จ์ˆ˜ ๋ชจ๋‘ VR ์กฐ์ด์Šคํ‹ฑ์œผ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฐฉํ–ฅ๊ฐ’์œผ๋กœ ์ƒˆ๋กœ์šด fov ๊ฐ’์„ ๊ณ„์‚ฐํ•˜์—ฌ ์นด๋ฉ”๋ผ .fieldOfView์— ๋„ฃ์–ด์ค€๋‹ค.

FixedLensZoom๊ฐ™์€ ๊ฒฝ์šฐ โ€˜Scriptable Objectโ€˜์ธ lensPreset์— ๋ฏธ๋ฆฌ ์ •์˜๋œ fov๊ฐ’๊ณผ ๋น„๊ตํ•˜์—ฌ ๊ฐ€์žฅ ๊ฐ’์ด ๋น„์Šทํ•œ ๋ Œ์ฆˆ์˜ fov๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ˜•ํƒœ์ด๋‹ค.

LensPresets.cs
#region Initialize
/// <summary>
/// This function should be called on Start.
/// </summary>
public void CreateDefaultLens()
{
    if (!_isInitialized)
    {
        List<LensPreset> defaults = new List<LensPreset>();
        defaults.Add(new LensPreset() { Name = "21mm", FoV = 60f });
        defaults.Add(new LensPreset() { Name = "35mm", FoV = 38f });
        defaults.Add(new LensPreset() { Name = "58mm", FoV = 23f });
        defaults.Add(new LensPreset() { Name = "80mm", FoV = 17f });
        defaults.Add(new LensPreset() { Name = "125mm", FoV = 10f });
        _lens = defaults.ToArray();
        _isInitialized = true;
    }
}
#endregion

๊ณ„์‚ฐ๋œ ๊ฐ’์ด _threshold๋ณด๋‹ค ๋‚ฎ๋‹ค๋ฉด ๋ Œ์ฆˆ๋ฅผ ์ฐพ์•˜๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ํ•ด๋‹น ์ธ๋ฑ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

LensPresets.cs
/// <summary>
/// Get the index of the lens preset that matches the fov value.
/// </summary>
/// <param name="fov"></param>
/// <returns></returns>
public int GetLensIndex(float fov)
{
    for (int i = 0; i < _lens.Length; ++i)
    {
        if (Mathf.Abs(_lens[i].FoV) - Mathf.Abs(fov) <= _threshold)
            return i;
    }
    return -1;
}

c. CameraFocus

Camera Auto Focus
Fig 3. Camera Auto Focus.

ํฌ์ปค์Šค ๊ธฐ๋Šฅ์€ ์ค‘์•™์ ์—์„œ ๋ฐ”๋ผ๋ณด๊ณ  ์žˆ๋Š” ๋ฌผ์ฒด์— ์ž๋™์œผ๋กœ ์ดˆ์ ์„ ๋งž์ถ˜๋‹ค. ํฌ์ปค์Šค ๋ชจ๋“œ์—๋Š” ๋งค๋‰ด์–ผ ํฌ์ปค์Šค ๋ชจ๋“œ์™€ ์˜คํ†  ํฌ์ปค์Šค ๋ชจ๋“œ๊ฐ€ ์žˆ๋‹ค.

autoFocus ๊ธฐ๋Šฅ์€ ๋ฉ”๋‰ด๋ฅผ ํ†ตํ•ด ์ผœ๊ฑฐ๋‚˜ ๋Œ ์ˆ˜ ์žˆ๋‹ค.

CameraFocus.cs
#region Subscribed Functions
/// <summary>
/// ๋งค๋‰ด์–ผ ํฌ์ปค์Šค, ์˜คํ†  ํฌ์ปค์Šค ๋ชจ๋‘ ๊ฐ™์€ ํ•จ์ˆ˜๋กœ ์‹คํ–‰๋œ๋‹ค. 
/// </summary>
private void Focus()
{
    StartCoroutine(Focusing());
}

private IEnumerator Focusing()
{
    if (_isCoroutineRunning) yield break;
    _isCoroutineRunning = true;

    var ray = new Ray(_cam.transform.position, _cam.transform.forward);

    if (Physics.SphereCast(ray, rayRadius, out var hit, rayLength))
    {
        var hitDst = Vector3.Distance(_cam.transform.position, hit.point);
        var elapsedTime = 0f;

        while (elapsedTime <= focusTime)
        {
            var lerpVal = Mathf.Lerp(_dof.focusDistance.value, hitDst,
                focusAc.Evaluate(elapsedTime / focusTime));
            _dof.focusDistance.value = lerpVal;
            elapsedTime += Time.fixedDeltaTime;
            yield return null;
        }
        _dof.focusDistance.value = hitDst;
    }
    _isCoroutineRunning = false;
}

ํฌ์ปค์Šค ๊ธฐ๋Šฅ์€ .SphereCast๋ฅผ ์ด์„œ ๋งž์ถ˜ ๋Œ€์ƒ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ๋ฅผ _dof์˜ .focusDistance.value์— ๋„ฃ์–ด์ค€๋‹ค. ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ „ํ™˜์„ ์ฃผ๊ธฐ ์œ„ํ•ด Mathf.Lerp์™€ AnimationCurve๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

d. CameraStabilizer

camera stabilizer
Fig 4. Camera Stabilizer.

VR ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์‚ฌ์šฉ์ž์˜ ์›€์ง์ž„์„ ์ •ํ™•ํ•˜๊ฒŒ ๋”ฐ๋ผ๊ฐ€์•ผ ํ•˜๋ฏ€๋กœ ์ž‘์€ ์›€์ง์ž„๋„ ๋‹ค ํŒŒ์•…ํ•œ๋‹ค. ํ•˜์ง€๋งŒ ์‚ฌ์šฉ์ž ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋ฏผ๊ฐ๋„๋Š” ์นด๋ฉ”๋ผ๋ฅผ ๋“ค๊ณ  ์ดฌ์˜ ์‹œ ์น˜๋ช…์ ์ผ ์ˆ˜ ์žˆ๋‹ค. CameraStabilizer๋Š” ์ด๋Ÿฌํ•œ ์† ๋–จ๋ฆผ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด์ค€๋‹ค.

CameraStabilizer.cs
[Header("Camera"), SerializeField]
private Transform filmCam = null;
[SerializeField]
private Transform camAnchor = null;
[Header("Stabilizer"), SerializeField]
private float positionStabilizeValue = 0.6f;
[SerializeField]
private float rotationStabilizeValue = 0.6f;

void Update()
{
    var positionOffset = Vector3.Distance(camAnchor.position, filmCam.position) + 1;
    filmCam.position = Vector3.Lerp(filmCam.position, camAnchor.position,
        positionOffset * Time.deltaTime * positionStabilizeValue * 10);

    var rotationOffset = Quaternion.Angle(camAnchor.rotation, filmCam.rotation);
    filmCam.rotation = Quaternion.Lerp(filmCam.rotation, camAnchor.rotation,
        rotationOffset * Time.deltaTime * rotationStabilizeValue / 1.5f);
}

โ€˜Unity Cameraโ€˜์˜ ํฌ์ง€์…˜๊ฐ’๊ณผ ๋กœํ…Œ์ด์…˜๊ฐ’์€ ์„ค์ •ํ•œ camAnchor๋กœ ํฌ์ง€์…˜์€ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ, ๋กœํ…Œ์ด์…˜์€ ๊ฐ๋„์— ๋”ฐ๋ผ Lerpํ•œ ๊ฐ’์ด ์ ์šฉ๋œ๋‹ค.

e. ClapperBoard & Preview Screen

camera screen info
Fig 5. Camera screen info.

ClapperBoard๋Š” ์˜์ƒ ํŽธ์ง‘์ž์—๊ฒŒ ์นด๋ฉ”๋ผ์˜ PreviewScreen์€ ์ดฌ์˜ ๊ฐ๋…์—๊ฒŒ ์•„์ฃผ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

๋…นํ™”๊ฐ€ ์‹œ์ž‘๋˜๋ฉด ClapperBoard๊ฐ€ ์˜์ƒ ์ •๋ณด์™€ ํ•จ๊ป˜ ์•Œ์•„์„œ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. PreviewScreen์—๋Š” ๋ Œ์ฆˆ ์ •๋ณด, ์ดฌ์˜ ๋‚ ์งœ/์‹œ๊ฐ„, ํ…Œ์ดํฌ ์ •๋ณด, ๊ทธ๋ฆฌ๊ณ  ๋…นํ™” ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.

ClapperBoardController.cs
#region Subscribed Functions
/// <summary>
/// ๋…นํ™” ์‹œ์ž‘์‹œ ํ•œ๋ฒˆ ํ˜ธ์ถœ๋˜๊ณ  ๊ธฐ๋ก ๋‚ด์šฉ์„ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
/// </summary>
private void Clap()
{
    if (!_canClap) return;
    gameObject.SetActive(true);

    studioInfo.text = studioPreset.Studio;
    productionInfo.text = studioPreset.Production;
    directorInfo.text = studioPreset.Director;
    dateInfo.text = DateTime.UtcNow.ToLocalTime().ToString("yyyy.MM.dd");
    sceneInfo.text = studioPreset.Scene;
    takeInfo.text = studioPreset.TakeCount.ToString();

    Invoke("Action", delayTime);
}
private void Action() => _animator.SetTrigger("action");

ClapperBoard ์ •๋ณด ๋Œ€๋ถ€๋ถ„์€ studioPreset ์ด๋ผ๋Š” Scriptable Object์— ๊ธฐ๋ก๋œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ๋…นํ™”๊ฐ€ ์‹œ์ž‘๋˜๋ฉด ์ž‘์€ ๋”œ๋ ˆ์ด ์ดํ›„์— ์Šฌ๋ ˆ์ดํŠธ๋ฅผ ์น˜๊ฒŒ ๋œ๋‹ค.

2. ControlDeskSystem

ControlDeskSystem Buttons
Fig 6. ControlDeskSystem Buttons.

ControlDeskSystem๋Š” ์ดฌ์˜๊ฐ๋…์ด ํ•œ๊ณณ์—์„œ ์—ฌ๋Ÿฌ ๋Œ€ ์นด๋ฉ”๋ผ๋ฅผ ํ•œ ๋ฒˆ์— ์ œ์–ดํ•˜๊ฑฐ๋‚˜ ์ฐ์€ ์˜์ƒ์„ ํ™•์ธํ•˜๋Š” ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค. FilmCamera์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋„ˆ๋ฌด ๋งŽ์€ ๋ฒ„ํŠผ์„ ํ•œ๊บผ๋ฒˆ์— ์ œ๊ณตํ•˜๋ฉด ์‚ฌ์šฉ๊ฐ€ ํ–‡๊ฐˆ๋ ค ํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋” ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ™์€ ๋ฒ„ํŠผ์„ ๋ชจ๋“œ ์Šค์œ„์น˜๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ๋” ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

ControlDeskSystem๋Š” ๋‘๊ฐ€์ง€ ๋ชจ๋“œ๊ฐ€ ์žˆ๊ณ  ๋ชจ๋“œ์— ๋”ฐ๋ผ ๊ฐ™์€ ๋ฒ„ํŠผ์ด ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์„ ํ•˜๊ฒŒ ๋œ๋‹ค.

CameraControlMode

  • Mode Switch Button : VideoControlMode๋กœ ๋ณ€๊ฒฝ
  • Previous Button : ์ด์ „ ์นด๋ฉ”๋ผ์˜ ํ”„๋ฆฌ๋ทฐ ์Šคํฌ๋ฆฐ์„ ํ™”๋ฉด์— ๋„์šด๋‹ค
  • Next Button : ๋‹ค์Œ ์ธ๋ฑ์Šค์˜ ์นด๋ฉ”๋ผ ํ”„๋ฆฌ๋ทฐ ์Šคํฌ๋ฆฐ์„ ํ™”๋ฉด์— ๋„์šด๋‹ค
  • Primary Button : ์„ ํƒ๋œ ์นด๋ฉ”๋ผ ๋…นํ™”/์ค‘์ง€
  • Secondary Button : ํ˜„์žฌ ์นด๋ฉ”๋ผ ์„ ํƒ/ํ•ด์ œ

VideoControlMode

  • Mode Switch Button: CameraControlMode๋กœ ๋ณ€๊ฒฝ
  • Previous Button: ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์ด์ „ ์˜์ƒ์„ ์žฌ์ƒ
  • Next Button: ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๋‹ค์Œ ์ธ๋ฑ์Šค์˜ ์˜์ƒ์„ ์žฌ์ƒ
  • Primary Button: ์˜์ƒ ์žฌ์ƒ/์ผ์‹œ์ •์ง€, ์˜์ƒ ์‚ญ์ œ ํ™•์ธ
  • Secondary Button: ์˜์ƒ ์‚ญ์ œ, ์˜์ƒ ์‚ญ์ œ ์ทจ์†Œ

a. ๊ตฌ์กฐ

ControlDeskSystem์˜ ๊ตฌ์กฐ๋Š” FilmCamera์˜ ๊ตฌ์กฐ์™€ ๋น„์Šทํ•˜๋‹ค. ControlDeskSystem์˜ ๊ธฐ๋Šฅ์€ ๋ชจ๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ฝœ๋ฐฑํ•จ์ˆ˜์— ๊ตฌ๋…๋˜๋Š” ๊ธฐ๋Šฅ์ด ๋ฐ”๋€Œ๊ฒŒ ๋œ๋‹ค.

: Delegate Funcitons
#region Delegates
public delegate void ModeControlDeskButton(ControlMode mode);
public delegate void ControlDeskButton();

public ModeControlDeskButton ModeSwitchAction = null;
public ControlDeskButton PreviousAction = null;
public ControlDeskButton NextAction = null;
public ControlDeskButton PrimaryAction = null;
public ControlDeskButton SecondaryAction = null;
#endregion
: ControlDeskSystem Sequence Diagram

b. FilmCameraManager

ControlDeskSystem์—์„œ ๋ชจ๋“œ ๋ณ€๊ฒฝ ์‹ ํ˜ธ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ๊ตฌ๋…๋œ OnModeChange() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  ๋ชจ๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ฝœ๋ฐฑํ•จ์ˆ˜์— ๊ตฌ๋…ํ•˜๊ฑฐ๋‚˜ ์ทจ์†Œํ•˜๊ฒŒ ๋œ๋‹ค.

FilmCameraManager.cs
private void OnModeChange(ControlMode mode)
{
    _currentMode = mode;
    if (_currentMode.Equals(ControlMode.CameraViewer))
    {
        _deskController.PreviousAction += ShowPreviousCam;
        _deskController.NextAction += ShowNextCam;
        _deskController.PrimaryAction += RecordSelectedCam;
        _deskController.SecondaryAction += SelectCam;
    }
    else
    {
        _deskController.PreviousAction -= ShowPreviousCam;
        _deskController.NextAction -= ShowNextCam;
        _deskController.PrimaryAction -= RecordSelectedCam;
        _deskController.SecondaryAction -= SelectCam;
    }
}

FilmCameraManager๋Š” ์—ฌ๋Ÿฌ ๋Œ€์˜ ์นด๋ฉ”๋ผ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

Switch Camera View
Fig 7. Switch Camera View.
FilmCameraManager.cs
private void ShowScreen(int index)
{
    if (_filmCameras.Count > index)
    {
        var sc = _filmCameras[index].GetComponentInChildren<ScreenController>();
        _viewScreen.GetComponent<Renderer>().material.mainTexture = sc.NotRecordingRt;
    }
}

NextButton์ด๋‚˜ PreviousButton์ด ํ˜ธ์ถœ๋˜๋ฉด ๋‹ค์Œ ์ธ๋ฑ์Šค๋ฅผ ShowScreen()์— ๋„˜๊ฒจ์ค€๋‹ค. ๋„˜๊ฒจ๋ฐ›์€ ์ธ๋ฑ์Šค์— ํ•ด๋‹นํ•˜๋Š” ์นด๋ฉ”๋ผ์˜ RenderTexture๋ฅผ ScreenController๋กœ ๋ถ€ ํ„ฐ ๋ฐ›์•„์˜จ๋‹ค. ๋ฐ›์•„์˜จ RT๋ฅผ ํ™”๋ฉด ๋ฉ”ํ…Œ๋ฆฌ์–ผ์— ๋„ฃ์–ด์ค€๋‹ค.

c. VideoPlayerManager

Watch Saved Video
Fig 8. Watch Saved Video.

VideoPlayerManager๋Š” ์ฐ์€ ์˜์ƒ์„ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

VideoPlayerManager.cs
/// <summary>
/// Update video name list.
/// ์˜์ƒ์ด ์ €์žฅ๋˜๋ฉด ํ˜ธ์ถœ๋œ๋‹ค.
/// </summary>
public void UpdateVideoList()
{
    var fileNames = Directory.GetFiles(CAPTURE_PATH, "*.mp4");
    Array.Sort(fileNames);

    if (_videoNames == null)
    {
        _videoNames = new List<string>();
    }
    else if (_videoNames.Count > 0)
    {
        _videoNames.Clear();
    }

    foreach (var name in fileNames)
    {
        _videoNames.Add(Path.GetFileNameWithoutExtension(name));
    }
}

์˜์ƒ ๋ชฉ๋ก์€ ์˜์ƒ ์ด๋ฆ„์œผ๋กœ ๊ด€๋ฆฌ๋ฅผ ํ•œ๋‹ค. ์ƒˆ๋กœ์šด ์˜์ƒ์ด ์ฐํžˆ๋ฉด UpdateVideoList()๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  ํ•ญ์ƒ ์ตœ์‹  ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค.

VideoPlayerManager.cs
private void PlayPause()
{
    if (_videoPlayer.isPlaying)
        _videoPlayer.Pause();
    else
        _videoPlayer.Play();
}

private void PlayNew(int index)
{
    if (_videoPlayer.isPlaying)
        _videoPlayer.Stop();
    if (_videoNames.Count > index)
        StartCoroutine(LoadVideo(_videoNames[index]));
}

private IEnumerator LoadVideo(string name)
{
    if (_isLoading) yield break;
    _isLoading = true;
    _videoPlayer.url = CAPTURE_PATH + name + ".mp4";
    _isLoading = false;
}

ํ˜„์žฌ ์žฌ์ƒ ์˜์ƒ์€ ์ธ๋ฑ์Šค๋กœ ๊ด€๋ฆฌ๋œ๋‹ค. ์ƒˆ๋กœ์šด ์˜์ƒ์„ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ๋Š” ์ธ๋ฑ์Šค์— ํ•ด๋‹นํ•˜๋Š” ์˜์ƒ ์ด๋ฆ„์œผ๋กœ ์ฐพ๋Š”๋‹ค.

3. CameraPathCreator

Path Creator
Fig 9. Path Creator.

CameraPathCreator๋กœ ์›จ์ดํฌ์ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์†์‰ฝ๊ฒŒ ์ดฌ์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. Anchor Point์™€ Control Point๋ฅผ VR ์ƒ์—์„œ ์›ํ•˜๋Š” ๊ณณ์— ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ๊ณ  Cart์— ์นด๋ฉ”๋ผ๋ฅผ ์žฅ์ฐฉํ•˜๊ณ  ์ถœ๋ฐœ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์„ค์ •ํ•œ ์†๋„๋กœ Cart๊ฐ€ ์›€์ง์ด๊ฒŒ ๋œ๋‹ค.

a. ๊ตฌ์กฐ

CameraPathCreator๋Š” Object Pool์—์„œ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ Point๋“ค์„ ๋ฐฐ์น˜ํ•˜๊ณ  PathViewer๊ฐ€ Point๋“ค์„ ์ด์–ด์„œ Path๋ฅผ ๋งŒ๋“ ๋‹ค.

: CameraPathCreator Sequence Diagram

b. BezierCurves

Bezier Curve
Fig 10. Bezier Curve.
BezierCurves.cs

CameraPathCreator์˜ ํ•ต์‹ฌ์ธ BezierCurve์ด๋‹ค. ์œ„ํ‚ค์˜ ๊ณต์‹์„ ์ฐธ๊ณ ํ•ด์„œ static ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜์˜€๋‹ค.

public static Vector3 LinearBezierCurves(Vector3 a, Vector3 b, float t)
{
    return a + t * (b - a);
}

public static Vector3 QuadraticBezierCurves(Vector3 a, Vector3 b, Vector3 c, float t)
{
    return Mathf.Pow(1 - t, 2) * a + 2 * (1 - t) * t * b + Mathf.Pow(t, 2) * c;
}

public static Vector3 CubicBezierCurves(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float t)
{
    return Mathf.Pow(1 - t, 3) * a + 3 * Mathf.Pow(1 - t, 2) * t * b +
        3 * (1 - t) * Mathf.Pow(t, 2) * c + Mathf.Pow(t, 3) * d;
}

c. PathCreator

Create Path
Fig 11. Create Path.

PathCreator๋Š” CameraPathCreator์˜ ์ค‘์‹ฌ์ถ•์œผ๋กœ Object Pool์™€ PathViewer ์‚ฌ์ด์—์„œ ์ปจํŠธ๋กค ํ•˜๋Š” ์ž‘์—…์„ ํ•œ๋‹ค.

PathCreator.cs
/// <summary>
/// Gets called when an active Path Point is not detected
/// and when the VR button is pressed.
/// </summary>
public void AddPoint()
{
    if (!_detector.IsTriggerStay)
    {
        var point = _pool.ActivatePoint(spawnPoint.position, spawnPoint.rotation);
        if (point != null)
        {
            _points.Add(point);
        }
        if (_points.Count > 1)
        {
            _cart.transform.position = _points[0].position;
            _cart.SetActive(true);
        }
    }
}

/// <summary>
/// Gets called when an active Path Point is detected
/// and when the VR button is pressed.
/// </summary>
/// <param name="point"></param>
public void RemovePoint(Transform point)
{
    int index = _points.FindIndex(p => p == point);
    if (index != -1)
    {
        _pool.TerminatePoint(_points[index]);
        _points.RemoveAt(index);
    }

    if (_points.Count < 2)
    {
        _cart.SetActive(false);
    }
}

Point์˜ ์ƒ์„ฑ์€ PointDetector์— Point๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š”๊ฒƒ์ด ์—†์„๋•Œ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค.

Point์˜ ์‚ญ์ œ๋Š” PointDetector๋กœ ๋ถ€ํ„ฐ ๊ฑด๋„ค ๋ฐ›์€ Point์˜ Transform์˜ ์ธ๋ฑ์Šค๋ฅผ ์ฐพ์•„ ObjectPool์—์„œ .TerminatePoint() ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค.

d. PathPool

PathPool์€ Stack์„ ์ด์šฉํ–ˆ๋‹ค. has-a ๋ฐฉ์‹์œผ๋กœ Object Pool์„ ์ƒ์„ฑํ•˜์—ฌ MonoBehaviour์˜ life cycle์„ ํƒ€์ง€ ์•Š๊ฒŒ ํ•˜์˜€๋‹ค.

PathPool.cs
public PathPool(MonoBehaviour mono, GameObject spawnObject, int pathCount)
{
    InitVariables(mono, spawnObject, pathCount);
    CreatePool();
}

private void CreatePool()
{
    _poolRoot = GameObject.Find("PathPool");
    if (_poolRoot == null)
    {
        _poolRoot = new GameObject("PathPool");
    }
    for (int i = 0; i < _pathCount; i++)
    {
        var point = MonoBehaviour.Instantiate(_spawnObject, _mono.transform.position,
            Quaternion.identity, _poolRoot.transform);
        point.SetActive(false);
        _pathPool.Push(point);
    }
}

e. PathViewer

Camera Path + Cart
Fig 12. Camera Path + Cart.

PathViewer๋Š” ์ฒ˜์Œ ์ƒ์„ฑ์ž์—์„œ ๋„˜๊ฒจ๋ฐ›์€ Point ๋ฆฌ์ŠคํŠธ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ๊ฐ€์ง€๊ณ  LineRenderer๋ฅผ ํ†ตํ•ด ์„ ์„ ๊ทธ๋ฆฐ๋‹ค.

PathViewer.cs
/* Cubic Bezier Curve */
case var expression when points > 3:
    t = 0f;
    for (int i = 0; i < SegmentCount(); i++)
    {
        var cp1 = _points[i * 3 + 1].GetComponent<Renderer>();
        var cp2 = _points[i * 3 + 2].GetComponent<Renderer>();
        cp1.material = _controlPointMat;
        cp2.material = _controlPointMat;

        Vector3[] segment = GetPointsInSegment(i);
        for (int j = 0; j < _lr.positionCount; j++)
        {
            _lr.SetPosition(j, BezierCurves.CubicBezierCurves(
                segment[0], segment[1], segment[2], segment[3], t));
            t += (1 / (float)_lr.positionCount);
        }
    }
    break;

.GetPointsInSegment()๋Š” Point๋ฅผ 3๊ฐœ์”ฉ ๊ฒน์น˜๋„๋ก ๋ฐ˜ํ™˜ ํ•œ๋‹ค.


updated_at 17-02-2021