5.2 在三角形上添加纹理

问题

你想绘制一个颜色漂亮的三角形并可以完全控制三角形上的颜色。

解决方案

你的显卡允许你指定一张图像,从这张图像可以采样想要的颜色。

这意味着你需要将一张2D图像导入到XNA项目中,并在绘制三角形之前将它传递到显卡。对每个顶点,你要指定2D图像的哪个位置对应顶点。

工作原理

首先将一张2D图像导入到项目中,如教程3-1所示。在LoadContent方法中将它链接到一个变量:

myTexture = content.Load<Texture2D>("texture"); 

然后你要定义三角形的顶点。如前面的教程所述,你需要指定每个顶点的3D位置。这次你无需定义颜色而是定义在2D纹理中的对应位置,这一步需要在每个顶点中存储2D纹理坐标。如果三角形的每个顶点对应一个2D纹理坐标,显卡就会将你指定的2D图像的某一部分填充到三角形内部。

当指定纹理坐标时,要记住纹理的左上为坐标(0,0),右上为(1,0)。这意味着第二个坐标值表示垂直位置,所以右下坐标为(1,1)。图5-4显示了对应不同纹理坐标的纹理区域。

图5-4

图5-4 为顶点指定纹理坐标

你需要存储一个2D纹理坐标而不是每个顶点的颜色。不是存储一个包含VertexPositionColor元素的数组,而是创建一个包含VertexPositionTexture元素的数组,如这个名称所述,它将存储纹理坐标和一个3D位置:

private void InitVertices() 
{
    vertices = new VertexPositionTexture[9]; 
    int i = 0; 
    
    vertices[i++] = new VertexPositionTexture(new Vector3(-10, 1, -5), new Vector2(0,0)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(-7, 5, -5), new Vector2(0.5f, 1)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(-4, 1, -5), new Vector2(1,0)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(-3, 1, -5), new Vector2(0, 0.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(0, 5, -5), new Vector2(1, 0)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(3, 1, -5),new Vector2(1, 1)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(4, 1, -5), new Vector2(0.25f, 0.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(7, 5, -5), new Vector2(0.5f, 0)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(10, 1, -5), new Vector2(1, 1)); 
    
    myVertexDeclaration=new VertexDeclaration(device, VertexPositionTexture.VertexElements); 
} 

这里定义的九个顶点拥有对应图5-4中的三个三角形的纹理坐标。通过指定纹理坐标,你可以显示如图5-4所示的三角形。

注意: 三角形在3D空间中的实际位置定义为一个Vector3。例如,图5-4中的第一个三角形是倒置的,因为两个顶点的Y坐标是1,中间一个顶点的Y坐标为5,这使得三角形中间的字母XNA也是颠倒绘制的(译者注:图5-4中的第一个三角形中的XNA并没有颠倒,但在源代码中是颠倒的,怀疑作者截图有误)。

你还需要告知显卡顶点现在包含不同的信息。所以需要基于VertexPositionTexture的VertexElement创建一个VertexDeclaration,这样可以在绘制三角形前将它传递到显卡。

定义了顶点后,你就做好了绘制三个三角形的准备:

device.RenderState.CullMode = CullMode.None; 
basicEffect.World = Matrix.Identity;
basicEffect.View = fpsCam.ViewMatrix; 
basicEffect.Projection = fpsCam.ProjectionMatrix; 
basicEffect.Texture = myTexture; 
basicEffect.TextureEnabled = true; 

basicEffect.Begin(); 
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes) 
{
    pass.Begin();
    device.VertexDeclaration=myVertexDeclaration; 
    device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, 3); 
    pass.End(); 
}
basicEffect.End(); 

你需要将纹理传递到显卡,这一步需要将这个纹理设置为BasicEffect的当前纹理。然后,不像前一个教程那样使用定义在顶点中的颜色,本教程通过将 TextureEnabled设置为true指定BasicEffect从纹理采样颜色。因为你使用了一个不同的顶点格式,所以需要将新的VertexDeclaration传递到显卡。

最后绘制三个三角形。

纹理寻址模式

如前所述,纹理坐标(0,0)对应纹理的左上点的像素,(1,1)对应右下点。

但这并没有限定纹理坐标的值必须在[0,1]范围内,例如设定为(1.5f, –0.5f)也可以。在这种情况中,你需要通过设置U和V(U是第一个纹理坐标,V是第二个)的纹理模式让XNA知道如何处理这样的坐标。下面的代码定义了一个U和V坐标超过[0,1]范围的三角形:

private void InitVertices() 
{
    vertices = new VertexPositionTexture[3]; 
    
    int i = 0; 
    vertices[i++] = new VertexPositionTexture(new Vector3(-3, -3, -1), new Vector2(-0.5f,1.5f));
    vertices[i++] = new VertexPositionTexture(new Vector3(0, 5, -1), new Vector2(0.5f, -1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(3, -3, -1), new Vector2(1.5f,1.5f)); 
    myVertexDeclaration=new VertexDeclaration(device, VertexPositionTexture.VertexElements); 
} 

然后你要通过设置纹理采样器的寻址模式让XNA知道如何处理这些坐标,这用来从纹理采样颜色。

TextureAddressMode.Clamp

使用下列代码将UV模式设置为Clamp:

device.SamplerStates[0].AddressU = TextureAddressMode.Clamp; 
device.SamplerStates[0].AddressV = TextureAddressMode.Clamp; 

这个寻址模式让所有的纹理坐标截取到[0,1]范围内,所有小于0的坐标截取到0,大于1的截取到1。

结果是,超出[0,1] 范围的纹理坐标的所有像素的颜色为纹理边缘的颜色,如图5-5所示。图中显示的三角形是前面已经定义的。

下面是显卡在从图像采样颜色前首先映射的纹理坐标的例子:

图5-5

图5-5 Clamp (左)和Wrap (右)纹理寻址模式

TextureAddressMode.Wrap

这是默认的纹理寻址模式,在使用了一个不同的模式后你可以使用下列代码重新选择使用wrap模式:

device.SamplerStates[0].AddressU = TextureAddressMode.Wrap; 
device.SamplerStates[0].AddressV = TextureAddressMode.Wrap; 

使用这个模式,显卡会从坐标加上或减去1直到坐标仍回到[0,1]范围。

这会导致原始纹理被复制,如图5-5右边所示。

下面是一些纹理坐标映射的例子:

TextureAddressMode.Mirror

使用下列代码将纹理寻址模式设置为:

device.SamplerStates[0].AddressU = TextureAddressMode.Mirror; 
device.SamplerStates[0].AddressV = TextureAddressMode.Mirror; 

这个模式导致原始纹理被复制,但与Wrapping模式不同的是,复制的图像与相邻的原始图像是互为镜像的。复制的图像在原始图像的上方和下方是垂直镜像的,左方和右方是水平映射的。对角线上的复制沿着两边,翻转180度,如图5-6左图所示。

图5-6

图5-6 Mirror (左)和MirrorOne (右)纹理寻址模式

这个模式很有用,因为可以在放大图像避免边缘锐化。

下面是一些坐标映射的例子:

TextureAddressMode.MirrorOnce

你可以选择MirrorOnce模式:

device.SamplerStates[0].AddressU = TextureAddressMode.MirrorOnce; 
device.SamplerStates[0].AddressV = TextureAddressMode.MirrorOnce;

这个模式将[-1,1] 区域内的纹理坐标镜像到[0,1]区间,而所有超出[-1,1]区间的坐标会截取到–1 (小于–1的值)或1 (大于1的值),如图5-5右边所示。

下面是采样坐标的映射例子:

TextureAddressMode.Border

你可以给纹理坐标超出[0,1]区间的像素设置一个指定的颜色,这个颜色叫做 BorderColor,可以使用下列代码存储在SamplerState中:

device.SamplerStates[0].BorderColor = Color.LightSeaGreen; 
device.SamplerStates[0].AddressU = TextureAddressMode.Border; 
device.SamplerStates[0].AddressV = TextureAddressMode.Border;

当调试时这个模式很有用,因为它清晰地显示了所有超出[0,1]区间的纹理坐标,所有超出此区间的像素都使用BorderColor绘制。

当心:在XBox360平台上只能使用白色作为边界颜色。

代码

首先需要定义顶点,因为需要保护3D位置和对应的纹理位置,你需要使用VertexPositionTexture elements。别忘了初始化对应的VertexDeclaration。

private void InitVertices() 
{
    vertices = new VertexPositionTexture[3]; 
    
    int i = 0; 
    vertices[i++] = new VertexPositionTexture(new Vector3(-3, -3, -1), new Vector2(-0.5f, 1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(0, 5, -1), new Vector2(0.5f, -1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(3, -3, -1), new Vector2(1.5f, 1.5f)); 
    
    myVertexDeclaration= new VertexDeclaration(device,VertexPositionTexture.VertexElements); 
}

有了存储在数组中的顶点和一个有效的VertexDeclaration,你可以设置一个纹理寻址模式并绘制三角形:

protected override void Draw(GameTime gameTime) 
{
    device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.CornflowerBlue, 1, 0); 
    
    //draw triangles 
    device.RenderState.CullMode = CullMode.None; 
    basicEffect.World = Matrix.Identity; 
    basicEffect.View = fpsCam.ViewMatrix; 
    basicEffect.Projection = fpsCam.ProjectionMatrix; 
    basicEffect.Texture = myTexture; 
    basicEffect.TextureEnabled = true; 
    device.SamplerStates[0].AddressU=TextureAddressMode.Mirror; 
    device.SamplerStates[0].AddressV = TextureAddressMode.Mirror; 
    
    basicEffect.Begin();
    foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes) 
    {
        pass.Begin(); 
        device.VertexDeclaration = myVertexDeclaration; 
        device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, 1);
        pass.End(); 
    }
    basicEffect.End(); 
    
    base.Draw(gameTime); 
} 

发布时间:2009/9/8 下午1:36:05  阅读次数:7702

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号