Блендинг террейна и меша в Unity

Автор оригинала: Louis Waloschek
  • Перевод
Всем привет, в июне OTUS вновь запускает курс «Разработчик игр на Unity». В преддверии старта курса, мы подготовили перевод интересного материала по теме.




Сегодня мы поговорим о том, как делать блендинг меша с террейном (или с другими мешами) в Unity. Это руководство довольно продвинутое, но я постарался разбить его на отдельные этапы. Предполагается, что у вас есть общие навыки работы с Unity и базовое знание С#. Руководство разработано для amplify, но я думаю, что его можно также применить и к Shader Graph. Это мое первое руководство, поэтому надеюсь, что оно получится достаточно понятным. Если вы захотите что-то добавить, дайте знать. Я включил сюда стартовый набор, в котором есть некоторые дополнительные шейдеры для этого проекта, а также базовый комплект для начала работы. Все файлы проекта из сегодняшнего руководства доступны моим патронам за $5, но в ближайшем будущем, они будут доступны всем желающим.

Стартовый набор
Весь проект

Примечание: Учитывая длину этого руководства, я хочу быть уверен, что вы знаете о преимуществах и недостатках этого шейдера. Сегодня мы поговорим о двух версиях шейдера. Первая позволяет выполнять блендинг одной текстуры, поэтому шейдер подходит для текстур с высоким разрешением, но получается, что вы ограничены всего одной текстурой. Второй шейдер позволяет делать блендинг любого количества текстур террейнов с низким разрешением или одного цвета (или мешей текстур террейнов с некоторыми модификациями) и работает на террейнах unity и на мешах террейнов. У этих шейдеров нет нормального полнофункционального блендинга, что возможно пригодилось бы в нестилизованных играх, однако есть способ реализовать его самостоятельно.

Итак, начнем. Для начала я выделю проблему и углублюсь в теорию, на которой будет основано решение. Чтобы сделать блендинг между террейном и другими мешами нужно как-то сказать шейдеру, где происходит пересечение с террейном. Сказать легче, чем сделать, но способ есть. И в чем же он заключается? Нам поможет Render Texture!

Что такое Render Texture?


Render Texture – это, по сути, позиция камеры, которая сохранена в asset-файле. Render Texture в играх чаще всего используется для таких вещей, как создание экранов видеонаблюдения. Мы можем использовать Render Texture для сохранения вида с камеры в редакторе, чтобы затем эту текстуру можно было использовать как шейдер. Полезно это, потому что мы можем запекать информацию о нашем террейне, такую как высота, нормали и цвета, в текстуру, которую затем можно использовать в рантайме для блендинга между мешами и террейном.


Рендеринг текстур используется для вывода изображения гуся на телевизоры в Untitled Goose Game

Настройка


Для начала давайте создадим новую сцену и террейн. Установим какой-нибудь удобный для работы размер, например, 200x200. Теперь вы можете обустроить террейн, как вам захочется. Затем создайте новый слой и назовите его “Terrain” и присвойте террейн этому слою. Это нужно, чтобы маска камеры могла записывать террейн в Render Texture.


Мой шедевральный террейн

В исходных файлах проекта есть префаб, который называется “BlendBakingCamera” – перетащите его на сцену. Вы получите простую ортографическую камеру. На камере вам нужно поставить culling mask на новый слой террейна. Расположите камеру по центру террейна немного выше самой высокой точки на террейне. Затем отрегулируйте far clip plane так, чтобы камера видела пол террейна. В итоге выглядеть сцена должна примерно так:


Replacement Shader


Теперь, когда камера настроена, нужно найти способ записывать данные террейна. Для этого нам понадобятся Replacement Shaders. Replacement Shader кажется сомнительной перспективой, я и сам долгое время не понимал, как он работает. Но на самом деле все очень просто и эффективно. Использование Replacement Shader, по сути, означает визуализацию каждого объекта в поле зрения камеры с помощью одного шейдера, независимо от того, какой шейдер наложен на объекты. В итоге все объекты будут рендериться с помощью выбранного Replacement Shader, который на самом деле просто является обычным шейдером.

Шейдер, который нам понадобится для блендинга – это шейдер глубины. Он рендерит глубину сцены и является ключевым компонентом в создании нашего эффекта блендинга, поскольку он записывает значения глубины нашей камеры в текстуру, чтобы позже мы смогли их прочитать. Чтобы узнать больше об этом шейдере и о Replacement Shader в целом, рекомендую ознакомиться с этим руководством от Making Stuff Look Good in Unity.


Пример шейдера глубины

Начнем запекать


Давайте создадим новый класс и назовем его “TerrainBlendingBaker”. Начнем с реализации маски глубины для базового террейна. Позже мы еще вернемся к этому скрипту, чтобы добавить цвета и нормали.

Определим несколько переменных.

//Shader that renders object based on distance to camera
public Shader depthShader;
//The render texture which will store the depth of our terrain
public RenderTexture depthTexture;
//The camera this script is attached to
private Camera cam;

Теперь давайте создадим новый метод и назовем его «UpdateBakingCamera». В этом методе мы определим данные о камере, которые могут понадобиться шейдеру для рендера блендинга в глобальных переменных.

private void UpdateBakingCamera()
    {
        //if the camera hasn't been assigned then assign it
        if (cam == null)
        {
            cam = GetComponent<Camera>();
        }
 
        //the total width of the bounding box of our cameras view
        Shader.SetGlobalFloat("TB_SCALE", GetComponent<Camera>().orthographicSize * 2);
        //find the bottom corner of the texture in world scale by subtracting the size of the camera from its x and z position
        Shader.SetGlobalFloat("TB_OFFSET_X", cam.transform.position.x - cam.orthographicSize);
        Shader.SetGlobalFloat("TB_OFFSET_Z", cam.transform.position.z - cam.orthographicSize);
        //we'll also need the relative y position of the camera, lets get this by subtracting the far clip plane from the camera y position
        Shader.SetGlobalFloat("TB_OFFSET_Y", cam.transform.position.y - cam.farClipPlane);
        //we'll also need the far clip plane itself to know the range of y values in the depth texture
        Shader.SetGlobalFloat("TB_FARCLIP", cam.farClipPlane);
 
        //NOTE: some of the arithmatic here could be moved to the shader but keeping it here makes the shader cleaner so ¯\_(ツ)_/¯
    }

Теперь давайте запечем глубину террейна в текстуру.

// The context menu tag allows us to run methods from the inspector (https://docs.unity3d.com/ScriptReference/ContextMenu.html)
[ContextMenu("Bake Depth Texture")]
public void BakeTerrainDepth()
{
    //call our update camera method 
    UpdateBakingCamera();
 
    //Make sure the shader and texture are assigned in the inspector
    if (depthShader != null && depthTexture != null)
    {
        //Set the camera replacment shader to the depth shader that we will assign in the inspector 
        cam.SetReplacementShader(depthShader, "RenderType");
        //set the target render texture of the camera to the depth texture 
        cam.targetTexture = depthTexture;
        //set the render texture we just created as a global shader texture variable
        Shader.SetGlobalTexture("TB_DEPTH", depthTexture);
    }
    else
    {
        Debug.Log("You need to assign the depth shader and depth texture in the inspector");
    }
}

Значения станут понятнее, когда мы начнем работать с шейдером. Вот небольшая картинка, которая может пролить немного света:


Мы собираемся перенести террейн на текстуру глубины, чтобы впоследствии прочитать ее и понять, где делать блендинг

Теперь у нас есть все, что нужно для создания базового эффекта блендинга в шейдере. На данном этапе скрипт выглядит примерно так: pastebin.com/xNusLJfh

Хорошо, теперь, когда у нас есть скрипт, мы можем добавить его к baking camera, которую мы добавили ранее. В начальных ассетах есть шейдер, который называется ‘DepthShader’ (Inresin/Shaders/DepthShader) и Render Texture, которая называется ‘DepthTerrainRT’ (Inresin/RenderTextures/DepthTextureRT), вам нужно поместить их в соответствующие поля в инспекторе.

После этого просто запустите метод через контекстное меню, чтобы запечь нашу глубину террейна в Render Texture.


Шейдер


Давайте, наконец, создадим шейдер для блендинга. Создайте новый стандартный amplify shader и откройте его, назовите ‘TerrainBlending’ или около того.

Теперь нам нужно создать UV для Render Texture. Это будет разница между точкой, которая рендерится и позицией baking camera, масштабированной относительно общей площади. Три глобальные переменные здесь те, которые мы только что объявили в коде. Также мы задаем worldY как локальную переменную, она нам понадобится позже.



Давайте возьмем текстуру глубины, которую мы назначили в качестве глобальной переменной (для этого добавьте texture sample node, сделайте его глобальным и назовите ‘TB_DEPTH’), если мы поместим выходные данные в поле debug amplify shader’a, мы сможем увидеть, что происходит. Создайте плоскость с материалом, к которому будет применен наш новый шейдер.


Итак, в шейдере у нас есть информация о глубине, теперь нужно добавить смещение по y, чтобы получить блендинг.



Этот блок масштабирует положение по y маски far clip plane, вычитает это значение из позиции мира по оси у точки, которая рендерится, а затем, наконец, смещает его на к нижней стороне ограничительной рамки камеры (положение камеры по оси y минус farclip plane).

Уже что-то! Мы видим, как края плоскости сливаются с террейном.



Хорошо, давайте сделаем блендинг более подконтрольным.



Теперь мы можем контролировать толщину и область блендинга террейна.



Думаю, что мы можем даже добавить немного шума. Давайте воспользуемся world position, чтобы сгенерировать шум из текстуры.



Текстуры шума лежат в папке с текстурами в стартовом проекте, их можно назначить в инспекторе или как константу в самом шейдере.

Наконец-то пришло время добавить немного текстур! Для начала давайте используем несколько простых одноцветных текстур, я добавил две текстуры в папку с ассетами текстур. Сделайте текстуру ‘SingleColorGrass’ текстурой террейна. Затем в шейдере нужно создать текстуру террейна и object texture node. Мы будем переключаться между ними по красному каналу маски, которую мы только что создали.




А вот и полный шейдер



Добавление кастомного освещение toon или модели unlit lighting обеспечит лучшие результаты работы этого шейдера. Я включил unlit шейдер террейна и unlit версию шейдера в полный пакет, доступный спонсорам.


Unlit террейн и меши

Также я рекомендую добавить трипланарный шейдер террейну и, возможно, другим мешам. Я могу рассмотреть этот вопрос в следующем руководстве.

Что же, мы почти закончили с основной темой сегодняшнего руководства. Я добавил несколько разделов, которые могут помочь с расширением шейдера.

Расширение шейдера — нормали


Я добавил в файлы обычный шейдер, вы можете использовать его для записи нормалей к поверхности террейна и также использовать блендинг. Для моей игры мне не нужен блендинг нормалей, поэтому я просто немного поэкспериментировал, и похоже, что эксперимент удался, и моя идея хорошо работает для террейна без высокой степени изменения высоты. Я включил шейдер маппинга нормалей в директорию с шейдерами стартового набора. Здесь вы можете увидеть мою базовую реализацию карты нормалей:



Код почти такой же, как и в случае с картой глубины, но на этот раз мы будем использовать карту нормалей в качестве replacement shader. Также я добавил набор render texture для записи нормали (здесь может понадобиться дополнительная настройка).

Чтобы нормали работали хорошо, может понадобиться чуть больше усилий связанных с настройкой, также может возникнуть ограничение, связанное с низко находящимися террейнами (но я не проводил достаточно тестов, чтобы утверждать это).

Расширение шейдера – все цвета


Я не собираюсь глубоко вдаваться в подробности этой темы, поскольку она выходит за рамки того, что требуется для моей игры, но я додумался до этого во время написания этого руководства. Чтобы добавить блендинг множества цветов, мы можем выбрать unlit цвета террейна и сохранить их как текстуры. В этом случае мы ограничены достаточно низким разрешением, но этот метод хорошо работает при использовании одноцветных текстур террейна и текстур с низким разрешением, или же при использовании faint bleed. С учетом небольших корректировок, они также могут быть применены к мешам террейнов.

Здесь приведен код для многоцветного варианта:

[Header("The following settings are only if using the multi-color terrain shader")]
//Shader that renders the unlit terraom of an object
public Shader unlitTerrainShader;
//The render texture which will store the normals of our terrain
public RenderTexture surfaceTexture;
//An unlit terrain material used to capture the texture of our terrain without any lighting
public Material unlitTerrainMaterial;
//The terrain you want to capture the textures of
public Terrain yourTerrain;
 
[ContextMenu("Bake Surface Texture")]
public void BakeTerrainSurface()
{
    UpdateBakingCamera();
 
    //return if there is no terrain assigned
    if (yourTerrain == null)
    {
        Debug.Log("You need to assign a terrain to capture surface texture");
        return;
    }
 
    StartCoroutine(BakeColors());
}
 
IEnumerator BakeColors()
{
    Material tempTerrainMaterial = yourTerrain.materialTemplate;
 
    yourTerrain.materialTemplate = unlitTerrainMaterial;
 
    yield return 0;
 
    cam.SetReplacementShader(unlitTerrainShader, "RenderType");
    cam.targetTexture = surfaceTexture;
    Shader.SetGlobalTexture("TB_SURFACE", surfaceTexture);
 
    yield return 0;
 
    cam.targetTexture = null;
    yourTerrain.materialTemplate = tempTerrainMaterial;
 
    yield return null;
 
}

Единственное изменение в шейдере заключается в том, что вместо использования предопределенной текстуры мы используем глобально заданную текстуру поверхности террейна, которая будет использовать относительное положение в качестве UV. Дополнительное расширение позволит получить более приятный блендинг текстур.


Блендинг нескольких текстур

Здесь представлен полный многоцветный граф с шейдером блендинга нормалей:




Заключение


Поздравляю вас, если вы дочитали до этого момента! Как я уже говорил, это мое первое руководство, поэтому я буду очень признателен за обратную связь. Если вы хотите поддержать меня в создании игр и руководств, загляните сюда или подпишитесь на мой Twitter.



Узнать подробнее о курсе
OTUS. Онлайн-образование
Цифровые навыки от ведущих экспертов

Похожие публикации

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое