Unity fog of war tutorial. Part 2. Mobile version (web demo)

This article is extension of my previous tutorial about fog of war implementation in Unity. My previous technique is good for mobile device, but it still too heavy for it. That’s why I’ll write a mobile version of fog of war for Unity in this little tutorial.  It will looks different bug will work faster.

Author: Sergey Taraban

Part 1

Web player demo

fog_of_war_vertex

Use WASD buttons to control player movement.

Download pack file: fog_of_war_tutorial.unitypackage

For Unity 5 add “alpha:blend” to line #pragma surface surf Lambert vertex:vert

New aperture shader

For increasing fps I move aperture computation algorithm from fragment to vertex shader. This will make my shader more lightweight.  After this shader pass number will depend on vertex number in 3D model and not from fragments number on screen. 3D model in out case is fog of war plane.

You need to create new shader and copy the code below to it. Then attach shader to fog of war material.

// FogOfWarVertex shader
// Copyright (C) 2013 Sergey Taraban 
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

Shader "Custom/FogOfWarGeom" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Color (RGB) Alpha (A)", 2D) = "white"
        _FogRadius ("FogRadius", Float) = 1.0
        _FogMaxRadius ("FogMaxRadius", Float) = 0.5
        _Player1_Pos ("UnitPos1", Vector) = (0,0,0,1)
        _Player2_Pos ("UnitPos2", Vector) = (0,0,0,1)
        _Player3_Pos ("UnitPos3", Vector) = (0,0,0,1)
    }

    SubShader {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite On
        Cull Off 

        CGPROGRAM
        #pragma target 3.0
        #pragma surface surf Lambert vertex:vert alpha:blend

        sampler2D _MainTex;
        fixed4 _Color;
        float4 _Player1_Pos;
        float4 _Player2_Pos;
        float4 _Player3_Pos;
        float _FogRadius;
        float _FogMaxRadius;

        struct Input {
            float4 pos;
            float alpha;
            float2 location;
        };

        float powerForPos(float4 pos, float2 nearVertex);

        void vert (inout appdata_full vertexData, out Input outData)
        {
            float2 posVertex = mul (_Object2World, vertexData.vertex).xz;
            outData.alpha = (1.0 - clamp(_Color.a 
                        + ( powerForPos(_Player1_Pos, posVertex) 
                        + powerForPos(_Player2_Pos, posVertex)
                        + powerForPos(_Player3_Pos, posVertex) ), 0, 1.0));
        }

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = _Color.rgb;
            o.Alpha = IN.alpha;
        }

        float powerForPos(float4 pos, float2 nearVertex) {
             float attenumat = clamp(_FogRadius - abs(length(pos.xz - nearVertex.xy)), 0.0, _FogRadius);
             if(attenumat > _FogRadius*_FogMaxRadius) {
                 attenumat = _FogRadius*_FogMaxRadius;  
             }
            return (1.0/_FogMaxRadius)*(attenumat/_FogRadius);
        }

        ENDCG
    }

Fallback "Transparent/VertexLit"
}

Script for plane generation

Now we need to write a script for plane generation with various vertex count. For basis I used CreatePlane editor script from Unity wikipedia. I changed it to MonoBehaviour script.

Copy code below to new script and attach them to for of war plane object. If you have Mesh Collider on it change it to Box Collider with size (1, 0.01, 1). Then set up object scale you needed.

using UnityEngine;
using System.Collections;

public class CreatePlaneScript : MonoBehaviour
{

    public enum Orientation
    {
        Horizontal,
        Vertical
    }

    public enum AnchorPoint
    {
        TopLeft,
        TopHalf,
        TopRight,
        RightHalf,
        BottomRight,
        BottomHalf,
        BottomLeft,
        LeftHalf,
        Center
    }

    public int widthSegments = 1;
    public int lengthSegments = 1;
    public float width = 1.0f;
    public float length = 1.0f;
    public Orientation orientation = Orientation.Horizontal;
    public AnchorPoint anchor = AnchorPoint.Center;
    public bool addCollider = false;
    public bool createAtOrigin = true;
    public string optionalName;

    void Start()
    {
        BuildPlane();
    }

    void Update()
    {
        widthSegments = Mathf.Clamp(widthSegments, 1, 254);
        lengthSegments = Mathf.Clamp(lengthSegments, 1, 254);
    }

    public void BuildPlane()
    {

        Vector2 anchorOffset;
        string anchorId;
        switch (anchor)
        {
        case AnchorPoint.TopLeft:
            anchorOffset = new Vector2(-width/2.0f,length/2.0f);
            anchorId = "TL";
            break;
        case AnchorPoint.TopHalf:
            anchorOffset = new Vector2(0.0f,length/2.0f);
            anchorId = "TH";
            break;
        case AnchorPoint.TopRight:
            anchorOffset = new Vector2(width/2.0f,length/2.0f);
            anchorId = "TR";
            break;
        case AnchorPoint.RightHalf:
            anchorOffset = new Vector2(width/2.0f,0.0f);
            anchorId = "RH";
            break;
        case AnchorPoint.BottomRight:
            anchorOffset = new Vector2(width/2.0f,-length/2.0f);
            anchorId = "BR";
            break;
        case AnchorPoint.BottomHalf:
            anchorOffset = new Vector2(0.0f,-length/2.0f);
            anchorId = "BH";
            break;
        case AnchorPoint.BottomLeft:
            anchorOffset = new Vector2(-width/2.0f,-length/2.0f);
            anchorId = "BL";
            break;            
        case AnchorPoint.LeftHalf:
            anchorOffset = new Vector2(-width/2.0f,0.0f);
            anchorId = "LH";
            break;            
        case AnchorPoint.Center:
        default:
            anchorOffset = Vector2.zero;
            anchorId = "C";
            break;
        }

        MeshFilter meshFilter = this.GetComponent();

        Mesh m = new Mesh();
        m.name = this.name;

        int hCount2 = widthSegments+1;
        int vCount2 = lengthSegments+1;
        int numTriangles = widthSegments * lengthSegments * 6;
        int numVertices = hCount2 * vCount2;

        Vector3[] vertices = new Vector3[numVertices];
        Vector2[] uvs = new Vector2[numVertices];
        int[] triangles = new int[numTriangles];

        int index = 0;
        float uvFactorX = 1.0f/widthSegments;
        float uvFactorY = 1.0f/lengthSegments;
        float scaleX = width/widthSegments;
        float scaleY = length/lengthSegments;
        for (float y = 0.0f; y < vCount2; y++)
        {
            for (float x = 0.0f; x < hCount2; x++)
            {
                if (orientation == Orientation.Horizontal)
                {
                    vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, 0.0f, y*scaleY - length/2f - anchorOffset.y);
                }
                else
                {
                    vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, y*scaleY - length/2f - anchorOffset.y, 0.0f);
                }
                uvs[index++] = new Vector2(x*uvFactorX, y*uvFactorY);
            }
        }

        index = 0;
        for (int y = 0; y < lengthSegments; y++)
        {
            for (int x = 0; x < widthSegments; x++)
            {
                triangles[index]   = (y     * hCount2) + x;
                triangles[index+1] = ((y+1) * hCount2) + x;
                triangles[index+2] = (y     * hCount2) + x + 1;

                triangles[index+3] = ((y+1) * hCount2) + x;
                triangles[index+4] = ((y+1) * hCount2) + x + 1;
                triangles[index+5] = (y     * hCount2) + x + 1;
                index += 6;
            }
        }

        m.vertices = vertices;
        m.uv = uvs;
        m.triangles = triangles;
        m.RecalculateNormals();

        meshFilter.mesh = m;
        m.RecalculateBounds();

        if (addCollider) {
            this.gameObject.AddComponent(typeof(BoxCollider));
        }

        //Selection.activeObject = plane;
    }
}

 

Related links