Houdini中全景摄像机shader立体左右眼成像方法

摘要:
即,分别基于两个相机的位置来计算渲染图像。渲染结果显示,垂直方向上的粉色圆圈确实是因为有一定的位移来查看列后面的东西,所以在这种情况下,两只眼睛实际上共享周围环境的相同旋转中心点,商店下面的ASADLens节点为我们提供了一个非常好的着色器模板,打开节点的脚本以获取当前极轴全景的相机:1]为空间中的像素定义的坐标系。

熟悉Houdini Shader部分的同学应该多多少少也了解camera自身也可以设定自己的shader。其中polar panoramic shader 能够非常方便的为艺术家渲染360全景视角的cg画面,但是这样渲染出来的画面只是单眼所看到的环境,如果引入立体双摄像机的渲染方法的话,默认的这个摄像机shader就会出现一个严重的问题,那就是所渲染出来的画面是分别以各自两台摄像机位置为原点所计算出来的。用文字说明可能有点绕口,看下图:

Houdini中全景摄像机shader立体左右眼成像方法第1张

图片中我把摄像头在一个水平轴向上移动了一点,渲染出来的结果发现垂直方向上粉红圈圈确实是因为一定的位移看到了柱子后面的东西,但是前后是反的,而且最要命的是水平方向上还是被挡着的,主要原因就是因为渲染的采样原点是摄像机自身中心点。这和我们实际旋转头部得到的影像是不一样的。生活中如果要看我们左边的事物,绝对不是两眼珠子自己左转九十度而是由我们的头部旋转来帮助眼睛看到目标。所以这种情况下实际上的两只眼睛对身边环境的成像是共享了同一个旋转中心点,且中心点绝不会在任意眼珠上。

如下图模型,O点才可能成为polar panoramic shader的旋转点,而射线投射点的位置待会再细聊:

Houdini中全景摄像机shader立体左右眼成像方法第2张

确定好正确的摄像机渲染原型之后就是怎样把这个方法放入到Houdini的摄像机上,好在Hou很灵活的提供了camera自身的shader入口,而且shop下面的ASAD Lens节点给我们提供了一个非常好的shader模板,里面含有perspective/polar pano/ cylinder pano 的shader方法。打开节点的script能够拿到当前polar全景的摄像机方法:

..................
 else if (projection == "polar")
    {
        float   xa = -PI*x;
        float   ya = (0.5*PI)*y;
        float   sx = sin(xa);
        float   cx = cos(xa);
        float   sy = sin(ya);
        float   cy = cos(ya);

        P = 0;
        I = set(cx*cy, sy, sx*cy);
    }
...................

短短几行,但是包含的内容实在太多了,我这里分别介绍一下不做太多扩展:

1:Houdini中camera shader的入口和出口

写shader的都知道一定会有入口和出口的定义,摄像机shader也不例外。其中入口参数有x,y,Time 等等, 输出端的参数则是P, I。具体对应什么摄像机的帮助文档写的比较详细了,这里截下来比较关键的定义:

//float x – X screen coordinate in the range -1 to 1
//
//float y – Y screen coordinate in the range -1 to 1
//
//float Time – Sample time
//
//float dofx – X depth of field sample value
//
//float dofy – Y depth of field sample value
//
//float aspect – Image aspect ratio (x/y)
//
//export vector P – Ray origin in camera space
//
//export vector I – Ray direction in camera space
//
//export int valid – Whether the sample is valid for measuring

理解起来也不会太难,x,y都是摄像机横轴纵轴的采样点,是[-1,1]空间里给像素点定义的坐标系,P 设摄像机发射出射线的起始点位置,I 则是射线方向。

这里涉及到的问题就在 P = 0; 上。

2:球形坐标系(Spherical coordinates)与笛卡尔坐标系(Cartesian coordinates)之间的关系:

笛卡尔坐标系大家都熟悉,就是(x,y,z)三个轴向的数据确定空间的一个点。而球形坐标的参数则有点不一样,我们拿地球做比,地球有经度与纬度,两个度数就能确定地球球面的任何一个位置,准确来讲是要加上地球半径才真的定位到了球面上,只不过我们已经在球面上了也不会混淆说成地底下所以从来不会去碰地球半径这个参数了。其实这就是球形坐标系的原型,纬度跨度有2π,经度跨度则是一个π。如下图:

Houdini中全景摄像机shader立体左右眼成像方法第3张

θ是纬度,φ是精度,ρ则是到原点的距离,由这三个数值我们就能建立球形坐标系在在笛卡尔坐标系中的表达了,另外考虑到houdini的摄像机空间是横轴纵轴都是[-1,1]。所以可以得到上面代码中的公式了:

x = cos(xa) * cos(ya)

y = sin(ya)

z = sin(xa) * cos(ya)

这些内容是为了理解摄像机的平面坐标到球形空间坐标的一个变换关系。如果还是觉得难以理解我把上面的方法直接通过vop运用到了一个grid上的每一个点上来观察。其中grid是在xy平面上大小为2的正方形面板,反正我们这里先不考虑画幅高宽的ratio。

Houdini中全景摄像机shader立体左右眼成像方法第4张

grid上面的每一个点可以看成屏幕或者摄像机的每一个像素点,整个屏幕每个点投射出去的射线正好能组成一个圆球的所有方向,这就是polar panorama的奥秘了。

回到上面留下来的问题 P = 0, 这个等式直接就把射线的投射点固定在了一个位置上,所以我们只要改变它,使它随着射线方向的变化而变化“位置”。

如图,假如我们设定一个投射方向k:

Houdini中全景摄像机shader立体左右眼成像方法第5张

那么两只眼睛的连线必与射线k垂直,而PD则定义了我们人的瞳距。射线k我们知道那么正向和反向旋转90度则能求出两只眼睛在xz平面上的方向,最后乘以瞳距的一半便能求出眼睛在当前射线上的具体位置。

废话了这么多基本上就是houdini 360全景双眼渲染的方法了。再贴一下我在cvex里面实现的这个方法:

//float x – X screen coordinate in the range -1 to 1
//
//float y – Y screen coordinate in the range -1 to 1
//
//float Time – Sample time
//
//float dofx – X depth of field sample value
//
//float dofy – Y depth of field sample value
//
//float aspect – Image aspect ratio (x/y)
//
//export vector P – Ray origin in camera space
//
//export vector I – Ray direction in camera space
//
//export int valid – Whether the sample is valid for measuring

#pragma hint    x       hidden
#pragma hint    y       hidden
#pragma hint    Time    hidden
#pragma hint    dofx    hidden
#pragma hint    dofy    hidden
#pragma hint    aspect  hidden
#pragma hint    P       hidden
#pragma hint    I       hidden

#pragma hint    side    oplist
#pragma choice  side    0 "right"
#pragma choice  side    1 "left"

#pragma label   offest  "Pupil Distance"

#include "math.h"

cvex
paronamaLens(
            // Inputs
            float x = 0;
            float y = 0;
            float Time = 0;
            float dofx = 0;
            float dofy = 0;
            float aspect = 1;
            float offest = 1;
            int side = 0;
      
            // Outputs
            export vector P = 0;
            export vector I = 0;
            )
{
   float   halfPI = 0.5 * PI;
   float   xa = -PI * x;
   float   ya = halfPI * y;
   float   sx = sin(xa);
   float   cx = cos(xa);
   float   sy = sin(ya);
   float   cy = cos(ya);

   //correspondent position for eyes
   float px, pz, rotation;
   rotation = lerp(-halfPI, halfPI, side);

   px = cos(xa + rotation) * cos(ya);
   pz = sin(xa + rotation) * cos(ya);

   P = 0.5 * offest * set(px, 0 , pz);
   I = set(cx*cy, sy, sx*cy);
}

 最后我把视距拉大一点看看极端效果:

左眼:

Houdini中全景摄像机shader立体左右眼成像方法第6张

右眼:

Houdini中全景摄像机shader立体左右眼成像方法第7张

很好,四个方向都是真确的偏移。打完收工。

免责声明:文章转载自《Houdini中全景摄像机shader立体左右眼成像方法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇(转)IntelliJ IDEA下的使用gitTo Java程序员:切勿用普通for循环遍历LinkedList下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

h5接入微信分享sdk,报错Cannot read property of undefined (reading 'title')

直接下载jweixin-1.6.0.js文件,引入文件 import wx from './jweixin-1.6.0.js'; 解决方法: 将jweixin-1.6.0.js文件中的this改为window就可以了 其他方法,参考: https://blog.csdn.net/illlllllllllll/article/details/112789...

winform右下角弹窗

网页是否经常在电脑右下角弹窗显示消息?其实Winform也是可以实现的。下面介绍两种方法。 第一步:设计窗体 第二步:实现代码 第一种方法 引用user32 声明常量 窗体Load事件 窗体FormClosing事件 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24...

Flink基础(四十):FLINK-SQL函数(4) 函数(五)自定义函数(三)

表值聚合函数 自定义表值聚合函数(UDTAGG)可以把一个表(一行或者多行,每行有一列或者多列)聚合成另一张表,结果中可以有多行多列。 上图展示了一个表值聚合函数的例子。假设你有一个饮料的表,这个表有 3 列,分别是 id、name 和 price,一共有 5 行。假设你需要找到价格最高的两个饮料,类似于 top2() 表值聚合函数。你需要遍历所有 5...

C#基础系列——多线程 信号量 异步 编程 Task Thread async和await

 多线程: ThreadStart 是一个委托函数 static void Main(string[] args) { Thread oGetArgThread = new Thread(new ThreadStart(() => {...

关于 SetProcessWorkingSetSize 和内存释放

在应用程序中,往往为了释放内存等,使用一些函数,其实,对于内存操作函数要谨慎使用,比如大家常常想到的 SetProcessWorkingSetSize,其实对于windows来说,系统会自动在程序闲置时(如程序被最小化)释放内存的,自己用内存释放 时,往往会造成一些莫名的内存错误,造成自己的应用程序及系统不稳定。 具体原理有人已经写得很清楚了,以下为转帖的...

我的Python之路:浏览器模拟

一、浏览器模拟——Header属性    有的时候,我们无法爬取一些网页,也就是说会出现403错误,这是因为这些网页为了防止有人恶意去采集其信息所以进行了一些反爬虫的设置。   为了可以获取这些数据我们使用一些两种方法: 1、使用 build opener() 由于urlopen()不支持HTTP的高级运用所以我们要修改头报。可以使用urllib.requ...