现在的位置: 首页 > 综合 > 正文

处理顶点——在三角形上添加纹理

2013年03月16日 ⁄ 综合 ⁄ 共 6147字 ⁄ 字号 评论关闭
文章目录

问题

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

解决方案

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

这意味着你需要将一张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显示了对应不同纹理坐标的纹理区域。

image

图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所示。图中显示的三角形是前面已经定义的。

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

  • (-0.5f, 1.5f) → (0, 1)
  • (0.5f, -1.5f) → (0.5f, 0)
  • (1.5f, 1.5f) → (1,1)
image

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

TextureAddressMode.Wrap

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

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

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

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

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

  • (-0.5f, 1.5f) → (0.5f, 0.5f)
  • (1.2f, -0.2f) → (0.2f,0.8f)
  • (-0.7f, -1.2f) → (0.3f,0.8f)

TextureAddressMode.Mirror

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

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

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

image

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

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

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

  • (-0.5f, 1.5f) → (0.5f, 0.5f)
  • (0.5f, -1.5f) → (0.5f, 0.5f)
  • (1.2f, 1.7f) → (0.8f, 0.3f)

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右边所示。

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

  • (-0.5f, 1.5f) → (0.5f, 1)
  • (0.5f, -1.5f) → (0.5f, -1)
  • (1.2f, 1.7f) →(1, 1)

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

抱歉!评论已关闭.