# Question
Title: How to use a Custom Class as a Dictionary Key in Unity, where one of the objects in the class is a List
I have a ScriptableObject that holds the data that I will put into a Dictionary:

```
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using Unity.VisualScripting;

[CreateAssetMenu(fileName = "New PronunciationData", menuName = "Pronunciation Data", order = 51)]

public class PronunciationData : ScriptableObject
{
    [SerializeField]
    public BlendKey thisBlendKey;

    [IncludeInSettings(true)]
    [System.Serializable]
    public class BlendKey{

        [SerializeField]
        public string soundName;

        [SerializeField]
        public List<NeemData> neemList;

    }

    [SerializeField]
    public AudioClip Pronunciation;

}

```

BlendKey will be the key for the Dictionary. It's a custom class which contains 2 items:

* A string (e.g. the letters used to make a particular sound)
* A list of items in another custom class called NeemData (e.g. a list of the sounds themselves).

NeemData is defined in another Scriptable Object. For the purposes of comparing 2 NeemDatas, I only really need to confirm that neemName is the same for both:

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

[CreateAssetMenu(fileName = "New NeemData", menuName = "Neem Data", order = 51)]

public class NeemData : ScriptableObject
{
    [SerializeField]
    public string neemName;

    [SerializeField]
    public Vector3 neemPosition;

    [SerializeField]
    public Sprite singleNeemSprite;

    [SerializeField]
    public Color baseColor;

    [SerializeField]
    public AudioClip Pronunciation;

    [SerializeField]
    public AudioClip placementAudio;

    [SerializeField]
    public List<PronunciationData> blendPronunciation;

    [SerializeField]
    public string feemText;

    [SerializeField]
    public List<FeemData> coreFeems;

    [SerializeField]
    public Color feemColor;

}

```

Pronunciation is just an Audioclip, and will be the value for the Dictionary.

The problem is that no matter what BlendKey I provide, the Dictionary returns a value of Null. In other words, it's unable to find anything using the keys I provide - even though I've done extensive checking to make sure that the key is exactly the same.

I think there are 2 possible causes:

1. I'm using a custom class as a Key. Some sources mention that when you do so, you "need to override GetHashCode() (and preferably also Equals())". https://stackoverflow.com/questions/6999191/use-custom-object-as-dictionary-key
2. One of the items in my Custom Class is a List. I've encountered problems comparing Lists before for a match, so imagine it would be even more tricky to do so here... where you have to match not only 2 lists, but also the 2 other strings (i.e. the soundNames).

I've looked into other possible data structures, but the challenge is that I really do need both the SoundName and the neemList to be a unique combination. So the Dictionary Key really does need to use both of those variables.

Is it possible to edit the Scriptable Object to use overrides, such that Unity can successfully look up values in the dictionary? Please let me know - thank you!

# Answer
> 2 votes
This can be easily solved by your first bullet point. Just implement the override. You will need to come up with a way to identify `NeemData` List on your own since I do not know the structure of it. It might be as easy as just taking the length of the array (might not be 100% unique but that would mean you first need to have the same soundname with the same amount of sound clips).

```
[Serializable]
public class BlendKey {
    [SerializeField]
    public string soundName;
    [SerializeField]
    public List<NeemData> neemList;

    public override bool Equals(object obj) {
        if (obj == null) return false;
        BlendKey c = obj as BlendKey ;
        if (c == null) return false;
        if (!soundName.Equals(c.soundName)) return false;
        if (neemList.Count != c.neemList.Count) return false;
        for (int index = 0; index < neemList.Count; index++) {
            if (!neemList[index].neemName.Equals(c.neemList[index].neemName)) return false;
        }

        return true;
    }

    public override int GetHashCode() {
        int listHash = 0;
        for (int index = 0; index < neemList.Count; index++) {
            listHash += neemList[index].neemName.GetHashCode();
        }
        return soundName.GetHashCode() + listHash;
    }
}

public class TestDic: MonoBehaviour {

    public Dictionary<BlendKey, string> testDic = new Dictionary<BlendKey, string>();
    public NeemData[] NeemDatas;

    private void Start() {
        BlendKey testKey = new BlendKey();
        testKey.soundName = "ABC";
        testKey.neemList = new List<NeemData>{NeemDatas[0], NeemDatas[1]};
        testDic.Add(testKey, "Sound 1");

        BlendKey testKey2 = new BlendKey();
        testKey2.soundName = "XYZ";
        testKey2.neemList = new List<NeemData>{NeemDatas[1], NeemDatas[2]};
        testDic.Add(testKey2, "Sound 2");

        BlendKey testKey3 = new BlendKey();
        testKey3.soundName = "ABC";
        testKey3.neemList = new List<NeemData>{NeemDatas[1], NeemDatas[0]};

        Debug.Log(testDic[testKey]);
        Debug.Log(testDic[testKey2]);
        Debug.Log(testDic[testKey3]);
    }
}

```

The third `testDic[testKey3]);` will give you a key not found error since it is not the same data as from testKey (it has the same NeemData, but the order is different). You could solve this by further adding everything for a sort interface but if that is really needed will depends on if the NeemData is generated dynamically or by hand.

---
Tags: unity, scriptable-object
---


title: Picture-in-picture in Unity

text:
# Question
Title: Picture-in-picture in Unity
This is my first post here and I'm not too experienced with Unity, so keep that in mind. I am working on a project (a virtual streaming app specifically) where I have four cameras that the user can switch between with number keys.

The program has a room with a table in it, and a character is located in front of that table. The character is controlled using outside software and the VMC protocol.

The four different cameras are located in the front of the room, pointing toward the character's head, on a table, and a first person camera with a fixed position, but whose rotation corresponds to that of one of the character's bones.

I wrote a simple C# script that allows the user to switch between the four:

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CamSwitch : MonoBehaviour
{
    public GameObject cam1, cam2, cam3, cam4;
    public GameObject headBone;
    public int camera = 1;

    void Start()
    {
        cam1.SetActive(true);
        cam2.SetActive(false);
        cam3.SetActive(false);
        cam4.SetActive(false);
    }

    void Update()
    {
        if (Input.GetButtonDown("Key1"))
        {
            camera = 1;
            cam1.SetActive(true);
            cam2.SetActive(false);
            cam3.SetActive(false);
            cam4.SetActive(false);
        }
        if (Input.GetButtonDown("Key2"))
        {
            camera = 2;
            cam2.SetActive(true);
            cam1.SetActive(false);
            cam3.SetActive(false);
            cam4.SetActive(false);
        }
        if (Input.GetButtonDown("Key3"))
        {
            camera = 3;
            cam3.SetActive(true);
            cam1.SetActive(false);
            cam2.SetActive(false);
            cam4.SetActive(false);
        }
        if (Input.GetButtonDown("Key4")) {
            camera = 4;
            cam4.SetActive(true);
            cam1.SetActive(false);
            cam2.SetActive(false);
            cam3.SetActive(false);
        }

        if (camera == 4)
        {
            cam4.transform.rotation = headBone.transform.rotation;
        }
    }
}

```

What I want is to make a picture-in-picture display something like this when the 1st-person view is turned on:  I've tried adding a second smaller camera, but to no avail. Also for what it matters, I found this script doesn't work unless all of the cameras are turned on before the project is run.

Thanks in advance.

# Answer
> 2 votes
The key is Render Textures.

A render texture is an "asset" which can be assigned to a camera to capture its render output and then be used as a texture to display that camera output in any context where a texture can be used. For example an image in the UI.

So the idea is:

1. Create your cameras
2. Create a Render Texture asset
3. Assign that render textures to the field "Target Texture" of your cameras.
4. Create a UI canvas with a Raw Image (not regular images, because those don't support render textures). Position that images on the canvas where you want the "picture in picture" to appear on the screen.
5. Assign the render texture as the texture of that image.

You should now have a tiny "mini-screen" on the screen which shows the output of the cameras. When multiple cameras are active, then you are probably going to see the output of the last camera, because it's going to overwrite all the others. So make sure you only have one camera active at the same time.

# Answer
> 0 votes
If you want your picture-in-picture view to occupy a fixed, screen-aligned rectangle, you can also achieve this effect without a render texture by using the camera's viewport rect.

Take your camera that renders the "main" scene view and set its Depth parameter to something like 0.

Then take your picture-in-picture camera and set its Depth to a greater value, like 1, so it renders after the main camera has finished drawing.

Set this PiP camera's viewport rect to occupy just a corner of the screen, something like:

```
x: 0  w: 0.25
y: 0  h: 0.25

```

These values are measured in relative coordinates, so "0" is the bottom/left corner of the screen, and "1" is the top/right or full width/height.

You can set these values in a script if you want to adapt the size and position of the PiP window to respond to changes in the size of the game window.

The advantage of rendering to a render texture is you're not limited to blitting the image directly to the screen — you can display that render texture in perspective, or map it to a virtual monitor that exists as a 3D model in your game world, or project it onto your geometry like a slide show, or render it distorted, translucent, or with arbitrary shader effects.

The downside of rendering to a render texture is that it's slightly more expensive to save and then sample that texture than to just dump pixels directly to the frame buffer in one go. You may also have to do some work to match the texture resolution to the display size, otherwise you might render more pixels to the texture than you ultimately display, or observe sampling artifacts where the grid of the texture's texels is mismatched to the grid of the screen's pixels.

---
Tags: unity, camera
---


title: Remove default menu controls

text:
# Question
Title: Remove default menu controls
I have a vertical layout group filled with UI buttons used as menus in my game. They work well for mousing over and clicking, and I am in the process of adding keyboard controls.

The issue I have is that there appears to be a default control mechanism that moves the selection when pressing Up or Down on the keyboard, so when I add my own, the two are fighting each other. I don't want this. How can I tell Unity to just allow me to handle the keyboard inputs for these menus?

# Answer
Your EventSystem has an "Input System UI Input Module" attached to it, which has a default Actions Asset linked to it. This is where the extra menu movements are coming from.

One solution is to change the Move option under Action Assets to NONE. This will remove the impact that pressing Up / Down has on menu selection here, while keeping the other pieces of this input you may depend on (mouse position etc..)
> 2 votes

---
Tags: unity, unity-ui
---


# Question
Title: How to find combination of vectors to equal target velocity?
I have a space craft in a 3d world that moves along the X Z plane. Given that I have 4 thrusters with one at each cardinal direction, and the ship can rotate itself about the y axis. Is it possible to find the correct combination of thrust vectors for the thrusters to equal a target velocity, given any possible rotation?

# Answer
> 2 votes
If your thrusters are aligned with the object's coordinate axes, then this is just a change of basis.

```
// Compute desired velocity change.
Vector2 deltaV = targetVelocity - rigidbody.velocity;

// Divide by time step to get acceleration to apply.
// ( If you're doing this in FixedUpdate,
//   Time.deltaTime will automatically return fixedDeltaTime )
Vector2 acceleration = deltaV / Time.deltaTime;

// Convert to a thrust, respecting limits on max thruster output.
Vector2 thrust = Vector2.ClampMagnitude(acceleration/rigidbody.mass, maxThrust);

// Convert world-space vector into a local one, ignoring scaling.
Vector2 localThrust = transform.InverseTransformDirection(thrust);

// Fire thruster on the opposite side as the direction you want to move.
if (localThrust.x > 0) {
    leftThruster.Fire(localThrust.x);
} else if (localThrust.x < 0) {
    rightThruster.Fire(-localThrust.x);
}

if (localThrust.y > 0) {
    rearThruster.Fire(localThrust.y);
} else if (localThrust.y < 0) {
    frontThruster.Fire(-localThrust.y);
}

```

Here I've limited the thrust to the same maximum value in any direction, which should help make the controls more intuitive. But it does mean that when thrusting at a 45-degree angle, the two firing thrusters will be using only \\$\frac 1 {\sqrt 2} \approx 70.7\\%\\$ of their maximum output. If they both fire at 100% power then you'd be able to accelerate faster on diagonals than directly ahead - which could be good for realism, or if you want to encourage diagonal maneuvers as a core skill/strategy, but might be distracting of confusing otherwise.

---
Tags: unity, physics, mathematics, vector, rigidbody
---


# Question
Title: DrawMesh() not working as intended in Unity
I rendered some UV unwrapped raw mesh data using a special shader, and Graphics.DrawMesh() to a render texture, but upon inspection the data is wrong (world normal and position).

* Left is a vanilla unlit shader modified to output the world normal from the game objects, normals align to the unity basis show in the upper right corner.
* Right is the target complex shader that use raw mesh with custom matrix, uv unwrap rendered to texture and read back as texture on the gameobject by another shader (the working is out of scope, context is roughly lightmap as a G buffer)

It seems to make sense since I'm bypassing the gameobject construct of Unity, therefore I have faulty matrix. Therefore, I tried passing my own matrix. But the worldnormal data don't seem to match, and doing the inverse of the transpose of my own matrix doesn't seem to do the trick (the standard stuff to transform normal) ...

To investigate further, as I suspect that raw mesh is the imported data before Unity applies its import settings, I created a little program that I attached to the game object to compare its render to various DrawMesh() commands ... but DrawMesh doesn't work as intended ...

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MeshVsObjectNormalTest : MonoBehaviour
{
    //public GameObject normalObject;
    Mesh[] rawMesh;
    Matrix4x4 objectMatrix;
    Camera cam;
    Material mat;

    // Start is called before the first frame update
    void Start()
    {
        rawMesh = new Mesh[1];
        rawMesh[0] = this.GetComponent<MeshFilter>().mesh;
        objectMatrix = Matrix4x4.identity;
        cam = Camera.main;
        mat = this.GetComponent<Renderer>().material;

    }

    // Update is called once per frame
    //void Update()
    void LateUpdate()
    //void OnPostRender()
    {
        //Graphics.DrawMesh(rawMesh[0], objectMatrix, mat,0, cam);
        //Graphics.DrawMesh(rawMesh[0], Vector3.zero, Quaternion.identity, mat,0, cam);
        Graphics.DrawMesh(rawMesh[0], Vector3.zero, Quaternion.identity, mat,0);

        //mat.SetPass(0);
        //Graphics.DrawMeshNow(rawMesh[0], objectMatrix);

        Debug.Log(cam);
    }

}

```

Nothing shows up, but it's how every example I can find when I search does it, and I'm already using that command successfully to render to texture ...

Is there something I'm missing?

# Answer
> 0 votes
It's a problem of scale

The mesh is too small by two magnitude and isn't visible at the camera distance.

We can verify that the normal colors are nearly identical from the miniature mesh to the texture sampled version on the right. That mean the matrix used to transform the normal aren't working when it's rendered to texture, which I need to figure out.

---
Tags: unity, rendering
---


# Question
Title: What's the difference between 'Dynamic' , 'Random', and 'Procedural' generations?
I've been really struggling lately with the difference between dynamically, randomly, and procedurally generated maps/levels, I previously thought they were the same but now I have no clue.

I've seen similar posts with people answering that dynamic generation is content that is "constantly changing", but I have no idea how that works with a map/level in a game. Is the map/level constantly changing, what does that even mean? Are some of these terms used interchangeably?

# Answer
> 36 votes
**Disclaimer**: If true random exists in our reality, and what is the nature of free will are topics beyond this answer. The random I speak about in this answer is pseudo-random (when I say "random" take "pseudo-random" as implied). Also, for the purposes of this answer parameter vectors are also considered seeds.

---

I found an often quoted in literature "taxonomy of procedural content generation" in the paper "Procedural Content Generation in Games" by Shaker et al. published in 2016. I want to mention they use the terms **deterministic** and **stochastic**. However this answer remains my own take on it.

---

About generation of content (referring in this content to maps, levels and other parts of the design of video games), we can say it can either be:

* **Authored (designed) content**: This is content generated by the authors of the game.

Authored content could use procedural generation tools which provides a result which is later refined by the author. This is known as **offline procedural content generation**.

*Authored content might be bundled with the game, or be downloadable content.*
* **Procedural content**: This is content generated by algorithms designed or selected by the authors of the game (there is a procedure that generates the content) during game play, also known as **online procedural content generation**.

*The tools include pseudo-random number generators, generative grammars, noise functions, cellular automata, fractals, artificial neural networks and so on.*

Procedural content generation can take different approaches:

+ **Constructive content generation**: The algorithm produces content that is added directly to the output.
    + **Generate-and-test content generation**: The algorithm has a loop where they produce content until it satisfies some fitness function. If it produces unsatisfactory content, it loops, until it produces satisfactory content.
    + **Search-based content generation**: The algorithm searches a latent space of possible content that can be generated, using an heuristic, util it finds content that satisfies a fitness function. *This is can be considered an special case of generate-and-test content generation.*

*The mentioned fitness functions could take the actions of the player into account.*
* **User-generated content**: This is content provided by the players of the game. This can also be classified as **online** and **offline** except this time we are talking about networking. The user-generated content confined to the game or the machine of the player is offline, and if it is shared over the network it is online. And then we can distinguish if the player receives user-generated content as part of normal gameplay, or they must request it.

---

Also the content could be:

* **Static**: It cannot change once generated.
* **Dynamic**: It can change during game play.

Authored content is often static. However it can be dynamic if it allows players to modify it (by building or destroying) or there is a automatic system that governs how it changes (either constantly or at specific events).

For example, a game map could have predefined positions for trees. But these trees could be dynamic in that the player can cut them, and there is a system that will make new ones grow.

An example of "constantly changing" dynamic content could be if the trees spread (new trees spawn and grow near where other trees exist). *See also city builders where the simulation would constantly build, change or destroy buildings.*

Similarly, procedural content could be either static or dynamic. For example, a game could be fully procedural (and random, see below), but offer the player no means to modify the environment, and thus making it static.

User generated content is usually dynamic, because it is content that the players input during game play. The exception would require modifications to the contents of the game while the game is not running (e.g. mods).

---

Furthermore, the content generation could be:

* **Predetermined**※: It is always the same *for everybody everywhere*.
* **Random (a.k.a pseudo-random or stochastic)**: It can be different every time. *Using pseudo-random number generators, noise functions and similar tools.*

※: What I call here **Predetermined** is often called **Deterministic**, however "predetermined" seems to be a better term because pseudo-random is deterministic (given the same input it produces the same output).

*See **On the nature of pseudo-random** below.*

Authored content is predetermined. Adding a system that makes it different every time would make it procedural content. However, procedural content could be predetermined (e.g. the designers hard-coded the seeds for content generation, so it is always the game).

User-generated content could also be either random or predetermined. However, predetermined user-generated content is more common (e.g. the user creates a custom scenario using an editor, and thus every time that custom scenario is the same).

Note that a common motivation to use procedural content generation is to make random content (which, again, is content that is different every time). Otherwise we would probably author the content. However, another motivation for procedural generation is to make a world much bigger that we could reasonably author (which might result is not very interesting worlds).

---

I also want to mention that procedural generation could happen **ahead of time** (i.e. the game generates the world before game play) or **just in time** (i.e. the game generates the world when needed, a.k.a. **on demand content generation**). Note that on demand content generation does not imply it random nor dynamic.

However, on demand content generation offers an additional opportunity to make the content generation dynamic (the algorithm could skew what content it generates according to the situation of the player, for example as a means of dynamic difficulty).

In regard to dynamic content, we can also talk about content being **persistent** or **not persistent**. Saying that the content is persistent, means that the game will store/remember the changes that happened during game play. Otherwise the changes are lost when the content is unloaded… Which could mean to return to the authored state, or to generate it again (implying on demand content generation). Furthermore, the case of on demand content generation could result in something different being generated (which might be the intention of the designers or not). *Notice that persistence is not an issue if the game does not allow you to go back.*

We will call content generation that takes the actions of the player into account as **adaptive** (and thus dynamic), otherwise we would say it is **generic**.

We could also classify the content generation based on whether or not the content generated is **necessary** to complete the game, or otherwise it is **optional**.

---

**On the nature of pseudo-random**

First of all, random generation does not mean uniformly random. We can manipulate the randomness distribution towards generating content that we deem interesting, or even decide leave some aspects fully deterministic.

---

Note that by random content generation depends on seed values that generator takes as input. Given the same seed values, the result would also be the same. Thus, to have an unpredictable results we need seed values that are unknown beforehand and hard to control. Common sources for such values include the current time, CPU performance counters, and microphone noise… Which also have in common that they are always changing. However, if the seed values are controlled (by design or by manipulation) the content generation becomes predictible. This means that pseudo-random is repeatable/reproducible (which is a good thing for testing).

The designers could offer control over the seed values to the player explicitly (there is an UI where they input the seed) or implicitly (where the game derives the seeds from actions that the player performs, but the player is not told these action seed the content generation).

We can also collect fingerprinting data (e.g. user id, network id, hardware id, and so on), and hash it (we can also use HMAC or key derivation functions) to produce a seed value. In this case the content generation will be quasi-unique for the player (baring collisions). This allows us to provide content generation that is always the same for the same player, but different depending on the player.

---

Be aware that common random number generators and noise functions will eventually loop (avoiding this would make the cost of generating steadily increase, which is not desirable)※. As mitigation we can re-seed the generator, and depending on the generator this could be noticeable by the user.

*※: This might not be a problem for small games. Be aware that games that generate huge worlds might also need to deal problems caused by floating point errors and overflows. And, of course, other optimizations relevant to huge worlds beyond this question.*

Furthermore, random number generators are sequential, and thus the order in which the generated numbers are used will affect the outcome. This have some notable consequences:

* Different versions of a software that use the generated numbers in different ways will yield different outputs from the same seeds.
* If there are actions that the user can do that use random numbers, then the actions of the user change the generation going forth.
* This opens a common vector to manipulate the generation by the players: exhaust the generated numbers (by repeating an action that use them) until the generator is in a desirable state.

On the other hand generation based on noise functions would not be vulnerable to this kind of manipulation because unlike pseudo-random number generators, noise functions are not sequential.

# Answer
> 2 votes
They all mean the same thing, more or less. Dynamic is a little spiffier, but not by much.

An easy way to say the player gets different maps on replays is saying maps are random. Of course, any decent random map generation has rules plus randomness; such as the old dungeons with 5-8 rooms placed randomly (with no overlaps and so on). You might describe that as a procedure with randomness as part of it -- "procedural" for short. Obviously, a better procedure will give better maps. As players got more sophisticated, and saw more simple, boring random levels (it's a field with 3-4 short walls that can be anywhere!), they started to figure this stuff out. Saying "procedurally generated levels" was a better marketing term.

In programming, dynamic means the procedure remembers and responds to history. In the case of game levels, the history is previous levels (or really, anything -- like how often the player died). Maybe the chance of a merchant appearing is 5% + 10% per level without a merchant. Maybe the new level type (forest, swamp, plains ... ) depends on the previous level types. You might have random bosses, but each has a boss-chain -- if you roll "mummy boss" the player gets the next type of mummy they haven't killed yet.

That last one feels dynamic in the common sense -- what's been killed stays dead; your actions changed the world. The others are dynamic in the programming sense. Which is the real meaning? Who can say? And how dynamic counts as dynamic? What about not getting a special level if the previous one was? What about only giving scrolls of spells they can learn? Every random level generation system probably has something that technically counts as dynamic. Put another way, "dynamic" means "created using an in-game process, with some dice-rolling, and also not just based on the level # -- also looking at some things that happened previously".

---
Tags: procedural-generation, terminology
---


# Question
Title: Map tile terrain transitions with 3-4 different types
Making transitions between two different tiles is a fairly well understood problem. The easiest way is to look at the 0.5, 0.5 and use the resulting 4 corner points to select a transition tile, as mentioned here for example: http://playtechs.blogspot.se/2007/04/tileset-design-tip.html

The problem comes when three or four tiles meet, for example:

(s for swamp, p for plains and . for water)

```
pppp
..sp
..pp

```

If we look at the offset in the corner, the edges in our transition tile looks like:

```
pp
.s

```

In this case there are no suitable transition tiles to be found, as they typically are created to work between 2 types only. Unless we work out a three way transition tile with these types, we appear to be stuck.

I'm well aware of this often referenced article on the tile transitions in Artifact: http://archive.gamedev.net/archive/reference/articles/article934.html

But looking at the actual screenshots from that game, it doesn't seem like it will yield professional looking maps: http://www.samugames.com/artifact/screenshots.htm

Aside from using intermediary tiles (which frankly would be problematic since two out of grassland/swamp/desert would then not be able to border directly to water), are there any solutions?

**EDIT**

I think I've found a solution that can will create terrain transitions for 3-4 different types, using only one extra transition tile to each set of transition tiles. I've yet to make an implementation of the algorithm, but the mockups look fine. This solution will work well for pixel art.

# Answer
I'm currently working on a terrain mapper, that should do exactly what you want. First of all i pregenerated all transitions between the tiles (so per 2 terrain types there would be 2 tiles for the plain terrain textures and around 12 tiles for the transitions). As the number of needed tiles would grow pretty high, when you have multiple terrains and want to support transitions between all the terrain, the tileset would grow pretty big.

Now currently I'm working on another approach, that in theory (and in my prototype) worked pretty well. I still have to implement it, so the needed tilesets are created on the fly, but this is how I do it:

I have a terrain definition, where all terrain types (for example water, grass, rocks, mountains, desert etc.) are stored. After that I establish an order. I'll explain later, why this order is needed. This order defines, how the different terrain types are related to each other. For example, water is lower than sand (beach), sand is lower than grass, grass is lower than mountains. Therefore you can also say, that water and sand are lower than mountains.

Now I have a special alpha texture, that defines the possible transitions.

Here is the tileset I used in my prototype:

You can see, that you have some terrain textures and an alpha map. I handle the alpha map also as tiles, but they are half sized (terrain textures are 64x64, blend textures are 32x32), this means that every terrain can have up to four blend textures (northwest, northeast, southwest, southeast).

Now basically what you do, when you have a transition is this: \- Draw the ground texture (blends are always applied on the higher terrain types). \- Draw the alpha map with ALPHA\_MAP (this only renders the alpha channel) \- Draw the second texture with ALPHA\_BLEND (this uses the underlying alpha channel, we rendered out before as alpha for the tile).

So basically this is the theory... But you need to do some things, that it works properly.

What I do is following:

* First generate the level (I use perlin noise, to assign the different terrain types). The output is simply a 2d array containing the terrain types (eg: water, dirt, sand).
* A processor goes through this array and checks for some things and outputs a MappedTileLayer (this is my datastructure, to hold the output). The processor does following:
    + Map all terrain types to the according tile in the mapped layer.
    + Find tiles, that have neighbours with different (lower) terrain types. If there is a lower neighbour, you also have to render this neighbour terrain underneath actual terrain tile, as otherwise the terrain would just blend to black.
    + Based on the neighbours, the correct blend tiles are used. Every terrain type consists of 4 blend tiles. So depending on the existing neighbours, the blend tiles are chosen.
* My renderer renders the tiles on the viewport:
    + Render all tiles without any transitions (also render the lowest terrain, where blends will be applied). The tiles rendered in this run can be rendered without the need to apply a blend, as they are the lowest terrain tiles.
    + Select first overlay tile (currently there are max 4 overlayes. This is the case, when the tile handled has the highest height in relation to the neighbours and every neighbour is from a different terrain type).
        - Render the four blend tiles with ALPHA\_MAP
        - Render the overlying tile with ALPHA\_BLEND
        - Repeat this step with the next overlay tile, until there are no tiles left

Like this, you do not have to care about multiple terrain types getting together, as they are rendered from lowest to highest terrain type and the transtitions only concern the higher type.

I know that it is a bit complicated at first, but I currently don't have more graphics, because I'm in the middle of refactoring my source. I will add some graphics representing what I do as soon as possible (also better explaining every step).

I hope this will help you.

One small addition: My mapper is built, that there is a default blend texture. But you can define specific blend textures for every terrain type. So for example if you want to make smoth transitions for all mappings besides the beach to water transition, you put the smooth transition as default and only set the custom transtition for the beach tiles. Also, my mapper allows to define multiple transition tiles for each tile type (so if you have a straight edge, you can make multiple blends, so they will alternate).

\** Appended \** Because I currently have no assets to show, I created a schematic drawing in paint.net. Keep in mind, I'm a programmer and not and 2d artist, so artistically it is very badly drawn, but it should show, how I tackle the problem with multiple terrain types (\>2) coming together). I left out optimizations for simplicity and applying the blending is also not mentioned, as I concentrate on the problem with the multiple terrains.

I will add an example with real textures and blends as soon as I find the time to do so, so this gfx is only temporary.

A explanation to the image I added:

1. In the first grid you see the terrain configuration. W stands for water, S for sand and G for grass.
2. The second grid shows, how the blend tiles are applied. Like I mentioned earlier, the width and height of the blend tiles is the half of the terrain tiles.
3. Here now the first render action happens. The lowest terrain type is water, so we fill all the cells (and all adjacent cells) with the water tiles. So allthough the water tiles only fill the first column of our definition, we have also to fill the second column.
4. Now we draw the next layer, but we only draw the tiles, that won't be blended. Water is a lower terrain then sand, so we do not need to expand to that side. But because the grass is higher, we have to fill the adjacent tiles with sand, where the grass blend will be rendered over.
5. Now based on some patterns, we render the blending parts of the sand. (Now you see, why we need to also fill the adjacent tiles).
6. Here we have repeated steps 4 and 5 (allthough step 4 wouldn't produce any output, as all grass tiles in this examples will contain a blend).

Now you see, in the fourth row in the second column, there is a grass tile. Left of it is a water tile and on top and bottom of it are sand tiles. When we look at the rendering only for this tile, this is what happens:

* The water tile is rendered without a blend.
* The sand tile is rendered with a blend.
* The grass tile is rendered with a blend.

And we could also switch one of the sand tiles and the grass tile on the right to different terrain types, it would still produce correct results.
> 6 votes

# Answer
Since you're presumably in a situation where you don't *need* an extremely low-budget solution for technical reasons, and you are also presumably not interested in creating the 200+ unique different tiles that you would need to handle every possible case, you may want to consider a solution that allows you to use alpha testing or blending to handle most cases (like Artifact's solution), but override specific transitions that look poor.

The "base" for any given tile type would be a single solid tile, and then you'd have the 16 or 32 transition tiles, like Artifact. But you could also add a customized transition tile set (another 16 or 32 tiles) between a specific pair of tiles that need additional help. The transition tiles could either be done alpha tested/blended, like the generic transitions, or they could completely replace the lower-precedence tile. This still isn't perfect—say you have a custom water-grass and water-hill transition, but what happens to water that has grass on one side and water on the other?—but it does at least allow you to prioritize.

Ultimately, though, I think this is very much about visual design. There's an art involved in getting a limited range of tiles to look correct, and I suspect that shading is extremely important—a boundary with no appearance of depth will be more jarring than one that's carefully designed.
> 4 votes

# Answer
I researched a lot about this and found only 2 or 3 articles with outdated graphics.

What I would suggest today to render a 2d game-map is to use textures and dominance-based approach, where each terrain-type defines its dominance value: more dominant tiles always overlap the less dominant ones and "blend" into them. Then instead of pre-rendering specific terrain transitions use "blendmaps" to render everything on-the-fly. One blendmap can be used to render many transitions between various textures.

Initially, you can start with this:

* define 3 terrain types and provide 3 textures for rendering
* define 1 blendmap that will be used to blend terrain transitions

If you go with the dominance-based approach then you must account for a possibility that rendering a single tile may require at most 9 blits, one for the tile itself and 8 for neighboring tiles that are more dominant. If you aggregate all surrounding tiles that have equal dominance then you will probably end up with 1-2 blits per tile, at least according to my experience.

Also, when you start implementing a renderer that takes into account surrounding tiles and requires expensive calculations to resolve "how" the tile should be rendered, I suggest to implement a mechanism to invalidate tiles that need such recalculation, and to not do this during the rendering process itself.

Some textures / blendmaps benefits:

* Textures look much nicer when the same tile repeats multiple times
* Adding a new terrain type is a matter of providing a new texture

Here is a high-level article about my implementation of such problem in JS:
> 1 votes

# Answer
I also think best way to use layered marching squares with alpha channels, made a simple demo with 3 layers: water, soil, grass

Here is the whole code, originally posted here:

```
// images:
// https://stuepfnick.macbay.net/js/tiles/test/assets/buch-outdoor-transparent.png
// https://stuepfnick.macbay.net/js/tiles/test/assets/cloudtest.png

// https://stuepfnick.macbay.net/js/tiles/test/config.js

/*
 * All important constants in one file
 * Â© Created by Stuepfnick
 */

// Config object as constant with frozen properties
const Config = Object.freeze ({
    // Tiles Source image URL
    tileMapSource: 'https://i.stack.imgur.com/kP1lm.png',
    tileMapColumns: 6,
    // Tile Size and Spacing in Pixels
    tileWidth: 16,
    tileHeight: 16,
    tileSpacing: 0,
    // MapSize in Tiles
    mapWidth: 64,
    mapHeight: 64,
    groundColor: '#344A61',
    /*
     *  1â€“â€“â€“2
     *  |   |
     *  4â€“â€“â€“8
     */
    marchingIndices: [
        [ // Dirt + 36
            9,                   // 0000 - 0
            50,                  // 0001 - 1
            49,                  // 0010 - 2
            [37, 38, 39, 40],    // 0011 - 3  (2+1)
            44,                  // 0100 - 4
            [42, 48, 54, 60],    // 0101 - 5  (4+1)
            [56, 61],            // 0110 - 6  (4+2)
            36,                  // 0111 - 7  (4+2+1)
            43,                  // 1000 - 8  (8)
            [55, 62],            // 1001 - 9  (8+1)
            [47, 53, 59, 65],    // 1010 - 10 (8+2)
            41,                  // 1011 - 11 (8+2+1)
            [67, 68, 69, 70],    // 1100 - 12 (8+4)
            66,                  // 1101 - 13 (8+4+1)
            71,                  // 1110 - 14 (8+4+2)
            [45, 46, 45, 51, 45, 52, 45, 57, 45, 58, 45, 63, 45, 64, 45, 45, 45]// 1111 - 15 (8+4+2+1 )
        ],
        [ // Grass
            9,                  // 0000 - 0
            14,                 // 0001 - 1
            13,                 // 0010 - 2
            [1, 2, 3, 4],       // 0011 - 3  (2+1)
            8,                  // 0100 - 4
            [6, 12, 18, 24],    // 0101 - 5  (4+1)
            [20, 25],           // 0110 - 6  (4+2)
            0,                  // 0111 - 7  (4+2+1)
            7,                  // 1000 - 8  (8)
            [19, 26],           // 1001 - 9  (8+1)
            [11, 17, 23, 29],   // 1010 - 10 (8+2)
            5,                  // 1011 - 11 (8+2+1)
            [31, 32, 33, 34],   // 1100 - 12 (8+4)
            30,                 // 1101 - 13 (8+4+1)
            35,                 // 1110 - 14 (8+4+2)
            [15, 10, 15, 16, 15, 21, 15, 22, 15, 27, 15, 28, 15, 15, 15, 15, 15]// 1111 - 15 (8+4+2+1 )
        ]
    ]
});

// view-source:https://stuepfnick.macbay.net/js/tiles/test/input.js

/*
 * All important main routines for Tiles!
 * Â© Created by Stuepfnick
 */

var isMouseDown, dataSet = false;
var data = 0;

var scrollContainer = {};//document.getElementById('scroll-container');
var drawCheckbox = {};

initInput = function() {
    //var canvas = document.getElementById('demo');
    scrollContainer = document.getElementById('scroll-container');
    drawCheckbox = document.getElementById('drawCheckbox');
    //Touch Handler
    canvas.addEventListener('touchstart', touchHandler);
    canvas.addEventListener('touchmove', touchHandler);
    canvas.addEventListener('touchend', mouseUp);
    canvas.addEventListener('touchcancel', mouseUp);

    //Mouse Handler
    canvas.addEventListener('mousedown', mouseDown);
    canvas.addEventListener('mousemove', mouseHandler);
    window.addEventListener('mouseup', mouseUp)

    scrollContainer.addEventListener('wheel', wheelHandler);

    scrollContainer.addEventListener('scroll', scrollHandler);
};

touchHandler = function(e) {
    if (drawCheckbox.checked) {
        if (e.touches) {
            e.preventDefault();
            var touchX = e.touches[0].pageX - canvas.offsetLeft + scrollContainer.scrollLeft;
            var touchY = e.touches[0].pageY - canvas.offsetTop + scrollContainer.scrollTop;
            coordinatesHandler(touchX, touchY);
       };
    }
};

mouseHandler = function(e) {
    if (isMouseDown) {
        coordinatesHandler(e.offsetX, e.offsetY);
    }
};

mouseDown = function(e) {
    isMouseDown = true;
    mouseHandler(e);
};
mouseUp = function(e) {
    isMouseDown = false;
    dataSet = false;
};

coordinatesHandler = function(x, y) {
    var tileX = Math.round(x / tiles.width);
    var tileY = Math.round(y / tiles.height);
    let numberOfLayers = Config.marchingIndices.length;
    if (!dataSet) {
        data = map.getRaw(tileX, tileY) + 1;
        if (data > numberOfLayers) {
            data = 0;
        }
        dataSet = true;
    };
    if (data != map.getRaw(tileX, tileY)) {
        map.setRaw(data, tileX, tileY);
        Game.regenerateMap(tileX - 1, tileY - 1, tileX, tileY, numberOfLayers);
    };
};

wheelHandler = function(e) {
    e.preventDefault();

    scrollContainer.scrollLeft += e.deltaX;
    scrollContainer.scrollTop += e.deltaY;
};

scrollHandler = function(e) {
    //console.log(scrollContainer.scrollLeft + ', ' + scrollContainer.scrollTop)
};

// view-source:https://stuepfnick.macbay.net/js/tiles/test/tiles.js

/*
 * All important main routines for Tiles!
 * Â© Created by Stuepfnick
 */

// Game Object (will move some of the values below in it eventually)
var Game = {}
// var Game = {
//     canvas: {},
//     ctx: {},
//     â€¦
// };

// Temp: Some global objects and values, will move some of them to the Game object eventually
var canvas = {};        // will become reference to Main Canvas
var ctx = {};           // wil become referene to '2d' Context of Main Canvas
var lastTimeStamp = 0;  // need a permanent variable to store the last time stamp
var speed = 60|0;       // Speed in pixels per second?

// Temp: For testing purposes only: moving coordinateX and bool for the moving direction
var reverse = false, cX = 0;

var clouds = {};

// Generate map object and fill in some values from Config
var map = {
    cols: Config.mapWidth,
    rows: Config.mapHeight,
    rawData: [],
    getRaw: function (col, row) {
        return this.rawData[row * this.cols + col]
    },
    setRaw: function (data, col, row) {
        // Get index using col & raw
        let idx = (row * this.cols + col)|0; // force to int
        // Check if data is boolean and in bounds of rawData
        if (typeof(data) === 'number' && idx < this.rawData.length) {
            this.rawData[idx] = data;
        } else { // Log the error
            console.log('Error: map.setRaw(' + data + ', ' + col + ', ' + row + ')');
        };
    },
    layers: [],
    getTile: function (layer, col, row) {
        return this.layers[layer][row * this.cols + col]
    },
    setTile: function (layer, tile, col, row) {
        // Get index using col & raw
        let idx = (row * this.cols + col)|0; // force to int
        // Check if data is number and in bounds of rawData
        if (typeof(tile) === 'number' && idx < this.layers[layer].length) {
            this.layers[layer][idx] = tile|0; //force to int
        } else { // Log the error
            console.log('Error: map.setTile(' + tile + ', ' + col + ', ' + row + ')');
        };
    }
};

// Generate tiles object and fill in some values from Config
var tiles = {
    atlas: {},
    width: Config.tileWidth,
    height: Config.tileHeight,
    spacing: Config.tileSpacing,
    cols: Config.tileMapColumns,
    ground: Config.groundIndex,
    marching: Config.marchingIndices
};

// Here everything starts
window.onload = function() {
    console.log('window.onload');
    clouds = new Image();
    clouds.src = 'https://i.stack.imgur.com/RKfb4.png'
    // Create and load the Tile Atlas
    tiles.atlas = new Image();
    tiles.atlas.src = Config.tileMapSource;

    // Need to wait for the image to fully load, before Game can be initialized and map can be drawn
    tiles.atlas.onload = function() {
        Game.init();
    };
};

// After everything is loaded, initalize the Game
Game.init = function() {
    console.log('Game.init');
    let numberOfLayers = Config.marchingIndices.length;
    console.log('layers: ' + numberOfLayers);
    // Get Canvas and Context
    canvas = document.getElementById('myCanvas');
    ctx = canvas.getContext('2d');
    // Generate Raw Data & Map
    this.generateRawData(numberOfLayers);
    this.generateMap(numberOfLayers);
    initInput();
    initStorage();
    // After Map Generation is finished, begin draw loop
    // CAUTION: needs to be bound to the Game object (.bind(Game)), as it is not called as a regular function!
    console.log('Begin draw â€¦');
    // requestAnimationFrame lets the browser handle the timer for the draw loop, so this is recommended over a timer
    requestAnimationFrame(this.draw);
};

// Main draw loop
Game.draw = function(timeStamp) {
    // Calculate delta from timeStamp and lastTimeStamp
    var delta = (timeStamp - lastTimeStamp) / 1000.0;
    // Clear the Canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // Draw the Tile Atlas itself for testing purposes
    //ctx.drawImage(tiles.atlas, cX, 30)
    // Draw the Offscreen Canvas, which contains the map
    ctx.drawImage(this.offCanvas, 0, 0);//-cX, 0);
    // draw clouds
    drawClouds = function(targetX, targetY) {
        ctx.drawImage(
            clouds,             // source image
            0,                  // source X
            0,                  // source Y
            1024,               // source Width
            1024,               // source Heigth
            targetX,            // target X
            targetY,            // target Y
            2048,               // target width
            2048                // target heigth
        );
    }
    let cloudX = cX - scrollContainer.scrollLeft * 2;
    let cloudY = - scrollContainer.scrollTop * 2;
    drawClouds(cloudX, cloudY);
    drawClouds(cX < 0 ? cloudX + 2048 : cloudX - 2048, cloudY);
    // temp: A simple query to move the clouds
    cX += delta * speed;
    if (cX > 2048) {
        cX = -2048;
    }
    // Store the actual timeStamp in lastTimeStamp
    lastTimeStamp = timeStamp;
    // Request next animation frame from the Browser with calling this method again â€¦
    requestAnimationFrame(Game.draw);
    // Bind the whole function to the Game object, so it is not needed to bind
}.bind(Game);

// Function to generate a boolean array that contains the raw data needed for the Marching Cubes
Game.generateRawData = function(numberOfLayers = Config.marchingIndices.length) {
    // Needs to have 1 extra column and row! (so I use <= instead of <)
    for (let y = 0; y <= map.rows; y++) {
        for (let x = 0; x <= map.cols; x++) {
            // Add a random Boolean to rawData
            map.rawData.push(((Math.random()* numberOfLayers) + 0.5 | 0));
        }
    }
};

// Function to Generate Map Array and draw it (temporary for testing purposes)
Game.generateMap = function(numberOfLayers = Config.marchingIndices.length) {
    // Create an Offscreen Canvas with the Size of the Map and get the '2d' Context
    this.offCanvas = createOffscreenCanvas(map.cols * tiles.width, map.rows * tiles.height);
    var tCtx = this.offCanvas.getContext('2d');
    console.log('Game.generateMap offCanvas created: ' + this.offCanvas);
    // Temporary
    tCtx.fillStyle = Config.groundColor;
    tCtx.fillRect(0, 0, map.cols * tiles.width, map.rows * tiles.height);
    // Loop for creating a map with the needed Size and â€¦
    for (let layer = 0; layer < numberOfLayers; layer++) {
        map.layers.push([]);
        for (let y = 0; y < map.rows; y++) {
            for (let x = 0; x < map.cols; x++) {
                // Append the tile to the layer array of the Map*/
                map.layers[layer].push(this.drawMarchingTile(tCtx, layer, x, y));
            }
        }
    }
    console.log('map generation finished!');
};

// Not simply a redraw, also sets the tiles (NOTE: should add a pure redraw too)
Game.regenerateMap = function(startX = 0, startY = 0, endX = map.cols - 1, endY = map.rows - 1, numberOfLayers = Config.marchingIndices.length) {
    // Get Context from Offscreen Canvas
    var tCtx = this.offCanvas.getContext('2d');
    // Check if parameters are there and numbers (otherwise autofill -> can be done in function signature)
    // if (typeof(numberOfLayers) !== 'number') {numberOfLayers = Config.marchingIndices.length;};
    // if (typeof(startX) !== 'number') {startX = 0;};
    // if (typeof(startY) !== 'number') {startY = 0;};
    // if (typeof(endX) !== 'number') {endX = map.cols-1;};
    // if (typeof(endY) !== 'number') {endY = map.rows-1;};
    // Cap the values and set to int, although map.setTile does a check itself
    startX = Math.max(startX|0, 0);
    startY = Math.max(startY|0, 0);
    endX = Math.min(endX|0, map.cols - 1);
    endY = Math.min(endY|0, map.rows - 1);
    console.log('regenerateMap: ' + startX + '-' + (endX) + ', ' + startY + '-' + (endY));
    // Redraw Background color
    let rwidth = (endX - startX + 1) * tiles.width;
    let rheight = (endY - startY + 1) * tiles.height;
    //tCtx.fillStyle = Config.groundColor;
    tCtx.fillRect(startX * tiles.width, startY * tiles.height, rwidth, rheight);
    // Loop through the part of the map that needs to be replaced
    for (let layer = 0; layer < numberOfLayers; layer++) {
        for (let y = startY; y <= endY; y++) {
            for (let x = startX; x <= endX; x++) {
                // set the tile at x, y
                map.setTile(layer, this.drawMarchingTile(tCtx, layer, x, y), x, y);
            }
        }
    }
};

Game.drawMarchingTile = function(tCtx, layer, x, y) {
    // Pass the values of rawData to marching Squares and get appropriate index, if same layer
    let mIndex = this.mSquares(map.getRaw(x, y) == layer, map.getRaw(x + 1 , y) == layer, map.getRaw(x, y + 1) == layer, map.getRaw(x+1, y+1) == layer);
    // Set the tile number to result of MS using tiles.marching Index
    let tile = tiles.marching[layer][mIndex];
    // if tile is an array, get a random one
    if (typeof(tile) === 'object' && Array.isArray(tile)) {
        tile = tile[(Math.random() * (tile.length - 1)) + 0.5|0];
    };
    // if tile is no number, fill in -1, so it draws nothing (transparent) at that place of the map
    if (typeof(tile) !== 'number') {
        tile = -1;
    }
    // force tile to int
    tile |= 0;
    // calculate sourceX and sourceY, dependent on tileSize and Spacing
    let sX = tiles.spacing + (tiles.width + tiles.spacing) * (tile % tiles.cols);
    let sY = tiles.spacing + (tiles.height + tiles.spacing) * Math.floor(tile/tiles.cols);
    // If the actual tile is something (>=0) draw a tile
    if (tile >= 0) {
        // Actually draw the tile
        tCtx.drawImage(
            tiles.atlas,        // source image
            sX,                 // source X
            sY,                 // source Y
            tiles.width,        // source Width
            tiles.height,       // source Heigth
            x * tiles.width,    // target X
            y * tiles.height,   // target Y
            tiles.width,        // target width
            tiles.height        // target heigth
            );
    }
    return tile;
};

Game.mSquares = function(up_left, up_right, down_left, down_right) {
    /*  We label the corners with those values:
    *
    *      1â€“â€“â€“2
    *      |   |
    *      4â€“â€“â€“8
    */
    let state = 0|0;
    // Determine which state we are in
    if (up_left){state |= 1;}
    if (up_right){state |= 2;}
    if (down_left){state |= 4;}
    if (down_right){state |= 8;}
    return state;
};

// Function for creating an Offscreen Canvas with a variable size
createOffscreenCanvas = function(width, height) {
    console.log('createOffscreenCanvas');
    var c = document.createElement('canvas');
    c.width = width;
    c.height = height;
    return c;
};

// view-source:https://stuepfnick.macbay.net/js/tiles/test/storage.js

/*
 * All important main routines for Tiles!
 * Â© Created by Stuepfnick
 */

var textarea = {};
var outButton = {};
var inButton = {};

initStorage = function() {
    textarea = document.getElementById('mapstorage');
    outButton = document.getElementById('exportButton');
    inButton = document.getElementById('importButton');

    outButton.onclick = exportMap;
    inButton.onclick = importMap;
}

const exportMap = function() {
    textarea.value = JSON.stringify(map.rawData);
}

const importMap = function() {
    map.rawData = JSON.parse(textarea.value);
    Game.regenerateMap();
}
```

```
* {
                padding: 0;
                margin: 0;
                text-align: center;
                color: #7df;
                background: #222;
            }
            canvas {
                background: #000;
                border:1px solid #000;
            }
            #scroll-container {
                width: 800px;
                height: 600px;
                overflow: auto;
                margin: auto;
            }
```

```
<div id="scroll-container">
            <canvas id="myCanvas" width="1024" height="1024">
                Your browser does not support the HTML canvas tag.
            </canvas>
        </div>
        <div id="scroll-container">
            <canvas id="myCanvas" width="1024" height="1024">
                Your browser does not support the HTML canvas tag.
            </canvas>
        </div>
        <input type="checkbox" id = "drawCheckbox" value = "draw">
        <label for="drawCheckbox">draw</label>
        <p>
            <textarea id = "mapstorage" rows="4" cols="50">
            </textarea>
        </p>
        <input id="exportButton" type="button" value="export"/>
        <input id="importButton" type="button" value="import"/>
```

The map is purely random at the moment, of course some kind of noise would work better. "draw" checkbox is only useful for touch devices. And you can import/export the array as JSON array - so you also can see how the data looks like.
> 1 votes

# Answer
I know this is old but this has not been mentioned. Your assumption of the transition tile is wrong. Your transition tile gets 8 transitions instead of your 4. On top of that it can have multiple overlapping layers in order of precedence. In the article they mention they have the following precedence.

Jungle over forest -\> forest over rocks -\> rocks over hill -\> hill over swamp -\> swamp over desert -\> desert over grass -\> grass over water.

Of course this comes with it's limitations but not in the scope you are asking for. I'll take your example layout but cut off the eastern tiles so we end up with 9, we don't need more since there won't be overlaps with tiles not being direct neighbors.

```
ppp
..s
..p

```

The middle water tile has a double transition in it's top right corner and bottom right corner. So since swamp goes over plains we first draw **all** the plains transitions. Then we draw the swamp transition. And thus the 2 corners will end up with 2 transitions drawn over the original water tile. It is up to you to set precedence and the art but this should give you good results. This solution does not offer different transitions for each other type but it is possible with a simple if statement, however your needed assets grow exponentially and that may not worth your while. But I can imagine a transition from swamp into water looks very different then into plains. This is essentially what you are showing in the comments, the mountains blend in very well with the grass but they won't blend in with the water that well. The reason you don't see the grass blend with the water is because the mountain overlaps it. Personally I think it looks fine but I see a lot of room for improvement, the tiles used are not much better then your average programmers art (Sorry to offend the artist, but I'm sure he agree's if he practiced more since then :) ).
> 0 votes

---
Tags: tiles, terrain
---


# Question
Title: Character Controller shows in scene but not in game
When I press play my character controller turns black, the scale on the z axis is 0 and all the children are not visible. Now the thing this only happens when I press play — if I don't, everything is fine in my scene and game view.

Player (Parent object)

Player move script

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour
{
    public CharacterController controller;
    public float gravity = -30;
    public float sphereRadius = 0.2f;
    public Transform groundCheck;
    public LayerMask groundMask;
    public float jumpHeight = 3f;
    public float sprintSpeed = 20f;
    public float walkSpeed = 15f;
    public float crouchHeight = 0.5f;
    public float crouchSpeed;
    public float startHeight;

    private Vector3 velocity;
    private bool isGrounded = true;
    private float speed;

    private void Start() {
        velocity.y = 0f;
        speed = walkSpeed;
        crouchSpeed = speed / 2;
        startHeight = transform.localScale.y;
    }

    // Update is called once per frame
    private void Update()
    {
        isGrounded = Physics.CheckSphere(groundCheck.position, sphereRadius, groundMask);

        float x = Input.GetAxisRaw("Horizontal");
        float z = Input.GetAxisRaw("Vertical");

        Vector3 move = transform.right * x + transform.forward * z;
        controller.Move(move.normalized * speed * Time.deltaTime);

        if (velocity.y < -0.35f)
        {
            velocity.y = -0.35f;
        }

        // Jumping and crouching
        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            velocity.y = jumpHeight;
        } else if (Input.GetKey(KeyCode.LeftShift)) {
            speed -= 7.5f;
            transform.localScale = new Vector3(transform.localScale.x, crouchHeight, transform.localRotation.z);
        } else if (!Input.GetKey(KeyCode.LeftShift)) {
            speed = walkSpeed;
            transform.localScale = new Vector3(transform.localScale.x, startHeight, transform.localRotation.z);
        }

        // Sprinting
        if (Input.GetKey(KeyCode.LeftControl))
        {
            speed = sprintSpeed;
        }
        else if (!Input.GetKey(KeyCode.LeftControl))
        {
            speed = walkSpeed;
        }

        velocity.y += gravity * Time.deltaTime * Time.deltaTime;
        controller.Move(velocity);
    }
}

```

Player script

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public float health = 100f;

    public void TakeDamage(float amount)
    {
        health -= amount;
        if (health <= 0)
            Destroy(gameObject);
    }
}

```

Main Camera object

Camera look script

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraLook : MonoBehaviour
{
    public float sensitivity = 100f;
    public Transform playerBody;

    private float xRotation;

    private void Start() {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    // Update is called once per frame
    void Update()
    {
        float mouseX = Input.GetAxis("Mouse X") * sensitivity * Time.fixedDeltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * sensitivity * Time.fixedDeltaTime;

        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, -90f, 90f);

        transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
        playerBody.Rotate(Vector3.up * mouseX);
    }
}

```

Hierarchy

Here is a comparison for when I am playing versus when not:

not playing

playing

# Answer
> 0 votes
When you see an object flatten, your first step should be to check whether anything is setting its transform scale properties (or its parent's/grandparent's/etc.).

That leads us to these lines:

```
} else if (Input.GetKey(KeyCode.LeftShift)) {
    speed -= 7.5f;
    transform.localScale = new Vector3(transform.localScale.x, crouchHeight, transform.localRotation.z);
} else if (!Input.GetKey(KeyCode.LeftShift)) {
    speed = walkSpeed;
    transform.localScale = new Vector3(transform.localScale.x, startHeight, transform.localRotation.z);
}

```

Note that the x component of the new scale is set to the x component of the old scale,
but the *z* component of the new scale is set to the z component of the *rotation* quaternion!

This isn't anything geometrically meaningful - a quaternion's xyz components are imaginary numbers representing the sine of half the angle of rotation times the unit axis, so they don't make sense to use as scale factors. In particular, for a character controller that only ever rotates in yaw, we expect only the y and w components of the quaternion to have non-zero values, leading to flattening when the z=0 component is used as a scale.

This was probably an error caused by hasty acceptance of an auto-complete suggestion, coupled with the fact that the line is long so the offending "rotation" part of the name might have scrolled out of view while you were hunting for the bug.

Try a pattern like this instead - it's less typing, clearer about your intention, and not so prone to hidden bugs:

```
// Copy the scale to a stack variable (no GC allocation, don't worry!)
var scale = transform.localScale;
// Modify only the part you want to change.
scale.y = startheight;
// Re-assign it.
transform.localScale = scale;

```

You can also, if you want, make a little extension method to streamline this type of operation:

```
public static class VectorExtensions {
    public Vector3 ReplaceY (this Vector3 v, float y) {
        v.y = y;
        return v;
    }
    // Repeat for x, z...
}

```

Then this becomes a one-liner that won't scroll out of your reading column:

```
transform.localScale = transform.localScale.ReplaceY(startHeight);

```

---
Tags: unity, lighting, character-controller
---


title: How do I get my game made?

text:
# Question
Title: How do I get my game made?
So, I have the idea, the concept, and know how I want to market my game... so now what? The game that I have designed is to vast to create alone, but I know that it can work. I need a large team, but I dunno how or where to get one, or how to go about this. I understand code enough to know if something can work properly, but not enough to actually make the game myself, and I am not skilled with creating art, but I know how I want things to look. What do I do? Clearly, as I have asked before, a big game company won't hire me.

# Answer
**You pay people.**

If you don't have the skills or the person-hours capacity to execute on your vision alone, then you need other people to provide that labour. That labour has value, so they need to be compensated, both as a moral imperative, but also for practicality: they need the means to feed, house, and clothe themselves and their families over the potentially many years of work it takes to build a large game.

They can't do that just on the promise of a share of future profits when the game releases and (hopefully) sells millions. No developer with any experience would take such a gamble, knowing that the large majority of games never turn a profit (even ones based on amazing ideas the creators were certain would succeed). And it would be exploitation to take advantage of inexperienced creators who don't yet understand these risks.

For a single skilled programmer, that can easily run into hundreds of thousands of dollars per year, plus benefits. So unless you're heir to a large fortune or the winner of a big lottery payout / investment windfall, it's not something you'll be able to fund alone.

So you'd probably need investors - venture capital, crowdfunding, government arts grants, etc. Here the challenge is that to invest in your proposal, these folks will expect to see evidence that you can accomplish what you claim. Without a portfolio of past successful games, that's going to be a difficult sell.

Beware of "get rich quick" schemes like cryptocurrency "initial coin offerings" as ways to raise capital - this area is rife with scams and bad actors, and consumers are rightfully becoming wary of them.

---

So, if you don't have/can't immediately get the money to fund such a project, what do you do?

**Accept that this game will not be made.**

**...at least, not *yet***.

Be willing to put this idea on the shelf for now. You can keep revisiting and polishing it every so often, until the time is right. It will wait for you.

Many game creators who eventually release their magnum opus describe it as something they had in mind for years, even decades, before the stars aligned in such a way that they could finally execute on it. Often, the idea grew and shifted with them as they developed as creators and the market evolved in the meantime, so what was eventually a success might not have been so successful, had they been able to run with their first idea when they originally thought of it.

Your first game will not be your dream game. Expect to work up to it gradually, making smaller games to build your skills, experience, and resources; or spending years contributing to others' games as just one collaborator in a team, until you develop the seniority, connections, and clout to help steer large projects.

I'm 15 years into my professional game development career, spanning roles in indie, AAA, and academia, and I still haven't made something I could call "my dream game". But I've nevertheless been able to earn a comfortable living doing a job I enjoy, and made games of which I'm fiercely proud, and I feel joy whenever I see folks play them, or hear heartfelt feedback from fans. Along the way, the games I dream of have changed more than once, and I'm still keeping notes and doing experiments that may one day culminate in a dream game, or find their way to be expressed in some form in the games I work on in the meantime.

Ze Frank's "Invocation for Beginnings" has a couple lines that have always stuck with me:

> Let me not think of my work only as a stepping stone to something else, and if it is, **let me become fascinated by the shape of the stone.**
>
> Let me take the idea that has gotten me this far and put it to bed. What I am about to do will not be that, But it will be something.

Find something to love in everything you make, and it won't sting so much that you're not able to immediately make the thing you love most.
> 8 votes

# Answer
I see a couple of options:

* Produce a full game design document and take out your wallet: there are companies out there that will produce a game for you, but you'll need to pay them. The good news is that you have the final word on what the game will look like, the bad news is that you take all the risk, because you'll be the one taking the money out of your pocket, not them.
* Reduce the scope of your project and start learning more aspects of game development, programming, art, audio, and start working on your game. You can then hit the recruitment section some web sites to try and have people come on board (GDNet comes to mind).
* Reduce the scope of your project, and start a degree in game development, then for the final project, hope your team wishes to produce your idea.
* Reduce the scope of your project and start learning more aspects of game development, programming, art, audio, and start working on your game. Work at another place to get money and pay a freelancer to have them produce the aspects of the development you're having more trouble with.
* Reduce the scope of your project to a point where it completely fits within your field of expertise, and release the game by yourself.

I understand that "Reduce the scope of your project" is there often. You need to keep a couple things in mind:

* making games is hard
* those who have never worked on a game can work for free for a time
* people working for free may, at some point, feel exploited, so they'll either tire out and stop working for you because they don't get anything in return (it's *your* pet project, not theirs), or they'll turn into freelancers
* freelancers don't work for free as working for you is their living, so you'll need to pay them.
> 6 votes

---
Tags: game-design
---


# Question
Title: How to add a fallback font to an existing Unity game?
I am trying to make a mod that will fix a game that failed to ship all used fonts with it.

For example, this code point displays in Windows but not under Wine:

> ✔ U+2713

(The reason seems to be a combination of the fact that the game's font is missing this glyph, and Wine does not handle fallback.)

How can I fix this problem?

* Is it possible to add a fallback font with a runtime script?
* Is it possible to modify the resource to add a fallback font?

I feel it is unlikely to be fixed by changing the font. The problem is that I need a CJK + symbol, and it seems that Wine does not handle fallback. And I don't know of a single font that contains this CJK + symbol.

====Update====
One workaround for Wine is to copy Segoe fonts (Segoe UI symbol for this code point) to Wine.
Unity Editor manual mentions "hard-coded global list of fallback fonts" but does not tells the exact fonts.

# Answer
One workaround for Wine is to copy Segoe fonts (Segoe UI symbol for this code point) to Wine.
Unity Editor manual mentions "hard-coded global list of fallback fonts" but does not tells the exact fonts.

AFAIK Segoe fonts are not free font, so usually modders should not distribute it.
If Wine user is unable to get Segoe fonts, it should be possible to config Wine FontLink as substitution. (I didn't test it)
Full example: https://gist.github.com/swordfeng/c3fd6b6fcf6dc7d7fa8a

```
REGEDIT4

[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink]
"Arial"="wqy-microhei.ttc"

```
> 2 votes

---
Tags: unity, windows, modding, fonts, linux
---


# Question
Title: Why does this Get Size Delta of a Rect Transform always return a value of zero when called on Start?
I have the following Unity Visual Script triggered on a Start:

It looks at a child GameObject that uses Rect Transform and has a Text Mesh Pro component, and attempts to calculate the width of the text using the Get Size Delta of the Rect Transform.

Here's the odd thing: the attached script always returns a width of 0. I think that's because the script is triggered on Start, and by then maybe the anchors haven't been set yet. That's my theory at least, because if I run this exact same code on another event besides Start... the Size Delta returns appropriate data, and it always calculates the width properly.

I'm pretty sure I could set some sort of time-based delay, and I would get the proper results... but I'd like to understand the issue better and not just kludge it.

When exactly do anchor points get set? Is there a way I could call this code in such a way that it consistently generates a width that's non-zero?

# Answer
> 2 votes
From the documentation for Canvas.ForceUpdateCanvases():

> Force all canvases to update their content.
>
> A canvas performs its layout and content generation calculations at the end of a frame, just before rendering, in order to ensure that it's based on all the latest changes that may have happened during that frame. **This means that in the Start callback and the first Update callback, the layout and content under the canvas may not be up-to-date.**
>
> Code that relies on up-to-date layout or content can call this method to ensure it before executing code that relies on it.

So you can call this method to update the anchors on demand. Just note that calling it repeatedly in a frame - such as in the Start() method for many UI elements that were all spawned that frame - might be expensive due to redundant computation. You may want to orchestrate your code so the method is called just once from a central manager script, which then calls a custom layout update method on each spawned item to respond to the new info.

Or you might want to implement your custom layout logic as its own auto layout component, by implementing the `ILayoutElement`, `ILayoutGroup`, or `ILayoutSelfController` interfaces as needed. Then your custom layout logic will get called within the flow of the canvas's main layout update, in correct dependency order. This saves you from kludging around the layout updates in Start/Update/coroutine functions.

# Answer
> 0 votes
I was able to get this working by changing two things:

1. I made it into a coroutine
2. Just before the width is calculated, I added code to make it Wait For Next Frame.

Those two tweaks fixed it! I guess Unity needs an extra frame to set the anchor points. Still not sure why anchors take time to be set, but this should fix the problem for now.

---
Tags: unity
---


# Question
Title: How to rotate door on y-axis, triggered by the player
I am trying to create the door that is triggered by the player in an FPS game.

I can't figure out how to make the door rotate only on y-axis. I want the door to be triggered by the player, but still stay on the y axis at the same time. Instead my code is making the door rotate on all angles towards the target.

public class DoorController : MonoBehaviour
{
    public GameObject Door;
    public GameObject player;
    public bool isOpen;
    public float rotationSpeed;
    private Transform target;
    private Quaternion lookRotation;
    private Vector3 direction;
    //public float moveSpeed;

    void Update()
    {
        if (isOpen)
        {
            target = player.transform;
            direction = (target.position - transform.position).normalized;
            lookRotation = Quaternion.LookRotation(direction);
            transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * rotationSpeed);

        }
    }

    void OnTriggerEnter(Collider other)
    {
        isOpen = true;
    }

    void OnTriggerExit(Collider other)
    {
        isOpen = false;
    }
}
```
</code></pre>

# Answer
As shown in gobs of past Q&A here (remember to search before posting):

```
target = player.transform;

//No benefit from normalizing this.
direction = target.position - transform.position;

// Flatten this into the horizontal plane.
direction.y = 0;

lookRotation = Quaternion.LookRotation(direction);

// No benefit from Slerp here — use cheaper Lerp.
transform.rotation = Quaternion.Lerp(transform.rotation, lookRotation, Time.deltaTime * rotationSpeed);

// Or, if you want constant rotation speed, use RotateTwoards.

```
> 0 votes

---
Tags: unity, c#, rotation, first-person-shooter
---


title: How does Unreal Engine 5's Nanite work?

text:
# Question
Title: How does Unreal Engine 5's Nanite work?
I have seen Unreal Engine 5 showcasing their Nanite system a long time ago. Like everyone, I was impressed. Since this is something that would revolutionize the gaming industry, I expected it to come into the hand of the public, by them giving it to us or leaking it out. But so far, I haven’t been able to find it. So, I jumped on Stack Overflow to ask this question, hoping to find someone who knows how it works and are willing to share it with the community.

# Answer
I'm no expert, but since no one else has jumped in, I'll do my best to summarize what I gathered.

First, here are the descriptions of Nanite directly from the Unreal folks:

# How Nanite Works

## GPU-driven rendering

First off, it's a GPU-driven rendering pipeline meaning culling (frustum and occlusion) and level-of-detail (LOD) selection happens on the GPU using compute shaders. Compute fills vertex and index buffers which are then rendered using indirect draw calls. For more see this talk from the Ubisoft folks ~2015 following Assassin's Creed Unity. Traditionally, the CPU would cull and select LODs for the frame then use draw calls to draw each visible mesh. Note that Unreal Engine 4/5's default renderer is also GPU-driven rendering.

## 128-triangle clusters hierarchically arranged by LOD

Next, in traditional rendering systems you have different LODs per-mesh, and your artists are heavily involved in creating good per-mesh LODs.

In Nanite, LODs are instead determined at a sub-mesh level with much less artist involvement. A whole tree (well a directed acyclic graph, or DAG, technically) of 128-triangle clusters (also called "meshlets") is computed for each mesh at asset import time. Each node in the tree is \<=128 triangles and the children of any node represents a more detailed view of that node. Then a "cut" of this tree is determined at runtime (on the GPU!), and based on this "cut" a set of triangle clusters will be rendered. Culling also happens on a per-cluster not per-mesh basis. Here's an excerpt from those slides:

## A stable number of triangles per frame with little overdraw

When all this comes together well, you end up with a system with a very consistent number of triangles rendered to the screen per-frame (~25M for their demo scene) and very little overdraw (aka wasted shader work). This efficiency is what lets Nanite achieve crazy high detail at decent FPS.

Note that Nanite does not currently use any raytracing, although they mention this might change in the future.

# The devil is in the details

Now this glosses over a lot of really hard details! If you implement such a system naively, you end up with really bad cracks or seams between clusters especially at adjacent jumps in LOD level \>1, visual popping as you switch between LODs, memory issues as this is an enormous amount of data (each mesh's full tree of clusters), and performance issues since this involves a ton of GPU compute pre-shading work! And exactly how does this cluster-based visibility culling happen on a GPU?

## Mesh simplification and constructing the heirarchy

First, a ton of work went into determining how triangles should be clustered and hierarchically arranged at asset-import-time so that you don't see cracks and yet you can still efficiently determine a cut of this hierarchy at runtime. This involves complex graph partitioning and multi-dimensional optimization, see the slide 50 for more.

It's worth pointing out that the most detailed view of a mesh in Nanite (the leaf nodes in the cluster graph) are exactly the same triangles as the original asset. Nanite doesn't optimize away any details, instead it displays simplified triangle clusters (higher nodes) only when the detail change isn't visually perceptible.

## LOD N vs LOD N+1 difference/error calculations

Also very essential and related (and if I understand correctly this is one of the most novel contributions of Nanite) is calculating good perceptual error metrics between different LODs. Basically, you need to know how much worse a simpler triangle cluster is than its child (i.e. more detailed) clusters in order to do good LOD selection. Amazingly, if the error difference is \<1 pixel, and you have some temporal anti-aliasing on top, you won't notice any popping!

## Cutting the cluster heirarchy DAG (LOD selection)

These two, a good cluster hierarchy and LOD error calculations, come together at runtime with view-based information (in GPU compute, using a bounding volume hierarchy (BVH) and custom parallel task system) to determine the "cut" of the cluster DAG, which is the LOD selection for that frame.

## Cluster-based visibility culling

For visibility culling, they use a modified "two-pass occlusion culling" technique where you use what was visible in the previous frame (captured in a hierarchical z-buffer (HZB)) to massively speed up your determination of what is visible this frame. Nanite diverges somewhat from other two-pass culling systems because they use a bunch of information from the LOD selection phase described above to make this work. The output of this visibility check is then written to a "visibility buffer" that includes per-pixel depth and cluster index data.

## Streaming virtual geometry

For memory management, they aggressively eject unused clusters from working memory and stream in new ones from disk. For this reason, it seems a decent SSD is basically required for Nanite to work. They call this "virtual geometry", analogous to "virtual texturing". Formatting, compressing, and deciding what to stream is complex. About ~1M input asset triangles becomes ~11MB compressed Nanite data on disk (slide 144).

## Material selection and shading

Once they have this visibility data, they then need to do material shading which outputs to g-buffers and the rest of their deferred shading pipeline. One of the more illuminating slides for me was this description of their per pixel material shading:

Which did seem crazy to me, but they point out that they get a very good cache hit-rate and no overdraw.

Knowing which materials are visible and which pixels they are assigned to is another complex task, and they use a combination of repurposed HW depth-testing for material testing and screen tiles for material culling to do this. See slides 98 onward.

## Rasterization woes

It's also worth mentioning that because Nanite has such a crazy high level of detail, they ran into rasterization problems. (Rasterization is the process of matching triangles to pixels). They commonly had triangles as small as pixels, which preformed poorly on the built-in hardware rasterization, which is overwhelmingly the common way to do rasterization. So they wrote their own software rasterizer (called Micropoly?) and it runs in a compute shader. This includes doing their own depth-testing to create their z-buffer. They then chose between HW and SW rasterization per-cluster (big triangles still work better on HW.)

Relatedly, because triangles are so small, UV derivatives need special treatment (slide 106-107).

## Virtualized shadow maps

Next, there are shadows and multi-view rendering. I feel less confident summarizing this, so I'll refer you to slides 115-120, and also their blog about bringing virtual shadow maps to Fortnite. Apparently their unique triangle cluster approach lets them efficiently maintain high resolution (16k) "virtual shadow maps".

# Limitations

## Folliage

My understanding is that Nanite isn't great at traditional alpha-clipped foliage like leaves and grass, although that seems to have improved in 5.1 16 17. In the slides, they mention limitations of the software rasterizer as a key problem here.

Their blog about bringing Nanite to Fortnite describes a lot of the challenges with vegetation and ultimately they decided that their grass and leaves would be formed out of geometry instead of alpha-clipping.

## Tiny instances

And they call out tiny mesh instances (~1px in size) as a problem they're still working to solve, with hierarchical instancing being their chosen direction. Currently they use an imposter system. As an example, if you have a large building made of tilable wall segments and you zoom out such that these wall tiles are ~1px, Nanite doesn't perform well currently.

In 5.1 with their work to bring Nanite to Fortnite, they introduced a "Preserve Area" flag which helps with this a bit.

## ??

They also mention on slide 94 that "the reliance on previous frame depth for occlusion culling is one of Nanite’s biggest deficiencies", although it's unclear to me all of what that implies.

# Nanite goals, alternatives, and prior work

## Goals

At the start of their slides, they discuss Nanite's goals and other approaches they dismissed. The goal was to be able to render high fidelity assets without require a ton of up-front work by asset creators and instead let the engine dynamically change the level of detail to maintain real-time performance.

## Alternative approaches

Approaches they considered and dismissed for various reasons included: voxels (bad at hard surfaces; fundamentally uniform sampling), subdivision Surfaces, displacement maps, geometry images, and point rendering.

This keynote by Brian at High-Performance Graphics goes into a lot more detail about these alternatives and why they didn't fit Nanite's goals.

## Prior work

Nanite was built on a lot of prior work, some of the most important seems to be Quick-VDR (2004) and Batched Multi Triangulations (2005), but there's 85 other citations on slide 149 onward.

## Who made Nanite?

The main Epic folks behind Nanite seem to be (based on the slides) Brian Karis21 22, Rune Stubbe23, Graham Wihlidal24 25.
> 25 votes

---
Tags: unreal, unreal-5
---