2D-картографирование рельефа воды — Monogame

Спасибо, что нашли время, чтобы проверить мою проблему.

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

Проблема, с которой я сталкиваюсь, заключается в том, что пиксельный шейдер, кажется, смещает или смещает положение нарисованной цели рендеринга. Обратите внимание на эти две фотографии:


Это изображение представляет собой игру без запуска пиксельного шейдера. Обратите внимание на «мелководье» вокруг островов, которое здесь сплошного цвета. введите здесь описание изображения


При запущенном пиксельном шейдере мелководье последовательно смещается вправо. введите здесь описание изображения

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

Мой пиксельный шейдер HLSL выглядит так:

#if OPENGL
    #define SV_POSITION POSITION
    #define VS_SHADERMODEL vs
float4 MainPS(in VertexShaderOutput output) : COLOR
{
    float4 bumpColor = tex2D(bumpSampler, output.BumpMapSamplingPos.xy);
    //get offset 
    float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f;

    //apply offset to coordinates in original texture
    float2 currentCoords = output.BumpMapSamplingPos.xy;
    float2 perturbatedTexCoords = currentCoords + perturbation;

    //return the perturbed values
    float4 color = tex2D(waterSampler, perturbatedTexCoords);
    return color;
}
0 #define PS_SHADERMODEL ps
float4 MainPS(in VertexShaderOutput output) : COLOR
{
    float4 bumpColor = tex2D(bumpSampler, output.BumpMapSamplingPos.xy);
    //get offset 
    float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f;

    //apply offset to coordinates in original texture
    float2 currentCoords = output.BumpMapSamplingPos.xy;
    float2 perturbatedTexCoords = currentCoords + perturbation;

    //return the perturbed values
    float4 color = tex2D(waterSampler, perturbatedTexCoords);
    return color;
}
0 #else #define VS_SHADERMODEL vs_4_0_level_9_1 #define PS_SHADERMODEL ps_4_0_level_9_1 #endif matrix WorldViewProjection; float xWaveLength; float xWaveHeight; texture bumpMap; sampler2D bumpSampler = sampler_state { Texture = <bumpMap>; }; texture water; sampler2D waterSampler = sampler_state { Texture = <water>; }; // MAG,MIN,MIRRR SETTINGS? SEE RIEMERS struct VertexShaderInput { float4 Position : POSITION0; float2 TextureCords : TEXCOORD; float4 Color : COLOR0; }; struct VertexShaderOutput { float4 Pos : SV_POSITION; float2 BumpMapSamplingPos : TEXCOORD2; float4 Color : COLOR0; }; VertexShaderOutput MainVS(in VertexShaderInput input) { VertexShaderOutput output = (VertexShaderOutput)0; output.BumpMapSamplingPos = input.TextureCords/xWaveLength; output.Pos = mul(input.Position, WorldViewProjection); output.Color = input.Color; return output; } float4 MainPS(float4 pos : SV_POSITION, float4 color1 : COLOR0, float2 texCoord : TEXCOORD0) : COLOR { float4 bumpColor = tex2D(bumpSampler, texCoord.xy); //get offset float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f; //apply offset to coordinates in original texture float2 currentCoords = texCoord.xy; float2 perturbatedTexCoords = currentCoords + perturbation; //return the perturbed values float4 color = tex2D(waterSampler, perturbatedTexCoords); return color; } technique oceanRipple { pass P0 { //VertexShader = compile VS_SHADERMODEL MainVS(); PixelShader = compile PS_SHADERMODEL MainPS(); } };

И мой вызов отрисовки моноигры выглядит так:

    public void DrawMap(SpriteBatch sbWorld, SpriteBatch sbStatic, RenderTarget2D worldScene, GameTime gameTime)
    {

        // Set Water RenderTarget
        _graphics.SetRenderTarget(waterScene);
        _graphics.Clear(Color.CornflowerBlue);
        sbWorld.Begin(_cam, SpriteSortMode.Texture);
        foreach (var t in BoundingBoxLocations.OceanTileLocationList)
        {
            TilePiece tile = (TilePiece)t;
            tile.DrawTile(sbWorld);
        }
        sbWorld.End();

        // set up gamescene draw
        _graphics.SetRenderTarget(worldScene);
        _graphics.Clear(Color.PeachPuff);

        // water
        sbWorld.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
        oceanRippleEffect.Parameters["bumpMap"].SetValue(waterBumpMap);
        oceanRippleEffect.Parameters["water"].SetValue(waterScene);
        //oceanRippleEffect.Parameters["xWaveLength"].SetValue(3f);
        oceanRippleEffect.Parameters["xWaveHeight"].SetValue(0.3f);
        ExecuteTechnique("oceanRipple");
        sbWorld.Draw(waterScene, Vector2.Zero, Color.White);
        sbWorld.End();

        // land
        sbWorld.Begin(_cam, SpriteSortMode.Texture);
        foreach (var t in BoundingBoxLocations.LandTileLocationList)
        {
            TilePiece tile = (TilePiece)t;
            tile.DrawTile(sbWorld);
        }
        sbWorld.End();

    }

Может ли кто-нибудь увидеть какие-либо проблемы с моим кодом или иным образом, которые могут вызывать эту проблему смещения?

Любая помощь горячо приветствуется. Спасибо!

ИЗМЕНИТЬ

Если я изменяю параметр шейдера xWaveHeight, он меняется там, где появляется смещение. Значение 0 не будет смещено, но тогда рельефное отображение не применяется. Есть ли способ обойти это?

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

float4 MainPS(in VertexShaderOutput output) : COLOR
{
    float4 bumpColor = tex2D(bumpSampler, output.BumpMapSamplingPos.xy);
    //get offset 
    float2 perturbation = xWaveHeight * (bumpColor.rg - 0.5f)*2.0f;

    //apply offset to coordinates in original texture
    float2 currentCoords = output.BumpMapSamplingPos.xy;
    float2 perturbatedTexCoords = currentCoords + perturbation;

    //return the perturbed values
    float4 color = tex2D(waterSampler, perturbatedTexCoords);
    return color;
}

person Lamar    schedule 09.10.2019    source источник


Ответы (1)


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

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

Также можно сделать это отложенно (более похоже на то, что вы делаете сейчас) - на самом деле, есть несколько способов сделать это - но в любом случае вам все равно нужно будет взять образец окончательного цвета из текстуры неба, не из цели рендеринга, на которой были нарисованы ваши плитки. Вместо этого цель рендеринга из ваших плиток будет содержать «мета» информацию (в зависимости от того, как именно вы хотите это сделать). Эта информация может быть цветом, который умножается на цвет текстуры неба (создание «цветной» воды, например, для разных биомов или для имитации заката/восхода солнца), или простой 1 или 0, чтобы сказать, есть это или нет. это любой океан или карта рельефа для каждой плитки (что позволит вам применить «глобальное отображение экрана» и отображение рельефа «для каждой плитки» за один раз. Вам все равно понадобится способ сказать «этот пиксель не является океаном , ничего для этого не делайте" в цели рендеринга) или - если вы используете несколько целей рендеринга - все сразу. В любом случае, положение образца для выборки из ваших целей рендеринга не изменяется рельефным отображением, изменяется только положение образца текстуры, отражаемой океаном. Таким образом, нет никакого смещения океана, так как мы вообще не касаемся этих точек выборки.

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

texture noiseTexture;
sampler2D noiseSampler = sampler_state
{
    Texture = <noiseTexture>;
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};
float2 noiseOffset;
float2 noisePower;
float noiseFrequency;
VertexShaderOutput MainVS(in VertexShaderInput input)
{
    VertexShaderOutput output = (VertexShaderOutput)0;

    output.Pos = mul(input.Position, WorldViewProjection);
    output.Color = input.Color;

    return output;
}
float4 MainPS(float4 pos : SV_POSITION, float4 color1 : COLOR0, float2 texCoord : TEXCOORD0) : COLOR
{
    float4 noise = tex2D(noiseSampler, (texCoord.xy + noiseOffset.xy) * noiseFrequency);
    float2 offset = noisePower * (noise.xy - 0.5f) * 2.0f;

    float4 color = tex2D(waterSampler, texCoord.xy + offset.xy);
    return color;
}

Где шумовая мощность будет (максимум) прибл. 1 над количеством горизонтальных/вертикальных плиток на экране, шумовой сдвиг можно использовать для «перемещения» шума на экране во времени (должен быть в диапазоне [-1;1]), а шумовая частота является художественным параметром (я бы начните с удвоенной максимальной мощности шума, а затем измените ее оттуда, с более высокими значениями, делающими океан более искаженным). Таким образом, граница тайлов искажается, но никогда не перемещается более чем на один тайл в любом направлении (благодаря параметру NoisePower). Здесь также важно использовать правильную текстуру шума: белый шум, синий шум, может быть, «не совсем шумовая» текстура, построенная из синусоидальных волн и т. д. Важным является тот факт, что «среднее» значение каждого пикселя составляет около 0,5, поэтому общего смещения не происходит, и значения хорошо распределяются в текстуре. Помимо этого, посмотрите, какой шум лучше всего подходит вам.

Примечание к коду шейдера: я не тестировал этот код. Просто чтобы вы знали, а не то, что было бы много места для ошибок.

Редактировать: в качестве бокового узла: конечно, текстура неба не обязательно должна выглядеть как небо;)

person Bizzarrus    schedule 09.10.2019
comment
Большое спасибо за этот ответ. В настоящее время я перевариваю то, что вы сказали, и я думаю, что это исправляет многие неправильные представления, которые у меня были. Когда я вернусь домой, я проверю вашу рекомендацию использовать сэмплер шума. Думаю, я планирую попробовать разные типы шумовых текстур. Могу ли я просто взять любые текстуры шума, которые я найду в Интернете, и попробовать их? Или я должен как-то сам создавать и настраивать текстуру шума? Если второе, то знаете, как бы я это сделал? - person Lamar; 09.10.2019
comment
Думаю, я решил использовать здесь карту шума Перлина. Посмотрите на riemers.net/eng/Tutorials/XNA/ Csharp/Series4/Perlin_noise.php . Однако в этом уроке он генерирует карту шума (облака) в коде. Мне интересно, могу ли я просто загрузить окончательный результат и пропустить все выборки с разными разрешениями. Что вы думаете @Bizzarrus ? - person Lamar; 09.10.2019
comment
Это немного зависит от того, какой результат вы хотите. В общем, вы можете использовать любую карту шума, а также можете использовать для этого предварительно сгенерированную текстуру, да. Конечно, использование одной предварительно сгенерированной текстуры создает довольно фиксированный рисунок шума, который, особенно с белым шумом (например, перлином), может стать очевидным для игрока. Генерация шума на лету или использование нескольких предварительно сгенерированных текстур может сделать паттерны менее очевидными, но также может вызвать слишком быстрое мерцание. - person Bizzarrus; 09.10.2019
comment
Хорошая реализация, которую я когда-то видел, использовала 3 предварительно сгенерированных текстуры шума (или, скорее, одну с 3 разными шумовыми цветовыми каналами) и масштабировала позиции выборки для них в каждом кадре с помощью синусоидальных кривых разных частот, которые зависят от общего времени игра идет. Это создает хороший эффект колебания, так как шум продолжает меняться, но только медленно и довольно дешево (в основном это мой код, просто скопированный 3 раза с разными - и со временем медленно изменяющимися - значениями средний из 3-х или около того) @Lamar - person Bizzarrus; 09.10.2019
comment
Круто, я думаю, что у меня есть солидный набор информации, которая поможет мне пройти через это сейчас. Помощь очень ценится. Я приму этот ответ, как только немного поиграю с ним. - person Lamar; 09.10.2019
comment
Карта шума дала отличные результаты, но я столкнулся с некоторыми проблемами с ней и с движением камеры. Я создал для него новый вопрос здесь, если у вас есть время, чтобы проверить его @Bizzarrus stackoverflow.com/questions/58314963/2d-water-noise-monogame - person Lamar; 10.10.2019
comment
@Lamar, вам нужно изменить смещение шума на расстояние, которое камера переместила с момента последнего кадра, вот и все. (И помните, что перемещаемое расстояние, вероятно, находится в мировом пространстве, а смещение шума находится в пространстве экрана, поэтому вам нужно применить матрицу WorldViewProjection к вектору расстояния, поскольку вы можете изменить смещение шума с его помощью) - person Bizzarrus; 10.10.2019
comment
Я пробовал это несколькими разными способами сейчас. Изменение экранных координат вне hlsl и с помощью mul(). Я просто не могу заставить это работать. Вы уверены, что я должен компенсировать NoiseOffset? Даже если я сохраняю это значение равным 0 с течением времени (чтобы вода не двигалась), а затем перемещаю камеру, у меня все равно возникает та же проблема. @Bizzarrus - person Lamar; 12.10.2019
comment
@Lamar Трудно сказать наверняка без более глубокой отладки, но до тех пор, пока XNA не мешает значению texCoord и значение NoiseOffset правильно передается от ЦП к ГП: да, шумокоррекция просто должна быть смещена правильно. Если смещение шума остается равным 0, один и тот же пиксель на вашем экране всегда должен получать одно и то же значение смещения в пиксельном шейдере, поэтому искажение является постоянным для экрана. Поэтому, если камера движется, создается визуальная иллюзия эффекта искажения, движущегося по воде, в то время как на самом деле эффект остается там, где он есть, просто вода движется под ним. - person Bizzarrus; 13.10.2019
comment
@Lamar для отладки: просто верните значение шума в вашем пиксельном шейдере в качестве цвета. Если вы сделаете это, а NoiseOffset останется равным 0, а XNA не сделает ничего связанного, вы должны экранировать значения шума (вместо воды), и они не должны перемещаться относительно экрана (поэтому мир перемещается на экране, когда ходьба, но не шум). Если вы правильно сместите NoiseOffset, этот шум должен двигаться, как если бы это была вода. Вот тогда правильный зачет. - person Bizzarrus; 13.10.2019
comment
@Lamar PS: Если кажется, что это вообще не работает, попробуйте изменить (texCoord.xy + NoiseOffset.xy) * NoiseFrequency на (texCoord.xy * NoiseFrequency) + NoiseOffset.xy. - person Bizzarrus; 13.10.2019
comment
Я наконец понял это. На самом деле мне пришлось нормализовать вектор расстояния камеры относительно размера мира. так что-то вроде этого camMove.X = ((camDistance.X / (GameOptions.PrefferedBackBufferWidth * GameOptions.GameMapWidthMult))); Еще раз спасибо за помощь, @Bizzarrus - person Lamar; 18.10.2019