使用OpenGL绘制一个地球

一、程序简介

本项目使用 OpenGL 实现了球型3D模型的生成和渲染;进行了纹理贴图,同时使用了多个纹理,渲染了地球围绕太阳旋转的场景,并添加了简单的光照效果。

二、程序实现

1. 生成3D球模型并绘制

球的三维坐标表示为:
x^2 + y^2 + z^2 = 0
绘制带有纹理的球的难度问题在于:我们无法将这样一个连续的方程用计算机绘制出来,因此我们要进行离散化。若使用球的坐标方程进行离散化,其离散后两点之前的弧长并不一致,因此,引入球的参数坐标方程进行离散化。以(u,v)表示某一点的坐标且u,v \in [0 , 1],定义(u,v)(x,y,z)的转换如下:
\begin{cases} x = sin( \pi \times v)cos(2\pi \times u) \\ y = sin( \pi \times v)sin(2\pi \times u) \\ z = cos(\pi \times v) \end{cases}
我们声明一下步长uStep和vStep,即每一次在u轴上和v轴上怎加的值,这个步长是由两个整数uStepNum和vStepNumber决定的,即在每个方向上从0增加几次到1.0,这样保证了球恰好被渲染出来。通过这个参数坐标我们可以进行均匀的离散化,得到一个在单位正方形内均匀分布的(u,v)点阵,然后我们通过上述公式进行转换。

//转换代码
Point getPoint(double u, double v){
    double x = sin(PI * v)*cos(2 * PI * u);
    double y = sin(PI * v)*sin(2 * PI * u);
    double z = cos(PI * v);
    return Point(x, y, z);
}

//生成球的模型信息
void getSphere(){
    double ustep = 1 / (double)ustepNum, vstep = 1 / (double)vstepNum;
    points[0] = getPoint(0, 0);
    coords[0] = 0.5;
    coords[1] = 1;
    int c = 1;
    for (int i = 1; i < vstepNum; i++)
    {
        for (int j = 0; j <= ustepNum; j++)
        {
            points[c] = getPoint(ustep*j, vstep*i);
            //记录纹理坐标
            coords[2 * c] = ustep*j;
            coords[2 * c + 1 ] = vstep*i;
            c++;
        }
    }
    points[c] = getPoint(1, 1);
    
    //...
    //生成三角面片定点索引信息
}

有了球面上所有顶点的坐标信息,我们怎么画出一个球面呢,可以通过将这些点组成一个个三角面片,通过openGL内置画三角形的函数进行绘制,因此我们来进行三角形对于顶点的生成。


一个由三角形组成的“球”

通过转化公式我们可以发现,离散化后的点有一个特征:当v一定时,其z值是一定的,u值的变化会导致x和y值的变化,这样可以理解为球面与多个x-y平面相交的被切成了很多“圆”,每一个圆上又被均匀的细分成了多个点。这对我们考虑球模型三角面片的索引和纹理贴图坐标有很大的帮助。

首先我们考虑两个特殊的点,其参数坐标中v值为0和1,这对应着z=0和z=1的上下两个顶点,在这里,三角面片的序号顺寻应该是顶点和离顶点最近的两个圆上相邻的两个顶点组合而成。

实现:

for (int i = 0; i <= ustepNum ; i++)//球体上第一层
{
    indexes.push_back(0);//上顶点总是第一个点
    indexes.push_back(1 + i);
    indexes.push_back(1 + i + 1);
};


int last = 1 + (ustepNum+1) * (vstepNum - 1);
int start = 1 + (ustepNum+1) * (vstepNum - 2);
for (int i = 1 + (ustepNum+1) * (vstepNum - 2); i < 1 + ustepNum * (vstepNum - 1); i++)//球体上最后一层
{
    indexes.push_back(i); 
    indexes.push_back(last); //逆时针排列
    indexes.push_back(last + 1);
}

而在中间的部分则是相邻两个圆之间由若干个矩形组成的,一个矩形又由俩三角组成。


实现:

for (int i = 1; i < vstepNum - 1; i++){
    int start = 1 + (i - 1)*(ustepNum+1);
    for (int j = start; j < start + (1+ustepNum); j++){
        /*
        *       j
        *       |\
        *       | \
        *       |__\
        * 
        */
        indexes.push_back(j);
        indexes.push_back(j + ustepNum + 1);
        indexes.push_back(j + ustepNum + 2);
        /*
        * 
        *     j __ 
        *       \  |
        *        \ |
        *         \|
        *     
        */
        indexes.push_back(j);
        indexes.push_back(j+1);
        indexes.push_back(j + ustepNum + 2);
    }
}

至此,我们生成了一个球的模型。其点信息存放在points向量中。
通过这些信息,我们可以通过OpenGl中绘制三角形的函数,并配合EBO、VAO、VBO的使用,绘制这个球。

2. 纹理贴图

之前我们获得了求模型各个点的坐标信息和球面上各个三角面片的顶点索引信息,要给球体贴上纹理,最重要的是找到球体局部坐标系中各个点的三维坐标和纹理各个二维点上的映射关系。

之前我们也提到了球体参数坐标(u,v)到球体空间坐标(x,y,z)的映射,这里我们不妨使用其逆映射关系(x,y,z)->(u,v)进行三维到二维的映射。


earth.jpg

其中需要特殊考虑的还是两个顶点,由于球中的顶点铺平到二维平面后是(v=1或v=0)一条线。由于无法用一个点包含到一条线所有的信息,因此一开始考虑在线上进行随机采样并取其平均值。后观察 后发现,题目中所提供的图片在v=0或v=1时,其颜色基本一致,故最终顶点映射到了两条线上的中点。

实现: 我们在第一步进行离散化时,已知(u,v)到(x,y,z)的映射,因此只需记录在离散化时进行记录即可。

double ustep = 1 / (double)ustepNum, vstep = 1 / (double)vstepNum;
points[0] = getPoint(0, 0);
coords[0] = 0.5;
coords[1] = 1;
int c = 1;
for (int i = 1; i < vstepNum; i++)
{
    for (int j = 0; j <= ustepNum; j++)
    {
        points[c] = getPoint(ustep*j, vstep*i);
        //记录纹理坐标
        coords[2 * c] = 1 - ustep*j;
        coords[2 * c + 1 ] = vstep*i;
        c++;
    }
}
points[c] = getPoint(1, 1);

通过传入纹理坐标,再传入将纹理通过采样器传进fragmentShader,我们就可以得到一个“地球”。


项目地址:
https://github.com/tjuwhy/OpenGLDemo
所有三角形绘制和纹理贴图相关实现均参考于 OpenGL 官方学习文档

参考资料:
绘制球体
OpenGL学习三十三:球面映射
OpenGL 官方学习文档

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

推荐阅读更多精彩内容