2D Water Bump Mapping - Monogame - PullRequest
       97

2D Water Bump Mapping - Monogame

0 голосов
/ 09 октября 2019

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

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

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


Это изображение - игра без запуска пиксельного шейдера. Обратите внимание на " мелководье " вокруг островов, которое здесь сплошного цвета. enter image description here


При работе пиксельного шейдера эта мелкая вода последовательно смещается вправо. enter image description here

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

Мой пиксельный шейдер HLSL выглядит следующим образом:

#if OPENGL
    #define SV_POSITION POSITION
    #define VS_SHADERMODEL vs_3_0
    #define PS_SHADERMODEL ps_3_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 не будет смещено, но тогда рельефное отображение не применяется. Есть ли способ обойти это?

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

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;
}

1 Ответ

1 голос
/ 09 октября 2019

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

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

Также возможно сделать это отложенным (более похоже на то, что вы делаете сейчас) - на самом деле, есть несколько способов сделать это - но в любом случае вам все равно нужно будет отобрать окончательный цвет с неба. текстура, не от цели рендеринга, на которой были нарисованы ваши плитки. Вместо этого цель рендеринга из ваших плиток будет содержать «мета» информацию (в зависимости от того, как именно вы хотите это сделать). Эта информация может быть цветом, который умножается на цвет текстуры неба (создавая «цветную» воду, например, для разных биомов или имитировать заходы / восходы солнца), или простым 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;
}

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

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

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

...