zoukankan      html  css  js  c++  java
  • 在程序中使用NV 3D Vision 【转】

    http://www.cnblogs.com/gongminmin/archive/2010/11/21/1883392.html

    多年前NVIDIA就发布了3D Vision技术,能提供多种立体渲染的效果。随着2009年的电影阿凡达所带来的世界性3D狂潮,你是否也想在自己的程序中加入立体渲染呢?

    3D Vision的原理

    根据http://developer.nvidia.com/object/3d_stereo_dev.html,3D Vision的原理如下:

    在驱动内部,所有3D场景都渲染两次——一次用左眼,一次用右眼。驱动会自动“在线”修改典型的3D游戏vertex shader,所以在执行期可以产生正确的图像。

    注 意加粗的几个词所透露出来的信息。首先,你的每一个Draw call都被驱动变成了两个Draw call;其次,立体化的过程是自动的,无法自由控制;第三,它只能处理典型的vertex shader,而不是任意的vertex shader,比如sky box的vertex shader,就往往是个“非典型”的。NVIDIA的思路就是,把一切都封装起来,只有个别参数可以让开发人员和用户调整。程序能做的事就只能是把一切 交给驱动,祈祷最终结果正确,极其被动。

    其实,立体渲染没有那么stupid。比如,图形引擎可以分别从左右眼主动生成2张图像,都是 用正确的vertex shader,然后交给图形API。这么做就可以保证整条流水线都是支持立体的,包括视锥裁剪,结果就是所有物体均能100%渲染正确。而不会像3D Vision的方法,不但vertex shader必须“典型”,还得没法处理被场景管理器裁掉的物体(比如左眼能看到,右眼看不到的物体)。再比如Crytek在CryEngine3里的立 体渲染方法,把生成的单一图像通过image warp的方式分别得到左右眼的结果,不必渲染2遍,就能在性能基本不降低的情况下进行立体渲染。这些方式都不能使用自动的3D Vision,必须要通过生成2张image的方式才能实现。

    既然放弃了自动的3D Vision,我们就来开始探索如何手动把两张图提交给驱动,控制驱动产生立体渲染的方法。

    尝试1:NVAPI

    3D Vision并没有提供提交2张图像的方法,那就看看师出同门的NVAPI。NVAPI是NVIDIA提供的一个SDK,可以直接访问GPU和驱动的功能。本来我满怀欣喜地认为NVAPI一定给出了手动控制立体渲染的方法,结果发现公开版本的NVAPI最多也就提供了打开和关闭立体渲染这样的功能,并不能实现我们想要的。根据http://en.wikipedia.org/wiki/Nvidia_3D_Vision, 在专有版本的NVAPI(也就是签署了NDA的版本)包含有显式控制的功能。但这个专有的版本很难申请到,对于大公司还好,对于小作坊、业余开发的爱好者 之流,就根本没机会了(经常是提交申请后什么反应都没有)。对于开源开发就成了噩梦,NDA的东西无法随着开源软件一起发布,所以这条路成了死胡同。难道 就没有办法了吗?

    尝试2:OpenGL QuadBuffer

    OpenGL本身就提供了GL_LEFT_BACK、GL_RIGHT_BACK、GL_LEFT_FRONT和GL_RIGHT_FRONT四个缓冲区,内建了立体的支持。但这种方法的缺点也是明显的:

    1. 只支持OpenGL。游戏主流的D3D均无法使用quad buffer。
    2. 在Windows上,一般的OpenGL驱动均不支持quad buffer,只有Quadro支持。

    可以认为,这条路也失败了。难道,就真的没有办法了吗?

    尝试3:3D Video

    NVIDIA在发布3D Vision的同时,也发布了一款叫做3D Video player的软件,可以播放立体影片,并提供了一些样片下载。这些影片只是普通的视频格式,用一般播放器播放的时候是左右眼并排排列的,如下图所示:

    Knights Quest Snapshot

    但 用3D Video player播放的时候,就能呈现出立体效果。在播放视频的情况下,驱动没有3D信息、没有vertex shader,所以肯定不是用上文所说的“自动”方式得到立体效果的。虽然3D Video player非常可能用了专有的NVAPI,我仍然希望它是用一般的方法做到的。在GDC 2009上,NV的演讲提到了3D Video的显示方法,经过测试果然成功!那是NV留的一个后门,用来显示以有的立体数据。

    3D Video细节

    根据前面找到的材料,3D Video的处理方法如下:

    3D Video
    1. 把左右眼图像拷入一张大纹理中,大纹理的宽为w * 2,高为h + 1(w和h分别是原图像的宽和高)。左眼在左边,右眼在右边。
    2. 大纹理的最后一行加入特别的标志(此为关键所在)。
    3. 用StretchRect把大纹理拷入Back buffer。
    4. 当Back buffer显示出来的时候就是立体的了。

    看来,一切玄机尽在“特别的标志”中。正是那个标志,让驱动把纹理识别为立体图像,在StretchRect和Present的时候做特殊处理。该标志的定义是这样的:

    // Stereo Blit defines
    #define NVSTEREO_IMAGE_SIGNATURE 0x4433564e //NV3D

    typedef struct _Nv_Stereo_Image_Header
    {
    unsigned int dwSignature;
    unsigned int dwWidth;
    unsigned int dwHeight;
    unsigned int dwBPP;
    unsigned int dwFlags;
    } NVSTEREOIMAGEHEADER, *LPNVSTEREOIMAGEHEADER;

    // ORed flags in the dwFlags fiels of the _Nv_Stereo_Image_Header structure above
    #define     SIH_SWAP_EYES               0×00000001
    #define     SIH_SCALE_TO_FIT            0×00000002

    填充的方式:

    D3DLOCKED_RECT lr;
    pSurf->LockRect(&lr, NULL, 0);

    // 填到最后一行
    LPNVSTEREOIMAGEHEADER pSIH = reinterpret_cast<LPNVSTEREOIMAGEHEADER>(static_cast<unsigned char *>(lr.pBits) + (lr.Pitch * height));

    pSIH->dwSignature = NVSTEREO_IMAGE_SIGNATURE;
    pSIH->dwBPP = 32;
    pSIH->dwFlags = SIH_SWAP_EYES;
    pSIH->dwWidth = gImageWidth*2;
    pSIH->dwHeight = gImageHeight;

    pSurf->UnlockRect();

    这个标志可以在纹理建立的时候就填充上,以后每一帧只需要把左右眼的图像拷进去就行了。

    Direct3D 10/11的实现

    前面举得例子用的是D3D 9,而在D3D10/11中,情况又会如何呢?D3D10/11没有了StretchRect,取而代之的是CopyResource和CopySubresourceRegion,两者都没有缩放的能力。不管了,试试再说:

    D3D11_BOX box;
    box.left = 0;
    box.right = w;
    box.top = 0;
    box.bottom = h;
    box.front = 0;
    box.back = 1;
    d3d_imm_ctx->CopySubresourceRegion(backbuffer, 0, 0, 0, 0, surf, 0, &box);

    结果成功了!NV的驱动仍然会根据那个标志来特殊化CopySubresourceRegion。

    至此,D3D9/D3D10/D3D11/OpenGL下都可以通过2张图像来控制3D Vision显示出立体效果,你的程序也可以用同样的方法快步进入立体的行列。在KlayGE 3.11中,stereo模式也使用本文所述的方法。

    未解决的问题

    在 分别获得左右眼图像的过程中,应该是要关闭3D Vision的,否则左右眼图像也都会分别变成立体的,性能大减。通过NVAPI的NvAPI_Stereo_Deactivate确实可以关闭3D Vision,但这样的话连最后需要3D Vision的StretchRect等也失效了。由于某些原因NvAPI_Stereo_Activate这个函数出现在头文件和库文件中,却没出现在 文档里,对该函数的调用似乎需要等到下一帧才会启用3D Vision。结果还是不正确。暂时的解决方法是把左右眼图像略微减小,比如高度从h变成h – 2,驱动发现小于front buffer的渲染就会暂时关闭3D Vision。

    更多游戏引擎、图形编程、业界资讯,请见http://www.klayge.org

  • 相关阅读:
    单片机学习01__跑起你的流水灯
    python2与python3共存
    rpi-kali 搭建网络靶场
    P3388 【模板】割点(割顶)
    P3387 【模板】缩点
    P1069 细胞分裂
    The Unique MST[不严格的次小生成树]
    P3369 【模板】普通平衡树
    Netty的线程模型可不是Reactor这么简单
    SpringBoot+Mybatis+MySQL实现读写分离
  • 原文地址:https://www.cnblogs.com/mazhenyu/p/5534686.html
Copyright © 2011-2022 走看看