问题
你想创建一张新纹理并手动定义每个像素的颜色。当你想让用户创建一个新图像或生成诸如深度贴图之类的人工图像时是很有用的。
你想将这张纹理存储到文件中,例如,生成游戏截图或为了调试的目的。
解决方案
设置一张图像的颜色和将纹理用你选择的格式保存到一个文件中被XNA Framework直接支持。
你可以通过调用纹理的SetData方法改变它的内容,这个方法以一个包含每个像素颜色值的数组为参数。
你可以使用纹理的Save方法将它保存到一个文件中。
工作原理
首先创建一个数组,保存图像中每个像素的颜色:
int textureWidth = 512; int textureHeight = 512; Color[] textureColors = new Color[textureWidth* textureHeight]; int i = 0; for (int ver = 0; ver < textureHeight; ver++) for (int hor=0; hor<textureWidth; hor++) { float red = (float)hor / (float)textureWidth; float green = 0; float blue = (float)ver / (float)textureHeight; float alpha = 1; textureColors[i++] = new Color(new Vector4(red, green, blue, alpha)); }
上述代码首先创建一个可以为一个分辨率为512×512的纹理存储颜色的数组。然后使用两个for循环分别填充每个像素。使用这个填充顺序,你首先从左上角开始,沿着从左向右,从上到下的顺序遍历像素。
最后,你需要实际创建纹理并将数组中的内容加载到纹理内存中:
Texture2D newTexture = new Texture2D(device, textureWidth, textureHeight, 1, TextureUsage.None, SurfaceFormat.Color);
然后,在device和图像分辨率之后,你需要指定mipmap的数量(见后面的注意事项)。下一个参数让你可以设置TextureUsage。通过选择TextureUsage.AutoGenerateMipMap,XNA会自动生成你指定的mipmaps的数量。记住,如果你想使用mipmap,图像的宽和高必须是2的整数幂。
注意:一个mipmapped图像包含图像的多个分辨率,这在很多情况下是很有用得。当绘制一个3D场景时,如果一个带纹理的盒子距相机如此之远以至于在窗口中只有一个像素的大小。要确定盒子的颜色,显卡仍需要计算从哪个纹理坐标采样纹理。当稍微移动一下相机,纹理坐标可能会有少许不同,但可能对应纹理中一个截然不同的颜色。结果是,稍微一动一个相机也会导致盒子的颜色发生变化,导致屏幕上像素的闪烁。一个解决方法是在图像中存储不同分辨率的多个版本。对一个64 × 64像素的图像,如果你开启mipmapping,XNA还会在图像中创建32 × 32,16 × 16,8 × 8,4 × 4,2 × 2直到1 × 1的版本, 1 × 1图像的单个像素就是整个纹理的颜色。现在,在刚才很远的盒子的情况中,XNA会使用图像的1 × 1。这样,纹理坐标的变化不会导致颜色的变化,闪烁的像素会保持一个不变的颜色!
最后一个参数指定了纹理中像素的格式,这对XNA分配足够的内存存储纹理是必须的。 而且,当获取纹理中的值时,这也是你预期的格式,当你将数据写入到纹理时,必须提供这个格式的数据。
最后,只需简单地将Color数组存储在纹理中:
newTexture.SetData<Color>(textureColors);
将纹理保存到文件还要简单:
myTexture.Save("savedtexture.jpg", ImageFileFormat.Jpg);
最后一个参数指定文件的压缩格式,确保文件扩展名对应这个格式,否则试图从一个外部图像浏览器打开这个文件会遇到麻烦。
当运行这个代码时,文件会创建到与可执行文件相同的位置中。默认是在\bin\x86\Debug文件夹中。
代码
下面的代码创建一个纹理,使用颜色数据进行填充并返回这个纹理:
private Texture2D DefineTextureColors() { int textureWidth = 512; int textureHeight = 512; Color[] textureColors = new Color[textureWidth* textureHeight]; int i = 0; for (int ver = 0; ver < textureHeight; ver++) for(int hor=0; hor<textureWidth; hor++) { textureColors[i++] = new Color(new Vector4((float)hor / (float)textureWidth, 0, (float)ver / (float)textureHeight, 1)); } Texture2D newTexture = new Texture2D(device, textureWidth, textureHeight, 1, ResourceUsage.None, SurfaceFormat.Color); newTexture.SetData<Color>(textureColors); return newTexture; }
这个方法需要从LoadContent方法中调用,因为它需要设备实例化:
protected override void LoadContent() { device = graphics.GraphicsDevice; spriteBatch = new SpriteBatch(GraphicsDevice); myTexture = DefineTextureColors(); myTexture.Save("savedtexture.jpg", ImageFileFormat.Jpg); }