深入Managed DirectX9(十四)
使用纹理资源
Managed DirectX中所有的纹理资源都继承于BaseTexture类,而BaseTexture类又是从Resource类继承而来的。下面列出了BaseTexture对象中的几个新方法。
LevelCount
This read-only property specifies the number of levels this texture has. For example, a mipmapped texture (described in the sidebar "Dissecting Mipmaps") would have the number of mipmaps as its level count.
GenerateMipSubLevels
This method will automatically generate the mipmap sublevels we've just mentioned for you. It will use the next property to determine the filtering mode to use when creating these levels.
AutoGenerateFilterType
This read-write property describes how the mipmap sublevels are generated automatically. It takes a member of the TextureFilter enumeration, and will automatically re-create any mipmap sublevels if the filter type is changed. The default filter type for mipmap level generation is TextureFilter.Linear unless the driver does not support that mode, in which case it will use TextureFilter.Point. If the device does not support the selected filter type, this property set will throw an exception. All filter values are valid with the exception of "None". You can see the TextureFilterCaps member of the Caps structure to determine which filter types are supported by your device.
LevelOfDetail
Another read-write property that determines the most detailed level of detail for managed textures.
特别提示:Mipmaps详解
我们一直再说的mipmap究竟是什么呢?很简单,mipmap其实就是一系列的纹理,其中,第一个成员拥有最多的细节,接下来的纹理大小减少为上一张的一半。举个例子,假设有一张原大小为256×256的“高分辨率”纹理,那么下一级别的mipmap就是128×128的,再下来就是64×64的,以此类推。Direct3D就通过mipmap链来控制渲染时纹理的质量,当然,代价是mipmap将会消耗更多的内存。当对象离摄像机很近的时候,使用高质量的纹理,而物体远离摄像机时,就是用低分辨率的纹理。
当创建纹理时,可以使用参数来指定有多少个层次包含在纹理中。这里层次的数目直接与mipmap链相对应。把这个参数设置为0,Direct3D则会自动在mipmap链中创建一系列纹理,从原始纹理的大小开始,一直递减到大小为1×1。在我们所举的例子中,在一张256×256的纹理上把这个参数设为0,将会创建9个纹理:256×256、128×128、64×64,32×32、16×16,8×8,4×4、2×2、1×1。
当调用SetTexture方法时,Direct3D会根据当前sampler states类中MipFilter的属性值,自动在众多的mipmap中进行筛选。如果还记得第六章所写的Dodger游戏,你可能还记得我们为赛道的纹理设置了ninify和magnify过滤器。他和mipmap过滤器的内容是一样的。
锁定纹理、获得纹理描述
与早先讨论的顶点以及索引缓冲一样,纹理也都有锁存机制,同时还包括用来控制锁定特性的各种标志。对这些方法来说,纹理有两个锁定几何资源所不具有的特性:称为脏区域以及“backing”对象。在讲解这2个参数之前,先来看看纹理的锁存机制。
纹理的锁存机制与几何缓冲的锁存机制很类似,但有两个区别。第一个区别在于lock方法接受一个额外的“level”参数,用来指定锁定时使用的mipmap级别。第二个则是用来指定锁定区域的矩形(对于体积纹理volume textures来说则是立方体)。可以使用不含这个参数的重载来锁定整个纹理表面。
当处理立体纹理(cube textures)时,还需要一个额外的参数:所需要锁定的立方体的面。可以使用CubeMapFace枚举中的值做为这个参数。
你可能注意到了,LockRectangle方法的一些重载会返回一个pitch参数作为输出。如果你需要知道每一行中的总数据量,pitch参数就会提供给你。If you specify this parameter,it will return the number of bytes in a row of blows
虽然锁定纹理时可以指定需要锁定的数据类型,但是纹理通常都只包含颜色信息。使用需要接收数据类型的重载时,必须保证所指定的类型大小与纹理的象素格式一样。比如,32-bit的象素格式,就应该用一个32为的整数来锁定,而16-bit象素格式就应使用16位的整数来锁定。
现在来看一个锁定纹理的例子。使用第五章的MeshFile文件作为开始。我们将创建一个动态的纹理来替换当前渲染模型的纹理。我们希望这个纹理有两种模式,一种是颜色循环变化的纹理,而另一种则是颜色完全随机的纹理。添加以下变量的声明:
private uint texColor = 0;
private bool randomTextureColor = false;
使用32-bit的象素格式,同样,把循环颜色也储存为32位。我们还要指定创建随机颜色的纹理,还是使用颜色循环纹理,因此使用一个布尔变量。现在来编写真正完成锁定和更新纹理的方法。为了展示使用数组和数据流作为返回值的方法之间的区别,我们还将使用不安全代码。使用返回值为数组的方法如下所示:
private void FillTexture(Texture t, bool random)
{
SurfaceDescription s = t.GetLevelDescription(0);
Random r = new Random();
uint[,] data = (uint[,])t.LockRectangle(typeof(uint), 0,
LockFlags.None, s.Width, s.Height);
for (int i = 0; i < s.Width; i++)
{
for (int j = 0; j < s.Height; j++)
{
if (random)
{
data[i,j] = (uint)Color.FromArgb(r.Next(byte.Maxvalue),r.Next(byte.Maxvalue),r.Next(byte.Maxvalue)).ToArgb();
}
else
{
data[i,j] = texColor++;
if (texColor >= 0x00ffffff)
texColor = 0;
}
}
}
t.UnlockRectangle(0);
}
这段代码干了些什么呢?通过当前纹理,获得对它自身的描述:纹理的宽度,以及数据的大小,这样才能确定需要填充多少数据。有了数据的长度和宽度之后,就可以把数据锁定为一个二维数组。把数据锁定为32位无符号整数(因为使用32—bit的象素格式),并且关心锁定标志。因为只创建了一个级别的mipmap,所以锁定0级别。
锁定纹理之后,迭代所返回数组中的每一个元素,更新颜色:随机选一个颜色,或者每次增加一定的颜色值(即我们的循环颜色)。在所有数据更新完成之后,解锁纹理就可以了。
那么,使用使用不安全代码的版本将是什么样子呢?事实上非常简单,代码如下:
private unsafe void FillTextureUnsafe(Texture t, bool random)
{
SurfaceDescription s = t.GetLevelDescription(0);
Random r = new Random();
uint* pData = (uint*)t.LockRectangle(0,LockFlags.None).InternalData.ToPointer();
for (int i = 0; i < s.Width; i++)
{
for (int j = 0; j < s.Height; j++)
{
if (random)
{
*pData = (uint)Color.FromArgb(
r.Next(byte.Maxvalue),
r.Next(byte.Maxvalue),
r.Next(byte.Maxvalue)).ToArgb();
}
else
{
*pData = texColor++;
if (texColor >= 0x00ffffff)
texColor = 0;
}
pData++;
}
}
t.UnlockRectangle(0);
}
注意到没有,主要的区别只在于对不安全代码的声明,以及更新数据的方式。我们把缓冲锁定为一个图形流,使用“InternalData”成员(它实际是一个InPtr)来获得所要修改数据的指针。我们通过指针来直接控制数据,最后,解锁纹理。
为了使这段代码通过编译,必须在工程的属性中设置允许编译不安全代码。
好了,现在有了可以把纹理填充为奇异颜色的方法,因该修改一下LoadMesh方法了,通过上面的函数来获得纹理,而不是通过一个文件。如下所示,修改LoadMesh中控制材质的代码:
// Store each material and texture
for (int i = 0; i < mtrl.Length; i++)
{
meshMaterials[i] = mtrl[i].Material3D;
meshTextures[i] = new Texture(device,256, 256, 1, 0, Format.X8R8G8B8 ,Pool.Managed);
#if (UNSAFE)
FillTextureUnsafe(meshTextures[i], randomTextureColor);
#else
FillTexture(meshTextures[i], randomTextureColor);
#endif
}
如你所见,我们不再通过文件来加载纹理。对数组中的每一个纹理,我们都使用X8R8G8B8的像素格式创建一个新纹理。创建256*256,并且只有一个级别的纹理。接下来用我们刚才的方法填充纹理。你可能注意到了,我把填充纹理的方法包含在一个#if语句块中。你可以通过声明这是不安全代码来使用这个方法。
使用我们所写的方法填充了纹理之后,可以允许程序来看看了,模型现在看起来很漂亮吧。但很快,他就会变的很“呆板”,并且纹理不会变色了。我们应该每一帧都调用填充纹理的方法。同时解决这种呆板的效果。修改SetupCamera方法:
(注意:再强调一下我先前说过的东西:注意到当按下任意键选择随机颜色时模型的旋转速度改变没有?这是由于旋转是基于帧速率改变的而不是系统计时器)
private void SetupCamera()
{
device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4,this.Width / this.Height, 1.0f, 10000.0f);
device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, 580.0f),
new Vector3(), new Vector3(0,1,0));
device.RenderState.Lighting = false;
if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyAnisotropic)
device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
else if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyLinear)
device.SamplerState[0].MinFilter = TextureFilter.Linear;
if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyAnisotropic)
device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
else if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyLinear)
device.SamplerState[0].MagFilter = TextureFilter.Linear;
foreach(Texture t in meshTextures)
#if (UNSAFE)
FillTextureUnsafe(t, randomTextureColor);
#else
FillTexture(t, randomTextureColor);
#endif
}
现在,关闭了灯光来看看没有经过灯光着色的模型有多漂亮吧。同时我们还打开了一个纹理过滤器(如过系统支持)来改变纹理呆板的效果。最后要做的只剩下添加一个方法来打开“随机”纹理颜色。使用一个按键事件来选择纹理模式,添加如下代码:
protected override void onKeyPress(KeyPressEventArgs e)
{
randomTextureColor = !randomTextureColor;
}
使用随机颜色产生了动画的效果,看其来有些像当电视没有信号时的雪花点效果。
~~~~~~~~~~~~~~~~~第八章完~~~~~~~~~~~~~~~~~~
呵呵,下一部分我们会讨论关于mesh的更多内容^_^
代码如果不能运行,请根据自己所装的DirectX版本稍作修改
下载代码
Managed DirectX中所有的纹理资源都继承于BaseTexture类,而BaseTexture类又是从Resource类继承而来的。下面列出了BaseTexture对象中的几个新方法。
LevelCount
This read-only property specifies the number of levels this texture has. For example, a mipmapped texture (described in the sidebar "Dissecting Mipmaps") would have the number of mipmaps as its level count.
GenerateMipSubLevels
This method will automatically generate the mipmap sublevels we've just mentioned for you. It will use the next property to determine the filtering mode to use when creating these levels.
AutoGenerateFilterType
This read-write property describes how the mipmap sublevels are generated automatically. It takes a member of the TextureFilter enumeration, and will automatically re-create any mipmap sublevels if the filter type is changed. The default filter type for mipmap level generation is TextureFilter.Linear unless the driver does not support that mode, in which case it will use TextureFilter.Point. If the device does not support the selected filter type, this property set will throw an exception. All filter values are valid with the exception of "None". You can see the TextureFilterCaps member of the Caps structure to determine which filter types are supported by your device.
LevelOfDetail
Another read-write property that determines the most detailed level of detail for managed textures.
特别提示:Mipmaps详解
我们一直再说的mipmap究竟是什么呢?很简单,mipmap其实就是一系列的纹理,其中,第一个成员拥有最多的细节,接下来的纹理大小减少为上一张的一半。举个例子,假设有一张原大小为256×256的“高分辨率”纹理,那么下一级别的mipmap就是128×128的,再下来就是64×64的,以此类推。Direct3D就通过mipmap链来控制渲染时纹理的质量,当然,代价是mipmap将会消耗更多的内存。当对象离摄像机很近的时候,使用高质量的纹理,而物体远离摄像机时,就是用低分辨率的纹理。
当创建纹理时,可以使用参数来指定有多少个层次包含在纹理中。这里层次的数目直接与mipmap链相对应。把这个参数设置为0,Direct3D则会自动在mipmap链中创建一系列纹理,从原始纹理的大小开始,一直递减到大小为1×1。在我们所举的例子中,在一张256×256的纹理上把这个参数设为0,将会创建9个纹理:256×256、128×128、64×64,32×32、16×16,8×8,4×4、2×2、1×1。
当调用SetTexture方法时,Direct3D会根据当前sampler states类中MipFilter的属性值,自动在众多的mipmap中进行筛选。如果还记得第六章所写的Dodger游戏,你可能还记得我们为赛道的纹理设置了ninify和magnify过滤器。他和mipmap过滤器的内容是一样的。
锁定纹理、获得纹理描述
与早先讨论的顶点以及索引缓冲一样,纹理也都有锁存机制,同时还包括用来控制锁定特性的各种标志。对这些方法来说,纹理有两个锁定几何资源所不具有的特性:称为脏区域以及“backing”对象。在讲解这2个参数之前,先来看看纹理的锁存机制。
纹理的锁存机制与几何缓冲的锁存机制很类似,但有两个区别。第一个区别在于lock方法接受一个额外的“level”参数,用来指定锁定时使用的mipmap级别。第二个则是用来指定锁定区域的矩形(对于体积纹理volume textures来说则是立方体)。可以使用不含这个参数的重载来锁定整个纹理表面。
当处理立体纹理(cube textures)时,还需要一个额外的参数:所需要锁定的立方体的面。可以使用CubeMapFace枚举中的值做为这个参数。
你可能注意到了,LockRectangle方法的一些重载会返回一个pitch参数作为输出。如果你需要知道每一行中的总数据量,pitch参数就会提供给你。If you specify this parameter,it will return the number of bytes in a row of blows
虽然锁定纹理时可以指定需要锁定的数据类型,但是纹理通常都只包含颜色信息。使用需要接收数据类型的重载时,必须保证所指定的类型大小与纹理的象素格式一样。比如,32-bit的象素格式,就应该用一个32为的整数来锁定,而16-bit象素格式就应使用16位的整数来锁定。
现在来看一个锁定纹理的例子。使用第五章的MeshFile文件作为开始。我们将创建一个动态的纹理来替换当前渲染模型的纹理。我们希望这个纹理有两种模式,一种是颜色循环变化的纹理,而另一种则是颜色完全随机的纹理。添加以下变量的声明:
private uint texColor = 0;
private bool randomTextureColor = false;
使用32-bit的象素格式,同样,把循环颜色也储存为32位。我们还要指定创建随机颜色的纹理,还是使用颜色循环纹理,因此使用一个布尔变量。现在来编写真正完成锁定和更新纹理的方法。为了展示使用数组和数据流作为返回值的方法之间的区别,我们还将使用不安全代码。使用返回值为数组的方法如下所示:
private void FillTexture(Texture t, bool random)
{
SurfaceDescription s = t.GetLevelDescription(0);
Random r = new Random();
uint[,] data = (uint[,])t.LockRectangle(typeof(uint), 0,
LockFlags.None, s.Width, s.Height);
for (int i = 0; i < s.Width; i++)
{
for (int j = 0; j < s.Height; j++)
{
if (random)
{
data[i,j] = (uint)Color.FromArgb(r.Next(byte.Maxvalue),r.Next(byte.Maxvalue),r.Next(byte.Maxvalue)).ToArgb();
}
else
{
data[i,j] = texColor++;
if (texColor >= 0x00ffffff)
texColor = 0;
}
}
}
t.UnlockRectangle(0);
}
这段代码干了些什么呢?通过当前纹理,获得对它自身的描述:纹理的宽度,以及数据的大小,这样才能确定需要填充多少数据。有了数据的长度和宽度之后,就可以把数据锁定为一个二维数组。把数据锁定为32位无符号整数(因为使用32—bit的象素格式),并且关心锁定标志。因为只创建了一个级别的mipmap,所以锁定0级别。
锁定纹理之后,迭代所返回数组中的每一个元素,更新颜色:随机选一个颜色,或者每次增加一定的颜色值(即我们的循环颜色)。在所有数据更新完成之后,解锁纹理就可以了。
那么,使用使用不安全代码的版本将是什么样子呢?事实上非常简单,代码如下:
private unsafe void FillTextureUnsafe(Texture t, bool random)
{
SurfaceDescription s = t.GetLevelDescription(0);
Random r = new Random();
uint* pData = (uint*)t.LockRectangle(0,LockFlags.None).InternalData.ToPointer();
for (int i = 0; i < s.Width; i++)
{
for (int j = 0; j < s.Height; j++)
{
if (random)
{
*pData = (uint)Color.FromArgb(
r.Next(byte.Maxvalue),
r.Next(byte.Maxvalue),
r.Next(byte.Maxvalue)).ToArgb();
}
else
{
*pData = texColor++;
if (texColor >= 0x00ffffff)
texColor = 0;
}
pData++;
}
}
t.UnlockRectangle(0);
}
注意到没有,主要的区别只在于对不安全代码的声明,以及更新数据的方式。我们把缓冲锁定为一个图形流,使用“InternalData”成员(它实际是一个InPtr)来获得所要修改数据的指针。我们通过指针来直接控制数据,最后,解锁纹理。
为了使这段代码通过编译,必须在工程的属性中设置允许编译不安全代码。
好了,现在有了可以把纹理填充为奇异颜色的方法,因该修改一下LoadMesh方法了,通过上面的函数来获得纹理,而不是通过一个文件。如下所示,修改LoadMesh中控制材质的代码:
// Store each material and texture
for (int i = 0; i < mtrl.Length; i++)
{
meshMaterials[i] = mtrl[i].Material3D;
meshTextures[i] = new Texture(device,256, 256, 1, 0, Format.X8R8G8B8 ,Pool.Managed);
#if (UNSAFE)
FillTextureUnsafe(meshTextures[i], randomTextureColor);
#else
FillTexture(meshTextures[i], randomTextureColor);
#endif
}
如你所见,我们不再通过文件来加载纹理。对数组中的每一个纹理,我们都使用X8R8G8B8的像素格式创建一个新纹理。创建256*256,并且只有一个级别的纹理。接下来用我们刚才的方法填充纹理。你可能注意到了,我把填充纹理的方法包含在一个#if语句块中。你可以通过声明这是不安全代码来使用这个方法。
使用我们所写的方法填充了纹理之后,可以允许程序来看看了,模型现在看起来很漂亮吧。但很快,他就会变的很“呆板”,并且纹理不会变色了。我们应该每一帧都调用填充纹理的方法。同时解决这种呆板的效果。修改SetupCamera方法:
(注意:再强调一下我先前说过的东西:注意到当按下任意键选择随机颜色时模型的旋转速度改变没有?这是由于旋转是基于帧速率改变的而不是系统计时器)
private void SetupCamera()
{
device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4,this.Width / this.Height, 1.0f, 10000.0f);
device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, 580.0f),
new Vector3(), new Vector3(0,1,0));
device.RenderState.Lighting = false;
if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyAnisotropic)
device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
else if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyLinear)
device.SamplerState[0].MinFilter = TextureFilter.Linear;
if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyAnisotropic)
device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
else if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyLinear)
device.SamplerState[0].MagFilter = TextureFilter.Linear;
foreach(Texture t in meshTextures)
#if (UNSAFE)
FillTextureUnsafe(t, randomTextureColor);
#else
FillTexture(t, randomTextureColor);
#endif
}
现在,关闭了灯光来看看没有经过灯光着色的模型有多漂亮吧。同时我们还打开了一个纹理过滤器(如过系统支持)来改变纹理呆板的效果。最后要做的只剩下添加一个方法来打开“随机”纹理颜色。使用一个按键事件来选择纹理模式,添加如下代码:
protected override void onKeyPress(KeyPressEventArgs e)
{
randomTextureColor = !randomTextureColor;
}
使用随机颜色产生了动画的效果,看其来有些像当电视没有信号时的雪花点效果。
~~~~~~~~~~~~~~~~~第八章完~~~~~~~~~~~~~~~~~~
呵呵,下一部分我们会讨论关于mesh的更多内容^_^
代码如果不能运行,请根据自己所装的DirectX版本稍作修改
下载代码