You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Monogame:缩放3D网格时实现纹理重复的方法求助

问题:3D网格缩放时纹理拉伸,需实现重复/拉伸模式切换

我正在开发一款以3D网格构建游戏世界的第一人称射击游戏,网格的每个面(前、后、左、右、上、下)均可设置纹理,当前核心实现代码如下:

public Block()
{
    vertexData = new List<VertexPositionNormalTexture>();

    //front
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0.0f, 1.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(1.0f, 1.0f)));

    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(1.0f, 1.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(1.0f, 0.0f)));

    SetUp(TexType.front);
    vertexData = new List<VertexPositionNormalTexture>();

    //right
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0.0f, 1.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(1.0f, 1.0f)));

    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(1.0f, 1.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(1.0f, 0.0f)));

    SetUp(TexType.right);
    vertexData = new List<VertexPositionNormalTexture>();

    //and so on for the remaining faces...
}


public void SetUp(TexType texType)
{
    vertexBuffer = new VertexBuffer(Basic.gDevice, typeof(VertexPositionNormalTexture), vertexData.Count, BufferUsage.WriteOnly);
    vertexBuffer.SetData(vertexData.ToArray());

    buffers.Add(new Tuple<VertexBuffer, TexType>(vertexBuffer, texType));
}

public void Draw(Matrix world)
{
        foreach (var buffer in buffers)
        {
            string texName = textures[buffer.Item2.ToString()];
            GameScreen.effect.Texture = Textures.textures[texName];

            SetEffectsAndDraw(world, buffer.Item1, buffer.Item1.VertexCount);
        }
}
private void SetEffectsAndDraw(Matrix world, VertexBuffer buffer, int primitiveCount)
{
    GameScreen.effect.View = GameScreen.camera.view;
    GameScreen.effect.Projection = GameScreen.camera.projection;
    GameScreen.effect.EnableDefaultLighting();
    GameScreen.effect.World = world;

    GameScreen.effect.FogEnabled = true;
    GameScreen.effect.FogColor = Color.Black.ToVector3();
    GameScreen.effect.FogStart = 4.0f;
    GameScreen.effect.FogEnd = 12.0f;

    //GameScreen.effect.TextureEnabled = true;

    GameScreen.effect.CurrentTechnique.Passes[0].Apply();

    Basic.gDevice.SetVertexBuffer(buffer);
    Basic.gDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, primitiveCount / 3);
}

目前可对网格进行各轴方向的缩放,但缩放后网格表面的纹理会被拉伸(例如scale.Y设为10时效果极差)。希望实现网格缩放时纹理在对应面上重复显示,同时支持在拉伸与重复纹理模式间切换。尝试过以下设置SamplerState的代码但无效果:

sampler = new SamplerState();
sampler.Filter = TextureFilter.Point;
sampler.AddressU = TextureAddressMode.Wrap;
sampler.AddressV = TextureAddressMode.Wrap;

gDevice.SamplerStates[0] = sampler;

核心问题分析

直接设置设备的SamplerStates无效,是因为Effect的Pass.Apply()会重置设备采样器状态到自身默认值,覆盖了你手动设置的状态。此外,当前顶点纹理坐标固定为0-1,缩放后自然会拉伸,需要根据缩放比例调整纹理坐标范围。

解决方案步骤

1. 添加纹理模式控制字段

为Block类添加枚举和字段,用于切换纹理显示模式:

public enum TextureWrapMode
{
    Stretch,
    Repeat
}

public TextureWrapMode CurrentWrapMode { get; set; } = TextureWrapMode.Repeat;
public Vector3 Scale { get; set; } = Vector3.One;

2. 重构顶点数据生成逻辑

把构造函数中的顶点生成代码抽成独立方法,根据当前Scale调整纹理坐标,确保缩放后纹理能重复:

public void GenerateVertexData()
{
    buffers.Clear();
    vertexData = new List<VertexPositionNormalTexture>();

    // 前面:X轴对应纹理U,Y轴对应纹理V
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0.0f, Scale.Y)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(Scale.X, Scale.Y)));

    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(Scale.X, Scale.Y)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(Scale.X, 0.0f)));

    SetUp(TexType.front);
    vertexData.Clear();

    // 右面:Z轴对应纹理U,Y轴对应纹理V
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0.0f, Scale.Y)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(Scale.Z, Scale.Y)));

    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(Scale.Z, Scale.Y)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0.0f, 0.0f)));
    vertexData.Add(new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(Scale.Z, 0.0f)));

    SetUp(TexType.right);
    vertexData.Clear();

    // 剩余面(后、左、上、下)同理,根据面朝向对应缩放轴调整纹理坐标
    // ... 补充剩余面的代码
}

3. 修改Effect采样器状态设置

在SetEffectsAndDraw方法中,在Apply()之前设置Effect的采样器状态,避免被覆盖:

private void SetEffectsAndDraw(Matrix world, VertexBuffer buffer, int primitiveCount)
{
    GameScreen.effect.View = GameScreen.camera.view;
    GameScreen.effect.Projection = GameScreen.camera.projection;
    GameScreen.effect.EnableDefaultLighting();
    GameScreen.effect.World = world;

    GameScreen.effect.FogEnabled = true;
    GameScreen.effect.FogColor = Color.Black.ToVector3();
    GameScreen.effect.FogStart = 4.0f;
    GameScreen.effect.FogEnd = 12.0f;

    // 必须启用纹理才能显示
    GameScreen.effect.TextureEnabled = true;

    // 根据当前模式设置采样器状态
    if (CurrentWrapMode == TextureWrapMode.Repeat)
    {
        // 线性过滤+重复模式,用PointWrap则是像素化过滤
        GameScreen.effect.SamplerStates[0] = SamplerState.LinearWrap;
    }
    else
    {
        // 线性过滤+拉伸模式,用PointClamp则是像素化过滤
        GameScreen.effect.SamplerStates[0] = SamplerState.LinearClamp;
    }

    GameScreen.effect.CurrentTechnique.Passes[0].Apply();

    Basic.gDevice.SetVertexBuffer(buffer);
    Basic.gDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, primitiveCount / 3);
}

4. 使用方式

  • 修改Block缩放后,调用GenerateVertexData()重新生成顶点缓冲:
    block.Scale = new Vector3(1, 10, 1);
    block.GenerateVertexData();
    
  • 切换纹理模式:
    // 切换到重复模式
    block.CurrentWrapMode = TextureWrapMode.Repeat;
    // 切换到拉伸模式
    block.CurrentWrapMode = TextureWrapMode.Stretch;
    

内容的提问来源于stack exchange,提问作者Canox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 10:04:54