0

Thanks for taking the time to check out my issue.

I am working on improving the ocean in my first attempt at a game. I have decided on a using a bump map against my ocean tiles to add a little texture to the water. To do this, I draw my water tiles to a renderTarget and then apply a pixel shader while drawing the render target to the backbuffer.

The problem I am having is that the pixel shader seems to offset or displace the position of render target that is drawn. Observe these two photos:


This image is the game without running the pixel shader. Notice the "shallow water" around the islands which is a solid color here. enter image description here


With the pixel shader is run, that shallow water is offset to the right consistently. enter image description here

I am using the bump map provided in riemers novice bump mapping. One possible thought I had was that the dimensions of this bump map do not match the render target I am applying it on. However, I'm not entirely sure how I would create/resize this bump map.

My HLSL pixel shader looks like this:

#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();
    }
};

And my monogame draw call looks like this:

    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();

    }

Can anyone see any issues with my code or otherwise that might be causing this offset issue?

Any help is much appreciated. Thanks!

EDIT

If I modify the xWaveHeight shader parameter, it changes where the offset appears. A value of 0 will not offset, but then the bump mapping is not applied. Is there any way around this?

I understand that the offset is being caused by the pixel shader perturbation, but I'm wondering if there is a way to undo this offset while preserving the bump mapping. In the linked riemer's tutorial, a vertex shader is included. I'm not quite sure if I need this, but when I include my vertex shader in the technique, and modify the pixel shader to the following, no water is drawn.

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;
}
Lamar
  • 581
  • 7
  • 22

1 Answers1

1

First of all, for what you seem to be wanting to do, bump mapping is actually the wrong approach: bump mapping is about changing the surface normal (basicly "rotating" the pixel in 3D space), so following light calculations (such as reflection) see your surface as more complex then it really is (Notice that the texture of that pixel stays where it is). So, bump mapping would not at all modify the position of the ocean tile texture, but modify what is reflected by the ocean (for example, by changing the sample position of a skybox, so the reflection of the sky in the water is distorted). The way you are implementing it is more like "What if my screen would be an ocean and would reflect an image of tiles with ocean textures".

If you really want to use bump mapping, you would need some kind of big sky texture, and then, while (not after) drawing the ocean tiles, you would calculate a sample position of the reflection of this sky texture (based on the position of the tile on the screen) and then modify that sample position with bump mapping. All while drawing the tiles, not after drawing them to a render target.

It is also possible to do this deffered (more similar to what you are doing now) - actually, there are multiple ways of doing so - but either way you would still need to sample the final color from a sky texture, not from the render target your tiles were drawn on. The render target from your tiles would instead contain "meta" informations (depending on how exactly you want to do this). These informations could be a color that is multiplied with the color from the sky texture (creating "colored" water, eg. for different bioms or to simulate sun sets/sun rises), or a simple 1 or 0 to tell wether or not there is any ocean, or a per-tile bump map (which would you allow to apply a "screen global" and a "per tile" bump mapping in one go. You would still need a way to say "this pixel is not an ocean, don't do anything for that" in the render target), or - if you use multiple render targets - all of these at once. In any way, the sample position to sample from your render target(s) is not modified by bump mapping, only the sample position of the texture that is reflected by the ocean is. That way, there's also no displacement of the ocean, since we aren't touching that sample positions at all.

Now, to create a look that is more similar to what you seem to be wanting (according to your images), you wouldn't use bump mapping, but instead apply a small noise to the sample position in your pixel shader (the rest of the code doesn't need to change). For that, your shader would look more like this:

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

Where noisePower would be (at most) approx. 1 over the number of horizontal/vertical tiles on the screen, noiseOffset can be used to "move" the noise over time on the screen (should be in range [-1;1]), and noiseFrequency is an artistic parameter (I would start with twice the max noise power, and then modify it from there, with higher values making the ocean more distorted). This way, the border of the tiles is distorted, but never moved more then one tile in any direction (thanks to the noisePower parameter). It is also important to use the correct kind of noise texture here: white noise, blue noise, maybe a "not really noise" texture that's build out of sinus waves, etc. Important is the fact that the "average" value of each pixel is about 0.5, so there's no overall displacement happening, and that the values are well distributed in the texture. Appart from that, see what kind of noise looks best for you.

Side note to the shader code: I haven't tested that code. Just that you know, not that there would be much room for mistakes.

Edit: As a side node: Of course the sky texture doesn't need to actualy look like a sky ;)

Bizzarrus
  • 1,194
  • 6
  • 10
  • Thanks so much for this answer. I am currently digesting what you have said, and I think it is correcting a lot of misconceptions I had. When I get home, I will test out your recommendation to use a noise sampler. I guess I plan to try out different types of noise textures. Can I just pick up any noise textures I find online and try them? Or should I somehow be creating and tailoring the noise texture myself? If the latter, do you know how I would go about doing so? – Lamar Oct 09 '19 at 17:15
  • I think i've decided to use a perlin noise map here. Taking a look at http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series4/Perlin_noise.php . However, in this tutorial he generates the noise (cloud) map in the code. I'm wondering if I can just download the final result and skip all of the sampling at different resolutions. What do you think @Bizzarrus ? – Lamar Oct 09 '19 at 19:03
  • It depends a bit on what result you want. In general, you can use any noise map, and can also use a pre-generated texure for that, yes. Of course, using a single pre-generated texture creates quite fixed noise pattern, that - especially with white noise (like perlin) can become obvious to the player. Generating the noise "on the fly" or using multiple pre-generated textures can make the patterns less obvious, but may also cause way too fast "flickering". – Bizzarrus Oct 09 '19 at 19:46
  • A nice implementation I once saw used 3 pre-generated noise textures (or, rather one with 3 different noise-color-channels) and scaled the sampling positions for them each frame with sin curves of different frequencies, that depend on the overal time the game is running. That creates a nice "wobbling" effect, since the noise keeps changing, but only slowly - and quite cheap (it's basicly my code, just 3 times copied with different - and over time slowly changing - noiseOffset and noiseFrequency values and then the final noise value is the average, of the 3 or so) @Lamar – Bizzarrus Oct 09 '19 at 19:51
  • Awesome, I think I have a solid set of info to help me move through this now. The help is much appreciated. I'll accept this answer once I play around with it a little bit. – Lamar Oct 09 '19 at 20:02
  • The noise map produced some cool results, but I ran into some problems with it and my camera movement. I created a new question for it here if you have any time to check it out @Bizzarrus https://stackoverflow.com/questions/58314963/2d-water-noise-monogame – Lamar Oct 10 '19 at 13:47
  • @Lamar you need to modify the noise offset by the distance the camera moved since the last frame, that's all. (And remember that the distance moved is propably in world space, while the noise offset is in screen space, so you need to apply the WorldViewProjection matrix to the distance vector bevor you can modify the noise offset by it) – Bizzarrus Oct 10 '19 at 19:57
  • I've tried this a few different ways now. Changing to screen coords outside of hlsl, and within using mul(). I just can't seem to get this to work. Are you sure that I should be offsetting the noiseOffset? Even if i keep this value at 0 over time (so there is no water movement), and then move the camera, I still have the same problem. @Bizzarrus – Lamar Oct 12 '19 at 00:47
  • @Lamar It's hard to tell for sure without more in-depth debuging, but as long as XNA doesn't interfere with the texCoord value and the noiseOffset value is transfered correctly from CPU to GPU: yes, noiseOffset just need to be offsetted correctly. If noise offset stays at 0, the same pixel on your screen should allways get the same offset value in your pixel shader, so the distortion is screen-constant. Therefore, if the camera moves, it creates a visual illusion of the distortion effect moving on the water, while the effect actually stays where it is, just the water is moving beneath it. – Bizzarrus Oct 12 '19 at 23:37
  • @Lamar for debugging: Just return the noise value in your pixel shader as the color. If you do this, and noiseOffset stays 0, and XNA doesn't do anything wired, you should screen the noise values (instead of the water), and that should not move relativ to the screen (so the world moves on the screen when walking, but not the noise). If you offset the noiseOffset correctly, this noise should move as if it were the water. That's then the right offsetting. – Bizzarrus Oct 12 '19 at 23:41
  • @Lamar PS: If this doesn't seem to work at all, try if changing (texCoord.xy + noiseOffset.xy) * noiseFrequency to (texCoord.xy * noiseFrequency) + noiseOffset.xy would do the trick – Bizzarrus Oct 12 '19 at 23:43
  • I finally figured this out. I actually had to normalize the camera distance vector against the world size. so something like this `camMove.X = ((camDistance.X / (GameOptions.PrefferedBackBufferWidth * GameOptions.GameMapWidthMult)));` Thanks for your help again, @Bizzarrus – Lamar Oct 17 '19 at 23:37