视图平截锥体(view frustum)几何体渲染 -ThreeJS camera例子

视图平截锥体(view frustum)几何体渲染

threejs中webgl_camera例子中渲染出了视图平截锥体的形状,其实现了frustum geometry对象用于专门负责view frustum几何体数据的生成。

下面我们仿照threejs中camera的实现原理使用C++在OpenGL ES上实现view frustum几何形状的渲染。iOS项目源码可从github中获取。

渲染效果图如下

camera_frusum_rendering_2020_0329.jpg

视图平截锥体(view frustum)几何体数据的生成

要绘制view frusutum,首先需要确定frustum形状的几何数据。由于我们只是要绘制出frustum的基础轮廓,因而我们会准备相关线段的顶点以及顶点色彩属性数据。我们对顶点数据的准备会分为两个步骤。

1. 根据眼睛空间中frustum的典型形状,大略准备出顶点数据

投射变换矩阵参数会在眼睛空间中确定出一个view frusutum,在本文中,我门使用右手性的投射矩阵(right handedness),因此,我们的frustum应该完全位于眼睛空间的负z轴之上。通常情况下,我们通过fov,aspect ratio和near/far平面z值参数确定出frustum。借助这些参数,其实我们可以计算出frustum的顶点数据。但是,这种方式不是很方便,因为每次都要确定顶点的位置信息,而同类的信息我们在生成投射矩阵时还要再次确定。所以,在第一步中,我们只是指出顶点数据的大略信息,比如顶点的数目,还有每个顶点要绘制的色彩信息,而顶点的位置数据我们在第二步中处理,在第一步只是给出占位信息。我们需要设置的顶点数据如下代码实例中所展示:

    // 线段的色彩信息
    Cvec3 colorFrustum = hexStringToRGB("0xffaa00");
    Cvec3 colorCone = hexStringToRGB("0xff0000");
    Cvec3 colorUp = hexStringToRGB("0x00aaff");
    Cvec3 colorTarget = hexStringToRGB("0xffffff");
    Cvec3 colorCross = hexStringToRGB("0x333333");
    
    //注意:下面每段中每个点的名称,这些名称代表了frustum锥体每个顶点的名称
    
    // 近平面方形的四条线段和对应色彩
    addLine( "n1", "n2", colorFrustum );
    addLine( "n2", "n4", colorFrustum );
    addLine( "n4", "n3", colorFrustum );
    addLine( "n3", "n1", colorFrustum );
    
    // 远平面方形的四条线段和对应色彩
    addLine( "f1", "f2", colorFrustum );
    addLine( "f2", "f4", colorFrustum );
    addLine( "f4", "f3", colorFrustum );
    addLine( "f3", "f1", colorFrustum );

    // 从近平面到远平面锥体边缘线段和色彩
    addLine( "n1", "f1", colorFrustum );
    addLine( "n2", "f2", colorFrustum );
    addLine( "n3", "f3", colorFrustum );
    addLine( "n4", "f4", colorFrustum );
    
    // 从眼睛坐标原点到近平面的锥体顶端线段和色彩
    addLine( "p", "n1", colorCone );
    addLine( "p", "n2", colorCone );
    addLine( "p", "n3", colorCone );
    addLine( "p", "n4", colorCone );
    
    // 指示上方方向的三角形线段和色彩
    addLine( "u1", "u2", colorUp );
    addLine( "u2", "u3", colorUp );
    addLine( "u3", "u1", colorUp );

    // 锥体中线线段和色彩
    addLine( "c", "t", colorTarget );
    addLine( "p", "c", colorCross );

    // 近平面和远平面中间轴对齐十字线线段和色彩
    addLine( "cn1", "cn2", colorCross );
    addLine( "cn3", "cn4", colorCross );

    addLine( "cf1", "cf2", colorCross );
    addLine( "cf3", "cf4", colorCross );
    
    
    ...
    
    //其中,每个addLine确定两个顶点信息
    void addLine(string a,string b,Cvec3 color){
      addPoint(a, color);
      addPoint(b, color); 
    }
    
    
    //对于每个顶点的位置信息,我们只是记录占位信息,这些占位信息在第二步更新
    void addPoint(string id, Cvec3 color){
      //存储每个点的占位位置信息
      vertices.push_back(Cvec3(0,0,0));
      //存储每个点的色彩信息
      colors.push_back(color);
    
      //我们使用GL_LINES primtive类型绘制线段,每个线段由两个点确定,
      //根据我们的绘制方式,那么相对于锥体的每个真实顶点,这些线段会多次使用锥体的每个顶点。
      //所以,我们会记录每个顶点的使用详情,便于在第二步更新时根据顶点名称更新真实位置信息
      if (pointMap.find(id)==pointMap.end()){
          pointMap[id] = vector<int>();
      }
      pointMap[id].push_back((int)vertices.size() - 1);
    }

2. 更新frustum锥体每个顶点的位置信息

在OpenGL中,投射矩阵将视图平截锥体变换为经典立方体(经透视除法-perspective division-之后)。如果要渲染view frustum,关键在于如何生成view frustum几何体。如果我们在眼睛空间直接描述,则面临着针对每一种不同的view frustum规格(投射矩阵参数所确定),都要根据参数重新指定view frustum的各个顶点。但是假如我们变换角度,从标准化设备空间的经典立方体开始,则很容易获得眼睛空间中view frustum各顶点的眼睛坐标。

经典立方体各顶点的坐标很容易确定,并且是固定不变的。因为经典立方体在x/y/z轴的坐标区间都是[-1..1]。因而我们可以轻松确定view frustum近平面和远平面上的8个顶点,加上眼睛坐标的原点和远近平面的中心点。那么这几乎就是我们需要的所有顶点。

我们可以设置顶点如下:

      int w = 1, h = 1;
    
    // 近平面和远平面中心点
    setPoint( "c", 0, 0, 1 );
    setPoint( "t", 0, 0, -1 );
    
    // 近平面顶点
    setPoint( "n1", - w, - h, 1 );
    setPoint( "n2", w, - h, 1 );
    setPoint( "n3", - w, h, 1 );
    setPoint( "n4", w, h, 1 );
    
    // 远平面顶点
    setPoint( "f1", - w, - h, -1 );
    setPoint( "f2", w, - h, -1 );
    setPoint( "f3", - w, h, -1 );
    setPoint( "f4", w, h, -1 );
    
    // 指示向上方向的三角形顶点
    setPoint( "u1", w * 0.7, h * 1.1, 1 );
    setPoint( "u2", - w * 0.7, h * 1.1, 1 );
    setPoint( "u3", 0, h * 2, 1 );
    
    // 远平面十字相交线顶点
    setPoint( "cf1", - w, 0, -1 );
    setPoint( "cf2", w, 0, -1 );
    setPoint( "cf3", 0, - h, -1 );
    setPoint( "cf4", 0, h, -1 );
    
    // 近平面十字相交线顶点
    setPoint( "cn1", - w, 0, 1 );
    setPoint( "cn2", w, 0, 1 );
    setPoint( "cn3", 0, - h, 1 );
    setPoint( "cn4", 0, h, 1 );

要获得这些顶点对应的眼睛坐标,我们还需要投射矩阵,利用这个矩阵的反转矩阵,我们就可以获得各顶点的眼睛坐标。经过反转投射变换后,近平面被投射到眼睛坐标中view frustum的近平面之上,远平面被投射到对应的远平面之上。注意,反转投射后,我们所获得的点和投射变换所获得的点一样,都为同质坐标,需要将坐标的各个部件都除以第4部件,来获得眼睛空间的仿射坐标。那么,获得眼睛坐标的代码示例如下:

void setPoint(string p,float x,float y,float z){
    //先将ndc坐标反转投射为eye coordinate
    Cvec4 eyeP4 = inv(projMat) * Cvec4(x,y,z,1.0);
    //点的同质坐标需要转换为标准仿射坐标
    Cvec4 eyeP4Affine = eyeP4/eyeP4[3];
    Cvec3 eyePos = Cvec3(eyeP4Affine);
    
    //根据顶点名称,更新vertices中占位位置信息的真实值
    vector<int> points = pointMap[p];
    if (points.size()>0) {
        for (int i = 0, l = (int)points.size(); i < l; i ++ ) {
            vertices[points[i]] = eyePos;
        }   
    } 
}

至此,我们就获得了frustum锥体几何形状绘制所需的顶点信息,剩下的就是使用OpenGL将这个信息绘制出来。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容