3.8 将场景绘制到纹理
问题
你想将屏幕上的内容保存在一个纹理文件中,这样可以实现屏幕截图,或者将场景绘制到一个纹理,而这个纹理用于深度贴图/折射贴图或作为post-processing effect的输入。
解决方案
最简单的方法是使用device. ResolveBackBuffer方法,这个方法将当前后备缓冲中的内容写入到一个纹理中。如果你只想将场景绘制到一个纹理,或指定大小的纹理,你需要使用device.SetRenderTarget 方法将渲染目标从屏幕变为你在内存中定义的那个目标。一旦场景已经完整地绘制到了那个目标,你就可以将它的内容存储在一张纹理中了。
工作原理
最简单的方法是将场景绘制到屏幕并将后备缓冲中的内容复制到ResolveTexture2D中。这样你首先要创建一个ResolveTexture2D对象用来存储后备缓冲的内容。这意味着这个ResolveTexture2D对象的大小和格式要和后备缓冲一样。在LoadContent方法中添加以下代码创建一个ResolveTexture2D对象:
PresentationParameters pp = device.PresentationParameters; resolveTexture = new ResolveTexture2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format);
第一行代码获取当前PresentationParameters,这个PresentationParameters包含了关于图像设备当前配置的所有细节,这些细节包括当前宽度、高度和后备缓冲的数据格式。第二行代码基于PresentationParameters创建了一个ResolveTexture2D对象。创建完ResolveTexture2D对象后,你就可以使用它存储当前后备缓冲的内容了。在Draw方法中,在你想提取后备缓冲的地方加入以下代码:
device.ResolveBackBuffer(resolveTexture); resolveTexture.Save("output.bmp", ImageFileFormat.Bmp);
上面的代码会将当前后备缓冲的内容存储在resolveTexture变量中。因为ResolveTexture2D是从Texture2D类继承的,你可以把这个ResolveTexture2D当成普通的Texture2D处理。在前面的例子中,我们将这个纹理保存为一个文件。
注意:因为硬件限制,在调用完device. ResolveBackBuffer方法后,后备缓冲中的内容会被丢弃!
使用与屏幕一样大小的自定义渲染目标(Render Target)
你可以自定义一个RenderTarget2D并在绘制前activate这个RenderTarget2D,而不是绘制到后备缓冲。渲染目标——顾名思义:是一段可以用来绘制其上的内存。当所有绘制完成后,你再次将渲染目标的内容复制到一个纹理中。添加一个变量:
RenderTarget2D renderTarget;
然后在LoadContent方法中将它实例化:
PresentationParameters pp = device.PresentationParameters; renderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format);
你定义了渲染目标的宽和高,指定了mipmap的级别为1(见教程3-6),设置了数据格式。在这个例子中,你使用和后备缓冲相同的值。
注意:mipmap只有在纹理的宽/高是2的整数幂时才能执行,当你指定其他大于1的mipmap发生错误时,原因往往就是纹理尺寸不是2的整数幂。
当你初始化渲染目标后,你可以在Draw方法中使用下面的代码将它设置为active渲染目标。在这行代码之后的所有东西都会绘制到这个自定义的渲染目标:
device.SetRenderTarget(0, renderTarget);
你需要在Draw方法调用Clear前加入这行代码,因为渲染目标的内容需要在绘制其他东西前进行清除。然后,你绘制整个场景,当场景绘制完后,将渲染目标的内容复制到一张纹理。在这之前,你还要通过activating另一个渲染目标deactivate这个渲染目标。在这个代码中,你通过将第二个参数设置为null将后备缓冲作为渲染目标:
device.SetRenderTarget(0, null); Texture2D resolvedTexture = renderTarget.GetTexture();
既然你已经activat默认的后备缓冲,那么之后渲染的所有东西又和以前一样被绘制到屏幕上了。
最后一行代码将自定义渲染目标中的内容载入到resolvedTexture变量中。
设置一个尺寸不等于屏幕大小的自定义渲染目标
你也可以选择绘制到一个与屏幕大小不同的渲染目标。这个很有用,例如可以用在post-processing effect的中间过程中。在模糊、intensity和边缘检测shader中可以使用一张小一点的图像减轻显卡的负担。
如果长宽比例与窗口相同,这不是问题。如果你想设置一个长宽比和窗口不同的渲染目标,你的投影矩阵对渲染目标来说将不正确,因为这个投影矩阵是使用窗口长宽比创建的(见教程2-1),所以你需要定义一个新的对应渲染目标的投影矩阵:
PresentationParameters pp = device.PresentationParameters; int width = pp.BackBufferWidth / 2; int height = pp.BackBufferHeight / 4; renderTarget = new RenderTarget2D(device, width, height, 1,device.DisplayMode.Format); rendertargetProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)width / (float)height, 0.5f, 100.0f);
首先你定义了渲染目标的分辨率并创建了对应的渲染目标和投影矩阵。新的渲染目标的宽设置为窗口宽的一半,高为四分之一,这样渲染目标的长宽比与窗口是不同的。如果你还是使用老的投影矩阵,那么场景看起来好像是在竖直方向被压扁了。现在只要你是在把场景渲染到渲染目标内,你就得使用rendertargetProjectionMatrix取代对应窗口的Projection矩阵。代码初始化一个RenderTarget2D对象并设置为你想要的分辨率。注意如果你将内容保存在纹理中,这个纹理的大小必须和渲染目标的大小是一样的。
protected override void LoadContent() { device = graphics.GraphicsDevice; basicEffect = new BasicEffect(device, null); cCross = new CoordCross(device); spriteBatch = new SpriteBatch(device); PresentationParameters pp = device.PresentationParameters; int width = pp.BackBufferWidth / 2; int height = pp.BackBufferHeight / 4; renderTarget = new RenderTarget2D(device, width, height, 1, device.DisplayMode.Format); rendertargetProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)width / (float)height, 0.5f, 100.0f); }
在Draw方法中,你activate自定义渲染目标,将场景渲染其中,然后deactivate这个渲染目标,保存其中的内容到一个纹理。注意使用rendertargetProjectionMatrix将3D物体绘制到渲染目标。而这个纹理也可以使用SpriteBatch显示在屏幕上:
protected override void Draw(GameTime gameTime) { device.SetRenderTarget(0, renderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,Color.CornflowerBlue, 1, 0); cCross.Draw(fpsCam.ViewMatrix, rendertargetProjectionMatrix); device.SetRenderTarget(0, null); Texture2D resolvedTexture = renderTarget.GetTexture(); graphics.GraphicsDevice.Clear(Color.Tomato); spriteBatch.Begin(); spriteBatch.Draw(resolvedTexture, new Vector2(100, 100), Color.White); spriteBatch.End(); base.Draw(gameTime); }
发布时间:2009/5/8 上午9:18:06 阅读次数:9147