# Showcase Sharing a Non Euclidean impossible space effect

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

1. ### 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.
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.             {
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.             {
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.

Code (CSharp):
2. {
3.     Properties
4.     {
5.         [IntRange] _PortalRef("Portal Ref Number", Range(0,255)) = 0
6.     }
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):
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.     }
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.
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

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

3. ### 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