Interior Mapping

Emil Persson’s Cube Map based interior mapping

Just does the interior mapping with no additional effects (exterior walls, random lighting, etc.). Does do a random flip / rotation which effectively recreates the basic look of the cube map array version, but not the actual functionality as Emil was lazy and just used the same 6 textures over and over to create 8 “unique” cube maps.

// Adapted to Unity from http://www.humus.name/index.php?page=3D&ID=80
Shader "Custom/InteriorMapping - Cubemap"
{
    Properties
    {
        _RoomCube ("Room Cube Map", Cube) = "white" {}
        [Toggle(_USEOBJECTSPACE)] _UseObjectSpace ("Use Object Space", Float) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma shader_feature _USEOBJECTSPACE
         
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
            #ifdef _USEOBJECTSPACE
                float3 uvw : TEXCOORD0;
            #else
                float2 uv : TEXCOORD0;
            #endif
                float3 viewDir : TEXCOORD1;
            };

            samplerCUBE _RoomCube;
            float4 _RoomCube_ST;

            // psuedo random
            float3 rand3(float co){
                return frac(sin(co * float3(12.9898,78.233,43.2316)) * 43758.5453);
            }
         
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

            #ifdef _USEOBJECTSPACE
                // slight scaling adjustment to work around "noisy wall" when frac() returns a 0 on surface
                o.uvw = v.vertex * _RoomCube_ST.xyx * 0.999 + _RoomCube_ST.zwz;

                // get object space camera vector
                float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
                o.viewDir = v.vertex.xyz - objCam.xyz;

                // adjust for tiling
                o.viewDir *= _RoomCube_ST.xyx;
            #else
                // uvs
                o.uv = TRANSFORM_TEX(v.uv, _RoomCube);

                // get tangent space camera vector
                float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
                float3 viewDir = v.vertex.xyz - objCam.xyz;
                float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                float3 bitangent = cross(v.normal.xyz, v.tangent.xyz) * tangentSign;
                o.viewDir = float3(
                    dot(viewDir, v.tangent.xyz),
                    dot(viewDir, bitangent),
                    dot(viewDir, v.normal)
                    );

                // adjust for tiling
                o.viewDir *= _RoomCube_ST.xyx;
            #endif
                return o;
            }
         
            fixed4 frag (v2f i) : SV_Target
            {
            #ifdef _USEOBJECTSPACE
                // room uvws
                float3 roomUVW = frac(i.uvw);

                // raytrace box from object view dir
                float3 pos = roomUVW * 2.0 - 1.0;
                float3 id = 1.0 / i.viewDir;
                float3 k = abs(id) - pos * id;
                float kMin = min(min(k.x, k.y), k.z);
                pos += kMin * i.viewDir;

                // randomly flip & rotate cube map for some variety
                float3 flooredUV = floor(i.uvw);
                float3 r = rand3(flooredUV.x + flooredUV.y + flooredUV.z);
                float2 cubeflip = floor(r.xy * 2.0) * 2.0 - 1.0;
                pos.xz *= cubeflip;
                pos.xz = r.z > 0.5 ? pos.xz : pos.zx;
            #else
                // room uvs
                float2 roomUV = frac(i.uv);

                // raytrace box from tangent view dir
                float3 pos = float3(roomUV * 2.0 - 1.0, 1.0);
                float3 id = 1.0 / i.viewDir;
                float3 k = abs(id) - pos * id;
                float kMin = min(min(k.x, k.y), k.z);
                pos += kMin * i.viewDir;

                // randomly flip & rotate cube map for some variety
                float2 flooredUV = floor(i.uv);
                float3 r = rand3(flooredUV.x + 1.0 + flooredUV.y * (flooredUV.x + 1));
                float2 cubeflip = floor(r.xy * 2.0) * 2.0 - 1.0;
                pos.xz *= cubeflip;
                pos.xz = r.z > 0.5 ? pos.xz : pos.zx;
            #endif

                // sample room cube map
                fixed4 room = texCUBE(_RoomCube, pos.xyz);
                return fixed4(room.rgb, 1.0);
            }
            ENDCG
        }
    }
}

Test cubemap:
2751518--198473--colored_cube.png

2751518--198479--interiormapping-2dAtlas.jpg
Andrew Willmott’s SimCity 5 style texture atlas interior mapping

Started with something like the above cube map based interior mapping shader, but uses a single 2D texture atlas with variable room depth. Room depth is stored in the alpha channel of the atlas texture. For a cube shaped room the back wall should be 1/2 the size of the visible tile, and a value of 128 in the alpha channel. If you render these out you want to use a camera with a horizontal FOV of 53.13 degrees a room width back from the opening.

Shader "Custom/InteriorMapping - 2D Atlas"
{
    Properties
    {
        _RoomTex ("Room Atlas RGB (A - back wall fraction)", 2D) = "white" {}
        _Rooms ("Room Atlas Rows&Cols (XY)", Vector) = (1,1,0,0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
         
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 tangentViewDir : TEXCOORD1;
            };

            sampler2D _RoomTex;
            float4 _RoomTex_ST;
            float2 _Rooms;
         
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _RoomTex);

                // get tangent space camera vector
                float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
                float3 viewDir = v.vertex.xyz - objCam.xyz;
                float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                float3 bitangent = cross(v.normal.xyz, v.tangent.xyz) * tangentSign;
                o.tangentViewDir = float3(
                    dot(viewDir, v.tangent.xyz),
                    dot(viewDir, bitangent),
                    dot(viewDir, v.normal)
                    );
                o.tangentViewDir *= _RoomTex_ST.xyx;
                return o;
            }

            // psuedo random
            float2 rand2(float co){
                return frac(sin(co * float2(12.9898,78.233)) * 43758.5453);
            }
         
            fixed4 frag (v2f i) : SV_Target
            {
                // room uvs
                float2 roomUV = frac(i.uv);
                float2 roomIndexUV = floor(i.uv);

                // randomize the room
                float2 n = floor(rand2(roomIndexUV.x + roomIndexUV.y * (roomIndexUV.x + 1)) * _Rooms.xy);
                roomIndexUV += n;

                // get room depth from room atlas alpha
                fixed farFrac = tex2D(_RoomTex, (roomIndexUV + 0.5) / _Rooms).a;
                float depthScale = 1.0 / (1.0 - farFrac) - 1.0;

                // raytrace box from view dir
                float3 pos = float3(roomUV * 2 - 1, -1);
                // pos.xy *= 1.05;
                i.tangentViewDir.z *= -depthScale;
                float3 id = 1.0 / i.tangentViewDir;
                float3 k = abs(id) - pos * id;
                float kMin = min(min(k.x, k.y), k.z);
                pos += kMin * i.tangentViewDir;

                // 0.0 - 1.0 room depth
                float interp = pos.z * 0.5 + 0.5;

                // account for perspective in "room" textures
                // assumes camera with an fov of 53.13 degrees (atan(0.5))
                float realZ = saturate(interp) / depthScale + 1;
                interp = 1.0 - (1.0 / realZ);
                interp *= depthScale + 1.0;
             
                // iterpolate from wall back to near wall
                float2 interiorUV = pos.xy * lerp(1.0, farFrac, interp);
                interiorUV = interiorUV * 0.5 + 0.5;

                // sample room atlas texture
                fixed4 room = tex2D(_RoomTex, (roomIndexUV + interiorUV.xy) / _Rooms);
                return fixed4(room.rgb, 1.0);
            }
            ENDCG
        }
    }
}

Single tile version for basic debugging. Note it looks faded out here because it’s alpha value is 50%!
2751518--198476--interior_2d.png

A 4x2 atlas with varying room lengths (1/2 room, cube, 2x length, 4x length) and lit / unlit variants. Again looks funny because of the alpha.
2751518--198477--interior_2d_Atlas.png

Both of these could be made better in several ways, especially with better art.

15 Likes