当前位置: 首页 > news >正文

在程序中使用3D Vision--转帖

出处:http://www.klayge.org/wp/?p=344

先转过来再说,生的想看的时候找不到了。呵呵。

多年前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。

相关文章:

  • 7.cadence原理图后续[原创]
  • linux内核线程的创建及在QEMU上的测试方法
  • unity 查看prefab层次
  • anmpp限制php文件函数可以操作的目录
  • SMTP 550错误
  • 【监控】WebServer入库与缓存更新代码优化小计
  • Ext JS 4前瞻:快速、易用和稳定
  • C# 调用C++ CLR dll类库时,实现从 string 到 sbyte* 的转换
  • BaseTDI.sys 瑞星卡巴冲突,导致机器蓝屏
  • Freemodbus介绍及测试
  • struts tech
  • html代码中显示系统时间
  • 提高工作效率的一些方法
  • ASP.NET通过Global.asax和Timer定时器 定时调用WebService 运行后台代码(转)
  • POJ3216 最小路径覆盖
  • 2017 前端面试准备 - 收藏集 - 掘金
  • Angular6错误 Service: No provider for Renderer2
  • Git 使用集
  • HTML-表单
  • httpie使用详解
  • isset在php5.6-和php7.0+的一些差异
  • JS函数式编程 数组部分风格 ES6版
  • js写一个简单的选项卡
  • Puppeteer:浏览器控制器
  • rabbitmq延迟消息示例
  • React 快速上手 - 07 前端路由 react-router
  • sessionStorage和localStorage
  • spark本地环境的搭建到运行第一个spark程序
  • Tornado学习笔记(1)
  • Vue2.x学习三:事件处理生命周期钩子
  • 关于Flux,Vuex,Redux的思考
  • 如何设计一个比特币钱包服务
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 我有几个粽子,和一个故事
  • 一文看透浏览器架构
  • 交换综合实验一
  • #include
  • #微信小程序:微信小程序常见的配置传旨
  • #在 README.md 中生成项目目录结构
  • (70min)字节暑假实习二面(已挂)
  • (day 12)JavaScript学习笔记(数组3)
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (二)PySpark3:SparkSQL编程
  • (五)Python 垃圾回收机制
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .chm格式文件如何阅读
  • .gitignore文件设置了忽略但不生效
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .NET Core 和 .NET Framework 中的 MEF2
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .Net6 Api Swagger配置
  • .Net环境下的缓存技术介绍
  • .net知识和学习方法系列(二十一)CLR-枚举