Author Topic: Tasharen Fog of War  (Read 115205 times)

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #120 on: February 06, 2015, 10:14:28 PM »
Linear color space, despite its name, is not linear -- so interpolation isn't smoothly going from A to B, but rather following a curve. The textures used by the fog of war shouldn't be using linear color space. They need to be forced to use gamma. Try the "bypass sRGB sampling" option.

FuriousBroccoli

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 2
    • View Profile
Re: Tasharen Fog of War
« Reply #121 on: February 07, 2015, 12:54:42 AM »
Great, that did the trick - the fog textures actually need to have their color space match that of the player.  This was an easy fix in code:

FOWSystem.cs
  1. mTexture0 = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false);
  2. mTexture1 = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false);
  3.  

becomes...

  1. mTexture0 = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false, QualitySettings.activeColorSpace == ColorSpace.Linear);
  2. mTexture1 = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false, QualitySettings.activeColorSpace == ColorSpace.Linear);
  3.  

and it works for both color spaces. Thanks!

ETM

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 0
  • Posts: 7
    • View Profile
Re: Tasharen Fog of War
« Reply #122 on: March 12, 2015, 05:16:32 AM »
I am having a hard time getting FOW to work in Unity 5.0. It is working correctly with a single camera using image effects, but when I use it with two cameras, the second one rendering to a rendertexture, the fog of war doesn't seem right. Random places appear dark and other visible. Before Unity 5.0 I used the custom shader solution, which worked great, but now I cannot use it anymore due to a shader issue which turns the material pink.

The shader throws the following error:
Too many texture interpolators would be used for ForwardBase pass (12 out of max 10)
 at line 28.


ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #123 on: March 12, 2015, 09:26:13 PM »
FOW Image Effect needs to be on the camera that doesn't render to texture. Your camera that renders to texture should be drawn first, followed by your main camera (that should also have FOWImageEffect on it).

piotrO

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 2
    • View Profile
Re: Tasharen Fog of War
« Reply #124 on: March 15, 2015, 05:37:15 PM »
Hi,

I have two pre-purchase questions regarding the FOW plugin.

1) I have a modular dungeon level with a top down camera. My own FOW solution (a semi-transparent plane above the level) works ok when camera looks down from a distance. But from up close it clearly shows that my trivial approach isn't working as expected (Two pics attached for a reference.). My question is: will your plugin handle well scenes like dungeons with corridors, and the fog will smoothly cover both the walls and floors?


2) Is it ease to integrate your plugin (the shader part is what interests me most) with my custom fow logic?

Thanks

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #125 on: March 16, 2015, 09:34:11 PM »
1. Yes, it will handle it just fine, especially if you stick to the image effect-based approach.

2. Depends on what you'd like to integrate. By default the kit will use an image-effect based approach, meaning it will just work as a post-process effect, without the need to create any shaders on your part.

ETM

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 0
  • Posts: 7
    • View Profile
Re: Tasharen Fog of War
« Reply #126 on: March 20, 2015, 09:00:41 AM »
FOW Image Effect needs to be on the camera that doesn't render to texture. Your camera that renders to texture should be drawn first, followed by your main camera (that should also have FOWImageEffect on it).

That doesn't help because there is no fog of war on the render texture.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #127 on: March 22, 2015, 08:30:32 AM »
Render texture camera erases depth that's used by the fog of war. That's why I said it needs to be taken care of first before main rendering begins.

ETM

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 0
  • Posts: 7
    • View Profile
Re: Tasharen Fog of War
« Reply #128 on: March 23, 2015, 09:27:02 AM »
I am confused, could you please provide a short step by step tutorial on how to get an rts-like fow working with ugui?
What I did is:
1. Set depth of orthographic camera (minimap) to 1, change to render texture, assign the render texture to a raw image component.
2. Set depth of perspective camera (main camera) to 0, added fowimageeffect to it.
3. Saw the main camera having fog of war, the minimap not so much.

How do you specify the order of drawing if not by setting depth?

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #129 on: March 23, 2015, 10:05:22 AM »
The minimap won't have a fog of war. Why would it? Image effect only affects the main camera. What you should be doing instead is using the FOW's textures and blending them in your minimap's shader. Here's a shader I use on my minimap in Windward, as an example:
  1. Shader "Minimap UI/Fog of War"
  2. {
  3.         Properties
  4.         {
  5.                 _MainTex ("TexCoord.y (R)", 2D) = "white" {}
  6.                 _FogTex0 ("Visible (R), Explored (G)", 2D) = "black" {}
  7.                 _FogTex1 ("Visible (R), Explored (G)", 2D) = "black" {}
  8.                 _Gradient ("Gradient", 2D) = "white" {}
  9.         }
  10.  
  11.         SubShader
  12.         {
  13.                 LOD 100
  14.  
  15.                 Tags
  16.                 {
  17.                         "Queue" = "Transparent"
  18.                         "IgnoreProjector" = "True"
  19.                         "RenderType" = "Transparent"
  20.                 }
  21.  
  22.                 Pass
  23.                 {
  24.                         Cull Off
  25.                         Lighting Off
  26.                         ZWrite Off
  27.                         Offset -1, -1
  28.                         Fog { Mode Off }
  29.                         ColorMask RGB
  30.                         Blend SrcAlpha OneMinusSrcAlpha
  31.                
  32.                         CGPROGRAM
  33.                         #pragma vertex vert
  34.                         #pragma fragment frag
  35.                         #include "UnityCG.cginc"
  36.  
  37.                         sampler2D _MainTex;
  38.                         sampler2D _FogTex0;
  39.                         sampler2D _FogTex1;
  40.                         sampler2D _Gradient;
  41.  
  42.                         float4 _MainTex_ST;
  43.                         float4 _FogTex0_ST;
  44.                         half _Blend;
  45.  
  46.                         struct appdata_t
  47.                         {
  48.                                 float4 vertex : POSITION;
  49.                                 half4 color : COLOR;
  50.                                 float2 texcoord : TEXCOORD0;
  51.                                 float2 texcoord1 : TEXCOORD1;
  52.                         };
  53.  
  54.                         struct v2f
  55.                         {
  56.                                 float4 vertex : POSITION;
  57.                                 half4 color : COLOR;
  58.                                 float2 texcoord : TEXCOORD0;
  59.                                 float2 texcoord1 : TEXCOORD1;
  60.                         };
  61.  
  62.                         v2f vert (appdata_t v)
  63.                         {
  64.                                 v2f o;
  65.                                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
  66.                                 o.color = v.color;
  67.                                 o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
  68.                                 o.texcoord1 = TRANSFORM_TEX(v.texcoord1, _FogTex0);
  69.                                 return o;
  70.                         }
  71.  
  72.                         half4 frag (v2f IN) : COLOR
  73.                         {
  74.                                 half4 fog = lerp(tex2D(_FogTex0, IN.texcoord1), tex2D(_FogTex1, IN.texcoord1), _Blend);
  75.                                 half offset = tex2D(_MainTex, IN.texcoord).r;
  76.        
  77.                                 half4 visible = tex2D(_Gradient, half2(0.25, offset));
  78.                                 half4 explored = tex2D(_Gradient, half2(0.5, offset));
  79.                                 half4 hidden = tex2D(_Gradient, half2(0.75, offset));
  80.        
  81.                                 half4 final = lerp(lerp(hidden, explored, fog.g), visible, fog.r) * IN.color;
  82.  
  83.                                 float2 temp = abs(IN.texcoord * 2.0 - 1.0);
  84.                                 float val = max(temp.x, temp.y);
  85.                                 val *= val;
  86.                                 val *= val;
  87.                                 val *= val;
  88.                                 val *= val;
  89.                                 final.a *= 1.0 - val;
  90.                                 return final;
  91.                         }
  92.                         ENDCG
  93.                 }
  94.         }
  95. }
  96.  
Note that it won't work for you out-of-the-box as Windward has a script that sets "terrainOffset" and "terrainScale" values inside, and I doubt that it will apply to your game. What I basically do is render the terrain with a top-down camera into a render texture, then draw this render texture as the minimap, applying the shader above. Inside the shader I sample the Fog of War textures (there are 2 of them!), blending from one to the other correctly.

ETM

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 0
  • Posts: 7
    • View Profile
Re: Tasharen Fog of War
« Reply #130 on: March 23, 2015, 11:32:34 AM »
That really looks like a backward solution to my problem. What's the point of having a fog of war that cannot be used for minimaps easily. The custom shader solution worked great without any problems before 5.0, but is unusable now while the image effect solution has obvious drawbacks. Do you plan to update the terrain shader or should I just ask the Unity for a refund? This whole problem set my release date back for several weeks now.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #131 on: March 24, 2015, 10:56:15 AM »
Actually my solution is the proper one. When drawing the minimap, it's not a good idea to draw *everything*. You should only draw only specific layers, then overlay clear indicators for things like NPCs. It's also a good idea to have a stylized looking map, not just a top-down view of the whole region. Look at what the minimap looks like in Windward. Not only is it clearly showing the region's coastlines (drawn with the shader I pasted in the previous post), but all units are clearly visible as icons.

The drawing of the map, since it happens into a render texture, is done only twice per second, rather than every frame like it would need to be done with your approach. This, combined with the need to only draw the terrain using an unlit replacement shader is extremely efficient -- order of magnitude more efficient than what would be happening with your approach.

I will look into why the terrain shader doesn't work in Unity 5, while it worked fine in 4 at some point, but I can't give you an exact timeframe as I have much on my plate.

I'm not sure why you don't just go back to Unity 4 here. Why did you upgrade to a new version right before a release? You yourself said that it worked fine before. Not sure how a refund is going to solve anything either, but that's certainly your choice.

ETM

  • Newbie
  • *
  • Thank You
  • -Given: 1
  • -Receive: 0
  • Posts: 7
    • View Profile
Re: Tasharen Fog of War
« Reply #132 on: March 24, 2015, 01:21:00 PM »
How do you setup the shader below then? I cannot get it to work. I've added
  1. public Material GetMaterial()
  2.         {
  3.                 return mMat;
  4.         }

to FOWImageEffect.cs and trying to get the textures like this
  1. public FOWImageEffect _imageEffect;
  2.         public Material _fowMaterial;
  3.  
  4.  
  5.         IEnumerator Start ()
  6.         {
  7.                 while (_imageEffect.GetMaterial() == null)
  8.                 {
  9.                         yield return new WaitForSeconds(1f);
  10.                 }
  11.  
  12.                 Material fowMat = _imageEffect.GetMaterial();
  13.                 _fowMaterial.SetTexture("_FogTex0", fowMat.GetTexture("_FogTex0"));
  14.                 _fowMaterial.SetTexture("_FogTex1", fowMat.GetTexture("_FogTex1"));
  15.         }

Not sure what the MainTex and Gradient parameters are and how to find them in the FOWImageEffect material. Notice: _fowMaterial refers to the material posted above. FogTex0 and FogTex1 are retrieved as expected, the texture shown by the RawImage component is white. Not sure where I would put "terrainOffset" and "terrainScale".


ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #133 on: March 24, 2015, 01:46:51 PM »
Any variable set in the shader must be set in OnRenderImage (which gets called prior to something being drawn). You can't just set it in Start(). Unity doesn't work like that.

Here are the two scripts I'm using:
  1. using UnityEngine;
  2.  
  3. /// <summary>
  4. /// Very basic game map script that takes a camera and renderes it into a texture using the specified replacement shader.
  5. /// </summary>
  6.  
  7. public class MinimapPlain : MonoBehaviour
  8. {
  9.         /// <summary>
  10.         /// Renderer used to draw the map.
  11.         /// </summary>
  12.  
  13.         public Camera mapRenderer;
  14.  
  15.         /// <summary>
  16.         /// Shader used to draw the map with.
  17.         /// </summary>
  18.  
  19.         public Shader mapRendererShader;
  20.  
  21.         /// <summary>
  22.         /// Material to update.
  23.         /// </summary>
  24.  
  25.         public Material material;
  26.  
  27.         /// <summary>
  28.         /// Width of the minimap's texture.
  29.         /// </summary>
  30.  
  31.         public int width = 128;
  32.  
  33.         /// <summary>
  34.         /// Height of the minimap's texture.
  35.         /// </summary>
  36.  
  37.         public int height = 128;
  38.  
  39.         /// <summary>
  40.         /// How often the map will be checked for updates.
  41.         /// </summary>
  42.  
  43.         public float updateFrequency = 0.25f;
  44.  
  45.         [System.NonSerialized] protected Transform mTrans;
  46.         [System.NonSerialized] protected RenderTexture mRT;
  47.         [System.NonSerialized] protected float mNextUpdate = 0f;
  48.         [System.NonSerialized] protected Vector3 mLastPos = Vector3.zero;
  49.         [System.NonSerialized] protected float mLastSize = 0f;
  50.         [System.NonSerialized] protected int mWidth = 0;
  51.         [System.NonSerialized] protected int mHeight = 0;
  52.         [System.NonSerialized] protected bool mRefresh = true;
  53.         [System.NonSerialized] protected bool mSizeChanged = false;
  54.  
  55.         /// <summary>
  56.         /// Return 'false' if you don't want to render the map (such as while the world is loading).
  57.         /// </summary>
  58.  
  59.         protected virtual bool canRender { get { return (Terrain.activeTerrain != null); } }
  60.  
  61.         /// <summary>
  62.         /// Cache the transform and register callbacks.
  63.         /// </summary>
  64.  
  65.         protected virtual void Awake ()
  66.         {
  67.                 mRefresh = true;
  68.                 mTrans = transform;
  69.  
  70.                 if (mapRenderer == null)
  71.                 {
  72.                         Debug.LogError("Expected to find a map renderer to work with", this);
  73.                         enabled = false;
  74.                         return;
  75.                 }
  76.         }
  77.  
  78.         protected void Start () { OnStart(); Update(); }
  79.  
  80.         /// <summary>
  81.         /// Mark the map as changed.
  82.         /// </summary>
  83.  
  84.         protected void Invalidate () { mRefresh = true; mSizeChanged = true; }
  85.         protected void OnApplicationFocus (bool focus) { mRefresh = true; mSizeChanged = true; }
  86.  
  87.         /// <summary>
  88.         /// Update what's necessary.
  89.         /// </summary>
  90.  
  91.         protected virtual void Update ()
  92.         {
  93.                 if (mLastPos != mapRenderer.transform.position || mLastSize != mapRenderer.orthographicSize)
  94.                 {
  95.                         mLastPos = mapRenderer.transform.position;
  96.                         mLastSize = mapRenderer.orthographicSize;
  97.                         mRefresh = true;
  98.                 }
  99.  
  100.                 if (mWidth != width || mHeight != height)
  101.                 {
  102.                         mSizeChanged = true;
  103.                         mWidth = width;
  104.                         mHeight = height;
  105.                         mRefresh = true;
  106.                 }
  107.  
  108.                 if (canRender && (mRefresh || mNextUpdate < Time.time))
  109.                 {
  110.                         mNextUpdate = Time.time + updateFrequency;
  111.  
  112.                         if (mRefresh)
  113.                         {
  114.                                 Terrain ter = Terrain.activeTerrain;
  115.  
  116.                                 if (ter != null)
  117.                                 {
  118.                                         Shader.SetGlobalFloat("terrainOffset", ter.transform.position.y);
  119.                                         Shader.SetGlobalFloat("terrainScale", ter.terrainData.heightmapScale.y);
  120.                                 }
  121.  
  122.                                 if (mSizeChanged && mRT != null)
  123.                                 {
  124.                                         Destroy(mRT);
  125.                                         mRT = null;
  126.                                 }
  127.  
  128.                                 if (mRT == null)
  129.                                 {
  130.                                         mRT = new RenderTexture(mWidth, mHeight, 24, RenderTextureFormat.ARGB32);
  131.                                         mRT.name = name;
  132.                                         mRT.generateMips = false;
  133.                                 }
  134.  
  135.                                 // Render the map into the render texture
  136.                                 mapRenderer.targetTexture = mRT;
  137.                                 mapRenderer.RenderWithShader(mapRendererShader, "");
  138.                                 mapRenderer.targetTexture = null;
  139.                         }
  140.  
  141.                         OnUpdate(mRefresh);
  142.                         mRefresh = false;
  143.                         mSizeChanged = false;
  144.                 }
  145.         }
  146.  
  147.         /// <summary>
  148.         /// Invalidate the render texture's dimensions when returning to the app.
  149.         /// </summary>
  150.  
  151.         protected void OnApplicationPause (bool isPaused)
  152.         {
  153.                 if (!isPaused)
  154.                 {
  155.                         mRefresh = true;
  156.                         mSizeChanged = true;
  157.                         Update();
  158.                 }
  159.         }
  160.  
  161.         /// <summary>
  162.         /// Clear the refresh flag at the end.
  163.         /// </summary>
  164.  
  165.         protected void LateUpdate () { OnLateUpdate(); }
  166.  
  167.         /// <summary>
  168.         /// Anything you need to do in Start.
  169.         /// </summary>
  170.  
  171.         protected virtual void OnStart () { }
  172.  
  173.         /// <summary>
  174.         /// Anything else you might want to update (target indicators and such).
  175.         /// The 'rebuild' parameter will be 'true' if the map texture was rebuilt.
  176.         /// </summary>
  177.  
  178.         protected virtual void OnUpdate (bool rebuild) { }
  179.  
  180.         /// <summary>
  181.         /// Anything you need to do in Late Update.
  182.         /// </summary>
  183.  
  184.         protected virtual void OnLateUpdate () { if (material != null) material.mainTexture = mRT; }
  185.  
  186.         /// <summary>
  187.         /// Anything you need to do in OnDestroy.
  188.         /// </summary>
  189.  
  190.         protected virtual void OnDestroy ()
  191.         {
  192.                 if (mRT != null)
  193.                 {
  194.                         Destroy(mRT);
  195.                         mRT = null;
  196.                 }
  197.         }
  198. }
  199.  
The first script references a top-down orthographic camera (mapRenderer) as well as the replacement shader to use (mapRendererShader). The top-down camera should be positioned and sized in such a way that it's able to cover all of your visible region (or at least whatever you want to be actually visible). It must also be disabled. The script above will take care of enabling it as needed. The shader is simply:
  1. Shader "Minimap/Heightmap"
  2. {
  3.         SubShader
  4.         {
  5.                 LOD 300
  6.                 Tags { "RenderType" = "Opaque" }
  7.                 Fog { Mode Off }
  8.  
  9.                 Pass
  10.                 {
  11.                         CGPROGRAM
  12.                         #pragma vertex vert
  13.                         #pragma fragment frag
  14.                         #include "UnityCG.cginc"
  15.  
  16.                         float terrainOffset = 0.0;
  17.                         float terrainScale = 16.0;
  18.  
  19.                         struct appdata_t
  20.                         {
  21.                                 float4 vertex : POSITION;
  22.                         };
  23.  
  24.                         struct v2f
  25.                         {
  26.                                 float4 pos : POSITION;
  27.                                 float2 uv : TEXCOORD0;
  28.                         };
  29.  
  30.                         v2f vert (appdata_t v)
  31.                         {
  32.                                 v2f o;
  33.                                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  34.                                 float4 worldPos = mul(_Object2World, v.vertex);
  35.                                 float f = (worldPos.y + terrainOffset) / terrainScale;
  36.                                 o.uv = float2(f, f);
  37.                                 return o;
  38.                         }
  39.  
  40.                         fixed4 frag (v2f IN) : COLOR
  41.                         {
  42.                                 return half4(IN.uv.xxx, 1.0);
  43.                         }
  44.                         ENDCG
  45.                 }
  46.         }
  47.         FallBack Off
  48. }
The second script extends the first one, adding Fog of War to it.
  1. using UnityEngine;
  2.  
  3. public class MinimapFoW : MinimapPlain
  4. {
  5.         /// <summary>
  6.         /// Gradient texture used to color the map.
  7.         /// </summary>
  8.  
  9.         public Texture2D gradient;
  10.  
  11.         [System.NonSerialized] protected float mFogScale = 1f;
  12.         [System.NonSerialized] protected float mFogOffset = 0f;
  13.  
  14.         protected override void OnLateUpdate ()
  15.         {
  16.                 base.OnLateUpdate();
  17.  
  18.                 if (FOWSystem.instance != null)
  19.                 {
  20.                         float camRange = mapRenderer.GetComponent<Camera>().orthographicSize * 2f;
  21.                         mFogScale = camRange / FOWSystem.instance.worldSize;
  22.                         mFogOffset = (1f - mFogScale) * 0.5f;
  23.  
  24.                         material.SetFloat("_Blend", FOWSystem.instance.blendFactor);
  25.                         material.SetTexture("_FogTex0", FOWSystem.instance.texture0);
  26.                         material.SetTexture("_FogTex1", FOWSystem.instance.texture1);
  27.                         material.SetTexture("_Gradient", gradient);
  28.                         material.SetTextureScale("_FogTex0", new Vector2(mFogScale, mFogScale));
  29.                         material.SetTextureOffset("_FogTex0", new Vector2(mFogOffset, mFogOffset));
  30.                 }
  31.         }
  32. }
  33.  
« Last Edit: March 24, 2015, 03:41:33 PM by ArenMook »

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Tasharen Fog of War
« Reply #134 on: March 24, 2015, 01:52:37 PM »
As for the gradient... It's a texture that has 3 parts: left, middle and right.

I made it so that the left part is used when the region is visible. Middle = explored, but not currently visible. Right = not yet explored.