четверг, 19 декабря 2019 г.

Portal shader

In this post you will find a portal shader for Unity 3D and a set of textures for it.



Create a file "Portal.shader", copy&paste the code of the shader into the file. Place the file into the assets catalog of your project.

In order to use the shader in your project you need to create material and to choose "My/PortalShader" as your shader.

The code of the shader. 

Shader "My/PortalShader"
{
    Properties
    {
        //0 - Main world (grab texture)
        //1 - Mix worlds
        //2 - Another world (_AnotherWorldTexture)
        _PortalFactor("Portal Factor", Range(-1.0, 1.0)) = 0
        [Toggle]_PortalOpening("Portal Opening", Float) = 1
        
        _LightingBorderSize("Lighting Border Size", Range(0, 0.1)) = 0.03
        _WorldColorFactor("World Color Factor", Range(0, 1)) = 0.7
        
        _AnotherWorldTexture("Another World Texture", 2D) = "white" {}
        _AnotherWorldPortalColor("Another World Portal Color", 2D) = "white" {}
        
        _SurfaceDistortion("Surface Distortion", 2D) = "white" {}
        _DistortionScale("Distortion Scale", Range(0.1, 3)) = 1
        _DistortionSize("Distortion Size", Range(0, 5)) = 3
        _DistortionSpeed("Distortion Speed", Range(0.01, 0.3)) = 0.05
        
        _PortalColors("Portal Colors", 2D) = "white" {}
        _PortalColorsWidthScale("Portal Colors Wid Scale", Float) = 0.0625
        
        _SurfaceNoise("Surface Noise", 2D) = "white" {}
        _NoiseSpeed("Noise Speed", Range(0.001, 0.03)) = 0.005
        _NoiseScale("Noise Scale", Range(0, 0.5)) = 0.1
        
        _PortalFrame("Portal Frame", 2D) = "black" {}
        _PortalFrameDistortionFactor("Frame Distortion factor", Range(1,20)) = 5
    }
    SubShader
    {
        Tags { "Queue" = "Transparent" }
        GrabPass { "_BackgroundTexture" }

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

            //Parameters
            float _PortalFactor;
            float _PortalOpening;
            float _LightingBorderSize;
            float _WorldColorFactor;
            float _PortalColorsWidthScale;
            float _DistortionScale;
            float _DistortionSize;
            float _DistortionSpeed;
            float _NoiseSpeed;
            float _NoiseScale;
            float _PortalFrameDistortionFactor;

            //Textures
            sampler2D _AnotherWorldTexture;
            sampler2D _AnotherWorldPortalColor;
            sampler2D _SurfaceDistortion;
            sampler2D _PortalColors;
            sampler2D _SurfaceNoise;
            sampler2D _PortalFrame;
            sampler2D _BackgroundTexture;
            
            //For TRANSFORM_TEX
            float4 _PortalFrame_ST;

            struct v2f
            {
                float4 grabPos : TEXCOORD0;
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD1;
                float2 noiseuv : TEXCOORD2;
                float2 distortuv : TEXCOORD3;
                float2 frameUV: TEXCOORD4;
            };
            
            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };

            v2f vert(appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.grabPos = ComputeGrabScreenPos(o.pos);
                o.uv = v.uv;
                o.distortuv = v.uv * _DistortionScale;
                o.frameUV = TRANSFORM_TEX(v.uv, _PortalFrame);
                
                o.noiseuv = v.uv * _NoiseScale;
                return o;
            }

            half4 frag(v2f i) : SV_Target
            {
                float2 offset1 = float2(_Time.y * _DistortionSpeed, _Time.y * _DistortionSpeed);
                float2 offset2 = -float2(
                    _Time.y * _DistortionSpeed * 1.1, 
                    _Time.y * _DistortionSpeed * 0.9) + float2(0.5, 0.5);
                half4 distortionValue1 = (tex2D(_SurfaceDistortion, i.distortuv + offset1) * 2 - 1);
                half4 distortionValue2 = (tex2D(_SurfaceDistortion, i.distortuv + offset2) * 2 - 1);

                half4 distortionValue = (distortionValue1 + distortionValue2) * 0.5;
                half4 distortionNoise = distortionValue * _DistortionSize;

                //Calculate color texture UV
                float2 colorUV1 = (i.uv * float2(_PortalColorsWidthScale, 1)) + float2(_Time.y * 0.015, 1);
                float2 colorUV2 = (i.uv * float2(_PortalColorsWidthScale, 1)) + float2(_Time.y * 0.015, 1);
                
                //Portal color
                float2 colorNoise = float2(distortionValue.x, distortionValue.y) * 0.03;
                half4 color1 = tex2D(_PortalColors, colorUV1 + colorNoise);
                half4 color2 = tex2D(_PortalColors, colorUV2 + colorNoise);
                half4 color = (color1 + color2) * 0.5;
                
                //Another world portal color
                half4 anotherColor1 = tex2D(_AnotherWorldPortalColor, colorUV1 + colorNoise);
                half4 anotherColor2 = tex2D(_AnotherWorldPortalColor, colorUV2 + colorNoise);
                half4 anotherColor = (anotherColor1 + anotherColor2) * 0.5;
        
                //Sample noise texture
                float2 noiseOffset1 = float2(_Time.y * _NoiseSpeed, _Time.y * _NoiseSpeed);
                float2 noiseOffset2 = -float2(
                   _Time.y * _NoiseSpeed * 1.1, _Time.y * _NoiseSpeed * 0.9) + float2(0.5, 0.5);
                half4 noiseValue1 = tex2D(_SurfaceNoise, i.noiseuv + noiseOffset1);
                half4 noiseValue2 = tex2D(_SurfaceNoise, i.noiseuv + noiseOffset2);
                half4 noiseValuePow = pow(noiseValue1 * noiseValue2, 2);
                half4 noiseValueSum = noiseValue1 + noiseValue2 - 1;

                //Another world texture
                half4 anotherWorldColor = tex2Dproj(
                    _AnotherWorldTexture, 
                    i.grabPos + half4(distortionNoise.x, distortionNoise.y, 0, 0));

                //Portal world border size calculation.
                half worldBorder = 0.0;
                half worldBorderSize = _LightingBorderSize;
                half worldBorderMin = worldBorder - worldBorderSize * 0.5;
                half worldBorderMax = worldBorder + worldBorderSize * 0.5;
                
                //Portal frame
                half4 portalFrame = tex2D(
                    _PortalFrame, i.frameUV + colorNoise * _PortalFrameDistortionFactor);
                half portalBorderFactor = portalFrame.x * (1 - abs(_PortalFactor));
                
                half worldFactor = noiseValueSum.x + _PortalFactor;
                if(_PortalOpening > 0.5)
                    worldFactor += portalBorderFactor;
                else
                    worldFactor -= portalBorderFactor;
                
                //Add portal border lighting
                half4 noiseLighting;
                if(worldFactor > worldBorderMin && worldFactor < worldBorderMax)
                    noiseLighting = half4(1,1,1,0);
                else
                    noiseLighting = half4(0,0,0,1);
                
                //Mix world colors
                half4 portalWorldColor;
                half4 portalColor;
                if(worldFactor < worldBorderMax)
                {
                    portalWorldColor = tex2Dproj(
                        _BackgroundTexture, 
                        i.grabPos + half4(distortionNoise.x, distortionNoise.y, 0, 0));
                    portalColor = color;
                }
                else
                {
                    portalWorldColor = tex2Dproj(
                        _AnotherWorldTexture, 
                        i.grabPos + half4(distortionNoise.x, distortionNoise.y, 0, 0));
                    portalColor = anotherColor;
                }
                
                //Calculate result color
                half bgColorFactor = clamp(_WorldColorFactor, 0, 1);
                
                return 
                    portalWorldColor * bgColorFactor + 
                    portalColor * (1 - bgColorFactor) + 
                    noiseLighting;
            }
            ENDCG
        }

    }
}

Portal material parameters

The camera that renders the world behind the portal to texture is bound to the main camera.



The camera that renders the main world ignores the layer with the world behind the portal. 





The world behind the portal is in a separate layer and in a separate scene. The source of light is situated in the same layer and it lights up the world behind the portal only. It is possible to change the light in both scenes independently.

Noise texture

Textures with additional colors for the portal. The first one is for the main world. The second one is for the world behind the portal. They can be used for the portal to change its color from time to time.





The texture of the portal frame.


So that the border between the worlds shines, you can use a post process effect "Bloom".







The code and textures in this blog post are under the CC0 license. You can use them for free. No attribution/credits needed.


If this information was useful for you, you can support me by having a look at my game on Steam.




If you don't want to miss future shaders you may follow me on twitter.






Комментариев нет:

Отправить комментарий