LoginSignup
12
7

More than 5 years have passed since last update.

鏡の表現をやってみたくて調べてみた。

Last updated at Posted at 2017-11-07

鏡の表現に興味があったので調べてみました。

参考

UnityのMirrorRefrectionによる鏡の表現
知りたいことがそのものずばり!という感じです。
ただ、Unity2017で動かしてみると、色々おかしい?
調べてみましょう。

Unity5で鏡面表現
とりあえずWarningから検索してみると、いくつか表記の修正点の記事を発見。

挙動が不審なのを調べてみる

まずはmirror用に作られているはずのcameraを探します。

  • CreateMirrorObjects()

この中でミラー用のObjectを生成しているようです。

m_ReflectionTexture.hideFlags = HideFlags.DontSave;
go.hideFlags = HideFlags.HideAndDontSave;

Objectを作成しているけどHideフラグで見えないようにしているみたいです。
コメントアウトすると、もりもりCameraが作成されていくのが確認できます。

引き続きソースを見ていきます。
流れとしては

reflectionCamera = m_ReflectionCameras[currentCamera] as Camera;

↑これを見て、Cameraがなければ作成するという流れのようです。

ここが上手く動いていないような雰囲気です。
ということでまるっとList<>に置き換えてみました。

m_RefrectionCameras > _m_RefrectionCameras

こんな感じで変更しているので
元ソースと見比べてみてください。

使い勝手がいいように少し機能追加します

OnWillRenderObject()の中のVetor3 normalで鏡面の面を決めているようなので
  Modeを設定できるように改造しました。
UpdateCameraModes()の中でFarClipPlaneNearClipPlaneを決めています。
元はCamera.currentの設定をコピーしているようなので、固定値を設定できるように改造

    public class MirrorReflection : MonoBehaviour
    {
      public enum Mode
      {
        water,
        wall
      }

      public Mode _Mode = Mode.water;

      public bool m_DisablePixelLights = true;
      public int m_TextureSize = 256;
      [SerializeField]
      private float m_ClipPlaneOffset = 0.07f;
      [SerializeField]
      private float m_ClipPlaneNear = 0.3f;
      [SerializeField]
      private float m_ClipPlaneFar = 1000.0f;

    public LayerMask m_ReflectLayers = -1;

    private List<Camera> _m_RefrectionCameras = new List<Camera>();// Camera -> Camera table

    private RenderTexture m_ReflectionTexture = null;
    private int m_OldReflectionTextureSize = 0;
    private static bool s_InsideRendering = false;

    // This is called when it's known that the object will be rendered by some
    // camera. We render reflections and do other updates here.
    // Because the script executes in edit mode, reflections for the scene view
    // camera will just work!
    public void OnWillRenderObject()
    {
        var rend = GetComponent<Renderer>();
        if (!enabled || !rend || !rend.sharedMaterial || !rend.enabled)
            return;

        Camera cam = Camera.current;
        if (!cam)
            return;

        // Safeguard from recursive reflections.        
        if (s_InsideRendering)
            return;
        s_InsideRendering = true;

        Camera reflectionCamera;
        CreateMirrorObjects(cam, out reflectionCamera);

        // find out the reflection plane: position and normal in world space
        Vector3 pos = transform.position;
        Vector3 normal;
        if (_Mode == Mode.water) {
            normal = transform.up;
        } else
        {
            normal = transform.forward;
        }

        // Optionally disable pixel lights for reflection
        int oldPixelLightCount = QualitySettings.pixelLightCount;
        if (m_DisablePixelLights)
            QualitySettings.pixelLightCount = 0;

        UpdateCameraModes(cam, reflectionCamera);

        // Render reflection
        // Reflect camera around reflection plane
        float d = -Vector3.Dot(normal, pos) - m_ClipPlaneOffset;
        Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);

        Matrix4x4 reflection = Matrix4x4.zero;
        CalculateReflectionMatrix(ref reflection, reflectionPlane);
        Vector3 oldpos = cam.transform.position;
        Vector3 newpos = reflection.MultiplyPoint(oldpos);
        reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;

        // Setup oblique projection matrix so that near plane is our reflection
        // plane. This way we clip everything below/above it for free.
        Vector4 clipPlane = CameraSpacePlane(reflectionCamera, pos, normal, 1.0f);
        //Matrix4x4 projection = cam.projectionMatrix;
        Matrix4x4 projection = cam.CalculateObliqueMatrix(clipPlane);
        reflectionCamera.projectionMatrix = projection;

        reflectionCamera.cullingMask = ~(1 << 4) & m_ReflectLayers.value; // never render water layer
        reflectionCamera.targetTexture = m_ReflectionTexture;
        GL.invertCulling = true;
        reflectionCamera.transform.position = newpos;
        Vector3 euler = cam.transform.eulerAngles;
        reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
        reflectionCamera.Render();
        reflectionCamera.transform.position = oldpos;
        GL.invertCulling = false;
        Material[] materials = rend.sharedMaterials;
        foreach (Material mat in materials)
        {
            if (mat.HasProperty("_ReflectionTex"))
                mat.SetTexture("_ReflectionTex", m_ReflectionTexture);
        }

        // Restore pixel light count
        if (m_DisablePixelLights)
            QualitySettings.pixelLightCount = oldPixelLightCount;

        s_InsideRendering = false;
    }


    // Cleanup all the objects we possibly have created
    void OnDisable()
    {
        if (m_ReflectionTexture)
        {
            DestroyImmediate(m_ReflectionTexture);
            m_ReflectionTexture = null;
        }

        if (_m_RefrectionCameras.Count != 0)
        {
            if (_m_RefrectionCameras[0] != null)
            {
                foreach (Camera Cam in _m_RefrectionCameras)
                {
                    DestroyImmediate(Cam.gameObject);
                }
            }
        }
        _m_RefrectionCameras.Clear();
    }


    private void UpdateCameraModes(Camera src, Camera dest)
    {
        if (dest == null)
            return;
        // set camera to clear the same way as current camera
        dest.clearFlags = src.clearFlags;
        dest.backgroundColor = src.backgroundColor;
        if (src.clearFlags == CameraClearFlags.Skybox)
        {
            Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
            Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
            if (!sky || !sky.material)
            {
                mysky.enabled = false;
            }
            else
            {
                mysky.enabled = true;
                mysky.material = sky.material;
            }
        }
        // update other values to match current camera.
        // even if we are supplying custom camera&projection matrices,
        // some of values are used elsewhere (e.g. skybox uses far plane)
        dest.farClipPlane = m_ClipPlaneFar;// src.farClipPlane;
        dest.nearClipPlane = m_ClipPlaneNear;// src.nearClipPlane;
        dest.orthographic = src.orthographic;
        dest.fieldOfView = src.fieldOfView;
        dest.aspect = src.aspect;
        dest.orthographicSize = src.orthographicSize;
    }

    // On-demand create any objects we need
    private void CreateMirrorObjects(Camera currentCamera, out Camera reflectionCamera)
    {
        reflectionCamera = null;

        // Reflection render texture
        if (!m_ReflectionTexture || m_OldReflectionTextureSize != m_TextureSize)
        {
            if (m_ReflectionTexture)
                DestroyImmediate(m_ReflectionTexture);
            m_ReflectionTexture = new RenderTexture(m_TextureSize, m_TextureSize, 16);
            m_ReflectionTexture.name = "__MirrorReflection" + GetInstanceID();
            m_ReflectionTexture.isPowerOfTwo = true;
            m_ReflectionTexture.hideFlags = HideFlags.DontSave;
            m_OldReflectionTextureSize = m_TextureSize;
        }

        // Camera for reflection
        if (_m_RefrectionCameras.Count == 0)
        {
            GameObject go = new GameObject("Mirror Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
            reflectionCamera = go.GetComponent<Camera>();
            reflectionCamera.enabled = false;
            reflectionCamera.transform.position = transform.position;
            reflectionCamera.transform.rotation = transform.rotation;
            reflectionCamera.gameObject.AddComponent<FlareLayer>();
            go.hideFlags = HideFlags.HideAndDontSave;
            _m_RefrectionCameras.Add(reflectionCamera);
        } else
        {
            if (_m_RefrectionCameras[0] == null)
            {
                GameObject go = new GameObject("Mirror Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox));
                reflectionCamera = go.GetComponent<Camera>();
                reflectionCamera.enabled = false;
                go.hideFlags = HideFlags.HideAndDontSave;
                _m_RefrectionCameras.Add(reflectionCamera);
            }
            else {
                reflectionCamera = _m_RefrectionCameras[0];
            }
            reflectionCamera.transform.position = transform.position;
            reflectionCamera.transform.rotation = transform.rotation;
        }
    }

以降のソースは変更無しです。

  • private static float sgn(float a)
  • private Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
  • private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)

ということで動くようになりました!

MirrorSample.png
Unity2017.2.0f3で動作確認しています。

その後は

ランタイムで動作させるとなると、ちょっと負荷が気になります。
一旦動くようになったので、後は一つ一つ処理を追いかけて修正していこうかと思います。

12
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7