2D 水凹凸贴图 - Monogame
2D Water Bump Mapping - Monogame
感谢您花时间查看我的问题。
我在第一次尝试游戏时致力于改善海洋。我已经决定在我的海洋瓷砖上使用凹凸贴图来为水添加一点纹理。为此,我将水砖绘制到 renderTarget,然后在将渲染目标绘制到后备缓冲区时应用像素着色器。
我遇到的问题是像素着色器似乎偏移或置换了绘制的渲染目标的位置。观察这两张照片:
此图像是没有 运行 像素着色器的游戏。注意岛屿周围的“浅水”,这里是纯色。
使用像素着色器 运行,浅水始终向右偏移。
我正在使用 riemers novice bump mapping 中提供的凹凸贴图。我有一个可能的想法是这个凹凸贴图的尺寸与我正在应用它的渲染目标不匹配。但是,我不完全确定如何 create/resize 这个凹凸贴图。
我的 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 不会偏移,但不会应用凹凸贴图。有什么解决办法吗?
我知道偏移是由像素着色器扰动引起的,但我想知道是否有办法在保留凹凸贴图的同时撤消此偏移。在链接的 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;
}
首先,对于您似乎想要做的事情,凹凸贴图实际上是错误的方法:凹凸贴图是关于改变表面法线(基本上 "rotating" 3D 中的像素 space),因此在进行光计算(例如反射)后,您的表面会比实际情况更复杂(请注意该像素的纹理保持原样)。因此,凹凸贴图根本不会修改海洋瓷砖纹理的位置,但会修改海洋反射的内容(例如,通过改变天空盒的采样位置,所以天空在水中的反射是扭曲的)。您实现它的方式更像是 "What if my screen would be an ocean and would reflect an image of tiles with ocean textures".
如果你真的想使用凹凸贴图,你需要某种大天空纹理,然后,同时(不是之后)绘制海洋瓷砖,你会计算此天空纹理反射的样本位置(基于屏幕上瓷砖的位置),然后使用凹凸贴图修改该样本位置。所有同时绘制图块,而不是在将它们绘制到渲染目标之后。
也可以延迟执行此操作(与您现在所做的更相似)- 实际上,有多种方法可以执行此操作-但无论哪种方式,您仍然需要从天空纹理中采样最终颜色, not 来自您绘制图块的渲染目标。您的图块中的渲染目标将改为包含 "meta" 信息(取决于您想要执行此操作的精确程度)。这些信息可以是一种颜色,它与天空纹理的颜色相乘(创建 "colored" 水,例如,用于不同的生物群或模拟太阳 sets/sun 升起),或者一个简单的 1 或 0 来告诉无论是否有任何海洋,或每块凹凸贴图(您将允许一次性应用 "screen global" 和 "per tile" 凹凸贴图。您仍然需要一种方法来说明 "this pixel is not an ocean, don't do anything for that" 在渲染目标中),或者 - 如果您使用多个渲染目标 - 所有这些一次。无论如何,要从渲染目标采样的采样位置 不是 通过凹凸贴图修改的,只有海洋反射的纹理采样位置是。这样一来,海洋也没有位移,因为我们根本没有触及样本位置。
现在,要创建更类似于您想要的外观(根据您的图像),您不会使用凹凸贴图,而是对像素中的样本位置应用小噪声着色器(其余代码不需要更改)。为此,您的着色器看起来更像这样:
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 超过屏幕上 horizontal/vertical 个图块的数量,noiseOffset 可用于 "move" 屏幕上随时间推移的噪声(应在 [-1;1] 范围内),noiseFrequency 是一种艺术参数(我会从最大噪声功率的两倍开始,然后从那里修改它,更高的值使海洋更加扭曲)。这样,瓦片的边界就会变形,但在任何方向上都不会移动超过一个瓦片(多亏了 noisePower 参数)。在这里使用正确类型的噪声纹理也很重要:白噪声、蓝噪声,也许 "not really noise" 由正弦波构建的纹理等。重要的是 "average" 的值每个像素约为 0.5,因此不会发生整体位移,并且值在纹理中分布良好。除此之外,看看哪种噪音最适合你。
着色器代码的旁注:我尚未测试该代码。只是你知道,并不是说会有很大的错误空间。
编辑:作为侧节点:当然,天空纹理实际上不需要看起来像天空;)
感谢您花时间查看我的问题。
我在第一次尝试游戏时致力于改善海洋。我已经决定在我的海洋瓷砖上使用凹凸贴图来为水添加一点纹理。为此,我将水砖绘制到 renderTarget,然后在将渲染目标绘制到后备缓冲区时应用像素着色器。
我遇到的问题是像素着色器似乎偏移或置换了绘制的渲染目标的位置。观察这两张照片:
此图像是没有 运行 像素着色器的游戏。注意岛屿周围的“浅水”,这里是纯色。
使用像素着色器 运行,浅水始终向右偏移。
我正在使用 riemers novice bump mapping 中提供的凹凸贴图。我有一个可能的想法是这个凹凸贴图的尺寸与我正在应用它的渲染目标不匹配。但是,我不完全确定如何 create/resize 这个凹凸贴图。
我的 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 不会偏移,但不会应用凹凸贴图。有什么解决办法吗?
我知道偏移是由像素着色器扰动引起的,但我想知道是否有办法在保留凹凸贴图的同时撤消此偏移。在链接的 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;
}
首先,对于您似乎想要做的事情,凹凸贴图实际上是错误的方法:凹凸贴图是关于改变表面法线(基本上 "rotating" 3D 中的像素 space),因此在进行光计算(例如反射)后,您的表面会比实际情况更复杂(请注意该像素的纹理保持原样)。因此,凹凸贴图根本不会修改海洋瓷砖纹理的位置,但会修改海洋反射的内容(例如,通过改变天空盒的采样位置,所以天空在水中的反射是扭曲的)。您实现它的方式更像是 "What if my screen would be an ocean and would reflect an image of tiles with ocean textures".
如果你真的想使用凹凸贴图,你需要某种大天空纹理,然后,同时(不是之后)绘制海洋瓷砖,你会计算此天空纹理反射的样本位置(基于屏幕上瓷砖的位置),然后使用凹凸贴图修改该样本位置。所有同时绘制图块,而不是在将它们绘制到渲染目标之后。
也可以延迟执行此操作(与您现在所做的更相似)- 实际上,有多种方法可以执行此操作-但无论哪种方式,您仍然需要从天空纹理中采样最终颜色, not 来自您绘制图块的渲染目标。您的图块中的渲染目标将改为包含 "meta" 信息(取决于您想要执行此操作的精确程度)。这些信息可以是一种颜色,它与天空纹理的颜色相乘(创建 "colored" 水,例如,用于不同的生物群或模拟太阳 sets/sun 升起),或者一个简单的 1 或 0 来告诉无论是否有任何海洋,或每块凹凸贴图(您将允许一次性应用 "screen global" 和 "per tile" 凹凸贴图。您仍然需要一种方法来说明 "this pixel is not an ocean, don't do anything for that" 在渲染目标中),或者 - 如果您使用多个渲染目标 - 所有这些一次。无论如何,要从渲染目标采样的采样位置 不是 通过凹凸贴图修改的,只有海洋反射的纹理采样位置是。这样一来,海洋也没有位移,因为我们根本没有触及样本位置。
现在,要创建更类似于您想要的外观(根据您的图像),您不会使用凹凸贴图,而是对像素中的样本位置应用小噪声着色器(其余代码不需要更改)。为此,您的着色器看起来更像这样:
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 超过屏幕上 horizontal/vertical 个图块的数量,noiseOffset 可用于 "move" 屏幕上随时间推移的噪声(应在 [-1;1] 范围内),noiseFrequency 是一种艺术参数(我会从最大噪声功率的两倍开始,然后从那里修改它,更高的值使海洋更加扭曲)。这样,瓦片的边界就会变形,但在任何方向上都不会移动超过一个瓦片(多亏了 noisePower 参数)。在这里使用正确类型的噪声纹理也很重要:白噪声、蓝噪声,也许 "not really noise" 由正弦波构建的纹理等。重要的是 "average" 的值每个像素约为 0.5,因此不会发生整体位移,并且值在纹理中分布良好。除此之外,看看哪种噪音最适合你。
着色器代码的旁注:我尚未测试该代码。只是你知道,并不是说会有很大的错误空间。
编辑:作为侧节点:当然,天空纹理实际上不需要看起来像天空;)