shader - 使用纹理图集时发生 HLSL 颜色渗色

标签 shader textures hlsl texture-atlas

我正在用 C#/XNA 制作游戏。我目前正在研究将用于地形的着色器。我使用纹理图集来提高速度和效率,但我遇到了图 block 之间的纹理/颜色渗色问题: /image/249Ha.png

我在 FX Composer 和我的游戏本身中都获得了这种效果。这是我的着色器:

//-----------------------------------------------------------------------------
// InstancedModel.fx
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------


// Camera settings.
float4x4 World : World < string UIWidget="None"; >;
float4x4 View : View < string UIWidget="None"; >;
float4x4 Projection : Projection < string UIWidget="None"; >;

// This sampler uses a simple Lambert lighting model.
float3 LightDirection = normalize(float3(-1, -1, -1));
float3 DiffuseLight = 1.25;
float3 AmbientLight = 0.25;

float TextureSide = 0; //0 = top, 1 = side, 2 = bottom
float2 TextureCoord;
texture Texture;
float2 TextureSize = 2.0;

sampler Sampler = sampler_state
{
    Texture = (Texture);
    MinFilter = Linear;
    MipFilter = Linear;
    MagFilter = Linear;
    AddressU = Clamp;
    AddressV = Clamp;
};


struct VertexShaderInput
{
    float4 Position : POSITION0;
    float3 Normal : NORMAL0;
    float2 TextureCoordinate : TEXCOORD0;
};


struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float4 Color : COLOR0;
    float2 TextureCoordinate : TEXCOORD0;
};

// Vertex shader helper function shared between the two techniques.
VertexShaderOutput VertexShaderCommon(VertexShaderInput input, float4x4 instanceTransform, float2 atlasCoord, float4 colour)
{
    VertexShaderOutput output;

    // Apply the world and camera matrices to compute the output position.
    float4 worldPosition = mul(input.Position, instanceTransform);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);

    // Compute lighting, using a simple Lambert model.
    float3 worldNormal = mul(input.Normal, instanceTransform);    
    float diffuseAmount = max(-dot(worldNormal, LightDirection), 0);    
    float3 lightingResult = saturate(diffuseAmount * DiffuseLight + AmbientLight);    
    output.Color = float4(lightingResult, 1);
    output.Color = output.Color * colour;

    //calculate texture coords  
    float2 InputTextureCoords = input.TextureCoordinate;// / TextureSize;
    float2 InputAtlasCoords = atlasCoord;// / TextureSize;  

    float2 textCoordsActual = InputTextureCoords + InputAtlasCoords;

    output.TextureCoordinate = textCoordsActual;

    return output;
}


// Hardware instancing reads the per-instance world transform from a secondary vertex stream.
VertexShaderOutput HardwareInstancingVertexShader(VertexShaderInput input,
                                                  float4x4 instanceTransform : BLENDWEIGHT,
                                                  float2 atlasCoord1 : TEXCOORD1, float2 atlasCoord2 : TEXCOORD2, float2 atlasCoord3 : TEXCOORD3, 
                                                  float4 colour : COLOR1)
{
    float2 atlasCoord = atlasCoord1;
    if (TextureSide == 1)
    {
        atlasCoord = atlasCoord1;
    }
    if (TextureSide == 2)
    {
        atlasCoord = atlasCoord2;
    }
    else if (TextureSide == 3)
    {
        atlasCoord = atlasCoord3;
    }
    return VertexShaderCommon(input, mul(World, transpose(instanceTransform)), atlasCoord, colour);
}


// When instancing is disabled we take the world transform from an effect parameter.
VertexShaderOutput NoInstancingVertexShader(VertexShaderInput input,
                                                  float4x4 instanceTransform : BLENDWEIGHT,
                                                  float2 atlasCoord1 : TEXCOORD1, float2 atlasCoord2 : TEXCOORD2, float2 atlasCoord3 : TEXCOORD3, 
                                                  float4 colour : COLOR1)
{
    return VertexShaderCommon(input, World, TextureCoord, float4(1,1,1,1));
}

float2 HalfPixileCorrectedCoords(float2 coords)
{
    float u = (coords.x) / TextureSize;
    float v = (coords.y) / TextureSize;

    return float2(u, v);
}

// Both techniques share this same pixel shader.
float4 PixelShaderFunction(VertexShaderOutput input, 
                            float2 atlasCoord1 : TEXCOORD1) : COLOR00
{                           
    float2 outputTextureCoords = HalfPixileCorrectedCoords(input.TextureCoordinate);    
    return tex2D(Sampler, outputTextureCoords) * input.Color;
}


// Hardware instancing technique.
technique HardwareInstancing
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 HardwareInstancingVertexShader();
        PixelShader = compile ps_3_0 PixelShaderFunction();
    }
}

// For rendering without instancing.
technique NoInstancing
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 NoInstancingVertexShader();
        PixelShader = compile ps_3_0 PixelShaderFunction();
    }
}

我的 FX Composer HLSL 配置文件: /image/HZCNI.png

和测试图集我使用: (无法发布,因为我需要更多声誉,我也许可以在后续中发布它?)

我已经阅读了很多有关此内容的文章,似乎我要么需要进行“半像素校正”,要么将像素包裹在图集中指定纹理的边缘。我已经尝试过这两种方法都没有成功。

问题: 如何解决我遇到的像素渗色问题?

最佳答案

如果您想使用图集获得漂亮的无缝平铺纹理,您必须创建一个比您预期大 4 倍的纹理(即 (2 x 宽度) x (2 x 高度))。

更具体地说, map 集中的每个图 block 应如下所示: image

整个图 block 应重复两次,从其中心 (u,v) 开始。

(u,v) 是图集纹理中图 block 的坐标。

但是,在对对象进行纹理化时应为此图 block 使用的坐标是:

(u0,v0)<--->(u1,v1)

您可以按如下方式计算它们:

rw = tile_width / atlas_width
rh = tile_height / atlas_height
u0 = u + 0.5 * rw
v0 = v + 0.5 * rh
u1 = u0 + rw
v1 = v0 + rh

使用纹理图集时渗色的主要问题之一是 mipmap。创建 mipmap 时,纹理会被下采样,并且相邻图 block 会混合在一起,这会导致伪影。我上面描述的方法通过提供足够的纹理区域储备来防止这种情况发生。

在采样纹理时出现伪影的另一个原因是纹理过滤。上述方法也对此有所帮助,因为在 (u0, v0) - (u1, v1) 范围内的样本附近总是有足够的区域被图 block 的纹理覆盖。

关于shader - 使用纹理图集时发生 HLSL 颜色渗色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16050574/

相关文章:

math - 顶点着色器世界变换,我们为什么要用4维向量?

c++ - std::vector 和 VBO 仅渲染最后一个形状

opengl - OpenGL 3.1 中已弃用 glLineStipple

c++ - 如何在 SDL2 中连接多个纹理?

java - 如何制作光滑的圆形纹理?

directx - HLSL 点 Sprite 纹理坐标适用于 ATI 而不是 NVIDIA

matrix - 三.js shader 点光源位置

c++ - 如何使用几何着色器从点数据绘制正方形

javascript - ThreeJS纹理加载: black for a while on Firefox

directx - HLSL 中的纹理采样器不进行插值