zoukankan      html  css  js  c++  java
  • 处理顶点——在三角形上添加纹理

    问题

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

    解决方案

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

    这意味着你需要将一张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);   
    } 
  • 相关阅读:
    Echarts 实现中国地图并轮播指定的地区?
    Linux安装Zookeeper
    初遇携程apollo配置中心
    eclipse安装lombok
    依赖layui form模块 复选框tree插件(拓展可根据属性单选还是多选,数据反选)
    centos7环境下mysql5.7的安装与配置
    中文算数验证码(加减乘除)
    获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址;
    jdk自带的MD5进行数据的加密与解密
    MyBatis-plus 代码生成器
  • 原文地址:https://www.cnblogs.com/AlexCheng/p/2120119.html
Copyright © 2011-2022 走看看