유니티

유니티 PlanarReflection

민또배기 2022. 4. 20. 15:19
반응형

유니티 버전 : 2020.3.25f1

작업환경 : Mac (Monterey 12.3.1)

 

이전엔 쉐이더 노드를 이용해서 반사효과를 구현했는데이 이번에는 일반?에서 작성해보려합니다.

 

유튜브 영상은 이분 것을 참고하였습니다. https://www.youtube.com/watch?v=tdIv9lJghVg 

 

 ReflectionManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ReflectionManager : MonoBehaviour
{
    Camera reflectionCam;
    Camera mainCamera;

    RenderTexture renderTarget;
    public GameObject reflectionPlane;
    public Material PlaneMaterial;
    

    [Range(0.0f,1.0f)]
    public float ReflectionFactor = 0.5f;

    void Start()
    {
        GameObject reflectionCamObject = new GameObject("RefecteionCamObject");
        reflectionCam = reflectionCamObject.AddComponent<Camera>();
        reflectionCam.enabled = false;

        mainCamera = Camera.main;
        renderTarget = new RenderTexture(Screen.width, Screen.height, 24);
    }

    // Update is called once per frame
    void Update()
    {
        Shader.SetGlobalFloat("_reflectionFactor", ReflectionFactor);
    }

    private void OnPostRender()
    {
        RenderReflection();
    }
    void RenderReflection()
    {
        reflectionCam.CopyFrom(mainCamera);

        Vector3 cameraDirectionWorldSpace = mainCamera.transform.forward;
        Vector3 cameraUpWorldSpace = mainCamera.transform.up;
        Vector3 cameraPositionWorldSpace = mainCamera.transform.position;

        Vector3 cameraDirectionPlaneSpace = reflectionPlane.transform.InverseTransformDirection(cameraDirectionWorldSpace);
        Vector3 cameraUpPlaneSpace = reflectionPlane.transform.InverseTransformDirection(cameraUpWorldSpace);
        Vector3 cameraPositionPlaneSpace = reflectionPlane.transform.InverseTransformPoint(cameraPositionWorldSpace);

        cameraDirectionPlaneSpace.y *= -1.0f;
        cameraUpPlaneSpace.y *= -1.0f;
        cameraPositionPlaneSpace.y *= -1.0f;

        cameraDirectionWorldSpace = reflectionPlane.transform.TransformDirection(cameraDirectionPlaneSpace);
        cameraUpWorldSpace = reflectionPlane.transform.TransformDirection(cameraUpPlaneSpace);
        cameraPositionWorldSpace = reflectionPlane.transform.TransformPoint(cameraPositionPlaneSpace);

        reflectionCam.transform.position = cameraPositionWorldSpace;
        reflectionCam.transform.LookAt(cameraPositionWorldSpace + cameraDirectionWorldSpace, cameraUpWorldSpace);

        reflectionCam.targetTexture = renderTarget;
        reflectionCam.Render();

        DrawQuad();
    }

    void DrawQuad()
    {
        GL.PushMatrix();

        PlaneMaterial.SetPass(0);
        PlaneMaterial.SetTexture("_ReflectionTex", renderTarget);
        GL.LoadOrtho();

        GL.Begin(GL.QUADS);
        GL.TexCoord2(1.0f, 0.0f);
        GL.Vertex3(0.0f, 0.0f, 0.0f);
        GL.TexCoord2(1.0f, 1.0f);
        GL.Vertex3(0.0f, 1.0f, 0.0f);
        GL.TexCoord2(0.0f, 1.0f);
        GL.Vertex3(1.0f, 1.0f, 0.0f);
        GL.TexCoord2(0.0f, 0.0f);
        GL.Vertex3(1.0f, 0.0f, 0.0f);
        GL.End();

        GL.PopMatrix();
    }
}

DrawQuad()의 내용은 OpenGL의 내용인데 이건 추후에 다뤄보도록 하겠습니다.

다음으론 Shader 코드를 작성해야합니다. URP에선 Shader Graph가 있었지만 URP가 아닌 일반환경에선 code로 작성해야 합니다.

두개를 작성해야하는데 스텐실 효과를 적용하기 때문이죠 

 

ReflectShader.shader

Shader "Unlit/ReflectShader"
{
    Properties
    {
        _ReflectionTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            Stencil{
                Ref 1
                Comp Equal
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _ReflectionTex;
            float _reflectionFactor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {

                fixed4 col = tex2D(_ReflectionTex, i.uv);
                col.a = (1.0 - _reflectionFactor);
                return col;
            }
            ENDCG
        }
    }
}

 

ReflectStencilShader.shader

Shader "Custom/ReflectStencilShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}

        LOD 200

        Stencil{
            Ref 1
            Comp always
            Pass replace
        }
        CGPROGRAM
        
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _reflectionFactor;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling

        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = (1.0 - _reflectionFactor);
            
        }
        ENDCG
    }
    FallBack "Diffuse"
}

위 작업을 다하셨다면 아래와 같은 결과가 나올겁니다. 

 

 

하지만 이 방법은 Material을 2개 사용합니다. 이게 맘에 들지 않는다면 하나로 할 수 있습니다.

맨 위 유튜브를 하신 분께서 Extra Part로 올리신 영상의 내용입니다.

 

ReflectionManager2.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ReflectionManager2 : MonoBehaviour
{
    Camera reflectionCam;
    Camera mainCamera;

    RenderTexture renderTarget;
    public GameObject reflectionPlane;
    public Material PlaneMaterial;
    

    [Range(0.0f,1.0f)]
    public float ReflectionFactor = 0.5f;

    void Start()
    {
        GameObject reflectionCamObject = new GameObject("RefecteionCamObject");
        reflectionCam = reflectionCamObject.AddComponent<Camera>();
        reflectionCam.enabled = false;

        mainCamera = Camera.main;
        renderTarget = new RenderTexture(Screen.width, Screen.height, 24);
    }

    // Update is called once per frame
    void Update()
    {
        Shader.SetGlobalFloat("_reflectionFactor", ReflectionFactor);
    }

    private void OnPreRender()
    {
        RenderReflection();
    }
    void RenderReflection()
    {
        reflectionCam.CopyFrom(mainCamera);

        Vector3 cameraDirectionWorldSpace = mainCamera.transform.forward;
        Vector3 cameraUpWorldSpace = mainCamera.transform.up;
        Vector3 cameraPositionWorldSpace = mainCamera.transform.position;

        Vector3 cameraDirectionPlaneSpace = reflectionPlane.transform.InverseTransformDirection(cameraDirectionWorldSpace);
        Vector3 cameraUpPlaneSpace = reflectionPlane.transform.InverseTransformDirection(cameraUpWorldSpace);
        Vector3 cameraPositionPlaneSpace = reflectionPlane.transform.InverseTransformPoint(cameraPositionWorldSpace);

        cameraDirectionPlaneSpace.y *= -1.0f;
        cameraUpPlaneSpace.y *= -1.0f;
        cameraPositionPlaneSpace.y *= -1.0f;

        cameraDirectionWorldSpace = reflectionPlane.transform.TransformDirection(cameraDirectionPlaneSpace);
        cameraUpWorldSpace = reflectionPlane.transform.TransformDirection(cameraUpPlaneSpace);
        cameraPositionWorldSpace = reflectionPlane.transform.TransformPoint(cameraPositionPlaneSpace);

        reflectionCam.transform.position = cameraPositionWorldSpace;
        reflectionCam.transform.LookAt(cameraPositionWorldSpace + cameraDirectionWorldSpace, cameraUpWorldSpace);

        reflectionCam.targetTexture = renderTarget;
        reflectionCam.Render();

        PlaneMaterial.SetTexture("_ReflectionTex", renderTarget);
    }
}

Reflect.shader

Shader "Custom/Reflect"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _ReflectionTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}

        LOD 200

        CGPROGRAM
        
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _ReflectionTex;

        struct Input
        {
            float2 uv_MainTex;
            float4 screenPos;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _reflectionFactor;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling

        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color

            float2 screenPos = IN.screenPos.xy / IN.screenPos.w;
            screenPos.x = 1.0 - screenPos.x;
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color * (1.0 - _reflectionFactor) + tex2D(_ReflectionTex,screenPos) * _reflectionFactor;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = (1.0 - _reflectionFactor);
            
        }
        ENDCG
    }
    FallBack "Diffuse"
}

잘못 된 점 혹은 보완점은 환영입니다.

 

Reflection.unitypackage
8.89MB

 

반응형