Search Unity

  1. Looking for a job or to hire someone for a project? Check out the re-opened job forums.
    Dismiss Notice
  2. Unity 2020 LTS & Unity 2021.1 have been released.
    Dismiss Notice
  3. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Showcase Sharing a Non Euclidean impossible space effect

Discussion in 'Scripting' started by Tharzzat, Oct 10, 2020.

  1. Tharzzat

    Tharzzat

    Joined:
    Apr 25, 2020
    Posts:
    8
    I made some maps that work in a non euclidean way.

    Two Hallways map


    Hypercube map


    5-D space test map


    5-D space map


    Portal test map


    I'm sharing how to do this.

    How it works is the portal is a separate 2D game object like a quad or a plane and a sector is a separate game object that is part of the map.

    The map is separated into sectors to deal with the impossible space.

    The portal has a stencil shader that writes to the stencil buffer and is set to always comparison.

    The sector game object has a stencil shader on it that is read by the portal stencil shader and the comparison is set to equal.

    The portal and sector stencil shader reference number is the same for both because the read stencil shader comparison is equal.

    An impossible space hallway would have two portals with write stencil shaders for the openings and the hallway sector has a read stencil shader.

    There are two overlapping sectors with different reference numbers and two portals for each sector then that would be impossible space hallways.

    The overlapping sectors are different layers and the layers are changed in the project settings to not interact with each other.

    The portal has a box collider trigger around it and a script attached that activates on trigger stay.

    The portal has a mathematical plane in the direction of the portal normals.

    Front side of the portal is positive and the back is negative.

    The player has a height of 3.2 and a radius of 0.8 with the camera at 0.8 y on player.

    The map scale is 4 = 1 world unit in Marathon.

    When the player is at a distance of 0.7 or less in front of the portal plane, the player layer changes to layer 8 or 9 and the comparison on the sector stencil shader changes from equal to always.

    When the player is at a distance in front of the plane greater than 0.7 then the sector stencil comparison changes back to equal and the player layer changes back to layer 0.

    The player layer changes to match the sectors layer and layer 0 interacts with both layers 8 and 9, but layers 8 and 9 don't interact with each other.

    The sector is rendered by the portals when compare is equal and is rendered normally when compare is always.

    For a differently sized player, change the distance to the plane to something else and change the cameras near clip if you have to.

    The sector stencil shader comparison changes to always before the player camera near clip reaches the portal.

    The portals can be made in blender or quads can be used.

    If you make portals in blender, move the portals origin from x 0, y 0, z 0 to directly on the portals center then the script will work correctly.

    If the map is imported then you have to enable read/write on the model so the portals work.

    Render queue is important.

    Different reference number portals and sectors will need a different render queue.

    Portals render before sectors.

    lower number is rendered before higher numbered.


    How to use the PortalSwitch script.


    The player game object has the tag player added to it.

    The main camera is part of the player game object.

    The sector you want to change rendering on goes in the sector area.

    The next sector is used to change the player layer when the player enters the portal to the next sector.

    The last sector is used to change the player layer when the player exits the portal to the last sector the player was in.

    Sectors can be different layers so the player layer changes to that sectors layer

    Portals can be different layers so the player layer interacts only with layers specified in project settings.

    Add portals is for turning on the mesh renderer of portals on when you enter a portal and turning it off when you exit the portal.

    Remove portals is for turning the mesh renderer of portals off when you enter a portal and turning it on when you exit the portal.

    You can put more than one portal in a spot so you can render two or more sectors while the portal game objects are turned off.

    This script is put on the portal quads.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Rendering;
    3.  
    4. public class PortalSwitch : MonoBehaviour
    5. {
    6.  
    7.     public GameObject Sector;
    8.  
    9.     public int NextSector;
    10.  
    11.     public int LastSector;
    12.  
    13.     public float PortalDistance = 0.7f;
    14.  
    15.     public GameObject[] AddPortals;
    16.  
    17.     public GameObject[] RemovePortals;
    18.  
    19.     private Material[] SectorMaterials;
    20.  
    21.     private Plane portalPlane;
    22.  
    23.     private GameObject Player;
    24.  
    25.     void Start()
    26.     {
    27.         Player = GameObject.FindWithTag("Player");
    28.         Mesh portalMesh = GetComponent<MeshFilter>().sharedMesh;
    29.         Vector3[] normals = portalMesh.normals;
    30.         for (int i = 0; i < normals.Length; ++i)
    31.         {
    32.             portalPlane.SetNormalAndPosition(transform.TransformDirection(normals[i]), transform.position);
    33.         }
    34.         SectorMaterials = Sector.GetComponent<Renderer>().sharedMaterials;
    35.         for (int i = 0; i < SectorMaterials.Length; ++i)
    36.         {
    37.             SectorMaterials[i].SetInt("_SectorComp", (int)CompareFunction.Equal);
    38.         }
    39.     }
    40.     void OnTriggerStay(Collider other)
    41.     {
    42.         if (portalPlane.GetDistanceToPoint(Camera.main.transform.position) < PortalDistance)
    43.         {
    44.             Player.gameObject.layer = NextSector;
    45.             for (int i = 0; i < SectorMaterials.Length; ++i)
    46.             {
    47.                 SectorMaterials[i].SetInt("_SectorComp", (int)CompareFunction.Always);
    48.             }
    49.             for (int i = 0; i < AddPortals.Length; ++i)
    50.             {
    51.                 AddPortals[i].GetComponent<Renderer>().enabled = true;
    52.             }
    53.             for (int i = 0; i < RemovePortals.Length; ++i)
    54.             {
    55.                 RemovePortals[i].GetComponent<Renderer>().enabled = false;
    56.             }
    57.         }
    58.         else
    59.         {
    60.             Player.gameObject.layer = LastSector;
    61.             for (int i = 0; i < SectorMaterials.Length; ++i)
    62.             {
    63.                 SectorMaterials[i].SetInt("_SectorComp", (int)CompareFunction.Equal);
    64.             }
    65.             for (int i = 0; i < AddPortals.Length; ++i)
    66.             {
    67.                 AddPortals[i].GetComponent<Renderer>().enabled = false;
    68.             }
    69.             for (int i = 0; i < RemovePortals.Length; ++i)
    70.             {
    71.                 RemovePortals[i].GetComponent<Renderer>().enabled = true;
    72.             }
    73.         }
    74.     }
    75.     void OnDestroy()
    76.     {
    77.         for (int i = 0; i < SectorMaterials.Length; ++i)
    78.         {
    79.             SectorMaterials[i].SetInt("_SectorComp", (int)CompareFunction.Equal);
    80.         }
    81.     }
    82. }
    83.  

    This is the write stencil shader for the portal quads.

    Code (CSharp):
    1. Shader "Custom/Portal"
    2. {
    3.     Properties
    4.     {
    5.         [IntRange] _PortalRef("Portal Ref Number", Range(0,255)) = 0
    6.     }
    7.         SubShader
    8.     {
    9.         Tags { "RenderType" = "Opaque" "Queue" = "Geometry-1"}
    10.  
    11.         Stencil
    12.         {
    13.             Ref[_PortalRef]
    14.             Comp Always
    15.             Pass Replace
    16.         }
    17.  
    18.         Pass
    19.         {
    20.             Blend Zero One
    21.             ZWrite Off
    22.         }
    23.     }
    24. }
    25.  

    This is the read stencil shader to put on overlapping sector game objects.

    Code (CSharp):
    1. Shader "Custom/Sector"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (1,1,1,1)
    6.         _MainTex("Albedo (RGB)", 2D) = "white" {}
    7.         _Glossiness("Smoothness", Range(0,1)) = 0.5
    8.         _Metallic("Metallic", Range(0,1)) = 0.0
    9.         [HDR] _Emission("Emission", color) = (0,0,0)
    10.         [Enum(Equal,3,Always,8)] _SectorComp("Sector Comp", int) = 3
    11.         [IntRange] _SectorRef("Sector Ref Number", Range(0,255)) = 0
    12.     }
    13.         SubShader
    14.         {
    15.             Tags { "RenderType" = "Opaque" }
    16.             LOD 300
    17.  
    18.             Stencil
    19.             {
    20.                 Ref[_SectorRef]
    21.                 Comp[_SectorComp]
    22.             }
    23.  
    24.             CGPROGRAM
    25.             // Physically based Standard lighting model, and enable shadows on all light types
    26.             #pragma surface surf Standard fullforwardshadows
    27.  
    28.             // Use shader model 3.0 target, to get nicer looking lighting
    29.             #pragma target 3.0
    30.  
    31.             sampler2D _MainTex;
    32.  
    33.             struct Input
    34.             {
    35.                 float2 uv_MainTex;
    36.             };
    37.  
    38.             half _Glossiness;
    39.             half _Metallic;
    40.             fixed4 _Color;
    41.             half3 _Emission;
    42.  
    43.             // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    44.             // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    45.             // #pragma instancing_options assumeuniformscaling
    46.             UNITY_INSTANCING_BUFFER_START(Props)
    47.                 // put more per-instance properties here
    48.             UNITY_INSTANCING_BUFFER_END(Props)
    49.  
    50.             void surf(Input IN, inout SurfaceOutputStandard o)
    51.             {
    52.                 // Albedo comes from a texture tinted by color
    53.                 fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    54.                 o.Albedo = c.rgb;
    55.                 // Metallic and smoothness come from slider variables
    56.                 o.Metallic = _Metallic;
    57.                 o.Smoothness = _Glossiness;
    58.                 o.Alpha = c.a;
    59.                 o.Emission = _Emission * tex2D(_MainTex, IN.uv_MainTex);
    60.             }
    61.             ENDCG
    62.         }
    63.             FallBack "Diffuse"
    64. }
    65.  
     
    Last edited: Dec 22, 2020
    Antypodish likes this.
  2. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    561
    Impressive. Thanks for sharing this.
     
  3. KokkuHub

    KokkuHub

    Joined:
    Feb 15, 2018
    Posts:
    707
    Very nice. I remember doing something like that while playing around with the level editor in Duke Nukem 3D, since the engine allowed it.
     
unityunity