zoukankan      html  css  js  c++  java
  • Direct3D渲染到纹理 (部分转)

    游戏中应用的例子:

    游戏中打开观察人物的界面,UI上显示的人物就是通过这种方式绘制出来的。

    渲染到纹理是D3D中的一项高级技术。一方面,它很简单,另一方面它很强大并能产生很多特殊效果。 比如说发光效果,环境映射,阴影映射,都可以通过它来实现。渲染到纹理只是渲染到表面的一个延伸。我们只需再加些东西就可以了。首先,我们要创造一个纹理,并且做好一些防范措施。第二步我们就可以把适当的场景渲染到我们创建的纹理上了。然后,我们把这个纹理用在最后的渲染上。
      ?main.cpp
      首先我们得声明所需要的对象。当然我们需要一张用来渲染的纹理。此外,我们还需要两个Surface对象。一个是用来存储后台缓冲区,一个用来当纹理的渲染对象。后面我再详细介绍它们。另外我们还需要两个矩阵,一个是用来当纹理的投影矩阵,另一个是存储原来的矩阵。
      LPDIRECT3DTEXTURE9 pRenderTexture = NULL;
      LPDIRECT3DSURFACE9 pRenderSurface = NULL,pBackBuffer = NULL;
      D3DXMATRIX matProjection,matOldProjection;
      现在我们来创建纹理。前两个参数是纹理的宽度和高度,第三个参数是纹理的多级渐进纹理序列参数,在这里是设为1,第四个参数非常重要而且必须设为D3DUSAGE_RENDERTARGET,表明我们所创建的纹理是用来渲染的。剩下的参数就是指纹理格式,顶点缓冲区的内存位置,和一个指向纹理的指针。当纹理是用来当渲染对象时,顶点缓冲区的内存位置必须设为D3D_DEFAILT。
      g_App.GetDevice()->CreateTexture(256,256,1,D3DUSAGE_RENDERTARGET,D3DFMT_R5G6B5,D3DPOOL_DEFAULT,&pRenderTexture,NULL);
      为了访问纹理内存对象,我们需要一个Surface对象,因为D3D中的纹理是用这样的一个Surface来存储纹理数据的。为了得到纹理表面的Surface,我们需要调用方法GetSurfaceLevel() 。第一个参数我们设为0,第二个参数为一个指向surface对象的指针。
      pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);
      下一步就是创建一个适合纹理维数的投影矩阵,因为纹理的横纵比和后台缓冲区的不一样。
      D3DXMatrixPerspectiveFovLH(&matProjection,D3DX_PI / 4.0f,1,1,100);
      在我们的循环渲染之前,我们必须保存后台缓冲区和它的投影矩阵。
      g_App.GetDevice()->GetTransform(D3DTS_PROJECTION,&matOldProjection);
      g_App.GetDevice()->GetRenderTarget(0,&pBackBuffer);
      渲染循环函数可以分为两个部分。第一部分是渲染到纹理的过程。因此,渲染对象必须设为纹理表面。然后我们就可以把东西渲染到这个对象上了。渲染到另一个表面上和正常地渲染到后台缓冲区差不多。只有一点不同,那就是先不调用Prensent()函数,因为纹理上的内容并不需要显示在屏幕上。象平时一样,我们先要重置表面颜色缓冲区,并且调用BeginSence()和EndSence()方法。为了能够适当的渲染,我们必须设置和纹理表面相符的投影矩阵。否则最后的图象可能被扭曲
      //render-to-texture
      g_App.GetDevice()->SetRenderTarget(0,pRenderSurface); //set new render target
      g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(100,100,100),1.0f,0); //clear texture
      g_App.GetDevice()->BeginScene();
      g_App.GetDevice()->SetTexture(0,pPyramideTexture);
      D3DXMatrixRotationY(&matRotationY,fRotation);
      D3DXMatrixTranslation(&matTranslation,0.0f,0.0f,5.0f);
      g_App.GetDevice()->SetTransform(D3DTS_WORLD,&(matRotationY * matTranslation));
      g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matProjection); //set projection matrix
      g_App.GetDevice()->SetStreamSource(0,pTriangleVB,0,sizeof(D3DVERTEX));
      g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLELIST,0,4);
      g_App.GetDevice()->EndScene();
      渲染循环的第二部分就是渲染最后场景的过程(也就是显示到屏幕上的过程)。渲染对象重新设为后台缓冲区,投影矩阵重新设为原来的投影矩阵。由于纹理已经准备好了,所以它和纹理层0相关联。
      //render scene with texture
      g_App.GetDevice()->SetRenderTarget(0,pBackBuffer); //set back buffer
      g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
      g_App.GetDevice()->BeginScene();
      g_App.GetDevice()->SetTexture(0,pRenderTexture); //set rendered texture
      g_App.GetDevice()->SetTransform(D3DTS_WORLD,&matTranslation);
      g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matOldProjection); //restore projection matrix
      g_App.GetDevice()->SetStreamSource(0,pQuadVB,0,sizeof(D3DVERTEX));
      g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);
      g_App.GetDevice()->EndScene();
      g_App.GetDevice()->Present(NULL,NULL,NULL,NULL);
      最后我们通过调用Release()方法释放Surface对象。
      pRenderSurface->Release();
      pRenderSurface = NULL;
      pBackBuffer->Release();
      pBackBuffer = NULL;
      渲染到纹理能让你做很多事情,但是你必须注意一些限制。首先深度缓冲区必须总是大于或等于渲染对象的大小。此外,渲染对象和深度缓冲区的格式必须一致。

    什么是纹理

    熟悉DX的兄弟们都知道什么叫纹理了,这里简单介绍一下,先看看现实生活中的例子吧,其实纹理的例子比比皆是,比如地板,墙面都是纹理。在图形学中,纹理主要是为了增强场景的真实感,比如你想绘制一个地面,简单一点可以直接使用一个矩形,稍微复杂一点可以用三角形网格,再复杂一点可以使用地面纹理,有了纹理以后真实感明显增强了。DX中的纹理其实就是就是对现实生活中纹理的模拟,但是它不仅仅是一张图片那么简单,有着完整的数据结构,比如宽度,高度,内存类型,表面(Surface)等。

    渲染到纹理

    常规的渲染操作都是直接将场景呈现到backbuffer中的,backbuffer说白了其实就是一个表面,再说白了就是一块内存,场景通过绘制函数载入显存后,再通过Present函数送至显示器。那么为什么还要渲染到纹理呢?这是为了实现一些特殊的效果,比如常见的环境映射,简单的说,想象你有一个光滑的球体,它应该是可以反射周围的物体的,这就是环境映射。

    实现步骤

    上面说了常规的渲染操作是将场景送至backbuffer,而backbuffer实际上是一个Surface,而纹理恰恰又包含了Surface,所以我们只需要取得纹理的Surface,其次将场景送至这个Surface,最后再把这个纹理渲染到backbuffer中即可。举个例子,假设你要在一面墙壁上画一幅画,你有两种方法

    1 直接在墙上画,这个很好理解,就对应常规的backbuffer渲染。

    2 先将画画在纸上,然后将纸贴到墙上,这就对应渲染到纹理的过程。

    这里墙壁相当于backbuffer,而纸张相当于纹理的Surface,在纸上作画相当于渲染到纹理,把纸贴到墙上相当于把纹理渲染到backbuffer,希望大家没有迷糊就好。具体的步骤如下

    1 创建纹理并获得纹理的表面(Surface)

    2 向纹理的表面渲染场景

    3 渲染纹理本身

    创建纹理并获取纹理表面

    为了在纹理上渲染,我们应该先准备一个纹理,使用DX的函数CreateTexture就可以创建一个纹理了,注意在设置参数的时候需要将usage设置为D3DUSAGE_RENDERTARGET,因为只有这样才能在纹理上渲染。

    IDirect3DTexture9*        g_pRenderTexture    = NULL ; // Create texture HRESULT hr = g_pd3dDevice->CreateTexture( 2562561, D3DUSAGE_RENDERTARGET, D3DFMT_R5G6B5, D3DPOOL_DEFAULT,&g_pRenderTexture, NULL) ; if (FAILED(hr)) { MessageBox(NULL, "Create texture failed!""Error"0) ;return E_FAIL ; }

    下面我们要取得纹理对应的Surface,以便在其上绘制场景,

    IDirect3DSurface9*        g_pRenderSurface    = NULL ; // Get texture surface hr = g_pRenderTexture->GetSurfaceLevel(0&g_pRenderSurface) ; if (FAILED(hr)) { MessageBox(NULL, "Get surface on texture failed!""Error"0) ; return E_FAIL ; }

    将场景渲染至纹理表面

    下面开始向纹理的Surface中绘制场景,为了简单起见,这里绘制一个茶壶,需要注意的是,绘制时需要将纹理的表面设置为当前的RenderTarget,所以首先要保存原来的RenderTarget。

    // 保存旧的RenderTarget g_pd3dDevice->GetRenderTarget(0&g_pOldRenderTarget) ; // 设置纹理表面为当前RenderTarget g_pd3dDevice->SetRenderTarget(0, g_pRenderSurface) ; g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff0000ff1.0f0 );   if( SUCCEEDED( g_pd3dDevice->BeginScene() ) ) { g_pd3dDevice->SetTexture(0, NULL) ; g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , FALSE ); g_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME) ; g_pTeapotMesh->DrawSubset(0) ; // 绘制茶壶 g_pd3dDevice->EndScene(); }

    渲染纹理本身

    接下来将纹理渲染到backbuffer,在这之前要恢复原来的RenderTarget,因为这次是将纹理送到backbuffer,而backbuff而就是原来的RenderTarget。

    // 恢复RenderTarget g_pd3dDevice->SetRenderTarget(0, g_pOldRenderTarget) ; g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff00ff001.0f0 );   if( SUCCEEDED( g_pd3dDevice->BeginScene() ) ) { g_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID) ; // 绘制纹理RenderQuad() ; g_pd3dDevice->EndScene(); }   g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

    虽然是两次渲染,但是只需要调用一次present函数,因为之前的绘制只是将场景送至显存,而Present函数才真正将场景显示出来。

    上面的代码用到了自定义函数RenderQuad,这个函数将纹理渲染出来,细节如下

    void RenderQuad() { // Setup texture g_pd3dDevice->SetTexture(0, g_pRenderTexture) ; g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP ); g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP ); // Set stream source g_pd3dDevice->SetStreamSource(0, g_pVB, 0sizeof(Vertex) ); g_pd3dDevice->SetFVF(VertexFVF) ; g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 02) ; }

    最后,看一下效果图

    其实这个图并没有真正体现渲染到纹理的威力,虽然技术上是,但给人的感觉就好像直接渲染了一张图片一样,为了实现更加高级的效果,我们可以设置一个Cube,并给这个Cube加上纹理,然后在每个纹理的表面绘制场景,甚至可以是动态场景,效果如下。

    其实这个Cube上的茶壶是动态旋转的,并且可以使用鼠标右键来旋转,滚轮来缩放,可以下载下面的文件来看一下动态效果。

    动态效果文件

    Happy coding!!!

    == THE END ==

  • 相关阅读:
    lamp配置多个虚拟站点
    linux下lamp环境修改网站根目录
    CentOS 6.7快速搭建lamp环境
    GridView 使用方法总结 (一)
    asp.net学习之Repeater控件
    通过js获取前台数据向一般处理程序传递Json数据,并解析Json数据,将前台传来的Json数据写入数据库表中
    ExtJs特点、优缺点及注意事项
    Js获取当前日期时间及其它操作
    C#实现MD5加密
    SqlDataReader、SqlDataAdapter與SqlCommand的 区别
  • 原文地址:https://www.cnblogs.com/kex1n/p/2158831.html
Copyright © 2011-2022 走看看